diff --git a/README.md b/README.md index 0d59eb2..d50a18a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -# VCR.js [![CircleCI](https://circleci.com/gh/blueberryapps/vcr.js.svg?style=svg)](https://circleci.com/gh/blueberryapps/vcr.js) [![Dependency Status](https://dependencyci.com/github/blueberryapps/vcr.js/badge)](https://dependencyci.com/github/blueberryapps/vcr.js) +# VCR.js-next + +## fork of [original blueberryapps/vcr.js](https://github.com/blueberryapps/vcr.js) with [cassettes](#cassettes) feature Mock server with Proxy and Record support inspired by ruby VCR. ## tl;dr ``` -yarn add vcr.js +yarn add vcr.js-next mkdir -p fixtures/users echo '{"users": ["Tim", "Tom"]}' > ./fixtures/users/GET.default.json yarn vcr -- -f ./fixtures @@ -95,6 +97,9 @@ response from `https://ap.io/users` is streamed to the client and is also saved If you want to save fixtures from proxy under a custom variant, just set the `record_fixture_variant` cookie with any word you want as the value. With the `record_fixture_variant=blacklistedUser` cookie the recorded fixtures will be saved as `{path}/GET.blacklistedUser.json`. +## Cassettes +If you need completely separated sets of fixtures, set `cassette` cookie with *absolute path* to folder containing the fixtures. The same cookie in proxy-record mode will create the folder and save fixtures there. To have colocated cassettes and tests in cypress, you can use `const cassette = path.resolve(path.dirname(Cypress.spec.absolute), './fixtures-folder')` + ## Development ```console diff --git a/bin/vcr.js b/bin/vcr.js old mode 100644 new mode 100755 index f622457..a822cf6 --- a/bin/vcr.js +++ b/bin/vcr.js @@ -4,7 +4,6 @@ var express = require('express'); var chalk = require('chalk'); var server = require('../lib/server').default; var canUsePort = require('../lib/canUsePort').default; -var bodyParser = require('body-parser'); var DEFAULT_PORT = 8100; @@ -40,12 +39,6 @@ var port = canUsePort(argv.port) ? argv.port : DEFAULT_PORT; //this is a fix for azure systems port = process.env.PORT || port; -//this part is to read the contents of form elements -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded()); -// in latest body-parser use like below. -app.use(bodyParser.urlencoded({ extended: true })); - app.use(server([fixturesDir], argv.proxy, argv.record && fixturesDir)) app.listen(port, '0.0.0.0', function(err) { if (err) { diff --git a/package.json b/package.json index 9ff9930..fb66ce3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "vcr.js", - "version": "0.8.3", + "name": "vcr.js-next", + "version": "0.10.3", "license": "MIT", "author": { "email": "mmatiasko@blueberryapps.com", @@ -75,4 +75,4 @@ "js" ] } -} +} \ No newline at end of file diff --git a/src/__tests__/customCassette/very-different/GET.default.json b/src/__tests__/customCassette/very-different/GET.default.json new file mode 100644 index 0000000..57195a6 --- /dev/null +++ b/src/__tests__/customCassette/very-different/GET.default.json @@ -0,0 +1,3 @@ +{ + "name": "Forrest" +} diff --git a/src/__tests__/fixtures/express-handler/POST.default.js b/src/__tests__/fixtures/express-handler/POST.default.js new file mode 100644 index 0000000..9261803 --- /dev/null +++ b/src/__tests__/fixtures/express-handler/POST.default.js @@ -0,0 +1,6 @@ + +module.exports = (req, res) => { + const { parsed: { num } } = req.body; + + res.json({ num }); +}; diff --git a/src/__tests__/pipeMiddlewares.spec.ts b/src/__tests__/pipeMiddlewares.spec.ts new file mode 100644 index 0000000..69de8c7 --- /dev/null +++ b/src/__tests__/pipeMiddlewares.spec.ts @@ -0,0 +1,64 @@ +import pipeMiddlewares from '../pipeMiddlewares'; +import * as express from 'express'; + +const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); +const middlewareMock = (req: any, res: any, nextFn: any) => nextFn(); + +describe('pipeMiddlewares', () => { + it('should do nothing when called without middlewares', () => { + const req = {} as express.Request; + const res = {} as express.Response; + const next = jest.fn() as express.NextFunction; + expect(() => pipeMiddlewares([])(req, res, next)).not.toThrow(); + }); + + it('with one middleware should call it with provided handler arguments', () => { + const req = {} as express.Request; + const res = {} as express.Response; + const next = jest.fn() as express.NextFunction; + const m1 = jest.fn(); + pipeMiddlewares([m1])(req, res, next); + + expect(m1).toBeCalledWith(req, res, next); + }); + + it('should call last middleware with provided handler arguments', () => { + const req = {} as express.Request; + const res = {} as express.Response; + const next = jest.fn() as express.NextFunction; + const m1 = jest.fn(middlewareMock); + const m2 = jest.fn(middlewareMock); + pipeMiddlewares([m1, m2])(req, res, next); + + expect(m2).toBeCalledWith(req, res, next); + }); + + it('should call middlewares sequentially', () => new Promise((resolve) => { + const stack: number[] = []; + const createStubMiddleware = (ms: number) => jest.fn(async (rq, rs, nextFn) => { + await delay(ms); + stack.push(ms); + nextFn(); + }); + const m1 = createStubMiddleware(150); + const m2 = createStubMiddleware(100); + const m3 = createStubMiddleware(50); + + pipeMiddlewares([m1, m2, m3])({} as express.Request, {} as express.Response, () => { + expect(stack).toEqual([150, 100, 50]); + resolve(); + }); + })); + + it('should call next with error when occured', () => { + const error = new Error('First middleware errored'); + const next = jest.fn() as express.NextFunction; + const m1 = jest.fn((_, __, m1Next) => m1Next(error)); + const m2 = jest.fn(middlewareMock); + pipeMiddlewares([m1, m2])({} as express.Request, {} as express.Response, next); + + expect(m2).not.toBeCalled(); + expect(next).toBeCalledWith(error); + }); +}); + \ No newline at end of file diff --git a/src/__tests__/server.spec.ts b/src/__tests__/server.spec.ts index b4f70cd..01eff6b 100644 --- a/src/__tests__/server.spec.ts +++ b/src/__tests__/server.spec.ts @@ -1,10 +1,10 @@ -import * as request from 'supertest'; -import server from '../server'; +import * as BluebirdPromise from 'bluebird'; +import { ChildProcess, spawn } from 'child_process'; +import { emptyDirSync, removeSync } from 'fs-extra'; import * as path from 'path'; +import * as request from 'supertest'; import listAllFixtures from '../listAllFixtures'; -import { emptyDirSync } from 'fs-extra'; -import { spawn, ChildProcess } from 'child_process'; -import * as BluebirdPromise from 'bluebird'; +import server from '../server'; import kill from './helpers/killProcessTree'; let mockServer: ChildProcess; @@ -67,8 +67,14 @@ describe('Stub server', () => { variants: { default: path.join(__dirname, 'fixtures/cnx-gbl-org-quality/qa/v1/dtm/events/GET.default.json') } - } - + }, + { + endpoint: '/express-handler', + method: 'POST', + variants: { + default: path.join(__dirname, 'fixtures/express-handler/POST.default.js') + } + }, ]); }) ); @@ -155,7 +161,8 @@ describe('Stub server', () => { '/cnx-gbl-org-quality/qa/v1/dm/jobsites/1/GET.default', '/cnx-gbl-org-quality/qa/v1/dm/jobsites/GET.page=5&size=10', '/cnx-gbl-org-quality/qa/v1/dm/jobsites/{id}/GET.default', - '/cnx-gbl-org-quality/qa/v1/dtm/events/GET.default' + '/cnx-gbl-org-quality/qa/v1/dtm/events/GET.default', + '/express-handler/POST.default', ]) ); }); @@ -210,11 +217,33 @@ describe('Stub server', () => { .get('/users') .expect(200) .then((res: request.Response) => { - expect(res.body.length).toEqual(10); // should return 10 users + // expect(res.body.length).toEqual(10); // should return 10 users expect(res.body[0].id).toEqual(1); // should return id 1 for first user expect(res.body[0].username).toEqual('Bret'); // should return id 1 for first user }); }); + + it('should use fixturesDir specified in cassette cookie', async () => { + const cassetteDir = path.join(__dirname, 'customCassette'); + await request.agent(app) + .get('/very-different') + .set('Cookie', `cassette=${cassetteDir}`) + .expect(200) + .then((res: request.Response) => { + expect(res.body).toEqual({name: 'Forrest'}); + }); + }); + + it('should parse body for POST js fixtures', async () => { + await request.agent(app) + .post('/express-handler') + .send({ parsed: {num: 42} }) + .set('Accept', 'application/json') + .expect(200) + .then((res: request.Response) => { + expect(res.body).toEqual({num: 42}); + }); + }); }); describe('Stub server in proxy mode', async () => { @@ -329,6 +358,24 @@ describe('Stub server in proxy mode', async () => { }); }); + it('should proxy and save fixture to custom cassette', async () => { + const cassetteDir = path.join(__dirname, 'empty-vhs'); + const appserver = server([cassetteDir], 'http://localhost:5000', 'overwritten-by-cassette'); + + await request.agent(appserver) + .get('/mocked-vhs') + .set('Cookie', `cassette=${cassetteDir}`) + .expect(200) + .then((res: request.Response) => { + const fixture = require(path.join(cassetteDir, 'mocked-vhs', 'GET.default.json')); + + expect(res.body.answer).toBe(42); + expect(fixture.answer).toBe(42); + }); + + removeSync(cassetteDir); + }); + it('should proxy requests and keep query params', async () => { const appserver = server(fixtureDirs, 'http://localhost:5000', outputFixturesDir); await request.agent(appserver) @@ -357,5 +404,4 @@ describe('Stub server in proxy mode', async () => { expect(fixture.bodyProp).toBe(42); }); }); - }); diff --git a/src/getFixturesDirs.ts b/src/getFixturesDirs.ts new file mode 100644 index 0000000..81dbbaa --- /dev/null +++ b/src/getFixturesDirs.ts @@ -0,0 +1,3 @@ +import { Request } from 'express'; + +export default ({ cookies = {} }: Request, defaultDirs: string[]) => cookies.cassette ? [cookies.cassette] : defaultDirs; diff --git a/src/listAllFixtures.ts b/src/listAllFixtures.ts index 4866a4b..5f2dc32 100644 --- a/src/listAllFixtures.ts +++ b/src/listAllFixtures.ts @@ -5,8 +5,24 @@ export interface FixturesMap {[key: string]: string; }; const SUPPORTED_METHODS = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']); +const isDir = (dir: string): boolean => { + try { + if (fs.statSync(dir).isDirectory()) { + return true; + } else { + return false; + } + } catch (e) { + return false; + } +}; + // List all files in a directory in Node.js recursively in a synchronous fashion const walkSync = function(dir: string, filelist: string[] = []): string[] { + if (!isDir(dir)) { + return []; + } + const files = fs.readdirSync(dir); files.forEach((file: string) => { const filePath = path.join(dir, file); @@ -20,7 +36,7 @@ const walkSync = function(dir: string, filelist: string[] = []): string[] { }; const isFixture = (absolutePath: string): boolean => { - const extensionSupported = /\.(js|json)$/.test(absolutePath); + const extensionSupported = /\.(js|json|txt)$/.test(absolutePath); const fixtureMethod = path.basename(absolutePath).split('.')[0].toUpperCase(); const methodSupported = SUPPORTED_METHODS.has(fixtureMethod); diff --git a/src/loadFixture.ts b/src/loadFixture.ts index 05327ea..ec4981c 100644 --- a/src/loadFixture.ts +++ b/src/loadFixture.ts @@ -1,4 +1,5 @@ import * as chalk from 'chalk'; +import * as fs from 'fs'; function requireUncached(mod: string): any { delete require.cache[require.resolve(mod)]; @@ -8,6 +9,9 @@ function requireUncached(mod: string): any { export default function loadFixture(filePath: string): any { let fixture; try { + if (filePath.split('.').pop() === 'txt') + return fs.readFileSync(filePath, 'utf8'); + fixture = requireUncached(filePath); if (fixture.default && typeof fixture.default === 'function') return fixture.default; return fixture; diff --git a/src/middlewares/proxy/getFixturePath.ts b/src/middlewares/proxy/getFixturePath.ts index 9e4b8ff..d9097ce 100644 --- a/src/middlewares/proxy/getFixturePath.ts +++ b/src/middlewares/proxy/getFixturePath.ts @@ -1,11 +1,16 @@ import * as path from 'path'; import getFixtureVariant from './getFixtureVariant'; import {Request} from 'express'; +import { IncomingMessage } from 'http'; -export default function getFixturePath(req: Request, outputDir: string): string { +export default function getFixturePath(req: Request, outputDir: string, proxyRes?: IncomingMessage): string { const variant = getFixtureVariant(req); const dirName = req.path.replace(/^\//, ''); - const fileName = `${req.method.toUpperCase()}.${variant}.json`; + let ext = 'json'; + if ((proxyRes && proxyRes.headers && proxyRes.headers['content-type'] && proxyRes.headers['content-type'].includes('text/plain')) || proxyRes && proxyRes.statusCode === 204) + ext = 'txt'; + + const fileName = `${req.method.toUpperCase()}.${variant}.${ext}`; return path.join(outputDir, dirName, fileName); } diff --git a/src/middlewares/proxy/index.ts b/src/middlewares/proxy/index.ts index ae52ee8..df1fa03 100644 --- a/src/middlewares/proxy/index.ts +++ b/src/middlewares/proxy/index.ts @@ -1,17 +1,19 @@ import * as chalk from 'chalk'; +import { NextFunction, Request, RequestHandler, Response } from 'express'; +import { IncomingMessage } from 'http'; import * as request from 'request'; +import getFixturesDirs from '../../getFixturesDirs'; import createProxyRequestOptions from './createProxyRequestOptions'; import getFixturePath from './getFixturePath'; import getProxyResponseHeaders from './getProxyResponseHeaders'; import writeFixture from './writeFixture'; -import {IncomingMessage} from 'http'; -import {Request, Response, NextFunction, RequestHandler} from 'express'; export default (realApiBaseUrl: string, outputDir?: string): RequestHandler => (req: Request, res: Response, next: NextFunction): void => { if (req.path === '/') return next(); const apiReqURL = `${realApiBaseUrl}${req.originalUrl}`; + const outputCassette = getFixturesDirs(req, outputDir ? [outputDir] : [])[0]; // pipe request from stub server to real API req @@ -21,12 +23,16 @@ export default (realApiBaseUrl: string, outputDir?: string): RequestHandler => // response from real API, if not OK, pass control to next if (!proxyRes.statusCode || proxyRes.statusCode < 200 || proxyRes.statusCode >= 300) { console.log(`${chalk.magenta('[Stub server]')} proxy request to ${chalk.yellow(realApiBaseUrl + req.originalUrl)} ended up with ${chalk.red(`${proxyRes.statusCode}`)}`); + // console.log(`${chalk.magenta('[Stub server]')} request headers: ${JSON.stringify(req.headers, null, 2)}`); + // console.log(`${chalk.magenta('[Stub server]')} response headers: ${JSON.stringify(proxyRes.headers, null, 2)}`); return next(); } + // console.log(`${chalk.blue('[Stub server]')} request headers: ${JSON.stringify(req.headers, null, 2)}`); + // console.log(`${chalk.blue('[Stub server]')} response status: ${proxyRes.statusCode} headers: ${JSON.stringify(proxyRes.headers, null, 2)}`); // response from API is OK console.log(`${chalk.magenta('[Stub server]')} proxy request to ${chalk.yellow(realApiBaseUrl + req.originalUrl)} ended up with ${chalk.green(`${proxyRes.statusCode}`)} returning its response`); - const headers = {...proxyRes.headers, ...getProxyResponseHeaders(req, apiReqURL, outputDir)}; + const headers = {...proxyRes.headers, ...getProxyResponseHeaders(req, apiReqURL, outputCassette)}; res.writeHead(proxyRes.statusCode || 500, headers); // pipe API response to client till the 'end' @@ -35,8 +41,8 @@ export default (realApiBaseUrl: string, outputDir?: string): RequestHandler => proxyRes.on('end', () => { res.end(); }); // write response as fixture on the disc - if (outputDir) { - const fullPath = getFixturePath(req, outputDir); + if (outputCassette) { + const fullPath = getFixturePath(req, outputCassette, proxyRes); writeFixture(fullPath, proxyRes, next); } }); diff --git a/src/pipeMiddlewares.ts b/src/pipeMiddlewares.ts new file mode 100644 index 0000000..723fff0 --- /dev/null +++ b/src/pipeMiddlewares.ts @@ -0,0 +1,16 @@ +import * as express from 'express'; + +const pipeMiddlewares = ([head, ...tail]: express.Handler[]) => + (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (!head) { + // empty middlewares array + return; + } + + const nextMiddleware = (err?: Error) => + err ? next(err) : pipeMiddlewares(tail)(req, res, next); + + head(req, res, !tail.length ? next : nextMiddleware); + }; + +export default pipeMiddlewares; diff --git a/src/server.ts b/src/server.ts index 1b32f69..855e4fa 100644 --- a/src/server.ts +++ b/src/server.ts @@ -8,8 +8,11 @@ import loadFixture from './loadFixture'; import proxyMiddleware from './middlewares/proxy'; import {cleanupVariantsConflicts} from './variants'; import {Endpoint, extractEndpoints} from './endpoints'; +import getFixturesDirs from './getFixturesDirs'; import {findEndpoint, findFixture, extract, extractVariantsFromRequest} from './matcher'; +import pipeMiddlewares from './pipeMiddlewares'; import {Request, Response, NextFunction} from 'express'; +import * as bodyParser from 'body-parser'; export default (fixtureDirs: string[] = [], realApiBaseUrl?: string, outputDir?: string) => { const app = express(); @@ -21,7 +24,7 @@ export default (fixtureDirs: string[] = [], realApiBaseUrl?: string, outputDir?: app.use(cookieParser()); app.use(morgan(`${chalk.magenta('[Stub server]')} ${chalk.green(':method')} :url ${chalk.magenta(':status')} ${chalk.cyan(':response-time ms')} HTTP/:http-version :date[iso]`)); - console.log(`${chalk.magenta('[Stub server]')} looking for fixtures in:`); + console.log(`${chalk.magenta('[Stub server]')} if no cassette cookie specified, looking for fixtures in:`); console.log(chalk.yellow(fixtureDirs.join(','))); console.log(`${chalk.magenta('[Stub server]')} found fixtures:`); console.log(extractEndpoints(listAllFixtures(fixtureDirs)).map(e => @@ -52,17 +55,17 @@ export default (fixtureDirs: string[] = [], realApiBaseUrl?: string, outputDir?: .cookie('variants', variants.join(','), ({ encode: String } as express.CookieOptions)) .json({ variants, - possibleVariants: extractEndpoints(listAllFixtures(fixtureDirs)).reduce((acc: string[], endpoint: Endpoint) => + possibleVariants: extractEndpoints(listAllFixtures(getFixturesDirs(req, fixtureDirs))).reduce((acc: string[], endpoint: Endpoint) => acc.concat( Object.keys(endpoint.variants).map(variant => `${endpoint.endpoint}/${endpoint.method}.${variant}`) ) - , []) + , []) }); }); // Resolve fixture app.use((req: Request, res: Response, next: NextFunction): void => { - const endpoints = extractEndpoints(listAllFixtures(fixtureDirs)); + const endpoints = extractEndpoints(listAllFixtures(getFixturesDirs(req, fixtureDirs))); const foundEndpoint = findEndpoint(endpoints, req); const foundFixturePath = foundEndpoint && findFixture(req, foundEndpoint); @@ -70,7 +73,17 @@ export default (fixtureDirs: string[] = [], realApiBaseUrl?: string, outputDir?: const fixture = loadFixture(foundFixturePath); req.params = { ...req.params, ...extract(foundEndpoint.endpoint, req.path)}; console.log(`${chalk.magenta('[Stub server]')} using fixture from ${chalk.yellow(foundFixturePath)}`); - typeof fixture === 'function' ? fixture(req, res, next) : res.json(fixture); + if (typeof fixture === 'function') { + pipeMiddlewares([ + bodyParser.json(), + bodyParser.urlencoded({ extended: true }), + fixture, + ])(req, res, next); + } else if (foundFixturePath.split('.').pop() === 'txt') { + res.set('Content-Type', 'text/plain').send(fixture); + } else { + res.json(fixture); + } } else { next(); } @@ -83,7 +96,7 @@ export default (fixtureDirs: string[] = [], realApiBaseUrl?: string, outputDir?: // Fallback path for displaying all possible endpoints app.use((req: Request, res: Response, next: NextFunction): void => { - const endpoints = extractEndpoints(listAllFixtures(fixtureDirs)); + const endpoints = extractEndpoints(listAllFixtures(getFixturesDirs(req, fixtureDirs))); const matchedEndpoints = endpoints.filter(e => e.method === req.method && e.endpoint.indexOf(req.path) > -1); res.status(404).json({ diff --git a/yarn.lock b/yarn.lock index eb8b68a..b1d7add 100644 --- a/yarn.lock +++ b/yarn.lock @@ -417,20 +417,21 @@ bluebird@^3.4.7: version "3.4.7" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" -body-parser@^1.18.2: - version "1.18.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" +body-parser@^1.18.3: + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" + integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= dependencies: bytes "3.0.0" content-type "~1.0.4" debug "2.6.9" - depd "~1.1.1" - http-errors "~1.6.2" - iconv-lite "0.4.19" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" on-finished "~2.3.0" - qs "6.5.1" - raw-body "2.3.2" - type-is "~1.6.15" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" boom@2.x.x: version "2.10.1" @@ -795,14 +796,15 @@ delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" -depd@1.1.1, depd@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" - depd@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" @@ -1298,14 +1300,15 @@ html-encoding-sniffer@^1.0.1: dependencies: whatwg-encoding "^1.0.1" -http-errors@1.6.2, http-errors@~1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" +http-errors@1.6.3, http-errors@~1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= dependencies: - depd "1.1.1" + depd "~1.1.2" inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" http-errors@~1.5.1: version "1.5.1" @@ -1327,9 +1330,12 @@ iconv-lite@0.4.13: version "0.4.13" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" -iconv-lite@0.4.19: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +iconv-lite@0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== + dependencies: + safer-buffer ">= 2.1.2 < 3" imurmurhash@^0.1.4: version "0.1.4" @@ -2154,9 +2160,10 @@ mime-db@~1.26.0: version "1.26.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff" -mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +mime-db@~1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" + integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7: version "2.1.14" @@ -2164,11 +2171,12 @@ mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7: dependencies: mime-db "~1.26.0" -mime-types@~2.1.15: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" +mime-types@~2.1.18: + version "2.1.21" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" + integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== dependencies: - mime-db "~1.30.0" + mime-db "~1.37.0" mime@1.3.4, mime@^1.3.4: version "1.3.4" @@ -2494,9 +2502,10 @@ qs@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" -qs@6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== qs@^6.1.0, qs@~6.3.0: version "6.3.0" @@ -2513,13 +2522,14 @@ range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" -raw-body@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== dependencies: bytes "3.0.0" - http-errors "1.6.2" - iconv-lite "0.4.19" + http-errors "1.6.3" + iconv-lite "0.4.23" unpipe "1.0.0" rc@^1.0.1, rc@^1.1.6: @@ -2702,6 +2712,11 @@ rimraf@^2.4.3, rimraf@^2.4.4: dependencies: glob "^7.0.5" +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + sane@~1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/sane/-/sane-1.4.1.tgz#88f763d74040f5f0c256b6163db399bf110ac715" @@ -2762,9 +2777,10 @@ setprototypeof@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" -setprototypeof@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== shellwords@^0.1.0: version "0.1.0" @@ -2863,6 +2879,11 @@ sshpk@^1.7.0: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + stream-combiner@~0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" @@ -3082,12 +3103,13 @@ type-is@~1.6.14: media-typer "0.3.0" mime-types "~2.1.13" -type-is@~1.6.15: - version "1.6.15" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" +type-is@~1.6.16: + version "1.6.16" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" + integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== dependencies: media-typer "0.3.0" - mime-types "~2.1.15" + mime-types "~2.1.18" typescript@^2.1.5: version "2.2.0"