mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-12 04:22:14 +00:00
Compare commits
11 Commits
@now/pytho
...
@now/bash@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66954e84fe | ||
|
|
15a21bb28c | ||
|
|
96ca1e1d8c | ||
|
|
587cb52191 | ||
|
|
95422ffd46 | ||
|
|
391a883799 | ||
|
|
43d6960df4 | ||
|
|
5c128003d8 | ||
|
|
2f8fd1b14b | ||
|
|
625553c146 | ||
|
|
3b0ce7bad3 |
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -7,3 +7,4 @@
|
||||
/packages/now-go @styfle @sophearak
|
||||
/packages/now-python @styfle @sophearak
|
||||
/packages/now-rust @styfle @mike-engel @anmonteiro
|
||||
/packages/now-ruby @styfle @coetry @nathancahill
|
||||
|
||||
@@ -13,6 +13,15 @@ exports.config = {
|
||||
maxLambdaSize: '10mb',
|
||||
};
|
||||
|
||||
// From this list: https://import.pw/importpw/import/docs/config.md
|
||||
const allowedConfigImports = new Set([
|
||||
'CACHE',
|
||||
'CURL_OPTS',
|
||||
'DEBUG',
|
||||
'RELOAD',
|
||||
'SERVER',
|
||||
]);
|
||||
|
||||
exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
||||
|
||||
exports.build = async ({
|
||||
@@ -24,10 +33,23 @@ exports.build = async ({
|
||||
await download(files, srcDir);
|
||||
|
||||
const configEnv = Object.keys(config).reduce((o, v) => {
|
||||
o[`IMPORT_${snakeCase(v).toUpperCase()}`] = config[v]; // eslint-disable-line no-param-reassign
|
||||
const name = snakeCase(v).toUpperCase();
|
||||
|
||||
if (allowedConfigImports.has(name)) {
|
||||
o[`IMPORT_${name}`] = config[v]; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
return o;
|
||||
}, {});
|
||||
|
||||
if (config && config.import) {
|
||||
Object.keys(config.import).forEach((key) => {
|
||||
const name = snakeCase(key).toUpperCase();
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
configEnv[`IMPORT_${name}`] = config.import[key];
|
||||
});
|
||||
}
|
||||
|
||||
const IMPORT_CACHE = `${workPath}/.import-cache`;
|
||||
const env = Object.assign({}, process.env, configEnv, {
|
||||
PATH: `${IMPORT_CACHE}/bin:${process.env.PATH}`,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/bash",
|
||||
"version": "0.2.3",
|
||||
"version": "0.3.0",
|
||||
"description": "Now 2.0 builder for HTTP endpoints written in Bash",
|
||||
"main": "index.js",
|
||||
"author": "Nathan Rajlich <nate@zeit.co>",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/build-utils",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.3",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PrepareCacheOptions,
|
||||
ShouldServeOptions,
|
||||
Meta,
|
||||
Config,
|
||||
} from './types';
|
||||
import { Lambda, createLambda } from './lambda';
|
||||
import download, { DownloadedFiles } from './fs/download';
|
||||
@@ -52,4 +53,5 @@ export {
|
||||
PrepareCacheOptions,
|
||||
ShouldServeOptions,
|
||||
shouldServe,
|
||||
Config,
|
||||
};
|
||||
|
||||
@@ -16,7 +16,13 @@ export interface Files {
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
[key: string]: string | string[] | boolean | number | undefined;
|
||||
[key: string]:
|
||||
| string
|
||||
| string[]
|
||||
| boolean
|
||||
| number
|
||||
| { [key: string]: string }
|
||||
| undefined;
|
||||
maxLambdaSize?: string;
|
||||
includeFiles?: string | string[];
|
||||
bundle?: boolean;
|
||||
@@ -24,6 +30,8 @@ export interface Config {
|
||||
helpers?: boolean;
|
||||
rust?: string;
|
||||
debug?: boolean;
|
||||
zeroConfig?: boolean;
|
||||
import?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/node",
|
||||
"version": "0.10.1",
|
||||
"version": "0.11.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"repository": {
|
||||
@@ -12,8 +12,7 @@
|
||||
"@now/node-bridge": "1.2.2",
|
||||
"@types/node": "*",
|
||||
"@zeit/ncc": "0.18.5",
|
||||
"@zeit/ncc-watcher": "1.0.3",
|
||||
"fs-extra": "7.0.1"
|
||||
"@zeit/ncc-watcher": "1.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "./build.sh",
|
||||
@@ -26,9 +25,11 @@
|
||||
"devDependencies": {
|
||||
"@types/content-type": "1.1.3",
|
||||
"@types/cookie": "0.3.3",
|
||||
"@types/etag": "1.8.0",
|
||||
"@types/test-listen": "1.1.0",
|
||||
"content-type": "1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"etag": "1.8.1",
|
||||
"node-fetch": "2.6.0",
|
||||
"test-listen": "1.1.0",
|
||||
"typescript": "3.5.2"
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
NowRequestQuery,
|
||||
NowRequestBody,
|
||||
} from './types';
|
||||
import { Stream } from 'stream';
|
||||
import { Server } from 'http';
|
||||
import { Bridge } from './bridge';
|
||||
|
||||
@@ -78,83 +77,122 @@ function status(res: NowResponse, statusCode: number): NowResponse {
|
||||
return res;
|
||||
}
|
||||
|
||||
function setContentHeaders(
|
||||
res: NowResponse,
|
||||
type: string,
|
||||
length?: number
|
||||
): void {
|
||||
if (!res.getHeader('content-type')) {
|
||||
res.setHeader('content-type', type);
|
||||
}
|
||||
|
||||
if (length !== undefined) {
|
||||
res.setHeader('content-length', length);
|
||||
}
|
||||
function setCharset(type: string, charset: string) {
|
||||
const { parse, format } = require('content-type');
|
||||
const parsed = parse(type);
|
||||
parsed.parameters.charset = charset;
|
||||
return format(parsed);
|
||||
}
|
||||
|
||||
function send(res: NowResponse, body: any) {
|
||||
const t = typeof body;
|
||||
|
||||
if (body === null || t === 'undefined') {
|
||||
res.end();
|
||||
return res;
|
||||
}
|
||||
|
||||
if (t === 'string') {
|
||||
setContentHeaders(
|
||||
res,
|
||||
'text/plain; charset=utf-8',
|
||||
Buffer.byteLength(body)
|
||||
);
|
||||
res.end(body);
|
||||
return res;
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(body)) {
|
||||
setContentHeaders(res, 'application/octet-stream', body.length);
|
||||
res.end(body);
|
||||
return res;
|
||||
}
|
||||
|
||||
if (body instanceof Stream) {
|
||||
setContentHeaders(res, 'application/octet-stream');
|
||||
body.pipe(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
switch (t) {
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'bigint':
|
||||
case 'object':
|
||||
return json(res, body);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'`body` is not a valid string, object, boolean, number, Stream, or Buffer'
|
||||
);
|
||||
function createETag(body: any, encoding: string | undefined) {
|
||||
const etag = require('etag');
|
||||
const buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body;
|
||||
return etag(buf, { weak: true });
|
||||
}
|
||||
|
||||
function json(res: NowResponse, jsonBody: any): NowResponse {
|
||||
switch (typeof jsonBody) {
|
||||
case 'object':
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'bigint':
|
||||
function send(req: NowRequest, res: NowResponse, body: any): NowResponse {
|
||||
let chunk: unknown = body;
|
||||
let encoding: string | undefined;
|
||||
|
||||
switch (typeof chunk) {
|
||||
// string defaulting to html
|
||||
case 'string':
|
||||
const body = JSON.stringify(jsonBody);
|
||||
setContentHeaders(
|
||||
res,
|
||||
'application/json; charset=utf-8',
|
||||
Buffer.byteLength(body)
|
||||
);
|
||||
res.end(body);
|
||||
return res;
|
||||
if (!res.getHeader('content-type')) {
|
||||
res.setHeader('content-type', 'text/html');
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'object':
|
||||
if (chunk === null) {
|
||||
chunk = '';
|
||||
} else if (Buffer.isBuffer(chunk)) {
|
||||
if (!res.getHeader('content-type')) {
|
||||
res.setHeader('content-type', 'application/octet-stream');
|
||||
}
|
||||
} else {
|
||||
return json(req, res, chunk);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'`jsonBody` is not a valid object, boolean, string, number, or null'
|
||||
);
|
||||
// write strings in utf-8
|
||||
if (typeof chunk === 'string') {
|
||||
encoding = 'utf8';
|
||||
|
||||
// reflect this in content-type
|
||||
const type = res.getHeader('content-type');
|
||||
if (typeof type === 'string') {
|
||||
res.setHeader('content-type', setCharset(type, 'utf-8'));
|
||||
}
|
||||
}
|
||||
|
||||
// populate Content-Length
|
||||
let len: number | undefined;
|
||||
if (chunk !== undefined) {
|
||||
if (Buffer.isBuffer(chunk)) {
|
||||
// get length of Buffer
|
||||
len = chunk.length;
|
||||
} else if (typeof chunk === 'string') {
|
||||
if (chunk.length < 1000) {
|
||||
// just calculate length small chunk
|
||||
len = Buffer.byteLength(chunk, encoding);
|
||||
} else {
|
||||
// convert chunk to Buffer and calculate
|
||||
const buf = Buffer.from(chunk, encoding);
|
||||
len = buf.length;
|
||||
chunk = buf;
|
||||
encoding = undefined;
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'`body` is not a valid string, object, boolean, number, Stream, or Buffer'
|
||||
);
|
||||
}
|
||||
|
||||
if (len !== undefined) {
|
||||
res.setHeader('content-length', len);
|
||||
}
|
||||
}
|
||||
|
||||
// populate ETag
|
||||
let etag: string | undefined;
|
||||
if (
|
||||
!res.getHeader('etag') &&
|
||||
len !== undefined &&
|
||||
(etag = createETag(chunk, encoding))
|
||||
) {
|
||||
res.setHeader('etag', etag);
|
||||
}
|
||||
|
||||
// strip irrelevant headers
|
||||
if (204 === res.statusCode || 304 === res.statusCode) {
|
||||
res.removeHeader('Content-Type');
|
||||
res.removeHeader('Content-Length');
|
||||
res.removeHeader('Transfer-Encoding');
|
||||
chunk = '';
|
||||
}
|
||||
|
||||
if (req.method === 'HEAD') {
|
||||
// skip body for HEAD
|
||||
res.end();
|
||||
} else {
|
||||
// respond
|
||||
res.end(chunk, encoding);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function json(req: NowRequest, res: NowResponse, jsonBody: any): NowResponse {
|
||||
const body = JSON.stringify(jsonBody);
|
||||
|
||||
// content-type
|
||||
if (!res.getHeader('content-type')) {
|
||||
res.setHeader('content-type', 'application/json; charset=utf-8');
|
||||
}
|
||||
|
||||
return send(req, res, body);
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
@@ -219,8 +257,8 @@ export function createServerWithHelpers(
|
||||
setLazyProp<NowRequestBody>(req, 'body', getBodyParser(req, event.body));
|
||||
|
||||
res.status = statusCode => status(res, statusCode);
|
||||
res.send = body => send(res, body);
|
||||
res.json = jsonBody => json(res, jsonBody);
|
||||
res.send = body => send(req, res, body);
|
||||
res.json = jsonBody => json(req, res, jsonBody);
|
||||
|
||||
await listener(req, res);
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { readFile } from 'fs-extra';
|
||||
import { Assets, NccOptions } from '@zeit/ncc';
|
||||
import { join, dirname, relative, sep } from 'path';
|
||||
import { NccWatcher, WatcherResult } from '@zeit/ncc-watcher';
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
shouldServe,
|
||||
} from '@now/build-utils';
|
||||
export { NowRequest, NowResponse } from './types';
|
||||
import { makeLauncher } from './launcher';
|
||||
|
||||
interface CompilerConfig {
|
||||
includeFiles?: string | string[];
|
||||
@@ -101,7 +101,8 @@ async function compile(
|
||||
assets = result.assets;
|
||||
watch = [...result.files, ...result.dirs, ...result.missing]
|
||||
.filter(f => f.startsWith(workPath))
|
||||
.map(f => relative(workPath, f));
|
||||
.map(f => relative(workPath, f))
|
||||
.concat(Object.keys(assets || {}));
|
||||
} else {
|
||||
const ncc = require('@zeit/ncc');
|
||||
const result = await ncc(input, {
|
||||
@@ -202,28 +203,11 @@ export async function build({
|
||||
config,
|
||||
meta
|
||||
);
|
||||
const launcherPath = join(__dirname, 'launcher.js');
|
||||
let launcherData = await readFile(launcherPath, 'utf8');
|
||||
|
||||
launcherData = launcherData.replace(
|
||||
'// PLACEHOLDER:shouldStoreProxyRequests',
|
||||
shouldAddHelpers ? 'shouldStoreProxyRequests = true;' : ''
|
||||
);
|
||||
|
||||
launcherData = launcherData.replace(
|
||||
'// PLACEHOLDER:setServer',
|
||||
[
|
||||
`let listener = require("./${entrypoint}");`,
|
||||
'if (listener.default) listener = listener.default;',
|
||||
shouldAddHelpers
|
||||
? 'const server = require("./helpers").createServerWithHelpers(listener, bridge);'
|
||||
: 'const server = require("http").createServer(listener);',
|
||||
'bridge.setServer(server);',
|
||||
].join(' ')
|
||||
);
|
||||
|
||||
const launcherFiles: Files = {
|
||||
'launcher.js': new FileBlob({ data: launcherData }),
|
||||
'launcher.js': new FileBlob({
|
||||
data: makeLauncher(entrypoint, shouldAddHelpers),
|
||||
}),
|
||||
'bridge.js': new FileFsRef({ fsPath: require('@now/node-bridge') }),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Bridge } from './bridge';
|
||||
export function makeLauncher(
|
||||
entrypoint: string,
|
||||
shouldAddHelpers: boolean
|
||||
): string {
|
||||
return `const { Bridge } = require("./bridge");
|
||||
|
||||
let shouldStoreProxyRequests: boolean = false;
|
||||
// PLACEHOLDER:shouldStoreProxyRequests
|
||||
|
||||
const bridge = new Bridge(undefined, shouldStoreProxyRequests);
|
||||
let bridge;
|
||||
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV =
|
||||
@@ -11,13 +12,35 @@ if (!process.env.NODE_ENV) {
|
||||
}
|
||||
|
||||
try {
|
||||
// PLACEHOLDER:setServer
|
||||
let listener = require("./${entrypoint}");
|
||||
if (listener.default) listener = listener.default;
|
||||
|
||||
if(typeof listener.listen === 'function') {
|
||||
const server = listener;
|
||||
bridge = new Bridge(server);
|
||||
} else if(typeof listener === 'function') {
|
||||
${
|
||||
shouldAddHelpers
|
||||
? [
|
||||
'bridge = new Bridge(undefined, true);',
|
||||
'const server = require("./helpers").createServerWithHelpers(listener, bridge);',
|
||||
'bridge.setServer(server);',
|
||||
].join('\n')
|
||||
: [
|
||||
'const server = require("http").createServer(listener);',
|
||||
'bridge = new Bridge(server);',
|
||||
].join('\n')
|
||||
}
|
||||
} else {
|
||||
console.error('Export in entrypoint is not valid');
|
||||
console.error('Did you forget to export a function or a server?');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
if (err.code === 'MODULE_NOT_FOUND') {
|
||||
console.error(err.message);
|
||||
console.error(
|
||||
'Did you forget to add it to "dependencies" in `package.json`?'
|
||||
);
|
||||
console.error('Did you forget to add it to "dependencies" in \`package.json\`?');
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.error(err);
|
||||
@@ -27,4 +50,5 @@ try {
|
||||
|
||||
bridge.listen();
|
||||
|
||||
exports.launcher = bridge.launcher;
|
||||
exports.launcher = bridge.launcher;`;
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
const express = require('express');
|
||||
|
||||
const app = express();
|
||||
|
||||
module.exports = app;
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.all('*', (req, res) => {
|
||||
res.status(200);
|
||||
|
||||
let who = 'anonymous';
|
||||
|
||||
if (req.body && req.body.who) {
|
||||
who = req.body.who;
|
||||
}
|
||||
|
||||
res.send(`hello ${who}:RANDOMNESS_PLACEHOLDER`);
|
||||
});
|
||||
@@ -3,7 +3,6 @@
|
||||
"builds": [
|
||||
{ "src": "index.js", "use": "@now/node" },
|
||||
{ "src": "ts/index.ts", "use": "@now/node" },
|
||||
{ "src": "express-compat/index.js", "use": "@now/node" },
|
||||
{ "src": "micro-compat/index.js", "use": "@now/node" },
|
||||
{
|
||||
"src": "no-helpers/index.js",
|
||||
@@ -35,12 +34,6 @@
|
||||
"path": "/ts",
|
||||
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
||||
},
|
||||
{
|
||||
"path": "/express-compat",
|
||||
"method": "POST",
|
||||
"body": { "who": "sara" },
|
||||
"mustContain": "hello sara:RANDOMNESS_PLACEHOLDER"
|
||||
},
|
||||
{
|
||||
"path": "/micro-compat",
|
||||
"method": "POST",
|
||||
|
||||
9
packages/now-node/test/fixtures/16-servers/express/index.js
vendored
Normal file
9
packages/now-node/test/fixtures/16-servers/express/index.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
const express = require('express');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.all('*', (req, res) => {
|
||||
res.send('hello from express:RANDOMNESS_PLACEHOLDER');
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
17
packages/now-node/test/fixtures/16-servers/hapi/index.js
vendored
Normal file
17
packages/now-node/test/fixtures/16-servers/hapi/index.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
const Hapi = require('@hapi/hapi');
|
||||
|
||||
const server = Hapi.server({
|
||||
port: 3000,
|
||||
host: 'localhost',
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/{p*}',
|
||||
handler: () => 'hello from hapi:RANDOMNESS_PLACEHOLDER',
|
||||
});
|
||||
|
||||
// server.listener is a node's http.Server
|
||||
// server does not have the `listen` method so we need to export this instead
|
||||
|
||||
module.exports = server.listener;
|
||||
5
packages/now-node/test/fixtures/16-servers/hapi/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/16-servers/hapi/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@hapi/hapi": "18.3.1"
|
||||
}
|
||||
}
|
||||
7
packages/now-node/test/fixtures/16-servers/index.js
vendored
Normal file
7
packages/now-node/test/fixtures/16-servers/index.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end('hello:RANDOMNESS_PLACEHOLDER');
|
||||
});
|
||||
|
||||
module.exports = server;
|
||||
9
packages/now-node/test/fixtures/16-servers/koa/index.js
vendored
Normal file
9
packages/now-node/test/fixtures/16-servers/koa/index.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
const Koa = require('koa');
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
app.use(async (ctx) => {
|
||||
ctx.body = 'hello from koa:RANDOMNESS_PLACEHOLDER';
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
5
packages/now-node/test/fixtures/16-servers/koa/package.json
vendored
Normal file
5
packages/now-node/test/fixtures/16-servers/koa/package.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"koa": "2.7.0"
|
||||
}
|
||||
}
|
||||
22
packages/now-node/test/fixtures/16-servers/now.json
vendored
Normal file
22
packages/now-node/test/fixtures/16-servers/now.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "**/*.js", "use": "@now/node" }],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "hello:RANDOMNESS_PLACEHOLDER"
|
||||
},
|
||||
{
|
||||
"path": "/express",
|
||||
"mustContain": "hello from express:RANDOMNESS_PLACEHOLDER"
|
||||
},
|
||||
{
|
||||
"path": "/koa",
|
||||
"mustContain": "hello from koa:RANDOMNESS_PLACEHOLDER"
|
||||
},
|
||||
{
|
||||
"path": "/hapi",
|
||||
"mustContain": "hello from hapi:RANDOMNESS_PLACEHOLDER"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -272,304 +272,7 @@ describe('req.body', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('res.send()', () => {
|
||||
test('res.send() should set body to ""', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send();
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.text()).toBe('');
|
||||
});
|
||||
|
||||
test('res.send(null) should set body to ""', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(null);
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.text()).toBe('');
|
||||
});
|
||||
|
||||
test('res.send(undefined) should set body to ""', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(undefined);
|
||||
});
|
||||
const res = await fetchWithProxyReq(url);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.text()).toBe('');
|
||||
});
|
||||
|
||||
test('res.send(String) should send as text/plain', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send('hey');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe('text/plain; charset=utf-8');
|
||||
expect(await res.text()).toBe('hey');
|
||||
});
|
||||
|
||||
test('res.send(String) should not override Content-Type', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.send('hey');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe('text/html');
|
||||
expect(await res.text()).toBe('hey');
|
||||
});
|
||||
|
||||
test('res.send(String) should set Content-Length', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send('½ + ¼ = ¾');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(Number(res.headers.get('content-length'))).toBe(12);
|
||||
expect(await res.text()).toBe('½ + ¼ = ¾');
|
||||
});
|
||||
|
||||
test('res.send(Buffer) should send as octet-stream', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(Buffer.from('hello'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe('application/octet-stream');
|
||||
expect(await res.text()).toBe('hello');
|
||||
});
|
||||
|
||||
test('res.send(Buffer) should not override Content-Type', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.send(Buffer.from('hello'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe('text/plain');
|
||||
expect(await res.text()).toBe('hello');
|
||||
});
|
||||
|
||||
test('res.send(Buffer) should set Content-Length', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(Buffer.from('½ + ¼ = ¾'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(Number(res.headers.get('content-length'))).toBe(12);
|
||||
expect(await res.text()).toBe('½ + ¼ = ¾');
|
||||
});
|
||||
|
||||
test('res.send(Object) should send as application/json', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send({ name: 'tobi' });
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('{"name":"tobi"}');
|
||||
});
|
||||
|
||||
test('res.send(Stream) should send as application/octet-stream', async () => {
|
||||
const { PassThrough } = require('stream');
|
||||
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
const stream = new PassThrough();
|
||||
res.send(stream);
|
||||
stream.push('hello');
|
||||
stream.end();
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe('application/octet-stream');
|
||||
expect(await res.text()).toBe('hello');
|
||||
});
|
||||
|
||||
test('res.send() should send be chainable', async () => {
|
||||
const spy = jest.fn();
|
||||
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
spy(res, res.send('hello'));
|
||||
});
|
||||
|
||||
await fetchWithProxyReq(url);
|
||||
|
||||
const [a, b] = spy.mock.calls[0];
|
||||
expect(a).toBe(b);
|
||||
});
|
||||
});
|
||||
|
||||
describe('res.json()', () => {
|
||||
test('res.json() should not override previous Content-Type', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.setHeader('Content-Type', 'application/vnd.example+json');
|
||||
res.json({ hello: 'world' });
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/vnd.example+json'
|
||||
);
|
||||
expect(await res.text()).toBe('{"hello":"world"}');
|
||||
});
|
||||
|
||||
test('res.json() should send as application/json', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json({ hello: 'world' });
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('{"hello":"world"}');
|
||||
});
|
||||
|
||||
test('res.json() should set Content-Length and Content-Type', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json({ hello: '½ + ¼ = ¾' });
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(Number(res.headers.get('content-length'))).toBe(24);
|
||||
expect(await res.text()).toBe('{"hello":"½ + ¼ = ¾"}');
|
||||
});
|
||||
|
||||
test('res.json(null) should respond with json for null', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json(null);
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('null');
|
||||
});
|
||||
|
||||
test('res.json() should throw an error', async () => {
|
||||
let _err;
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
try {
|
||||
res.json();
|
||||
} catch (err) {
|
||||
_err = err;
|
||||
} finally {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
|
||||
await fetchWithProxyReq(url);
|
||||
expect(_err).toBeDefined();
|
||||
expect(_err.message).toMatch(/not a valid object/);
|
||||
});
|
||||
|
||||
test('res.json(undefined) should throw an error', async () => {
|
||||
let _err;
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
try {
|
||||
res.json(undefined);
|
||||
} catch (err) {
|
||||
_err = err;
|
||||
} finally {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
|
||||
await fetchWithProxyReq(url);
|
||||
expect(_err).toBeDefined();
|
||||
expect(_err.message).toMatch(/not a valid object/);
|
||||
});
|
||||
|
||||
test('res.json(Number) should respond with json for number', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json(300);
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('300');
|
||||
});
|
||||
|
||||
test('res.json(String) should respond with json for string', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json('str');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('"str"');
|
||||
});
|
||||
|
||||
test('res.json(Array) should respond with json for array', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('["foo","bar","baz"]');
|
||||
});
|
||||
|
||||
test('res.json(Object) should respond with json for object', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json({ name: 'tobi' });
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('{"name":"tobi"}');
|
||||
});
|
||||
|
||||
test('res.json() should send be chainable', async () => {
|
||||
const spy = jest.fn();
|
||||
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
spy(res, res.json({ hello: 'world' }));
|
||||
});
|
||||
|
||||
await fetchWithProxyReq(url);
|
||||
|
||||
const [a, b] = spy.mock.calls[0];
|
||||
expect(a).toBe(b);
|
||||
});
|
||||
});
|
||||
|
||||
describe('res.status()', () => {
|
||||
describe('res.status', () => {
|
||||
test('res.status() should set the status code', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.status(404);
|
||||
@@ -595,3 +298,483 @@ describe('res.status()', () => {
|
||||
expect(a).toBe(b);
|
||||
});
|
||||
});
|
||||
|
||||
// tests based on expressjs test suite
|
||||
// see https://github.com/expressjs/express/blob/master/test/res.send.js
|
||||
describe('res.send', () => {
|
||||
test('should be chainable', async () => {
|
||||
const spy = jest.fn();
|
||||
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
spy(res, res.send('hello'));
|
||||
});
|
||||
|
||||
await fetchWithProxyReq(url);
|
||||
|
||||
const [a, b] = spy.mock.calls[0];
|
||||
expect(a).toBe(b);
|
||||
});
|
||||
|
||||
describe('res.send()', () => {
|
||||
test('should set body to ""', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send();
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.send(null)', () => {
|
||||
test('should set body to ""', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(null);
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-length')).toBe('0');
|
||||
expect(await res.text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.send(undefined)', () => {
|
||||
test('should set body to ""', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(undefined);
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.send(String)', () => {
|
||||
test('should send as html', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send('<p>hey</p>');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.headers.get('content-type')).toBe('text/html; charset=utf-8');
|
||||
expect(await res.text()).toBe('<p>hey</p>');
|
||||
});
|
||||
|
||||
test('should set Content-Length', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send('½ + ¼ = ¾');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(Number(res.headers.get('content-length'))).toBe(12);
|
||||
expect(await res.text()).toBe('½ + ¼ = ¾');
|
||||
});
|
||||
|
||||
test('should set ETag', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(Array(1000).join('-'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('ETag')).toBe(
|
||||
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
|
||||
);
|
||||
});
|
||||
|
||||
test('should not override Content-Type', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.send('hey');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
|
||||
expect(await res.text()).toBe('hey');
|
||||
});
|
||||
|
||||
test('should override charset in Content-Type', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain; charset=iso-8859-1');
|
||||
res.send('hey');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
|
||||
expect(await res.text()).toBe('hey');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.send(Buffer)', () => {
|
||||
test('should keep charset in Content-Type', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain; charset=iso-8859-1');
|
||||
res.send(Buffer.from('hi'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('Content-Type')).toBe(
|
||||
'text/plain; charset=iso-8859-1'
|
||||
);
|
||||
expect(await res.text()).toBe('hi');
|
||||
});
|
||||
|
||||
test('should set Content-Length', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(Buffer.from('½ + ¼ = ¾'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(Number(res.headers.get('content-length'))).toBe(12);
|
||||
expect(await res.text()).toBe('½ + ¼ = ¾');
|
||||
});
|
||||
|
||||
test('should send as octet-stream', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(Buffer.from('hello'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('Content-Type')).toBe('application/octet-stream');
|
||||
expect((await res.buffer()).toString('hex')).toBe(
|
||||
Buffer.from('hello').toString('hex')
|
||||
);
|
||||
});
|
||||
|
||||
test('should set ETag', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(Buffer.alloc(999, '-'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('ETag')).toBe(
|
||||
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
|
||||
);
|
||||
});
|
||||
|
||||
test('should not override Content-Type', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||
res.send(Buffer.from('hey'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
|
||||
expect(await res.text()).toBe('hey');
|
||||
});
|
||||
|
||||
test('should not override ETag', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.setHeader('ETag', '"foo"');
|
||||
res.send(Buffer.from('hey'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('ETag')).toBe('"foo"');
|
||||
expect(await res.text()).toBe('hey');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.send(Object)', () => {
|
||||
test('should send as application/json', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send({ name: 'tobi' });
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('Content-Type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('{"name":"tobi"}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the request method is HEAD', () => {
|
||||
test('should ignore the body', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send('yay');
|
||||
});
|
||||
|
||||
// TODO: fix this test
|
||||
// node-fetch is automatically ignoring the body so this test will never fail
|
||||
const res = await fetchWithProxyReq(url, { method: 'HEAD' });
|
||||
expect(res.status).toBe(200);
|
||||
expect((await res.buffer()).toString()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when .statusCode is 204', () => {
|
||||
test('should strip Content-* fields, Transfer-Encoding field, and body', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.statusCode = 204;
|
||||
res.setHeader('Transfer-Encoding', 'chunked');
|
||||
res.send('foo');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(204);
|
||||
expect(res.headers.get('Content-Type')).toBe(null);
|
||||
expect(res.headers.get('Content-Length')).toBe(null);
|
||||
expect(res.headers.get('Transfer-Encoding')).toBe(null);
|
||||
expect(await res.text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when .statusCode is 304', () => {
|
||||
test('should strip Content-* fields, Transfer-Encoding field, and body', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.statusCode = 304;
|
||||
res.setHeader('Transfer-Encoding', 'chunked');
|
||||
res.send('foo');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(304);
|
||||
expect(res.headers.get('Content-Type')).toBe(null);
|
||||
expect(res.headers.get('Content-Length')).toBe(null);
|
||||
expect(res.headers.get('Transfer-Encoding')).toBe(null);
|
||||
expect(await res.text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
// test('should always check regardless of length', async () => {
|
||||
// const etag = '"asdf"';
|
||||
|
||||
// mockListener.mockImplementation((req, res) => {
|
||||
// res.setHeader('ETag', etag);
|
||||
// res.send('hey');
|
||||
// });
|
||||
|
||||
// const res = await fetchWithProxyReq(url, {
|
||||
// headers: { 'If-None-Match': etag },
|
||||
// });
|
||||
// expect(res.status).toBe(304);
|
||||
// });
|
||||
|
||||
// test('should respond with 304 Not Modified when fresh', async () => {
|
||||
// const etag = '"asdf"';
|
||||
|
||||
// mockListener.mockImplementation((req, res) => {
|
||||
// res.setHeader('ETag', etag);
|
||||
// res.send(Array(1000).join('-'));
|
||||
// });
|
||||
|
||||
// const res = await fetchWithProxyReq(url, {
|
||||
// headers: { 'If-None-Match': etag },
|
||||
// });
|
||||
// expect(res.status).toBe(304);
|
||||
// });
|
||||
|
||||
// test('should not perform freshness check unless 2xx or 304', async () => {
|
||||
// const etag = '"asdf"';
|
||||
|
||||
// mockListener.mockImplementation((req, res) => {
|
||||
// res.status(500);
|
||||
// res.setHeader('ETag', etag);
|
||||
// res.send('hey');
|
||||
// });
|
||||
|
||||
// const res = await fetchWithProxyReq(url, {
|
||||
// headers: { 'If-None-Match': etag },
|
||||
// });
|
||||
// expect(res.status).toBe(500);
|
||||
// expect(await res.text()).toBe('hey');
|
||||
// });
|
||||
|
||||
describe('etag', () => {
|
||||
test('should send ETag', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send('kajdslfkasdf');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('ETag')).toBe('W/"c-IgR/L5SF7CJQff4wxKGF/vfPuZ0"');
|
||||
});
|
||||
|
||||
test('should send ETag for empty string response', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send('');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('ETag')).toBe('W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"');
|
||||
});
|
||||
|
||||
test('should send ETag for long response', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send(Array(1000).join('-'));
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('ETag')).toBe(
|
||||
'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"'
|
||||
);
|
||||
});
|
||||
|
||||
test('should not override ETag when manually set', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.setHeader('etag', '"asdf"');
|
||||
res.send('hello');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('ETag')).toBe('"asdf"');
|
||||
});
|
||||
|
||||
test('should not send ETag for res.send()', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.send();
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('ETag')).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// tests based on expressjs test suite
|
||||
// see https://github.com/expressjs/express/blob/master/test/res.json.js
|
||||
describe('res.json', () => {
|
||||
test('should send be chainable', async () => {
|
||||
const spy = jest.fn();
|
||||
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
spy(res, res.json({ hello: 'world' }));
|
||||
});
|
||||
|
||||
await fetchWithProxyReq(url);
|
||||
|
||||
const [a, b] = spy.mock.calls[0];
|
||||
expect(a).toBe(b);
|
||||
});
|
||||
|
||||
test('res.json() should send an empty body', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json();
|
||||
});
|
||||
|
||||
await fetchWithProxyReq(url);
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('');
|
||||
});
|
||||
|
||||
describe('.json(object)', () => {
|
||||
test('should not override previous Content-Types', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.setHeader('content-type', 'application/vnd.example+json');
|
||||
res.json({ hello: 'world' });
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/vnd.example+json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('{"hello":"world"}');
|
||||
});
|
||||
|
||||
test('should set Content-Length and Content-Type', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json({ hello: '½ + ¼ = ¾' });
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(Number(res.headers.get('content-length'))).toBe(24);
|
||||
expect(await res.text()).toBe('{"hello":"½ + ¼ = ¾"}');
|
||||
});
|
||||
|
||||
describe('when given primitives', () => {
|
||||
test('should respond with json for null', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json(null);
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('null');
|
||||
});
|
||||
|
||||
test('should respond with json for Number', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json(300);
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('300');
|
||||
});
|
||||
|
||||
test('should respond with json for String', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json('str');
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('"str"');
|
||||
});
|
||||
});
|
||||
|
||||
test('should respond with json when given an array', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('["foo","bar","baz"]');
|
||||
});
|
||||
|
||||
test('should respond with json when given an object', async () => {
|
||||
mockListener.mockImplementation((req, res) => {
|
||||
res.json({ name: 'tobi' });
|
||||
});
|
||||
|
||||
const res = await fetchWithProxyReq(url);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe(
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
expect(await res.text()).toBe('{"name":"tobi"}');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "@now/ruby",
|
||||
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -12,7 +13,6 @@
|
||||
"url": "https://github.com/zeit/now-builders.git",
|
||||
"directory": "packages/now-ruby"
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "tsc && jest",
|
||||
@@ -20,7 +20,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"execa": "^1.0.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"fs-extra": "^7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "3.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/static-build",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.2",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"files": [
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
getSpawnOptions,
|
||||
Files,
|
||||
BuildOptions,
|
||||
Config,
|
||||
} from '@now/build-utils';
|
||||
|
||||
interface PackageJson {
|
||||
@@ -50,9 +51,16 @@ function validateDistDir(distDir: string, isDev: boolean | undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
function getCommand(pkg: PackageJson, cmd: string) {
|
||||
const scripts = (pkg && pkg.scripts) || {};
|
||||
function getCommand(pkg: PackageJson, cmd: string, config: Config) {
|
||||
// The `dev` script can be `now dev`
|
||||
const nowCmd = `now-${cmd}`;
|
||||
const { zeroConfig } = config;
|
||||
|
||||
if (!zeroConfig && cmd === 'dev') {
|
||||
return nowCmd;
|
||||
}
|
||||
|
||||
const scripts = (pkg && pkg.scripts) || {};
|
||||
|
||||
if (scripts[nowCmd]) {
|
||||
return nowCmd;
|
||||
@@ -106,7 +114,7 @@ export async function build({
|
||||
|
||||
let output: Files = {};
|
||||
const routes: { src: string; dest: string }[] = [];
|
||||
const devScript = getCommand(pkg, 'dev');
|
||||
const devScript = getCommand(pkg, 'dev', config as Config);
|
||||
|
||||
if (meta.isDev && pkg.scripts && pkg.scripts[devScript]) {
|
||||
let devPort = nowDevScriptPorts.get(entrypoint);
|
||||
@@ -174,7 +182,7 @@ export async function build({
|
||||
'See the local development docs: https://zeit.co/docs/v2/deployments/official-builders/static-build-now-static-build/#local-development'
|
||||
);
|
||||
}
|
||||
const buildScript = getCommand(pkg, 'build');
|
||||
const buildScript = getCommand(pkg, 'build', config as Config);
|
||||
console.log(`Running "${buildScript}" script in "${entrypoint}"`);
|
||||
const found = await runPackageJsonScript(
|
||||
entrypointFsDirname,
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [
|
||||
{ "src": "package.json", "use": "@now/static-build" },
|
||||
{ "src": "subdirectory/package.json", "use": "@now/static-build" }
|
||||
{
|
||||
"src": "package.json",
|
||||
"use": "@now/static-build",
|
||||
"config": { "zeroConfig": true }
|
||||
},
|
||||
{
|
||||
"src": "subdirectory/package.json",
|
||||
"use": "@now/static-build",
|
||||
"config": { "zeroConfig": true }
|
||||
}
|
||||
],
|
||||
"probes": [
|
||||
{ "path": "/", "mustContain": "cow:RANDOMNESS_PLACEHOLDER" },
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@@ -969,6 +969,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/etag@1.8.0":
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/etag/-/etag-1.8.0.tgz#37f0b1f3ea46da7ae319bbedb607e375b4c99f7e"
|
||||
integrity sha512-EdSN0x+Y0/lBv7YAb8IU4Jgm6DWM+Bqtz7o5qozl96fzaqdqbdfHS5qjdpFeIv7xQ8jSLyjMMNShgYtMajEHyQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/events@*":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
|
||||
@@ -3292,6 +3299,11 @@ esutils@^2.0.2:
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
|
||||
integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
|
||||
|
||||
etag@1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||
|
||||
exec-series@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/exec-series/-/exec-series-1.0.3.tgz#6d257a9beac482a872c7783bc8615839fc77143a"
|
||||
|
||||
Reference in New Issue
Block a user