diff --git a/README.md b/README.md index 4c54e49..274e181 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ To ensure only valid updates are processed, all requests must provide an authent The configuration is managed in `config` directory. [The `default.json`](config/default.json) file as the name suggested is used as a base configuration, every entry can be overwritten in env-specific config files (see [`kovan.json`](config/kovan.json) as an example). Feel free to add your override to that directory. -To load a specific config file run with `NODE_ENV` environment variable set to the name of the config file. E.g. `NODE_ENV="kovan" node server.js` +To load a specific config file run with `NODE_ENV` environment variable set to the name of the config file. E.g. `NODE_ENV="kovan" node index.js` will run the server using `kovan.json` settings. Some parameters can also be overridden by environment variables. See [`config/custom-environment-variables.json`](config/custom-environment-variables.json) for all possible options. Notable ones: diff --git a/config/default.json b/config/default.json index f5954df..c44d2cc 100644 --- a/config/default.json +++ b/config/default.json @@ -11,7 +11,7 @@ "gasPrice": "0x4F9ACA000" }, "enabledTracks": ["stable", "beta"], - "secretHash": "ffa69b8d6bc6f7466e51ff21931295be5d5234dafc5f3ff034f68d59918744c4", + "secretHash": "9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658", "repository": "paritytech/parity", "assetsBaseUrl": "http://d1h4xl4cr1h0mo.cloudfront.net", "supportedPlatforms": ["x86_64-apple-darwin", "x86_64-pc-windows-msvc", "x86_64-unknown-linux-gnu"] diff --git a/config/kovan.json b/config/kovan.json index f53e839..6f00b2c 100644 --- a/config/kovan.json +++ b/config/kovan.json @@ -9,5 +9,6 @@ "address": "0x004E9a7d60af10f7A8866b868e36b86D4A9bA450", "gasPrice": null }, + "secretHash": "ffa69b8d6bc6f7466e51ff21931295be5d5234dafc5f3ff034f68d59918744c4", "enabledTracks": ["stable", "beta", "nightly", "master", "testing"] } diff --git a/config/production.json b/config/production.json new file mode 100644 index 0000000..0d3b22c --- /dev/null +++ b/config/production.json @@ -0,0 +1,3 @@ +{ + "secretHash": "ffa69b8d6bc6f7466e51ff21931295be5d5234dafc5f3ff034f68d59918744c4" +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..1cd022d --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +const config = require('config'); +const httpPort = config.get('http.port'); +const app = require('./server'); + +const server = app.listen(httpPort, function () { + const host = server.address().address; + const port = server.address().port; + console.log('push-release service listening at http://%s:%s', host, port); +}); diff --git a/package.json b/package.json index 03f868f..94e92db 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,11 @@ "version": "0.1.0", "description": "Web service to push a new release onto the chain", "author": "Parity Technologies ", - "main": "server.js", + "main": "index.js", "scripts": { - "start": "node server.js", - "test": "npm run lint", + "start": "node index.js", + "test": "npm run lint && npm run test:integration", + "test:integration": "mocha --exit", "lint": "eslint *.js" }, "dependencies": { @@ -18,12 +19,17 @@ "request": "^2.79.0" }, "devDependencies": { + "chai": "4.1.2", + "chai-http": "3.0.0", "eslint": "4.17.0", "eslint-config-semistandard": "12.0.0", "eslint-config-standard": "11.0.0-beta.0", "eslint-plugin-import": "2.8.0", "eslint-plugin-node": "6.0.0", "eslint-plugin-promise": "3.6.0", - "eslint-plugin-standard": "3.0.1" + "eslint-plugin-standard": "3.0.1", + "mocha": "5.0.0", + "mock-http-server": "0.2.0", + "sinon": "4.2.2" } } diff --git a/push-release.json b/push-release.json index 95fb01d..b3f33e0 100644 --- a/push-release.json +++ b/push-release.json @@ -1,7 +1,7 @@ [ { "name": "push-release", - "script": "server.js", + "script": "index.js", "log_date_format": "YYYY-MM-DD HH:mm Z", "merge_logs": false, "watch": false, @@ -15,7 +15,7 @@ }, { "name": "push-release-kovan", - "script": "server.js", + "script": "index.js", "log_date_format": "YYYY-MM-DD HH:mm Z", "merge_logs": false, "watch": false, diff --git a/server.js b/server.js index e605352..aca5e89 100644 --- a/server.js +++ b/server.js @@ -12,6 +12,7 @@ const api = new Parity.Api(transport); const app = express(); app.use(bodyParser.urlencoded({extended: true})); +module.exports = app; const reduceObject = (obj, prop) => ({ ...obj, [prop]: true }); const enabledTracks = config.get('enabledTracks').reduce(reduceObject, {}); @@ -45,7 +46,7 @@ const tracks = { app.post('/push-release/:tag/:commit', handleAsync(async function (req, res) { if (keccak256(req.body.secret || '') !== secretHash) { - throw new Error('Bad request'); + throw new Error('Invalid secret'); } const { commit, tag } = req.params; @@ -61,7 +62,11 @@ app.post('/push-release/:tag/:commit', handleAsync(async function (req, res) { console.log(`Pushing commit: ${commit} (tag: ${tag}/${goodTag})`); const miscBody = await fetchFile(commit, '/util/src/misc.rs'); - const branch = miscBody.match(`const THIS_TRACK. ..static str = "([a-z]*)";`)[1]; + const branch = match( + miscBody, + /const THIS_TRACK. ..static str = "([a-z]*)";/, + 'Unable to detect track' + )[1]; const track = tracks[branch] ? branch : 'testing'; console.log(`Track: ${branch} => ${track} (${tracks[track]}) [enabled: ${enabledTracks[track]}]`); @@ -71,24 +76,24 @@ app.post('/push-release/:tag/:commit', handleAsync(async function (req, res) { let ethereumMod = await fetchFile(commit, '/ethcore/src/ethereum/mod.rs'); const network = await getNetwork(); - const pattern = `pub const FORK_SUPPORTED_${network.toUpperCase()}: u64 = (\\d+);`; - const m = ethereumMod.match(pattern); - if (m === null) { - throw new Error(`Unable to detect supported fork with pattern: ${pattern}.`); - } - - const forkSupported = m[1]; - - // Return a response already. - res.send(`RELEASE: ${commit}/${track}/${branch}/${forkSupported}`); + const forkSupported = match( + ethereumMod, + `pub const FORK_SUPPORTED_${network.toUpperCase()}: u64 = (\\d+);`, + 'Unable to detect supported fork' + )[1]; console.log(`Fork supported: ${forkSupported}`); - let cargoToml = await fetchFile(commit, 'Cargo.toml'); - const version = cargoToml.match(/version = "([0-9]+)\.([0-9]+)\.([0-9]+)"/).slice(1); - const semver = +version[0] * 65536 + +version[1] * 256 + +version[2]; + const cargoToml = await fetchFile(commit, '/Cargo.toml'); + const versionMatch = match( + cargoToml, + /version = "([0-9]+)\.([0-9]+)\.([0-9]+)"/, + 'Unable to detect version' + ); + const [major, minor, patch] = versionMatch.slice(1).map(x => parseInt(x, 10)); + const semver = major * 65536 + minor * 256 + patch; - console.log(`Version: ${version.join('.')} = ${semver}`); + console.log(`Version: ${versionMatch.join('.')} = ${semver}`); const registryAddress = await api.parity.registryAddress(); console.log(`Registry address: ${registryAddress}`); @@ -97,16 +102,16 @@ app.post('/push-release/:tag/:commit', handleAsync(async function (req, res) { const operationsAddress = await registry.instance.getAddress.call({}, [operationsContract, 'A']); console.log(`Parity operations address: ${operationsAddress}`); console.log(`Registering release: 0x000000000000000000000000${commit}, ${forkSupported}, ${tracks[track]}, ${semver}, ${isCritical}`); - // Should be this... - // api.newContract(OperationsABI, a).instance.addRelease.postTransaction({from: account.address}, [`0x000000000000000000000000${commit}`, forkSupported, tracks[track], semver, isCritical]) - // ...but will have to be this for now... const hash = await sendTransaction(OperationsABI, operationsAddress, 'addRelease', [`0x000000000000000000000000${commit}`, forkSupported, tracks[track], semver, isCritical]); console.log(`Transaction sent with hash: ${hash}`); + + // Return the response + 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('Bad request'); + throw new Error('Invalid secret'); } const { tag, platform } = req.params; @@ -124,8 +129,12 @@ app.post('/push-build/:tag/:platform', handleAsync(async function (req, res) { throw new Error(`Invalid sha3 (${sha3}), tag (${tag}) or platform (${platform}).`); } - let body = await fetchFile(commit, '/util/src/misc.rs'); - const branch = body.match(`const THIS_TRACK. ..static str = "([a-z]*)";`)[1]; + const body = await fetchFile(commit, '/util/src/misc.rs'); + const branch = match( + body, + /const THIS_TRACK. ..static str = "([a-z]*)"/, + 'Unable to detect track' + )[1]; const track = tracks[branch] ? branch : 'testing'; console.log(`Track: ${branch} => ${track} (${tracks[track]}) [enabled: ${!!enabledTracks[track]}]`); @@ -136,34 +145,32 @@ app.post('/push-build/:tag/:platform', handleAsync(async function (req, res) { // make sure the node is running await getNetwork(); - // Respond already - res.send(out); const registryAddress = await api.parity.registryAddress(); const reg = api.newContract(RegistrarABI, registryAddress); const githubHintAddress = await reg.instance.getAddress.call({}, [githubHint, 'A']); console.log(`Registering on GithubHint: ${sha3}, ${url}`); - // Should be this... - // api.newContract(GitHubHintABI, g).instance.hintURL.postTransaction({from: account.address}, [`0x${sha3}`, url]).then(() => { - // ...but will have to be this for now... const hash = await sendTransaction(GitHubHintABI, githubHintAddress, 'hintURL', [`0x${sha3}`, url]); console.log(`Transaction sent with hash: ${hash}`); - const operationsAddress = reg.instance.getAddress.call({}, [operationsContract, 'A']); + const operationsAddress = await reg.instance.getAddress.call({}, [operationsContract, 'A']); console.log(`Registering platform binary: ${commit}, ${platform}, ${sha3}`); - // Should be this... - // return api.newContract(OperationsABI, o).instance.addChecksum.postTransaction({from: account.address}, [`0x000000000000000000000000${commit}`, platform, `0x${sha3}`]); - // ...but will have to be this for now... const hash2 = await sendTransaction(OperationsABI, operationsAddress, 'addChecksum', [`0x000000000000000000000000${commit}`, platform, `0x${sha3}`]); console.log(`Transaction sent with hash: ${hash2}`); + + // Respond already + res.send(out); })); -const server = app.listen(httpPort, function () { - const host = server.address().address; - const port = server.address().port; - console.log('push-release service listening at http://%s:%s', host, port); -}); +function match (string, pattern, comment) { + const match = string.match(pattern); + if (!match) { + throw new Error(`${comment} in ${string}`); + } + + return match; +} function handleAsync (asyncFn) { return (req, res) => asyncFn(req, res) diff --git a/test/server.test.js b/test/server.test.js new file mode 100644 index 0000000..e920438 --- /dev/null +++ b/test/server.test.js @@ -0,0 +1,174 @@ +const chai = require('chai'); +const chaiHttp = require('chai-http'); +const ServerMock = require('mock-http-server'); + +const expect = chai.expect; +chai.use(chaiHttp); + +const app = require('../server'); +const secret = 'test'; +const gasPrice = '0x4f9aca000'; + +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') + .type('form') + .send({ secret: 'xx' }) + ); + + expect(res).to.have.status(400); + expect(res.text).to.equal('Error while processing the request:\nInvalid secret\n'); + }); + + it('should reject invalid tags', async () => { + const test = async tag => { + let res = await request(app => app + .post(`/push-release/${tag}/123`) + .type('form') + .send({ secret }) + ); + + expect(res).to.have.status(400); + expect(res.text).to.equal(`Error while processing the request:\nInvalid tag: ${tag}\n`); + }; + + await test('xxx'); + await test('v1.9.5-ci0'); + }); + + beforeEach(done => server.start(done)); + afterEach(done => server.stop(done)); + + it('should push release succesfuly', async () => { + const requests = []; + server.on({ + method: 'POST', + path: '/', + reply: { + status: 200, + headers: { 'content-type': 'application/json' }, + body (req) { + requests.push(req.body); + return JSON.stringify({ + jsonrpc: '2.0', + id: 1, + result: 'kovan' + }) + } + } + }); + + let res = await request(app => app + .post('/push-release/v1.7.13/8b749367fd5fea897cee98bd892fff1ce90f8260') + .type('form') + .send({ secret }) + ); + + expect(res).to.have.status(200); + // Register in operations + expect(requests[3].method).to.equal('eth_sendTransaction'); + expect(requests[3].params).to.deep.equal([{ + data: '0x932ab2700000000000000000000000008b749367fd5fea897cee98bd892fff1ce90f826000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000001070d0000000000000000000000000000000000000000000000000000000000000000', + from: '0x0066ac7a4608f350bf9a0323d60dde211dfb27c0', + gasPrice, + to: '0x' + }]); + }); +}); + +describe('push-build', () => { + it('should reject invalid secret', async () => { + let res = await request(app => app + .post('/push-build/v1.9.5/x86_64-unknown-linux-gnu') + .type('form') + .send({ secret: 'xx' }) + ); + + expect(res).to.have.status(400); + expect(res.text).to.equal('Error while processing the request:\nInvalid secret\n'); + }); + + it('should 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' }) + ); + + 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`); + }; + + await test('xxx'); + await test('v1.9.5-ci0'); + }); + + beforeEach(done => server.start(done)); + afterEach(done => server.stop(done)); + + it('should push build succesfuly', async () => { + const requests = []; + server.on({ + method: 'POST', + path: '/', + reply: { + status: 200, + headers: { 'content-type': 'application/json' }, + body (req) { + requests.push(req.body); + return JSON.stringify({ + jsonrpc: '2.0', + id: 1, + result: 'kovan' + }) + } + } + }); + + let res = await request(app => app + .post('/push-build/v1.7.13/x86_64-unknown-linux-gnu') + .type('form') + .send({ + secret, + sha3: 'beefcafe', + commit: '8b749367fd5fea897cee98bd892fff1ce90f8260' + }) + ); + + 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', + from: '0x0066ac7a4608f350bf9a0323d60dde211dfb27c0', + gasPrice, + to: '0x' + }]); + // Build registration + expect(requests[5].method).to.equal('eth_sendTransaction'); + expect(requests[5].params).to.deep.equal([{ + data: '0x793b0efb0000000000000000000000008b749367fd5fea897cee98bd892fff1ce90f82607838365f36342d756e6b6e6f776e2d6c696e75782d676e750000000000000000beefcafe00000000000000000000000000000000000000000000000000000000', + from: '0x0066ac7a4608f350bf9a0323d60dde211dfb27c0', + gasPrice, + to: '0x' + }]); + }); +}); + +// Overcoming a bug in chai-http: https://github.com/chaijs/chai-http/issues/156 +function request (fn) { + return new Promise((resolve, reject) => { + return fn(chai.request(app)).end((err, res) => { + if (res) { + resolve(res); + } else { + reject(err); + } + }); + }); +} diff --git a/yarn.lock b/yarn.lock index 8505a4e..495269c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -98,6 +98,14 @@ assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" +assertion-error@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + +async@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -132,7 +140,7 @@ bignumber.js@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-2.3.0.tgz#597a02d791edc3d64f17850e21789e7a4095df66" -body-parser@1.18.2, body-parser@^1.15.2: +body-parser@1.18.2, body-parser@^1.15.2, body-parser@^1.18.1: version "1.18.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" dependencies: @@ -166,6 +174,10 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +browser-stdout@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" + builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -188,6 +200,27 @@ caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" +chai-http@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chai-http/-/chai-http-3.0.0.tgz#5460d8036e1f1a12b0b5b5cbd529e6dc1d31eb4b" + dependencies: + cookiejar "2.0.x" + is-ip "1.0.0" + methods "^1.1.2" + qs "^6.2.0" + superagent "^2.0.0" + +chai@4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" + dependencies: + assertion-error "^1.0.1" + check-error "^1.0.1" + deep-eql "^3.0.0" + get-func-name "^2.0.0" + pathval "^1.0.0" + type-detect "^4.0.0" + chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -210,6 +243,10 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +check-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + circular-json@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" @@ -244,6 +281,14 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" +commander@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + +component-emitter@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -263,6 +308,15 @@ config@1.29.4: json5 "0.4.0" os-homedir "1.0.2" +connect@^3.4.0: + version "3.6.5" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.5.tgz#fb8dde7ba0763877d0ec9df9dac0b4b40e72c7da" + dependencies: + debug "2.6.9" + finalhandler "1.0.6" + parseurl "~1.3.2" + utils-merge "1.0.1" + contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" @@ -283,6 +337,14 @@ cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" +cookiejar@2.0.x: + version "2.0.6" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.0.6.tgz#0abf356ad00d1c5a219d88d44518046dd026acfe" + +cookiejar@^2.0.6: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -307,18 +369,24 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -debug@2.6.9, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: ms "2.0.0" -debug@^3.1.0: +debug@3.1.0, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: ms "2.0.0" +deep-eql@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + dependencies: + type-detect "^4.0.0" + deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -351,6 +419,14 @@ destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" +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" @@ -394,14 +470,10 @@ escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -eslint-config-import@0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/eslint-config-import/-/eslint-config-import-0.13.0.tgz#082f3055e7ff651010526d35ac9a28ace438a544" - eslint-config-semistandard@12.0.0: version "12.0.0" resolved "https://registry.yarnpkg.com/eslint-config-semistandard/-/eslint-config-semistandard-12.0.0.tgz#e6482de68e05c2dbdddaf45c3078f6a97fe23f1a" @@ -580,7 +652,7 @@ express@^4.13.3: utils-merge "1.0.1" vary "~1.1.2" -extend@~3.0.1: +extend@^3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" @@ -612,6 +684,12 @@ fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + dependencies: + pend "~1.2.0" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -625,6 +703,18 @@ file-entry-cache@^2.0.0: flat-cache "^1.2.1" object-assign "^4.0.1" +finalhandler@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.6.tgz#007aea33d1a4d3e42017f624848ad58d212f814f" + dependencies: + debug "2.6.9" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.3.1" + unpipe "~1.0.0" + finalhandler@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" @@ -663,6 +753,14 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data@1.0.0-rc4: + version "1.0.0-rc4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.0-rc4.tgz#05ac6bc22227b43e4461f488161554699d4f8b5e" + dependencies: + async "^1.5.2" + combined-stream "^1.0.5" + mime-types "^2.1.10" + form-data@~2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" @@ -671,6 +769,16 @@ 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" + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -691,13 +799,17 @@ functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" dependencies: assert-plus "^1.0.0" -glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: +glob@7.1.2, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -727,6 +839,10 @@ graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" +growl@1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -763,6 +879,10 @@ hawk@~6.0.2: hoek "4.x.x" sntp "2.x.x" +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + hoek@4.x.x: version "4.2.0" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" @@ -830,6 +950,10 @@ inquirer@^3.0.6: strip-ansi "^4.0.0" through "^2.3.6" +ip-regex@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd" + ipaddr.js@1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" @@ -848,6 +972,12 @@ is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" +is-ip@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-1.0.0.tgz#2bb6959f797ccd6f9fdc812758bcbc87c4c59074" + dependencies: + ip-regex "^1.0.0" + is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -880,6 +1010,10 @@ 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" @@ -940,6 +1074,10 @@ 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" @@ -967,10 +1105,22 @@ 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" @@ -986,7 +1136,7 @@ merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" -methods@~1.1.2: +methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -994,7 +1144,7 @@ mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" -mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17: +mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: @@ -1004,6 +1154,10 @@ mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" +mime@^1.3.4: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -1018,16 +1172,46 @@ minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -mkdirp@^0.5.1: +mkdirp@0.5.1, mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" +mocha@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.0.tgz#cccac988b0bc5477119cba0e43de7af6d6ad8f4e" + dependencies: + browser-stdout "1.3.0" + commander "2.11.0" + debug "3.1.0" + diff "3.3.1" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.3" + he "1.1.1" + mkdirp "0.5.1" + supports-color "4.4.0" + +mock-http-server@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/mock-http-server/-/mock-http-server-0.2.0.tgz#edefec0e80be8bca286f1e0c00de8dbfebf0ec90" + dependencies: + body-parser "^1.18.1" + connect "^3.4.0" + multiparty "^4.1.2" + underscore "^1.8.3" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +multiparty@^4.1.2: + version "4.1.3" + resolved "https://registry.yarnpkg.com/multiparty/-/multiparty-4.1.3.tgz#3c43c7fcb1896e17460436a9dd0b6ef1668e4f94" + dependencies: + fd-slicer "~1.0.1" + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -1040,6 +1224,16 @@ 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" @@ -1153,12 +1347,26 @@ 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" dependencies: pify "^2.0.0" +pathval@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -1214,7 +1422,7 @@ punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -qs@6.5.1, qs@~6.5.1: +qs@6.5.1, qs@^6.1.0, qs@^6.2.0, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" @@ -1246,7 +1454,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -readable-stream@^2.2.2: +readable-stream@^2.0.5, readable-stream@^2.2.2: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -1335,6 +1543,10 @@ 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" @@ -1388,6 +1600,18 @@ 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" @@ -1477,6 +1701,27 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +superagent@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-2.3.0.tgz#703529a0714e57e123959ddefbce193b2e50d115" + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.0.6" + debug "^2.2.0" + extend "^3.0.0" + form-data "1.0.0-rc4" + formidable "^1.0.17" + methods "^1.1.1" + mime "^1.3.4" + qs "^6.1.0" + readable-stream "^2.0.5" + +supports-color@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + dependencies: + has-flag "^2.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -1487,6 +1732,12 @@ 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" @@ -1498,6 +1749,10 @@ 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" @@ -1534,6 +1789,10 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + type-is@~1.6.15: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" @@ -1545,6 +1804,10 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +underscore@^1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"