diff --git a/.gitignore b/.gitignore index eb03e3e..5a8b2e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules *.log +config/local.json diff --git a/error.js b/error.js new file mode 100644 index 0000000..eb136c7 --- /dev/null +++ b/error.js @@ -0,0 +1,12 @@ +class Boom extends Error { + constructor (message, statusCode = 500) { + super(message); + this.isBoom = true; + this.statusCode = statusCode; + } +} + +Boom.unauthorized = (message) => new Boom(message, 401); +Boom.accepted = (message) => new Boom(message, 202); + +module.exports = Boom; diff --git a/package.json b/package.json index 94e92db..fd59f03 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@parity/parity.js": "^0.2.120", "body-parser": "^1.15.2", + "celebrate": "7.0.4", "config": "1.29.4", "express": "^4.13.3", "js-sha3": "^0.5.5", @@ -29,7 +30,6 @@ "eslint-plugin-promise": "3.6.0", "eslint-plugin-standard": "3.0.1", "mocha": "5.0.0", - "mock-http-server": "0.2.0", - "sinon": "4.2.2" + "mock-http-server": "0.2.0" } } diff --git a/server.js b/server.js index aca5e89..564a7d5 100644 --- a/server.js +++ b/server.js @@ -4,19 +4,30 @@ const config = require('config'); const request = require('request'); const express = require('express'); const bodyParser = require('body-parser'); +const { celebrate, isCelebrate } = require('celebrate'); const keccak256 = require('js-sha3').keccak_256; const Parity = require('@parity/parity.js'); +const validate = require('./validation'); +const boom = require('./error'); + const transport = new Parity.Api.Transport.Http(`http://localhost:${config.get('rpc.port')}`); const api = new Parity.Api(transport); const app = express(); app.use(bodyParser.urlencoded({extended: true})); +// validate secret for every request +app.use((req, res, next) => { + if (keccak256(req.body.secret || '') !== secretHash) { + next(boom.unauthorized('Invalid secret')); + } else { + next(); + } +}); module.exports = app; const reduceObject = (obj, prop) => ({ ...obj, [prop]: true }); const enabledTracks = config.get('enabledTracks').reduce(reduceObject, {}); -const supportedPlatforms = config.get('supportedPlatforms').reduce(reduceObject, {}); const account = { address: config.get('account.address'), @@ -44,22 +55,23 @@ const tracks = { testing: 4 }; -app.post('/push-release/:tag/:commit', handleAsync(async function (req, res) { - if (keccak256(req.body.secret || '') !== secretHash) { - throw new Error('Invalid secret'); +const validateRelease = celebrate({ + params: { + tag: validate.tag, + commit: validate.commit + }, + body: { + secret: validate.secret } +}); +app.post('/push-release/:tag/:commit', validateRelease, handleAsync(async function (req, res) { const { commit, tag } = req.params; console.log(`curl --data "secret=${req.body.secret}" http://localhost:${httpPort}/push-release/${tag}/${commit}`); const isCritical = false; // TODO: should take from Git release notes for stable/beta. - const goodTag = isGoodTag(tag); - if (!goodTag) { - throw new Error(`Invalid tag: ${tag}`); - } - - console.log(`Pushing commit: ${commit} (tag: ${tag}/${goodTag})`); + console.log(`Pushing commit: ${commit} (tag: ${tag})`); const miscBody = await fetchFile(commit, '/util/src/misc.rs'); const branch = match( @@ -71,7 +83,7 @@ app.post('/push-release/:tag/:commit', handleAsync(async function (req, res) { console.log(`Track: ${branch} => ${track} (${tracks[track]}) [enabled: ${enabledTracks[track]}]`); if (!enabledTracks[track]) { - throw new Error(`Track not enabled: ${track}`); + throw boom.accepted(`Track not enabled: ${track}`); } let ethereumMod = await fetchFile(commit, '/ethcore/src/ethereum/mod.rs'); @@ -109,26 +121,28 @@ app.post('/push-release/:tag/:commit', handleAsync(async function (req, res) { res.send(`RELEASE: ${commit}/${track}/${branch}/${forkSupported}`); })); -app.post('/push-build/:tag/:platform', handleAsync(async function (req, res) { - if (keccak256(req.body.secret || '') !== secretHash) { - throw new Error('Invalid secret'); +const validateBuild = celebrate({ + params: { + tag: validate.tag, + platform: validate.platform + }, + body: { + secret: validate.secret, + sha3: validate.sha3, + filename: validate.filename, + commit: validate.commit } - +}); +app.post('/push-build/:tag/:platform', validateBuild, handleAsync(async function (req, res) { const { tag, platform } = req.params; const { commit, filename, sha3 } = req.body; console.log(`curl --data "secret=${req.body.secret}&commit=${commit}&filename=${filename}&sha3=${sha3}" http://localhost:${httpPort}/push-build/${tag}/${platform}`); const url = `${baseUrl}/${tag}/${platform}/${filename}`; - const goodTag = isGoodTag(tag); - const goodPlatform = !!supportedPlatforms[platform]; - const out = `BUILD: ${platform}/${commit} -> ${sha3}/${tag}/${filename}/${goodTag}/${goodPlatform} [${url}]`; + const out = `BUILD: ${platform}/${commit} -> ${sha3}/${tag}/${filename} [${url}]`; console.log(out); - if (sha3 === '' || !goodTag || !goodPlatform) { - throw new Error(`Invalid sha3 (${sha3}), tag (${tag}) or platform (${platform}).`); - } - const body = await fetchFile(commit, '/util/src/misc.rs'); const branch = match( body, @@ -140,12 +154,9 @@ app.post('/push-build/:tag/:platform', handleAsync(async function (req, res) { console.log(`Track: ${branch} => ${track} (${tracks[track]}) [enabled: ${!!enabledTracks[track]}]`); if (!enabledTracks[track]) { - throw new Error(`Track not enabled: ${track}`); + throw boom.accepted(`Track not enabled: ${track}`); } - // make sure the node is running - await getNetwork(); - const registryAddress = await api.parity.registryAddress(); const reg = api.newContract(RegistrarABI, registryAddress); const githubHintAddress = await reg.instance.getAddress.call({}, [githubHint, 'A']); @@ -163,6 +174,27 @@ app.post('/push-build/:tag/:platform', handleAsync(async function (req, res) { res.send(out); })); +// make sure that the errors are added at the end +app.use((err, req, res, next) => { + if (isCelebrate(err)) { + const fields = err.details.map(x => x.path && x.path.join ? x.path.join('.') : x.path); + if (fields.indexOf('platform') !== -1 || fields.indexOf('tag') !== -1) { + res.status(202).send(err.message); + } else { + res.status(400).send(err.message); + } + return; + } + + if (err.isBoom) { + res.status(err.statusCode).send(err.message); + return; + } + + console.error(err); + return res.status(500).send(err.message); +}); + function match (string, pattern, comment) { const match = string.match(pattern); if (!match) { @@ -173,7 +205,7 @@ function match (string, pattern, comment) { } function handleAsync (asyncFn) { - return (req, res) => asyncFn(req, res) + return (req, res, next) => asyncFn(req, res) .then(() => { if (!res.headersSent) { throw new Error('No response from handler'); @@ -181,7 +213,7 @@ function handleAsync (asyncFn) { }) .catch(err => { console.error(err); - res.status(400).end(`Error while processing the request:\n${err.message}\n`); + next(err); }); } @@ -209,10 +241,6 @@ async function getNetwork () { return network; } -function isGoodTag (tag) { - return tag === 'nightly' || /^v[0-9]+\.[0-9]+\.[0-9]+$/.test(tag); -} - function sendTransaction (abi, address, method, args) { let o = api.newContract(abi, address); let tx = { diff --git a/test/server.test.js b/test/server.test.js index e920438..801ac65 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -14,31 +14,42 @@ const server = new ServerMock({ host: 'localhost', port: 8545 }); describe('push-release', () => { it('should reject invalid secret', async () => { let res = await request(app => app - .post('/push-release/v1.9.5/123') + .post('/push-release/v1.9.5/8b749367fd5fea897cee98bd892fff1ce90f8260') .type('form') - .send({ secret: 'xx' }) + .send({ secret: 'xxx' }) ); - expect(res).to.have.status(400); - expect(res.text).to.equal('Error while processing the request:\nInvalid secret\n'); + expect(res).to.have.status(401); + expect(res.text).to.equal('Invalid secret'); }); - it('should reject invalid tags', async () => { + it('should gently reject invalid tags', async () => { const test = async tag => { let res = await request(app => app - .post(`/push-release/${tag}/123`) + .post(`/push-release/${tag}/8b749367fd5fea897cee98bd892fff1ce90f8260`) .type('form') .send({ secret }) ); - expect(res).to.have.status(400); - expect(res.text).to.equal(`Error while processing the request:\nInvalid tag: ${tag}\n`); + expect(res).to.have.status(202); + expect(res.text).to.have.string(`child "tag" fails`); }; await test('xxx'); await test('v1.9.5-ci0'); }); + it('should reject invalid commit', async () => { + let res = await request(app => app + .post('/push-release/v1.7.13/8b749367') + .type('form') + .send({ secret }) + ); + + expect(res).to.have.status(400); + expect(res.text).to.have.string(`child "commit" fails`); + }); + beforeEach(done => server.start(done)); afterEach(done => server.stop(done)); @@ -84,30 +95,71 @@ describe('push-build', () => { let res = await request(app => app .post('/push-build/v1.9.5/x86_64-unknown-linux-gnu') .type('form') - .send({ secret: 'xx' }) + .send({ + secret: 'xxx', + sha3: 'a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba', + commit: '8b749367fd5fea897cee98bd892fff1ce90f8260', + filename: 'parity' + }) ); - expect(res).to.have.status(400); - expect(res.text).to.equal('Error while processing the request:\nInvalid secret\n'); + expect(res).to.have.status(401); + expect(res.text).to.equal('Invalid secret'); }); - it('should reject invalid tags', async () => { + it('should gently reject invalid tags', async () => { const test = async tag => { let res = await request(app => app .post(`/push-build/${tag}/x86_64-unknown-linux-gnu`) .type('form') - .send({ secret, sha3: 'none' }) + .send({ + secret, + sha3: 'a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba', + commit: '8b749367fd5fea897cee98bd892fff1ce90f8260', + filename: 'parity' + }) ); - expect(res).to.have.status(400); - expect(res.text).to.equal( - `Error while processing the request:\nInvalid sha3 (none), tag (${tag}) or platform (x86_64-unknown-linux-gnu).\n`); + expect(res).to.have.status(202); + expect(res.text).to.have.string(`child "tag" fails`); }; await test('xxx'); await test('v1.9.5-ci0'); }); + it('should gently reject invalid platform', async () => { + let res = await request(app => app + .post(`/push-build/nightly/x86_64-debian-linux-gnu`) + .type('form') + .send({ + secret, + sha3: 'a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba', + commit: '8b749367fd5fea897cee98bd892fff1ce90f8260', + filename: 'parity' + }) + ); + + expect(res).to.have.status(202); + expect(res.text).to.have.string(`child "platform" fails`); + }); + + it('should reject missing fields', async () => { + let res = await request(app => app + .post(`/push-build/nightly/x86_64-unknown-linux-gnu`) + .type('form') + .send({ + secret, + sha3: 'a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba', + // commit: '8b749367fd5fea897cee98bd892fff1ce90f8260', + filename: 'parity' + }) + ); + + expect(res).to.have.status(400); + expect(res.text).to.have.string(`child "commit" fails`); + }); + beforeEach(done => server.start(done)); afterEach(done => server.stop(done)); @@ -135,24 +187,25 @@ describe('push-build', () => { .type('form') .send({ secret, - sha3: 'beefcafe', - commit: '8b749367fd5fea897cee98bd892fff1ce90f8260' + sha3: 'a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba', + commit: '8b749367fd5fea897cee98bd892fff1ce90f8260', + filename: 'parity' }) ); expect(res).to.have.status(200); // Githubhint registration - expect(requests[3].method).to.equal('eth_sendTransaction'); - expect(requests[3].params).to.deep.equal([{ - data: '0x02f2008dbeefcafe000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004f687474703a2f2f64316834786c3463723168306d6f2e636c6f756466726f6e742e6e65742f76312e372e31332f7838365f36342d756e6b6e6f776e2d6c696e75782d676e752f756e646566696e65640000000000000000000000000000000000', + expect(requests[2].method).to.equal('eth_sendTransaction'); + expect(requests[2].params).to.deep.equal([{ + data: '0x02f2008da00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004c687474703a2f2f64316834786c3463723168306d6f2e636c6f756466726f6e742e6e65742f76312e372e31332f7838365f36342d756e6b6e6f776e2d6c696e75782d676e752f7061726974790000000000000000000000000000000000000000', from: '0x0066ac7a4608f350bf9a0323d60dde211dfb27c0', gasPrice, to: '0x' }]); // Build registration - expect(requests[5].method).to.equal('eth_sendTransaction'); - expect(requests[5].params).to.deep.equal([{ - data: '0x793b0efb0000000000000000000000008b749367fd5fea897cee98bd892fff1ce90f82607838365f36342d756e6b6e6f776e2d6c696e75782d676e750000000000000000beefcafe00000000000000000000000000000000000000000000000000000000', + expect(requests[4].method).to.equal('eth_sendTransaction'); + expect(requests[4].params).to.deep.equal([{ + data: '0x793b0efb0000000000000000000000008b749367fd5fea897cee98bd892fff1ce90f82607838365f36342d756e6b6e6f776e2d6c696e75782d676e750000000000000000a00ead491c0e47efe4abefeb27ddc6ed8d1ea4daa43683d8e349e1e7459b74ba', from: '0x0066ac7a4608f350bf9a0323d60dde211dfb27c0', gasPrice, to: '0x' diff --git a/test/validation.test.js b/test/validation.test.js new file mode 100644 index 0000000..e2c0ded --- /dev/null +++ b/test/validation.test.js @@ -0,0 +1,8 @@ +const { expect } = require('chai'); +const { tag } = require('../validation'); + +it('should allow nightly', () => { + const result = tag.validate('nightly'); + + expect(result.value).to.equal('nightly'); +}); diff --git a/validation.js b/validation.js new file mode 100644 index 0000000..c728680 --- /dev/null +++ b/validation.js @@ -0,0 +1,18 @@ +const config = require('config'); +const { Joi } = require('celebrate'); + +const commit = Joi.string().hex().length(40).required(); +const filename = Joi.string().min(3).required(); +const platform = Joi.valid(config.get('supportedPlatforms')).required(); +const secret = Joi.string().min(3).required(); +const sha3 = Joi.string().hex().length(64).required(); +const tag = Joi.string().allow('nightly').regex(/^v[0-9]+\.[0-9]+\.[0-9]+$/).required(); + +module.exports = { + commit, + filename, + platform, + secret, + sha3, + tag +}; diff --git a/yarn.lock b/yarn.lock index 495269c..37a91ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -200,6 +200,14 @@ caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" +celebrate@7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/celebrate/-/celebrate-7.0.4.tgz#f1f9d9e24cd80268d81b7f3bfb8444d6ee5cbbe2" + dependencies: + escape-html "1.0.3" + fastseries "1.7.2" + joi "12.x.x" + chai-http@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chai-http/-/chai-http-3.0.0.tgz#5460d8036e1f1a12b0b5b5cbd529e6dc1d31eb4b" @@ -423,10 +431,6 @@ diff@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" -diff@^3.1.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" - doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -466,7 +470,7 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -escape-html@~1.0.3: +escape-html@1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -684,6 +688,13 @@ fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" +fastseries@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/fastseries/-/fastseries-1.7.2.tgz#d22ce13b9433dff3388d91dbd6b8bda9b21a0f4b" + dependencies: + reusify "^1.0.0" + xtend "^4.0.0" + fd-slicer@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" @@ -769,12 +780,6 @@ form-data@~2.3.1: combined-stream "^1.0.5" mime-types "^2.1.12" -formatio@1.2.0, formatio@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb" - dependencies: - samsam "1.x" - formidable@^1.0.17: version "1.1.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" @@ -1010,14 +1015,16 @@ is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" +isemail@3.x.x: + version "3.1.1" + resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.1.tgz#e8450fe78ff1b48347db599122adcd0668bd92b5" + dependencies: + punycode "2.x.x" + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -1026,6 +1033,14 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" +joi@12.x.x: + version "12.0.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-12.0.0.tgz#46f55e68f4d9628f01bbb695902c8b307ad8d33a" + dependencies: + hoek "4.x.x" + isemail "3.x.x" + topo "2.x.x" + js-sha3@^0.5.5, js-sha3@~0.5.2: version "0.5.7" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" @@ -1074,10 +1089,6 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -just-extend@^1.1.26: - version "1.1.27" - resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905" - levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -1105,22 +1116,10 @@ lodash.cond@^4.3.0: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - lodash@^4.17.4, lodash@^4.3.0: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" -lolex@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" - -lolex@^2.2.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.2.tgz#85f9450425103bf9e7a60668ea25dc43274ca807" - lru-cache@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" @@ -1224,16 +1223,6 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" -nise@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.2.tgz#9aa5edb500da38035884106e3c571341bc68b2c1" - dependencies: - formatio "^1.2.0" - just-extend "^1.1.26" - lolex "^1.6.0" - path-to-regexp "^1.7.0" - text-encoding "^0.6.4" - node-fetch@~1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04" @@ -1347,12 +1336,6 @@ path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" -path-to-regexp@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" - dependencies: - isarray "0.0.1" - path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -1418,6 +1401,10 @@ pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +punycode@2.x.x: + version "2.1.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" + punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -1517,6 +1504,10 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" +reusify@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + rimraf@^2.2.8: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" @@ -1543,10 +1534,6 @@ safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, s version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" -samsam@1.x: - version "1.3.0" - resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" - "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" @@ -1600,18 +1587,6 @@ signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" -sinon@4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.2.2.tgz#e039ab27bdb426fc61363c380726e996a2e2c620" - dependencies: - diff "^3.1.0" - formatio "1.2.0" - lodash.get "^4.4.2" - lolex "^2.2.0" - nise "^1.2.0" - supports-color "^5.1.0" - type-detect "^4.0.5" - slice-ansi@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" @@ -1732,12 +1707,6 @@ supports-color@^4.0.0: dependencies: has-flag "^2.0.0" -supports-color@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.1.0.tgz#058a021d1b619f7ddf3980d712ea3590ce7de3d5" - dependencies: - has-flag "^2.0.0" - table@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" @@ -1749,10 +1718,6 @@ table@^4.0.1: slice-ansi "1.0.0" string-width "^2.1.1" -text-encoding@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" - text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -1767,6 +1732,12 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +topo@2.x.x: + version "2.0.2" + resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182" + dependencies: + hoek "4.x.x" + tough-cookie@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" @@ -1789,7 +1760,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0, type-detect@^4.0.5: +type-detect@^4.0.0: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -1863,6 +1834,10 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" +xtend@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"