From 39dc93447a0a3be146613f90bc8600b1b0d0cca3 Mon Sep 17 00:00:00 2001 From: Marshall Thompson Date: Wed, 12 Feb 2020 21:58:33 -0700 Subject: [PATCH] Allow validating anything in context This introduces the `getContext` and `setContext` methods to the options object. The `getContext` receives the context and must return the object to be validated. The `setContext` receives the context AND the new values and should return the context. ```js const joiOptions = { convert: true, getContext(context) { return context.params.query; }, setContext(context, newValues) { Object.assign(context.params.query, newValues); return context }, }; ``` --- index.js | 145 +++++++++++++++++++++++-------------------- package.json | 3 +- test/invalid_spec.js | 11 ++-- test/valid_spec.js | 68 +++++++++++++++++++- yarn.lock | 108 ++++++++++++++++++++++++++++++-- 5 files changed, 257 insertions(+), 78 deletions(-) diff --git a/index.js b/index.js index b724cff..c3fa7e3 100755 --- a/index.js +++ b/index.js @@ -1,66 +1,79 @@ -/* eslint comma-dangle: 0, object-shorthand: 0, prefer-arrow-callback: 0*/ /* ES5 code */ -const errors = require('@feathersjs/errors'); -const utils = require('feathers-hooks-common/lib/services'); -const joiErrorsForForms = require('joi-errors-for-forms'); - -// We only directly need the convert option. The others are listed for convenience. -// See defaults at https://hapi.dev/family/joi/api/?v=17.1.0#anyvalidatevalue-options -const joiDefaults = { - abortEarly: true, - allowUnknown: false, - cache: true, - convert: true, - debug: false, - externals: true, - noDefaults: false, - nonEnumerables: false, - presence: 'optional', - skipFunctions: false, - stripUnknown: false -}; - -function setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest) { - if (!['undefined', 'object'].includes(typeof joiOptions)) { - throw new errors.GeneralError('joiOptions must be a valid object.'); - } - - const mergedOptions = Object.assign({}, joiDefaults, joiOptions); - - return async function validateWithJoi(context, next) { - utils.checkContext(context, 'before', ['create', 'update', 'patch'], 'validate-joi'); - const values = utils.getItems(context); - - - try { - const convertedValues = await joiSchema.validateAsync(values, mergedOptions); - - if (mergedOptions.convert === true) { - utils.replaceItems(context, convertedValues); - } - - if (typeof next === 'function') { - return next(null, context); - } - return context; - } catch (error) { - const formErrors = translator(error); - if (formErrors) { - // Hacky, but how else without a custom assert? - const msg = ifTest ? JSON.stringify(formErrors) : 'Invalid data'; - throw new errors.BadRequest(msg, { errors: formErrors }); - } - return formErrors || error; - } - }; -} - -module.exports = { - form: function (joiSchema, joiOptions, translations, ifTest) { - const translator = joiErrorsForForms.form(translations); - return setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest); - }, - mongoose: function (joiSchema, joiOptions, translations, ifTest) { - const translator = joiErrorsForForms.mongoose(translations); - return setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest); - } -}; +/* eslint comma-dangle: 0, object-shorthand: 0, prefer-arrow-callback: 0 */ +const errors = require('@feathersjs/errors'); +const utils = require('feathers-hooks-common/lib/services'); +const joiErrorsForForms = require('joi-errors-for-forms'); + +// We only directly need the convert option. The others are listed for convenience. +// See defaults at https://hapi.dev/family/joi/api/?v=17.1.0#anyvalidatevalue-options +const joiDefaults = { + abortEarly: true, + allowUnknown: false, + cache: true, + convert: true, + debug: false, + externals: true, + noDefaults: false, + nonEnumerables: false, + presence: 'optional', + skipFunctions: false, + stripUnknown: false, + getContext: undefined, + setContext: undefined +}; + +function setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest) { + if (!['undefined', 'object'].includes(typeof joiOptions)) { + throw new errors.GeneralError('joiOptions must be a valid object.'); + } + + const { getContext, setContext, ...mergedOptions } = { ...joiDefaults, ...joiOptions }; + + if ((getContext || setContext) && (!getContext || !setContext)) { + throw new errors.GeneralError('getContext and setContext must be used together'); + } + + return async function validateWithJoi(context, next) { + let values; + if (typeof getContext === 'function') { + values = getContext(context); + } else { + values = utils.getItems(context); + } + + try { + const convertedValues = await joiSchema.validateAsync(values, mergedOptions); + + if (mergedOptions.convert === true) { + if (typeof setContext === 'function') { + setContext(context, convertedValues); + } else { + utils.replaceItems(context, convertedValues); + } + } + + if (typeof next === 'function') { + return next(null, context); + } + return context; + } catch (error) { + const formErrors = translator(error); + if (formErrors) { + // Hacky, but how else without a custom assert? + const msg = ifTest ? JSON.stringify(formErrors) : 'Invalid data'; + throw new errors.BadRequest(msg, { errors: formErrors }); + } + return formErrors || error; + } + }; +} + +module.exports = { + form: function (joiSchema, joiOptions, translations, ifTest) { + const translator = joiErrorsForForms.form(translations); + return setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest); + }, + mongoose: function (joiSchema, joiOptions, translations, ifTest) { + const translator = joiErrorsForForms.mongoose(translations); + return setupValidateWithJoi(joiSchema, joiOptions, translator, ifTest); + } +}; diff --git a/package.json b/package.json index 88e8fb3..2c8e745 100755 --- a/package.json +++ b/package.json @@ -54,7 +54,8 @@ "eslint-plugin-jsx-a11y": "6.2.3", "eslint-plugin-react": "7.18.3", "istanbul": "^0.4.5", - "mocha": "^7.0.1" + "mocha": "^7.0.1", + "mongodb": "^3.5.3" }, "engines": { "node": ">4.2.4" diff --git a/test/invalid_spec.js b/test/invalid_spec.js index dbd8249..2df16a6 100755 --- a/test/invalid_spec.js +++ b/test/invalid_spec.js @@ -1,12 +1,13 @@ +/* eslint-disable no-template-curly-in-string */ /* eslint comma-dangle:0, newline-per-chained-call: 0, no-shadow: 0, object-shorthand: 0, one-var: 0, one-var-declaration-per-line: 0, prefer-arrow-callback: 0 */ /* ES5 code */ -const assert = require('chai').assert; +const { assert } = require('chai'); +const Joi = require('@hapi/joi'); const validate = require('../index'); -const Joi = require('@hapi/joi'); const name = Joi.string().trim().regex(/^[\sa-zA-Z0-9]{5,30}$/).required(); const password = Joi.string().trim().min(2).max(30).required(); const schema = Joi.object().keys({ @@ -169,10 +170,12 @@ describe('invalid data - form UI', () => { it('throws on error. translate using substrings', async () => { const translations = [ - { regex: 'at least 2 characters long', + { + regex: 'at least 2 characters long', message: '"${key}" must be 2 or more chars.' }, - { regex: /required pattern/, + { + regex: /required pattern/, message: '"${key}" is badly formed.' } ]; diff --git a/test/valid_spec.js b/test/valid_spec.js index ed1c415..57a8bdc 100755 --- a/test/valid_spec.js +++ b/test/valid_spec.js @@ -2,10 +2,10 @@ /* eslint newline-per-chained-call: 0, no-shadow: 0, one-var: 0, one-var-declaration-per-line: 0, prefer-arrow-callback: 0 */ /* ES5 code */ -const assert = require('chai').assert; +const { assert } = require('chai'); +const Joi = require('@hapi/joi'); const validate = require('../index'); -const Joi = require('@hapi/joi'); const name = Joi.string().trim().regex(/^[\sa-zA-Z0-9]{5,30}$/).required(); const password = Joi.string().trim().min(2).max(30).required(); const schema = Joi.object().keys({ @@ -14,6 +14,70 @@ const schema = Joi.object().keys({ confirmPassword: password.label('Confirm password'), }); +const { ObjectID } = require('mongodb'); +/** + * Custom objectId validator + */ +function objectId() { + return Joi.custom((value, helpers) => { + if (value === null || value === undefined) { + return value; + } + try { + return new ObjectID(value); + } catch (error) { + const errVal = helpers.error('any.invalid'); + errVal.message = `"${errVal.path.join('.')}" objectId validation failed because ${error.message}`; + return errVal; + } + }, 'objectId'); +} + +describe('custom context with getContext', async () => { + const schema = Joi.object({ + userId: objectId(), + }); + + it('allows validating params.query', async () => { + const joiOptions = { + convert: true, + getContext(context) { + return context.params.query; + }, + setContext(context, newValues) { + Object.assign(context.params.query, newValues); + }, + }; + const context = { + params: { + query: { + userId: '5e44b40855534a38798ba1aa', + }, + }, + }; + try { + const validateWithJoi = validate.form(schema, joiOptions); + const responseContext = await validateWithJoi(context); + assert(responseContext.params.query.userId instanceof ObjectID, 'params.query.userId should be converted to an objectId'); + } catch (error) { + assert(!error, 'should not have failed'); + } + }); + + it('throws if getContext is used without setContext', async () => { + const joiOptions = { + getContext() {}, + }; + try { + const validateWithJoi = validate.form(schema, joiOptions); + const responseContext = await validateWithJoi(context); + assert(!responseContext, 'should have failed'); + } catch (error) { + assert.equal(error.message, 'getContext and setContext must be used together'); + } + }); +}); + describe('valid values', () => { var joiOptions, values, converted, context; // eslint-disable-line no-var diff --git a/yarn.lock b/yarn.lock index 7293324..fb09e7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -353,6 +353,14 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== +bl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.0.tgz#e1a574cdf528e4053019bb800b041c0ac88da493" + integrity sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -373,6 +381,11 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== +bson@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.3.tgz#aa82cb91f9a453aaa060d6209d0675114a8154d3" + integrity sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg== + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -494,7 +507,7 @@ contains-path@^0.1.0: resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= -core-util-is@1.0.2: +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" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -583,6 +596,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +denque@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" + integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ== + diff@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -1208,7 +1226,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1324,7 +1342,7 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -isarray@^1.0.0: +isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -1493,6 +1511,11 @@ loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + mime-db@1.43.0: version "1.43.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" @@ -1569,6 +1592,19 @@ mocha@^7.0.1: yargs-parser "13.1.1" yargs-unparser "1.6.0" +mongodb@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.5.3.tgz#f2c7ce9b5fc9a13da116ff1b6e816f6256010a86" + integrity sha512-II7P7A3XUdPiXRgcN96qIoRa1oesM6qLNZkzfPluNZjVkgQk3jnQwOT6/uDk4USRDTTLjNFw2vwfmbRGTA7msg== + dependencies: + bl "^2.2.0" + bson "^1.1.1" + denque "^1.4.1" + require_optional "^1.0.1" + safe-buffer "^5.1.2" + optionalDependencies: + saslprep "^1.0.0" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -1852,6 +1888,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + process@0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -1908,6 +1949,19 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" +readable-stream@^2.3.5: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readdirp@~3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" @@ -1969,6 +2023,19 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +require_optional@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" + integrity sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g== + dependencies: + resolve-from "^2.0.0" + semver "^5.1.0" + +resolve-from@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" + integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -2015,17 +2082,29 @@ rxjs@^6.5.3: dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.2: +safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.7.0: +saslprep@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.5.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -2091,6 +2170,13 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= + dependencies: + memory-pager "^1.0.2" + spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" @@ -2191,6 +2277,13 @@ string.prototype.trimright@^2.1.1: define-properties "^1.1.3" function-bind "^1.1.1" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -2354,6 +2447,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"