diff --git a/README.md b/README.md index 5b78367ff..969ef50c2 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ With the Development Workflow activated. Manage Forest Admin schema. - `schema:apply` apply the current schema of your repository to the specified environment (using your `.forestadmin-schema.json` file). +- `schema:diff` allow to compare two environment schemas. - `schema:update` refresh your schema by generating files that do not currently exist. ## Docker diff --git a/package.json b/package.json index cd7e95c46..0e6ab0152 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "handlebars": "4.7.7", "inquirer": "6.2.0", "joi": "14.3.1", + "json-diff": "1.0.0", "jsonapi-serializer": "3.6.2", "jsonwebtoken": "8.5.1", "jwt-decode": "2.2.0", diff --git a/src/commands/schema/diff.js b/src/commands/schema/diff.js new file mode 100644 index 000000000..7d1236389 --- /dev/null +++ b/src/commands/schema/diff.js @@ -0,0 +1,55 @@ +const EnvironmentManager = require('../../services/environment-manager'); +const AbstractAuthenticatedCommand = require('../../abstract-authenticated-command'); + +class DiffCommand extends AbstractAuthenticatedCommand { + init(plan) { + super.init(plan); + const { + assertPresent, + chalk, + env, + environmentRenderer, + errorHandler, + } = this.context; + assertPresent({ chalk, env }); + this.chalk = chalk; + this.env = env; + this.environmentRenderer = environmentRenderer; + this.errorHandler = errorHandler; + } + + async runIfAuthenticated() { + const parsed = this.parse(DiffCommand); + const config = { ...this.env, ...parsed.flags, ...parsed.args }; + const manager = new EnvironmentManager(config); + + const { environmentIdFrom, environmentIdTo } = config; + try { + const [apimapFrom, apimapTo] = await Promise.all([ + manager.getEnvironmentApimap(environmentIdFrom), + manager.getEnvironmentApimap(environmentIdTo), + ]); + + this.environmentRenderer.renderApimapDiff(apimapFrom, apimapTo); + } catch (error) { + this.logger.error( + `Cannot fetch the environments ${this.chalk.bold(environmentIdFrom)} and ${this.chalk.bold(environmentIdTo)}.`, + ); + this.logger.error(manager.handleEnvironmentError(error)); + } + } +} + +DiffCommand.description = 'Allow to compare two environment schemas'; + +DiffCommand.flags = { + help: AbstractAuthenticatedCommand.flags.boolean({ + description: 'Display usage information.', + }), +}; + +DiffCommand.args = [ + { name: 'environmentIdFrom', required: true, description: 'ID of an environment to compare.' }, + { name: 'environmentIdTo', required: true, description: 'ID of an environment to compare.' }, +]; +module.exports = DiffCommand; diff --git a/src/context/dependencies-plan.js b/src/context/dependencies-plan.js index ad0fbf425..bad008da8 100644 --- a/src/context/dependencies-plan.js +++ b/src/context/dependencies-plan.js @@ -18,4 +18,5 @@ module.exports = (plan) => plan .addModule('joi', () => require('joi')) .addModule('openIdClient', () => require('openid-client')) .addModule('os', () => require('os')) - .addModule('superagent', () => require('superagent'))); + .addModule('superagent', () => require('superagent')) + .addUsingFunction('diffString', () => require('json-diff').diffString)); diff --git a/src/renderers/environment.js b/src/renderers/environment.js index 156618e13..5a28190c8 100644 --- a/src/renderers/environment.js +++ b/src/renderers/environment.js @@ -6,15 +6,18 @@ class EnvironmentRenderer { chalk, logger, Table, + diffString, }) { assertPresent({ chalk, logger, Table, + diffString, }); this.chalk = chalk; this.logger = logger; this.Table = Table; + this.diffString = diffString; } render(environment, config) { @@ -46,6 +49,16 @@ class EnvironmentRenderer { default: } } + + renderApimapDiff(apimapFrom, apimapTo) { + const diff = this.diffString(apimapFrom, apimapTo); + if (diff) { + this.logger.log(this.chalk.bold.yellow('⚠ The schemas have differences.')); + this.logger.log(diff); + } else { + this.logger.log(this.chalk.bold.green('√ The schemas are identical.')); + } + } } module.exports = EnvironmentRenderer; diff --git a/src/services/environment-manager.js b/src/services/environment-manager.js index 8c07ed12f..3080dd7a4 100644 --- a/src/services/environment-manager.js +++ b/src/services/environment-manager.js @@ -6,6 +6,7 @@ const EnvironmentSerializer = require('../serializers/environment'); const environmentDeserializer = require('../deserializers/environment'); const DeploymentRequestSerializer = require('../serializers/deployment-request'); const JobStateChecker = require('./job-state-checker'); +const { handleError } = require('../utils/error'); function EnvironmentManager(config) { const { @@ -35,6 +36,17 @@ function EnvironmentManager(config) { .then((response) => environmentDeserializer.deserialize(response.body)); }; + this.getEnvironmentApimap = async (environmentId) => { + const authToken = authenticator.getAuthToken(); + + const response = await agent + .get(`${env.FOREST_URL}/api/apimaps/${environmentId}`) + .set('Authorization', `Bearer ${authToken}`) + .set('forest-environment-id', environmentId) + .send(); + return response.body.data.apimap; + }; + this.createEnvironment = async () => { const authToken = authenticator.getAuthToken(); @@ -134,6 +146,14 @@ function EnvironmentManager(config) { .set('Authorization', `Bearer ${authToken}`) .set('forest-secret-key', `${config.envSecret}`); }; + + this.handleEnvironmentError = (rawError) => { + const error = handleError(rawError); + if (error === 'Forbidden') { + return 'You do not have the permission to perform this action on the given environments.'; + } + return error; + }; } module.exports = EnvironmentManager; diff --git a/src/utils/terminator.js b/src/utils/terminator.js index 0c4fe3cbd..55a57b0c6 100644 --- a/src/utils/terminator.js +++ b/src/utils/terminator.js @@ -26,6 +26,7 @@ module.exports = ({ if (logs.length) { logger.error(...logs); } + if (errorCode) { await eventSender.notifyError(errorCode, errorMessage, context); } else { diff --git a/test/commands/schema/diff.test.js b/test/commands/schema/diff.test.js new file mode 100644 index 000000000..53df4e3e5 --- /dev/null +++ b/test/commands/schema/diff.test.js @@ -0,0 +1,85 @@ +const jsonDiff = require('json-diff'); +const testCli = require('../test-cli-helper/test-cli'); +const DiffSchemaCommand = require('../../../src/commands/schema/diff'); +const { testEnvWithSecret } = require('../../fixtures/env'); +const { + loginValidOidc, getEnvironmentApimap, + getEnvironmentApimapForbidden, +} = require('../../fixtures/api'); + +describe('schema:diff', () => { + describe('when the user is not logged in', () => { + it('should login the user and does the diff', () => testCli({ + env: testEnvWithSecret, + api: [ + () => loginValidOidc(), + () => getEnvironmentApimap(10), + () => getEnvironmentApimap(11), + ], + commandClass: DiffSchemaCommand, + commandArgs: ['10', '11'], + std: [ + { out: '> Login required.' }, + { out: 'Click on "Log in" on the browser tab which opened automatically or open this link: http://app.localhost/device/check?code=ABCD' }, + { out: 'Your confirmation code: USER-CODE' }, + { out: '> Login successful' }, + { out: '√ The schemas are identical.' }, + ], + })); + }); + + describe('when the user is logged in', () => { + describe('when schemas are identical', () => { + it('display "identical" message', () => testCli({ + env: testEnvWithSecret, + token: 'any', + api: [ + () => getEnvironmentApimap(10), + () => getEnvironmentApimap(11), + ], + commandClass: DiffSchemaCommand, + commandArgs: ['10', '11'], + std: [ + { out: '√ The schemas are identical.' }, + ], + })); + }); + + describe('when schemas are not identical', () => { + const apiMapA = { collections: [{ name: 'Users' }] }; + const apiMapB = { collections: [{ name: 'Users' }, { name: 'Posts' }] }; + + it('display the diff message', () => testCli({ + env: testEnvWithSecret, + token: 'any', + api: [ + () => getEnvironmentApimap(10, apiMapA), + () => getEnvironmentApimap(11, apiMapB), + ], + commandClass: DiffSchemaCommand, + commandArgs: ['10', '11'], + std: [ + { out: '⚠ The schemas have differences.' }, + { in: jsonDiff.diffString(apiMapA, apiMapB) }, + ], + })); + }); + + describe('when there is an error', () => { + it('should display an error message', () => testCli({ + env: testEnvWithSecret, + token: 'any', + api: [ + () => getEnvironmentApimap(10), + () => getEnvironmentApimapForbidden(99999), + ], + commandClass: DiffSchemaCommand, + commandArgs: ['10', '99999'], + std: [ + { err: '× Cannot fetch the environments 10 and 99999.' }, + { err: '× Oops something went wrong.' }, + ], + })); + }); + }); +}); diff --git a/test/fixtures/api.js b/test/fixtures/api.js index f370cf128..f8233d8c4 100644 --- a/test/fixtures/api.js +++ b/test/fixtures/api.js @@ -365,6 +365,16 @@ module.exports = { .matchHeader('forest-environment-id', id) .get(`/api/environments/${id}`) .reply(404), + getEnvironmentApimap: (id, apimap = { collections: [] }) => nock('http://localhost:3001') + .matchHeader('forest-environment-id', id) + .get(`/api/apimaps/${id}`) + .reply(200, { + data: { apimap }, + }), + getEnvironmentApimapForbidden: (id) => nock('http://localhost:3001') + .matchHeader('forest-environment-id', id) + .get(`/api/apimaps/${id}`) + .reply(403), updateEnvironmentName: () => nock('http://localhost:3001') .put('/api/environments/182', { diff --git a/test/services/environment-manager.unit.test.js b/test/services/environment-manager.unit.test.js new file mode 100644 index 000000000..9ec5563b7 --- /dev/null +++ b/test/services/environment-manager.unit.test.js @@ -0,0 +1,31 @@ +const Context = require('@forestadmin/context'); +const EnvironmentManager = require('../../src/services/environment-manager'); +const defaultPlan = require('../../src/context/plan'); + +describe('services > EnvironmentManager', () => { + const buildManager = () => { + // we must init the context for enable the dependency injection + Context.init(defaultPlan); + + return new EnvironmentManager({}); + }; + describe('handleEnvironmentError', () => { + describe('when the error is unknown', () => { + it('should return the receives error', () => { + expect.assertions(1); + const error = new Error('error'); + const result = buildManager().handleEnvironmentError(error); + expect(result).toBe('error'); + }); + }); + + describe('when the error is a 403 Forbidden', () => { + it('should return a forbidden error', () => { + expect.assertions(1); + const error = new Error('Forbidden'); + const result = buildManager().handleEnvironmentError(error); + expect(result).toBe('You do not have the permission to perform this action on the given environments.'); + }); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index e4ceb440e..dc35d1627 100644 --- a/yarn.lock +++ b/yarn.lock @@ -504,6 +504,13 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@ewoudenberg/difflib@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@ewoudenberg/difflib/-/difflib-0.1.0.tgz#a2ae5d3321ffa7c1b47691cf0db189d1264aaaa4" + integrity sha512-OU5P5mJyD3OoWYMWY+yIgwvgNS9cFAU10f+DDuvtogcWQOoJIsQ4Hy2McSfUfhKjq8L0FuWVb4Rt7kgA+XK86A== + dependencies: + heap ">= 0.2.0" + "@forestadmin/context@1.31.0": version "1.31.0" resolved "https://registry.npmjs.org/@forestadmin/context/-/context-1.31.0.tgz" @@ -2644,6 +2651,17 @@ clean-stack@^3.0.0: dependencies: escape-string-regexp "4.0.0" +cli-color@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.3.tgz#73769ba969080629670f3f2ef69a4bf4e7cc1879" + integrity sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.61" + es6-iterator "^2.0.3" + memoizee "^0.4.15" + timers-ext "^0.1.7" + cli-columns@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/cli-columns/-/cli-columns-4.0.0.tgz" @@ -3080,6 +3098,14 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + dargs@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz" @@ -3324,6 +3350,13 @@ dottie@^2.0.0: resolved "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz" integrity sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg== +dreamopt@~0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/dreamopt/-/dreamopt-0.8.0.tgz#5bcc80be7097e45fc489c342405ab68140a8c1d9" + integrity sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg== + dependencies: + wordwrap ">=0.0.2" + duplexer2@~0.1.0: version "0.1.4" resolved "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz" @@ -3485,6 +3518,42 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.61, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.62" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +es6-weak-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" @@ -3777,6 +3846,14 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + event-stream@=3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" @@ -3846,6 +3923,13 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" @@ -4538,6 +4622,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +"heap@>= 0.2.0": + version "0.2.7" + resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc" + integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg== + hoek@6.x.x: version "6.1.3" resolved "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz" @@ -5022,6 +5111,11 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== +is-promise@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + is-property@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" @@ -5687,6 +5781,15 @@ json-buffer@3.0.1: resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== +json-diff@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-diff/-/json-diff-1.0.0.tgz#73fe9642778cbe5a99eec515ff0de065cf22de6a" + integrity sha512-9M6M60W4GUxmha6eoFhRVKF+tDw70jdqrxo3u6aqQ6/cwW6RhkSTPI0gvfCRcBwbWiUWvJo9jR6e9TZxcQdhnA== + dependencies: + "@ewoudenberg/difflib" "0.1.0" + cli-color "^2.0.0" + dreamopt "~0.8.0" + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" @@ -6235,6 +6338,13 @@ lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.1.tgz#8da8d2f5f59827edb388e63e459ac23d6d408fea" integrity sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA== +lru-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== + dependencies: + es5-ext "~0.10.2" + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" @@ -6394,6 +6504,20 @@ mdast-util-to-string@^2.0.0: resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz" integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w== +memoizee@^0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" + integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.53" + es6-weak-map "^2.0.3" + event-emitter "^0.3.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" + memory-pager@^1.0.2: version "1.5.0" resolved "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz" @@ -6792,6 +6916,11 @@ nerf-dart@^1.0.0: resolved "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz" integrity sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g== +next-tick@1, next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" @@ -8974,6 +9103,14 @@ through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8, through@~2.3, t resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +timers-ext@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" + integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + dependencies: + es5-ext "~0.10.46" + next-tick "1" + tiny-relative-date@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz" @@ -9202,6 +9339,16 @@ type-fest@^1.0.2: resolved "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz" @@ -9595,7 +9742,7 @@ word-wrap@^1.2.3, word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wordwrap@^1.0.0: +wordwrap@>=0.0.2, wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=