├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc └── cloudwatch-configuration.png ├── index.js ├── lib └── arg-parser.js ├── package.json ├── test ├── actions-test.js ├── arg-parser-test.js ├── commands-test.js ├── find-command-test.js ├── integration-test.js ├── response-structure-test.js └── responses-test.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .idea/* 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | script: 5 | - yarn lint 6 | - yarn test 7 | cache: yarn 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016 Localytics http://www.localytics.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lambda-slack-router 2 | 3 | [![Build Status](https://travis-ci.org/localytics/lambda-slack-router.svg?branch=master)](https://travis-ci.org/localytics/lambda-slack-router) 4 | [![NPM Version](https://img.shields.io/npm/v/lambda-slack-router.svg)](https://www.npmjs.com/package/lambda-slack-router) 5 | 6 | `lambda-slack-router` is a pattern for building [Slack slash commands](https://api.slack.com/slash-commands) using the Amazon AWS Lambda service and Node.js. It functions as a single endpoint that receives a JSON payload from Slack and returns an appropriate response. For instance, if you were to enter 7 | 8 | /testbot ping 9 | 10 | into a correctly configured Slack channel, it would call the appropriate `ping` command and return the generated response to the user. A `help` command is also generated that is based on the provided commands and their descriptions, so that you can also call 11 | 12 | /testbot help 13 | 14 | and a usage message will be returned. 15 | 16 | ## Installation 17 | 18 | This package is [hosted on npm](https://www.npmjs.com/package/lambda-slack-router). From the root of your project run: 19 | 20 | $ npm install --save lambda-slack-router 21 | 22 | or if you're using `yarn`, run: 23 | 24 | $ yarn add lambda-slack-router 25 | 26 | ## Configuration 27 | 28 | Commands are added to the slackbot through the `addCommand` function. Sample configuration for the above ping command would look like 29 | 30 | ```javascript 31 | var SlackBot = require('lambda-slack-router'); 32 | var slackbot = new SlackBot({ token: "" }); 33 | slackbot.addCommand('ping', 'Ping the lambda', function (options, callback) { 34 | callback(null, slackbot.inChannelResponse('Hello World')); 35 | }); 36 | ``` 37 | 38 | In the above code, a slackbot is created with the given token (used for verifying the authenticity of each request). The ping command is then added to the routing, and when called responds with an in-channel response of 'Hello World'. 39 | 40 | The first argument to the `addCommand` function is the name of the command. The second argument is the description of the function. This is used in the generated `help` command, and is useful to your users when they can't remember the syntax of your bot. 41 | 42 | The two arguments passed to the command callback are `options` and `callback`. The `options` object contains all of the properties that came from lambda, as well as the `args` property (the arguments passed to the function, as an object). The callback function is the same as the `context.done` function that's built into [lambda](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html). The function expects that an error will be passed in as the left argument if there is one, and otherwise a successful execution's response will be passed in as the right argument. 43 | 44 | The responses for Slack can either be ephemeral (returning to the user that invoked the function) or in-channel (returning to everyone in the channel in which the function was invoked). SlackBot has a built-in helper for each of these types of responses which are `ephemeralResponse` and `inChannelResponse` respectively. If you pass a string to either one of these functions they return a correctly-formatted object. If you want more fine-grained control, you can pass an object to them and they will set the `response_type` attribute. You can also ignore these functions entirely if you want to return a custom payload. 45 | 46 | ### Response Structure 47 | 48 | By default, the SlackBot will respond with the exact structure that slack expects. This will function properly using [our scaffold](https://github.com/localytics/serverless-slackbot-scaffold) because of the request template that we constructed: 49 | 50 | ```json 51 | "requestTemplates": { 52 | "application/x-www-form-urlencoded": "{\"body\" : $input.json(\"$\")}" 53 | } 54 | ``` 55 | 56 | You can, however, set up your own function to build the structure of your responses by setting the `structureResponse` property of the configuration object that you pass to the SlackBot constructor. This example below will be sufficient to get API gateway working out of the box: 57 | 58 | ```javascript 59 | var slackbot = new SlackBot({ 60 | structureResponse: function (response) { 61 | return { body: JSON.stringify(response) }; 62 | } 63 | }); 64 | ``` 65 | 66 | ## Responding to Buttons with Actions 67 | 68 | Before you can have `Buttons with Actions` you will need to setup a [SlackApp](https://api.slack.com/slack-apps). Note you do not need to publicly publish your SlackApp to utilize the Slack buttons. See the [slack documentation](https://api.slack.com/docs/message-buttons) for more information on buttons. 69 | 70 | The action configured must match the `name` value of the presented button. 71 | There's no need to use inChannelResponse or ephemeralResponse since your provided message will replace the existing message when using the callback. 72 | 73 | ```javascript 74 | slackbot.addAction('buttonPress', function (options, callback) { 75 | callback(null, { text: "You pressed a button" }); 76 | }); 77 | ``` 78 | 79 | ## Security 80 | 81 | Slackbots can optionally be locked down with the token provided by slack. If provided, the token will be checked against each request. It is recommended to provide a token whenever possible, as the endpoint is otherwise vulnerable to exploitation. It is usually easiest to maintain the token in an environment variable, like so: 82 | 83 | ```javascript 84 | var slackbot = new SlackBot({ token: process.env.SLACK_VERIFICATION_TOKEN }); 85 | ``` 86 | 87 | ## Cold Start 88 | 89 | After uploading a new version of your lambda function or extended periods of inactivity, AWS spins down your lambda in order to conserve resources. This results in the lambda being in a "cold" state, and the next time it is accessed it must spin up again, resulting in a "cold start penalty." This can be especially problematic for slackbots, as the extra time needed to spin up the lambda can take longer than slack is willing to wait for a response. To mediate this, you can set `pingEnabled` to true in the slackbot config and set up a CloudWatch event that will ping your lambda every 5 minutes to keep it running hot. Below is an example of the necessary configuration: 90 | 91 | ```javascript 92 | var slackbot = new SlackBot({ pingEnabled: true }); 93 | ``` 94 | 95 | ![CloudWatch configuration](doc/cloudwatch-configuration.png) 96 | 97 | If you're using Serverless to configure your lambda project, you can add the following to your `s-function.json` file to configure the event source on function deploy: 98 | 99 | ```json 100 | { 101 | "events": [ 102 | { 103 | "name": "ping-slackbot", 104 | "type": "schedule", 105 | "config": { 106 | "schedule": "rate(5 minutes)", 107 | "enabled": true 108 | } 109 | } 110 | ] 111 | } 112 | ``` 113 | 114 | ## Arguments 115 | 116 | Commands can optionally have arguments. When commands have arguments, the router will only invoke that command if the number of arguments matches. Arguments are specified as an array as the second argument to `addCommand`. As an example: 117 | 118 | ```javascript 119 | slackbot.addCommand('testing', ['one', 'two'], 'Testing', function (options, callback) { 120 | callback(null, slackbot.ephemeralResponse('One: ' + options.args.one + ', Two: ' + options.args.two)); 121 | }); 122 | ``` 123 | 124 | There are three types of arguments: required, optional, and splat. The two arguments in the above command are both required. To specify an optional command use an object with one key where the key is the name of the argument and the value is the optional value. To specify a splat argument (one that will be an array of indeterminant length) append an ellipsis to the end of the name. An example that uses all three is below: 125 | 126 | ```javascript 127 | slackbot.addCommand('echo', ['title', { lastName: 'User' }, 'words...'], 'Respond to the user', function (options, callback) { 128 | var response = 'Hello ' + options.args.title + ' ' + options.args.lastName; 129 | if (option.args.words.length) { 130 | response += ', ' + options.args.words.join(' '); 131 | } 132 | callback(null, slackbot.ephemeralResponse(response)); 133 | }); 134 | ``` 135 | 136 | ## Routing 137 | 138 | The routing for the commands is achieved by the Slackbot's router acting as the [handler function](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-han) for the lambda. After a Slackbot has been fully configured (adding in configuration, building the command callbacks, etc.), the handler should be set to the return value of the buildRouter function. 139 | 140 | ```javascript 141 | exports.handler = slackbot.buildRouter(); 142 | ``` 143 | 144 | ## Testing 145 | 146 | It's helpful in testing your function to also export the slackbot itself. If it's part of the module's exports, each function can be tested explicitly as opposed to having to go through the router (which would be testing library code instead of your own). A sample test using `mocha` and `chai` for the aforementioned `ping` function would look like 147 | 148 | ```javascript 149 | var expect = require('chai').expect; 150 | var slackbot = require('../slackbot/handler').slackbot; 151 | 152 | describe('slackbot', function () { 153 | it('responds to ping', function () { 154 | var received = false, receivedArgs = [], callback = function (error, success) { 155 | received = true; 156 | receivedArgs = [error, success]; 157 | }; 158 | 159 | slackbot.ping(null, callback); 160 | expect(received).to.be.true; 161 | expect(receivedArgs).to.deep.eq([null, slackbot.inChannelResponse('Hello World')]); 162 | }); 163 | }); 164 | ``` 165 | 166 | assuming your handler is named index.js and you had `exports.slackbot = slackbot` in your handler. 167 | -------------------------------------------------------------------------------- /doc/cloudwatch-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/localytics/lambda-slack-router/824e864080188a5146c08cb426c986ff1defdbcc/doc/cloudwatch-configuration.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ArgParser = require('./lib/arg-parser'); 4 | var qs = require('qs'); 5 | 6 | // wraps logic around routing 7 | function SlackBot(config) { 8 | this.config = config || {}; 9 | this.commands = {}; 10 | this.aliases = {}; 11 | 12 | if (!this.config.structureResponse) { 13 | this.config.structureResponse = function (response) { 14 | return response; 15 | }; 16 | } 17 | } 18 | 19 | // build a response to return to Slack 20 | SlackBot.prototype._buildResponse = function (response_type, response) { 21 | var modifiedResponse = response; 22 | if (typeof response === 'string') { 23 | modifiedResponse = { response_type: response_type, text: response }; 24 | } else { 25 | modifiedResponse.response_type = response_type; 26 | } 27 | return this.config.structureResponse(modifiedResponse); 28 | }; 29 | 30 | // add a command 31 | SlackBot.prototype.addCommand = function (command, args, desc, callback) { 32 | var realCallback = callback; 33 | var realDesc = desc; 34 | var realArgs = args; 35 | 36 | // if only 3 arguments are passed, then we're assuming no args are used for 37 | // this function, in which case we should shift all of the arguments down one 38 | if (arguments.length === 3) { 39 | realCallback = desc; 40 | realDesc = args; 41 | realArgs = []; 42 | } 43 | 44 | this[command] = realCallback; 45 | this.commands[command] = { args: realArgs, desc: realDesc }; 46 | }; 47 | 48 | // alias a command so that it can be called by multiple names 49 | SlackBot.prototype.aliasCommand = function (commandName) { 50 | var argIndex; 51 | 52 | if (!Object.prototype.hasOwnProperty.call(this.commands, commandName)) { 53 | throw new Error(commandName + ' is not a configured command'); 54 | } 55 | 56 | for (argIndex = 1; argIndex < arguments.length; argIndex += 1) { 57 | if (Object.prototype.hasOwnProperty.call(this.aliases, arguments[argIndex])) { 58 | throw new Error(arguments[argIndex] + ' is already aliased or is an invalid alias name'); 59 | } 60 | this.aliases[arguments[argIndex]] = commandName; 61 | } 62 | }; 63 | 64 | // call a stored command 65 | SlackBot.prototype.callCommand = function (commandName, event, callback) { 66 | var args; 67 | var modifiedEvent = event; 68 | 69 | if (!Object.prototype.hasOwnProperty.call(this.commands, commandName)) { 70 | return this.help(event, callback); 71 | } 72 | args = ArgParser.align(this.commands[commandName].args, event.args || []); 73 | if (args !== false) { 74 | modifiedEvent.args = args; 75 | return this[commandName](modifiedEvent, callback); 76 | } 77 | return this.help(event, callback); 78 | }; 79 | 80 | // add an action 81 | SlackBot.prototype.addAction = function (action, callback) { 82 | if (!this[action]) { 83 | this[action] = callback; 84 | } else { 85 | throw new Error('Action ' + action + ' is already defined as a command or action'); 86 | } 87 | }; 88 | 89 | // process an action 90 | SlackBot.prototype.processAction = function (action, event, callback) { 91 | if (!this[action]) { 92 | throw new Error('Tried to process ' + action + ' but did not find the corresponding action'); 93 | } else { 94 | return this[action](event, callback); 95 | } 96 | }; 97 | 98 | // respond to the whole channel 99 | SlackBot.prototype.inChannelResponse = function (response) { 100 | return this._buildResponse('in_channel', response); 101 | }; 102 | 103 | // respond to just the requesting user 104 | SlackBot.prototype.ephemeralResponse = function (response) { 105 | return this._buildResponse('ephemeral', response); 106 | }; 107 | 108 | // respond with a usage message 109 | SlackBot.prototype.help = function (event, callback) { 110 | var _this = this; 111 | var helpText = ''; 112 | var aliasText; 113 | 114 | Object.keys(this.commands).forEach(function (command) { 115 | helpText += command; 116 | 117 | // add argument description 118 | if (this.commands[command].args.length) { 119 | helpText += ' ' + this.commands[command].args.map(function (arg) { 120 | var optionalArgName; 121 | if (arg instanceof Object) { 122 | optionalArgName = Object.keys(arg)[0]; 123 | return optionalArgName + ':' + arg[optionalArgName]; 124 | } 125 | return arg.toString(); 126 | }).join(' '); 127 | } 128 | 129 | // add alias description 130 | aliasText = Object.keys(this.aliases).filter(function (alias) { 131 | return _this.aliases[alias] === command; 132 | }); 133 | if (aliasText.length) { 134 | helpText += ' (' + aliasText.join(', ') + ')'; 135 | } 136 | 137 | // add command description 138 | helpText += ': ' + this.commands[command].desc + '\n'; 139 | }.bind(this)); 140 | helpText += 'help: display this help message'; 141 | 142 | callback(null, this.ephemeralResponse({ 143 | text: 'Available commands:', 144 | attachments: [{ text: helpText }] 145 | })); 146 | }; 147 | 148 | /** 149 | * Find a command to match the given payload for "one two three", looks for matches for: 150 | * - "one two three" 151 | * - "one two" 152 | * - "one" 153 | */ 154 | SlackBot.prototype.findCommand = function (payload) { 155 | var splitPayload = payload.split(' '); 156 | var commandName; 157 | var commandNameLength; 158 | 159 | for (commandNameLength = payload.length - 1; commandNameLength > 0; commandNameLength -= 1) { 160 | commandName = splitPayload.slice(0, commandNameLength).join(' '); 161 | if (Object.prototype.hasOwnProperty.call(this.aliases, commandName)) { 162 | return { commandName: this.aliases[commandName], args: splitPayload.slice(commandNameLength) }; 163 | } 164 | if (Object.prototype.hasOwnProperty.call(this.commands, commandName)) { 165 | return { commandName: commandName, args: splitPayload.slice(commandNameLength) }; 166 | } 167 | } 168 | return { commandName: 'help' }; 169 | }; 170 | 171 | // control the flow of queries from Slack 172 | SlackBot.prototype.buildRouter = function () { 173 | return function (event, context, callback) { 174 | var foundCommand; 175 | var foundAction; 176 | var builtEvent = event; 177 | 178 | if (this.config.pingEnabled && event.source && event.source === 'aws.events' && 179 | event.resources && event.resources[0].indexOf('ping') !== -1) { 180 | return context.succeed('Ok'); 181 | } 182 | 183 | builtEvent.body = qs.parse(builtEvent.body); 184 | if (builtEvent.body.payload) { 185 | builtEvent.body = JSON.parse(builtEvent.body.payload); 186 | } 187 | 188 | if (this.config.token && (!builtEvent.body.token || builtEvent.body.token !== this.config.token)) { 189 | return context.fail('Invalid Slack token'); 190 | } 191 | 192 | // Handle actions from slack buttons 193 | if (builtEvent.body.actions) { 194 | // Though presented as an array, slack will only send a single action per incoming invocation. 195 | foundAction = builtEvent.body.actions[0].name; 196 | return this.processAction(foundAction, builtEvent, callback); 197 | } // Else route to commands 198 | foundCommand = this.findCommand(builtEvent.body.text); 199 | builtEvent.args = foundCommand.args; 200 | return this.callCommand(foundCommand.commandName, builtEvent, callback); 201 | }.bind(this); 202 | }; 203 | 204 | module.exports = SlackBot; 205 | -------------------------------------------------------------------------------- /lib/arg-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Utils = { 4 | alignArg: function (aligned, arg, value) { 5 | var returnValue = aligned; 6 | var optionalArgName; 7 | 8 | switch (this.typeOf(arg)) { 9 | case 'opt': 10 | optionalArgName = Object.keys(arg)[0]; 11 | if (typeof value === 'undefined') { 12 | returnValue[optionalArgName] = arg[optionalArgName]; 13 | } else { 14 | returnValue[optionalArgName] = value; 15 | } 16 | break; 17 | case 'splat': 18 | returnValue = 'splat'; 19 | break; 20 | default: 21 | if (typeof value === 'undefined') { 22 | return false; 23 | } 24 | returnValue[arg] = value; 25 | break; 26 | } 27 | return returnValue; 28 | }, 29 | 30 | minimumRequired: function (args) { 31 | return args.filter(function (arg) { 32 | return Utils.typeOf(arg) === 'req'; 33 | }).length; 34 | }, 35 | 36 | splatPattern: /\.\.\.$/, 37 | 38 | typeOf: function (arg) { 39 | if (arg instanceof Object) { 40 | return 'opt'; 41 | } 42 | if (arg.match(this.splatPattern)) { 43 | return 'splat'; 44 | } 45 | return 'req'; 46 | } 47 | }; 48 | 49 | module.exports = { 50 | align: function (args, values) { 51 | var aligned = {}; 52 | var alignArgValue; 53 | var argIndex; 54 | var splatIndex; 55 | 56 | if (values.length < Utils.minimumRequired(args)) { 57 | return false; 58 | } 59 | 60 | for (argIndex = 0; argIndex < args.length; argIndex += 1) { 61 | alignArgValue = Utils.alignArg(aligned, args[argIndex], values[argIndex]); 62 | if (alignArgValue === false) { 63 | return false; 64 | } else if (alignArgValue === 'splat') { 65 | splatIndex = argIndex; 66 | break; 67 | } 68 | aligned = alignArgValue; 69 | } 70 | 71 | if (alignArgValue === 'splat') { 72 | args.reverse(); 73 | values.reverse(); 74 | 75 | for (argIndex = 0; argIndex < args.length; argIndex += 1) { 76 | alignArgValue = Utils.alignArg(aligned, args[argIndex], values[argIndex]); 77 | if (alignArgValue === false) { 78 | return false; 79 | } else if (alignArgValue === 'splat') { 80 | break; 81 | } 82 | aligned = alignArgValue; 83 | } 84 | 85 | args.reverse(); 86 | values.reverse(); 87 | aligned[args[splatIndex].substring(0, args[splatIndex].length - 3)] = 88 | values.slice(splatIndex, values.length - argIndex); 89 | } 90 | 91 | return aligned; 92 | }, 93 | 94 | validate: function (args) { 95 | var argIndex; 96 | var foundOptionalArgs = false; 97 | var foundSplattingArgs = false; 98 | 99 | for (argIndex = 0; argIndex < args.length; argIndex += 1) { 100 | switch (Utils.typeOf(args[argIndex])) { 101 | case 'opt': 102 | if (foundSplattingArgs) { 103 | return false; // cannot have optional arguments after a splat 104 | } 105 | foundOptionalArgs = true; 106 | break; 107 | case 'splat': 108 | if (foundSplattingArgs) { 109 | return false; // cannot have two splatting arguments 110 | } 111 | foundSplattingArgs = true; 112 | break; 113 | default: 114 | if (foundOptionalArgs) { 115 | return false; // cannot have required arguments after optional arguments 116 | } 117 | break; 118 | } 119 | } 120 | return true; 121 | } 122 | }; 123 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-slack-router", 3 | "version": "1.8.2", 4 | "description": "A utility class for managing Slack slashcommands in an AWS lambda function", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha", 8 | "lint": "eslint ." 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/localytics/lambda-slack-router.git" 13 | }, 14 | "author": "Localytics", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/localytics/lambda-slack-router/issues" 18 | }, 19 | "homepage": "https://github.com/localytics/lambda-slack-router", 20 | "dependencies": { 21 | "qs": "^6.5.1" 22 | }, 23 | "devDependencies": { 24 | "chai": "^4.1.2", 25 | "dirty-chai": "^2.0.1", 26 | "eslint": "^4.13.1", 27 | "eslint-config-airbnb": "^16.1.0", 28 | "mocha": "^4.0.1", 29 | "sinon": "^4.1.3", 30 | "sinon-chai": "^2.14.0" 31 | }, 32 | "eslintConfig": { 33 | "extends": "airbnb/legacy", 34 | "env": { 35 | "node": true, 36 | "mocha": true 37 | }, 38 | "rules": { 39 | "camelcase": 0, 40 | "func-names": 0, 41 | "no-underscore-dangle": 0, 42 | "max-len": [ 43 | 2, 44 | { 45 | "code": 117 46 | } 47 | ] 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/actions-test.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var SlackBot = require('../index'); 4 | 5 | describe('managing actions', function () { 6 | var slackbot; 7 | var testAction; 8 | 9 | beforeEach(function () { 10 | slackbot = new SlackBot(); 11 | testAction = function () {}; 12 | }); 13 | 14 | it('adds an action', function () { 15 | slackbot.addAction('test', testAction); 16 | 17 | expect(slackbot.test).to.equal(testAction); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/arg-parser-test.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var ArgParser = require('../lib/arg-parser'); 4 | 5 | chai.use(require('sinon-chai')); 6 | chai.use(require('dirty-chai')); 7 | 8 | describe('arg-parser', function () { 9 | describe('align', function () { 10 | describe('simple cases', function () { 11 | it('correctly aligns when no arguments are expected', function () { 12 | expect(ArgParser.align([], [])).to.deep.equal({}); 13 | }); 14 | 15 | it('correctly fails when not enough arguments are given', function () { 16 | expect(ArgParser.align(['a', 'b'], [1])).to.be.false(); 17 | }); 18 | 19 | it('aligns direct mappings', function () { 20 | expect(ArgParser.align(['a', 'b', 'c'], [1, 2, 3])).to.deep.equal({ a: 1, b: 2, c: 3 }); 21 | }); 22 | }); 23 | 24 | describe('splatting', function () { 25 | it('correctly aligns required arguments before splatting', function () { 26 | var args = ['a', 'b', 'c...']; 27 | expect(ArgParser.align(args, [1])).to.be.false(); 28 | expect(ArgParser.align(args, [1, 2])).to.deep.equal({ a: 1, b: 2, c: [] }); 29 | expect(ArgParser.align(args, [1, 2, 3])).to.deep.equal({ a: 1, b: 2, c: [3] }); 30 | expect(ArgParser.align(args, [1, 2, 3, 4, 5])).to.deep.equal({ a: 1, b: 2, c: [3, 4, 5] }); 31 | }); 32 | 33 | it('correctly aligns required arguments around splatting', function () { 34 | var args = ['a', 'b', 'c...', 'd', 'e']; 35 | expect(ArgParser.align(args, [1, 2, 3])).to.be.false(); 36 | expect(ArgParser.align(args, [1, 2, 3, 4])).to.deep.equal({ 37 | a: 1, b: 2, c: [], d: 3, e: 4 38 | }); 39 | expect(ArgParser.align(args, [1, 2, 3, 4, 5])).to.deep.equal({ 40 | a: 1, b: 2, c: [3], d: 4, e: 5 41 | }); 42 | expect(ArgParser.align(args, [1, 2, 3, 4, 5, 6, 7])).to.deep.equal({ 43 | a: 1, b: 2, c: [3, 4, 5], d: 6, e: 7 44 | }); 45 | }); 46 | }); 47 | 48 | describe('optional', function () { 49 | it('allows objects to be passed instead of strings', function () { 50 | expect(ArgParser.align(['a', { b: 2 }], [1])).to.deep.equal({ a: 1, b: 2 }); 51 | }); 52 | 53 | it('correctly overrides defaults when a value is given', function () { 54 | expect(ArgParser.align(['a', { b: 2 }], [1, 3])).to.deep.equal({ a: 1, b: 3 }); 55 | }); 56 | 57 | it('functions when no values are passed for all optional arguments', function () { 58 | expect(ArgParser.align([{ a: 1 }, { b: 2 }], [])).to.deep.equal({ a: 1, b: 2 }); 59 | }); 60 | }); 61 | }); 62 | 63 | describe('validate', function () { 64 | describe('positive cases', function () { 65 | it('base case', function () { 66 | expect(ArgParser.validate([])).to.be.true(); 67 | }); 68 | 69 | it('only required arguments', function () { 70 | expect(ArgParser.validate(['a', 'b', 'c'])).to.be.true(); 71 | }); 72 | 73 | it('all optional arguments', function () { 74 | expect(ArgParser.validate([{ a: 1 }, { b: 2 }])).to.be.true(); 75 | }); 76 | 77 | it('required and optional arguments', function () { 78 | expect(ArgParser.validate(['a', 'b', { c: 3 }, { d: 4 }])).to.be.true(); 79 | }); 80 | 81 | it('just splatting', function () { 82 | expect(ArgParser.validate(['a...'])).to.be.true(); 83 | }); 84 | 85 | it('required arguments and splatting on end', function () { 86 | expect(ArgParser.validate(['a', 'b', 'c...'])).to.be.true(); 87 | }); 88 | 89 | it('required arguments around splatting', function () { 90 | expect(ArgParser.validate(['a', 'b...', 'c'])).to.be.true(); 91 | }); 92 | 93 | it('required arguments after splatting', function () { 94 | expect(ArgParser.validate(['a...', 'b', 'c'])).to.be.true(); 95 | }); 96 | 97 | it('required arguments, optional arguments, and splatting', function () { 98 | expect(ArgParser.validate(['a', 'b', { c: 3 }, { d: 4 }, 'e...'])).to.be.true(); 99 | }); 100 | }); 101 | 102 | describe('negative cases', function () { 103 | it('double splatting', function () { 104 | expect(ArgParser.validate(['a...', 'b', 'c...'])).to.be.false(); 105 | }); 106 | 107 | it('required arguments around optional arguments', function () { 108 | expect(ArgParser.validate(['a', { b: 2 }, 'c'])).to.be.false(); 109 | }); 110 | 111 | it('splatting before optional argument', function () { 112 | expect(ArgParser.validate(['a...', { b: 2 }])).to.be.false(); 113 | }); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /test/commands-test.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var SlackBot = require('../index'); 5 | 6 | chai.use(require('sinon-chai')); 7 | 8 | describe('managing commands', function () { 9 | var slackbot; 10 | var testCommand; 11 | 12 | beforeEach(function () { 13 | slackbot = new SlackBot(); 14 | testCommand = function () {}; 15 | }); 16 | 17 | it('adds a command with the correct description', function () { 18 | slackbot.addCommand('test', 'The test command', testCommand); 19 | 20 | expect(slackbot.test).to.equal(testCommand); 21 | expect(slackbot.commands.test.desc).to.equal('The test command'); 22 | }); 23 | 24 | it('adds a command with the correct arguments', function () { 25 | slackbot.addCommand('test', ['arg1', 'arg2'], 'The test command', testCommand); 26 | 27 | expect(slackbot.test).to.equal(testCommand); 28 | expect(slackbot.commands.test.args).to.deep.equal(['arg1', 'arg2']); 29 | }); 30 | 31 | it('calls the correct command with the correct arguments', function () { 32 | var spiedFunction = sinon.spy(); 33 | var callback = function () {}; 34 | var givenArgs; 35 | 36 | slackbot.addCommand('test', 'test function', spiedFunction); 37 | slackbot.callCommand('test', {}, callback); 38 | 39 | givenArgs = spiedFunction.getCall(0).args; 40 | expect(givenArgs[0]).to.deep.equal({ args: {} }); 41 | expect(givenArgs[1]).to.equal(callback); 42 | }); 43 | 44 | it('restricts calling without the correct number of arguments', function () { 45 | var spiedFunction = sinon.stub(slackbot, 'help'); 46 | var callback = function () {}; 47 | 48 | slackbot.addCommand('test', ['arg1'], 'test function', testCommand); 49 | slackbot.callCommand('test', {}, callback); 50 | 51 | expect(spiedFunction).to.have.been.calledWithExactly({}, callback); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/find-command-test.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var SlackBot = require('../index'); 4 | 5 | chai.use(require('sinon-chai')); 6 | 7 | describe('finding the right commands', function () { 8 | var slackbot; 9 | 10 | beforeEach(function () { 11 | slackbot = new SlackBot(); 12 | slackbot.addCommand('one', 'one function', function () {}); 13 | slackbot.addCommand('one two', 'two function', function () {}); 14 | slackbot.addCommand('one two three', 'three function', function () {}); 15 | }); 16 | 17 | it('finds the correct function when there is overlap', function () { 18 | var foundCommand = slackbot.findCommand('one two three arg1 arg2'); 19 | expect(foundCommand).to.deep.equal({ commandName: 'one two three', args: ['arg1', 'arg2'] }); 20 | }); 21 | 22 | it('finds the correct function with partial matches', function () { 23 | var foundCommand = slackbot.findCommand('one two four arg1 arg2'); 24 | expect(foundCommand).to.deep.equal({ commandName: 'one two', args: ['four', 'arg1', 'arg2'] }); 25 | }); 26 | 27 | it('finds the correct command with partial matches wrapping', function () { 28 | var foundCommand = slackbot.findCommand('one four three arg1 arg2'); 29 | expect(foundCommand).to.deep.equal({ commandName: 'one', args: ['four', 'three', 'arg1', 'arg2'] }); 30 | }); 31 | 32 | it('correctly aliases a command', function () { 33 | slackbot.aliasCommand('one two', 'alias'); 34 | expect(slackbot.aliases).to.deep.equal({ alias: 'one two' }); 35 | }); 36 | 37 | it('supports aliasing multiple times', function () { 38 | slackbot.aliasCommand('one two', 'alias1', 'alias2'); 39 | slackbot.aliasCommand('one two', 'alias3'); 40 | expect(slackbot.aliases).to.deep.equal({ alias1: 'one two', alias2: 'one two', alias3: 'one two' }); 41 | }); 42 | 43 | it('finds the correct command when aliased', function () { 44 | var foundCommand; 45 | slackbot.aliasCommand('one two three', 'alias'); 46 | foundCommand = slackbot.findCommand('alias arg1 arg2'); 47 | expect(foundCommand).to.deep.equal({ commandName: 'one two three', args: ['arg1', 'arg2'] }); 48 | }); 49 | 50 | it('throws an error when attempting to alias a command that does not exist', function () { 51 | expect(function () { slackbot.aliasCommand('invalid', 'alias'); }).to.throw(Error); 52 | }); 53 | 54 | it('throws an error when attempting to use an invalid alias', function () { 55 | slackbot.aliasCommand('one two', 'alias'); 56 | expect(function () { slackbot.aliasCommand('one', 'alias'); }).to.throw(Error); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/integration-test.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | var chai = require('chai'); 3 | var expect = chai.expect; 4 | var SlackBot = require('../index'); 5 | 6 | chai.use(require('sinon-chai')); 7 | 8 | describe('integration', function () { 9 | describe('testbot', function () { 10 | var context = {}; 11 | var callback = null; 12 | var slackbot = new SlackBot({ token: 'token' }); 13 | 14 | var assertHelp = function (options, lambdaContext, lambdaCallback) { 15 | var descriptions = [ 16 | 'testA (tA, A): Test command A', 17 | 'testB arg1 arg2 arg3:3: Test command B', 18 | 'testC arg1 arg2...: Test command C', 19 | 'help: display this help message' 20 | ]; 21 | 22 | slackbot.buildRouter()(options, lambdaContext, lambdaCallback); 23 | expect(lambdaCallback).to.have.been.calledWithExactly(null, { 24 | text: 'Available commands:', 25 | attachments: [{ text: descriptions.join('\n') }], 26 | response_type: 'ephemeral' 27 | }); 28 | }; 29 | 30 | slackbot.addCommand('testA', 'Test command A', function (options, cb) { 31 | cb(null, this.ephemeralResponse('A response')); 32 | }); 33 | slackbot.addCommand('testB', ['arg1', 'arg2', { arg3: 3 }], 'Test command B', function (options, cb) { 34 | cb(null, this.ephemeralResponse('B response')); 35 | }); 36 | slackbot.addCommand('testC', ['arg1', 'arg2...'], 'Test command C', function (options, cb) { 37 | cb(null, this.ephemeralResponse(options.args.arg2.join(' '))); 38 | }); 39 | slackbot.addAction('testButtonAction', function (options, cb) { 40 | cb(null, { text: 'You pressed the ' + options.body.actions[0].name + ' button' }); 41 | }); 42 | 43 | slackbot.aliasCommand('testA', 'tA', 'A'); 44 | 45 | beforeEach(function () { 46 | callback = sinon.spy(); 47 | context.fail = sinon.spy(); 48 | }); 49 | 50 | it('fails when the provided token is invalid', function () { 51 | var event = { 52 | body: { 53 | token: 'foo', 54 | text: 'help' 55 | } 56 | }; 57 | slackbot.buildRouter()(event, context, callback); 58 | expect(context.fail).to.have.been.calledWithExactly('Invalid Slack token'); 59 | }); 60 | 61 | it('responds with help text', function () { 62 | var event = { 63 | body: { 64 | token: 'token', 65 | text: 'help' 66 | } 67 | }; 68 | assertHelp(event, context, callback); 69 | }); 70 | 71 | it('routes to the help text by default', function () { 72 | var event = { 73 | body: { 74 | token: 'token', 75 | text: '' 76 | } 77 | }; 78 | assertHelp(event, context, callback); 79 | }); 80 | 81 | it('routes to the help text when invalid command is specified', function () { 82 | var event = { 83 | body: { 84 | token: 'token', 85 | text: 'invalid' 86 | } 87 | }; 88 | assertHelp(event, context, callback); 89 | }); 90 | 91 | it('routes to the appropriate command name', function () { 92 | var event = { 93 | body: { 94 | token: 'token', 95 | text: 'testA' 96 | } 97 | }; 98 | slackbot.buildRouter()(event, context, callback); 99 | 100 | expect(callback).to.have.been.calledWithExactly(null, { 101 | text: 'A response', 102 | response_type: 'ephemeral' 103 | }); 104 | }); 105 | 106 | it('routes to the appropriate action', function () { 107 | var event = { 108 | body: 'payload={"actions":[{"name":"testButtonAction","value":"testValue"}],"token":"token"}' 109 | }; 110 | slackbot.buildRouter()(event, context, callback); 111 | 112 | expect(callback).to.have.been.calledWithExactly(null, { 113 | text: 'You pressed the testButtonAction button' 114 | }); 115 | }); 116 | 117 | it('supports splatting the last argument', function () { 118 | var event = { 119 | body: { 120 | token: 'token', 121 | text: 'testC these are all my words' 122 | } 123 | }; 124 | slackbot.buildRouter()(event, context, callback); 125 | 126 | expect(callback).to.have.been.calledWithExactly(null, { 127 | text: 'are all my words', 128 | response_type: 'ephemeral' 129 | }); 130 | }); 131 | 132 | it('correctly splats when only one argument is given', function () { 133 | var event = { 134 | body: { 135 | token: 'token', 136 | text: 'testC arg1 arg2' 137 | } 138 | }; 139 | slackbot.buildRouter()(event, context, callback); 140 | 141 | expect(callback).to.have.been.calledWithExactly(null, { 142 | text: 'arg2', 143 | response_type: 'ephemeral' 144 | }); 145 | }); 146 | 147 | it('passes the entire event on to the command', function () { 148 | var event = { 149 | body: { 150 | token: 'token', 151 | text: 'testC arg1 arg2' 152 | }, 153 | foo: 'bar' 154 | }; 155 | var stub = sinon.stub(slackbot, 'testC'); 156 | slackbot.buildRouter()(event, context, callback); 157 | 158 | event.args = { arg1: 'arg1', arg2: ['arg2'] }; 159 | expect(stub).to.have.been.calledWithExactly(event, callback); 160 | stub.restore(); 161 | }); 162 | }); 163 | 164 | describe('examples', function () { 165 | var lambdaCallback = null; 166 | var slackbot = new SlackBot({ token: 'token' }); 167 | var args = ['title', { lastName: 'User' }, 'words...']; 168 | 169 | slackbot.addCommand('echo', args, 'Greetings', function (options, callback) { 170 | var response = 'Hello ' + options.args.title + ' ' + options.args.lastName; 171 | if (options.args.words.length) { 172 | response += ', ' + options.args.words.join(' '); 173 | } 174 | callback(null, slackbot.ephemeralResponse(response)); 175 | }); 176 | 177 | beforeEach(function () { 178 | lambdaCallback = sinon.spy(); 179 | }); 180 | 181 | it('returns the expected response', function () { 182 | var event = { 183 | body: { 184 | token: 'token', 185 | text: 'echo Sir User how are you today?' 186 | } 187 | }; 188 | slackbot.buildRouter()(event, {}, lambdaCallback); 189 | 190 | expect(lambdaCallback).to.have.been.calledWithExactly(null, { 191 | text: 'Hello Sir User, how are you today?', 192 | response_type: 'ephemeral' 193 | }); 194 | }); 195 | }); 196 | 197 | describe('no token provided', function () { 198 | var lambdaCallback = null; 199 | var slackbot = new SlackBot(); 200 | 201 | slackbot.addCommand('test', 'Test', function (options, callback) { 202 | callback(null, this.ephemeralResponse('test')); 203 | }); 204 | 205 | beforeEach(function () { 206 | lambdaCallback = sinon.spy(); 207 | }); 208 | 209 | it('does not raise an error for no token passed', function () { 210 | var event = { body: { text: 'test' } }; 211 | slackbot.buildRouter()(event, {}, lambdaCallback); 212 | expect(lambdaCallback).to.have.been.calledWithExactly(null, slackbot.ephemeralResponse('test')); 213 | }); 214 | }); 215 | 216 | describe('ping command', function () { 217 | var pingEvent = { source: 'aws.events', resources: ['test-ping-test'], body: { text: 'test' } }; 218 | 219 | it('returns quickly', function () { 220 | var succeed = sinon.spy(); 221 | new SlackBot({ pingEnabled: true }).buildRouter()(pingEvent, { succeed: succeed }); 222 | expect(succeed).to.have.been.calledWithExactly('Ok'); 223 | }); 224 | 225 | it('ignores when pingEnabled is falsy', function () { 226 | var slackbot = new SlackBot(); 227 | var lambdaCallback = sinon.spy(); 228 | 229 | slackbot.addCommand('test', 'Test', function (options, callback) { 230 | callback('foobar'); 231 | }); 232 | 233 | slackbot.buildRouter()(pingEvent, {}, lambdaCallback); 234 | expect(lambdaCallback).to.have.been.calledWithExactly('foobar'); 235 | }); 236 | }); 237 | }); 238 | -------------------------------------------------------------------------------- /test/response-structure-test.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var SlackBot = require('../index'); 4 | 5 | describe('structureResponse', function () { 6 | it('sets the default response structure', function () { 7 | expect(new SlackBot().ephemeralResponse('foo')).to.deep.equal({ 8 | response_type: 'ephemeral', 9 | text: 'foo' 10 | }); 11 | }); 12 | 13 | it('handles custom response structures', function () { 14 | var slackbot = new SlackBot({ 15 | structureResponse: function (response) { 16 | return { body: JSON.stringify(response) }; 17 | } 18 | }); 19 | 20 | expect(slackbot.inChannelResponse('bar')).to.deep.equal({ 21 | body: '{"response_type":"in_channel","text":"bar"}' 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/responses-test.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var expect = chai.expect; 3 | var SlackBot = require('../index'); 4 | 5 | describe('responses', function () { 6 | it('responds with the correct ephemeral response format', function () { 7 | expect(new SlackBot().ephemeralResponse('foo')).to.deep.equal({ 8 | response_type: 'ephemeral', 9 | text: 'foo' 10 | }); 11 | }); 12 | 13 | it('responds with the correct in-channel response format', function () { 14 | expect(new SlackBot().inChannelResponse('bar')).to.deep.equal({ 15 | response_type: 'in_channel', 16 | text: 'bar' 17 | }); 18 | }); 19 | 20 | it('adds attachments appropriately', function () { 21 | var response = new SlackBot().ephemeralResponse({ 22 | text: 'test', 23 | attachments: [{ 24 | text: 'attachment' 25 | }] 26 | }); 27 | 28 | expect(response).to.deep.equal({ 29 | response_type: 'ephemeral', 30 | text: 'test', 31 | attachments: [{ text: 'attachment' }] 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | acorn-jsx@^3.0.0: 6 | version "3.0.1" 7 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" 8 | dependencies: 9 | acorn "^3.0.4" 10 | 11 | acorn@^3.0.4: 12 | version "3.3.0" 13 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" 14 | 15 | acorn@^5.2.1: 16 | version "5.2.1" 17 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7" 18 | 19 | ajv-keywords@^1.0.0: 20 | version "1.5.1" 21 | resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" 22 | 23 | ajv@^4.7.0: 24 | version "4.11.8" 25 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" 26 | dependencies: 27 | co "^4.6.0" 28 | json-stable-stringify "^1.0.1" 29 | 30 | ajv@^5.3.0: 31 | version "5.5.2" 32 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" 33 | dependencies: 34 | co "^4.6.0" 35 | fast-deep-equal "^1.0.0" 36 | fast-json-stable-stringify "^2.0.0" 37 | json-schema-traverse "^0.3.0" 38 | 39 | ansi-escapes@^2.0.0: 40 | version "2.0.0" 41 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" 42 | 43 | ansi-regex@^2.0.0: 44 | version "2.1.1" 45 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 46 | 47 | ansi-regex@^3.0.0: 48 | version "3.0.0" 49 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 50 | 51 | ansi-styles@^2.2.1: 52 | version "2.2.1" 53 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 54 | 55 | ansi-styles@^3.1.0: 56 | version "3.2.0" 57 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" 58 | dependencies: 59 | color-convert "^1.9.0" 60 | 61 | argparse@^1.0.7: 62 | version "1.0.9" 63 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" 64 | dependencies: 65 | sprintf-js "~1.0.2" 66 | 67 | array-union@^1.0.1: 68 | version "1.0.2" 69 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" 70 | dependencies: 71 | array-uniq "^1.0.1" 72 | 73 | array-uniq@^1.0.1: 74 | version "1.0.3" 75 | resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" 76 | 77 | arrify@^1.0.0: 78 | version "1.0.1" 79 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" 80 | 81 | assertion-error@^1.0.1: 82 | version "1.0.2" 83 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" 84 | 85 | babel-code-frame@^6.22.0: 86 | version "6.22.0" 87 | resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" 88 | dependencies: 89 | chalk "^1.1.0" 90 | esutils "^2.0.2" 91 | js-tokens "^3.0.0" 92 | 93 | balanced-match@^0.4.1: 94 | version "0.4.2" 95 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 96 | 97 | brace-expansion@^1.0.0, brace-expansion@^1.1.7: 98 | version "1.1.7" 99 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59" 100 | dependencies: 101 | balanced-match "^0.4.1" 102 | concat-map "0.0.1" 103 | 104 | browser-stdout@1.3.0: 105 | version "1.3.0" 106 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" 107 | 108 | buffer-shims@~1.0.0: 109 | version "1.0.0" 110 | resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" 111 | 112 | caller-path@^0.1.0: 113 | version "0.1.0" 114 | resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" 115 | dependencies: 116 | callsites "^0.2.0" 117 | 118 | callsites@^0.2.0: 119 | version "0.2.0" 120 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" 121 | 122 | chai@^4.1.2: 123 | version "4.1.2" 124 | resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" 125 | dependencies: 126 | assertion-error "^1.0.1" 127 | check-error "^1.0.1" 128 | deep-eql "^3.0.0" 129 | get-func-name "^2.0.0" 130 | pathval "^1.0.0" 131 | type-detect "^4.0.0" 132 | 133 | chalk@^1.1.0, chalk@^1.1.1: 134 | version "1.1.3" 135 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 136 | dependencies: 137 | ansi-styles "^2.2.1" 138 | escape-string-regexp "^1.0.2" 139 | has-ansi "^2.0.0" 140 | strip-ansi "^3.0.0" 141 | supports-color "^2.0.0" 142 | 143 | chalk@^2.0.0: 144 | version "2.0.1" 145 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d" 146 | dependencies: 147 | ansi-styles "^3.1.0" 148 | escape-string-regexp "^1.0.5" 149 | supports-color "^4.0.0" 150 | 151 | chalk@^2.1.0: 152 | version "2.1.0" 153 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" 154 | dependencies: 155 | ansi-styles "^3.1.0" 156 | escape-string-regexp "^1.0.5" 157 | supports-color "^4.0.0" 158 | 159 | check-error@^1.0.1: 160 | version "1.0.2" 161 | resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 162 | 163 | circular-json@^0.3.1: 164 | version "0.3.1" 165 | resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" 166 | 167 | cli-cursor@^2.1.0: 168 | version "2.1.0" 169 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" 170 | dependencies: 171 | restore-cursor "^2.0.0" 172 | 173 | cli-width@^2.0.0: 174 | version "2.1.0" 175 | resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" 176 | 177 | co@^4.6.0: 178 | version "4.6.0" 179 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 180 | 181 | color-convert@^1.9.0: 182 | version "1.9.0" 183 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" 184 | dependencies: 185 | color-name "^1.1.1" 186 | 187 | color-name@^1.1.1: 188 | version "1.1.3" 189 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 190 | 191 | commander@2.11.0: 192 | version "2.11.0" 193 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" 194 | 195 | concat-map@0.0.1: 196 | version "0.0.1" 197 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 198 | 199 | concat-stream@^1.6.0: 200 | version "1.6.0" 201 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" 202 | dependencies: 203 | inherits "^2.0.3" 204 | readable-stream "^2.2.2" 205 | typedarray "^0.0.6" 206 | 207 | core-util-is@~1.0.0: 208 | version "1.0.2" 209 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 210 | 211 | cross-spawn@^5.1.0: 212 | version "5.1.0" 213 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" 214 | dependencies: 215 | lru-cache "^4.0.1" 216 | shebang-command "^1.2.0" 217 | which "^1.2.9" 218 | 219 | debug@3.1.0, debug@^3.0.1: 220 | version "3.1.0" 221 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 222 | dependencies: 223 | ms "2.0.0" 224 | 225 | deep-eql@^3.0.0: 226 | version "3.0.1" 227 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" 228 | dependencies: 229 | type-detect "^4.0.0" 230 | 231 | deep-is@~0.1.3: 232 | version "0.1.3" 233 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" 234 | 235 | del@^2.0.2: 236 | version "2.2.2" 237 | resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" 238 | dependencies: 239 | globby "^5.0.0" 240 | is-path-cwd "^1.0.0" 241 | is-path-in-cwd "^1.0.0" 242 | object-assign "^4.0.1" 243 | pify "^2.0.0" 244 | pinkie-promise "^2.0.0" 245 | rimraf "^2.2.8" 246 | 247 | diff@3.3.1: 248 | version "3.3.1" 249 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" 250 | 251 | diff@^3.1.0: 252 | version "3.2.0" 253 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" 254 | 255 | dirty-chai@^2.0.1: 256 | version "2.0.1" 257 | resolved "https://registry.yarnpkg.com/dirty-chai/-/dirty-chai-2.0.1.tgz#6b2162ef17f7943589da840abc96e75bda01aff3" 258 | 259 | doctrine@^2.0.2: 260 | version "2.0.2" 261 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.2.tgz#68f96ce8efc56cc42651f1faadb4f175273b0075" 262 | dependencies: 263 | esutils "^2.0.2" 264 | 265 | escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: 266 | version "1.0.5" 267 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 268 | 269 | eslint-config-airbnb-base@^12.1.0: 270 | version "12.1.0" 271 | resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz#386441e54a12ccd957b0a92564a4bafebd747944" 272 | dependencies: 273 | eslint-restricted-globals "^0.1.1" 274 | 275 | eslint-config-airbnb@^16.1.0: 276 | version "16.1.0" 277 | resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-16.1.0.tgz#2546bfb02cc9fe92284bf1723ccf2e87bc45ca46" 278 | dependencies: 279 | eslint-config-airbnb-base "^12.1.0" 280 | 281 | eslint-restricted-globals@^0.1.1: 282 | version "0.1.1" 283 | resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" 284 | 285 | eslint-scope@^3.7.1: 286 | version "3.7.1" 287 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" 288 | dependencies: 289 | esrecurse "^4.1.0" 290 | estraverse "^4.1.1" 291 | 292 | eslint@^4.13.1: 293 | version "4.13.1" 294 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.13.1.tgz#0055e0014464c7eb7878caf549ef2941992b444f" 295 | dependencies: 296 | ajv "^5.3.0" 297 | babel-code-frame "^6.22.0" 298 | chalk "^2.1.0" 299 | concat-stream "^1.6.0" 300 | cross-spawn "^5.1.0" 301 | debug "^3.0.1" 302 | doctrine "^2.0.2" 303 | eslint-scope "^3.7.1" 304 | espree "^3.5.2" 305 | esquery "^1.0.0" 306 | estraverse "^4.2.0" 307 | esutils "^2.0.2" 308 | file-entry-cache "^2.0.0" 309 | functional-red-black-tree "^1.0.1" 310 | glob "^7.1.2" 311 | globals "^11.0.1" 312 | ignore "^3.3.3" 313 | imurmurhash "^0.1.4" 314 | inquirer "^3.0.6" 315 | is-resolvable "^1.0.0" 316 | js-yaml "^3.9.1" 317 | json-stable-stringify-without-jsonify "^1.0.1" 318 | levn "^0.3.0" 319 | lodash "^4.17.4" 320 | minimatch "^3.0.2" 321 | mkdirp "^0.5.1" 322 | natural-compare "^1.4.0" 323 | optionator "^0.8.2" 324 | path-is-inside "^1.0.2" 325 | pluralize "^7.0.0" 326 | progress "^2.0.0" 327 | require-uncached "^1.0.3" 328 | semver "^5.3.0" 329 | strip-ansi "^4.0.0" 330 | strip-json-comments "~2.0.1" 331 | table "^4.0.1" 332 | text-table "~0.2.0" 333 | 334 | espree@^3.5.2: 335 | version "3.5.2" 336 | resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca" 337 | dependencies: 338 | acorn "^5.2.1" 339 | acorn-jsx "^3.0.0" 340 | 341 | esprima@^4.0.0: 342 | version "4.0.0" 343 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" 344 | 345 | esquery@^1.0.0: 346 | version "1.0.0" 347 | resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" 348 | dependencies: 349 | estraverse "^4.0.0" 350 | 351 | esrecurse@^4.1.0: 352 | version "4.1.0" 353 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220" 354 | dependencies: 355 | estraverse "~4.1.0" 356 | object-assign "^4.0.1" 357 | 358 | estraverse@^4.0.0, estraverse@^4.1.1, estraverse@^4.2.0: 359 | version "4.2.0" 360 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" 361 | 362 | estraverse@~4.1.0: 363 | version "4.1.1" 364 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2" 365 | 366 | esutils@^2.0.2: 367 | version "2.0.2" 368 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 369 | 370 | external-editor@^2.0.4: 371 | version "2.0.4" 372 | resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.4.tgz#1ed9199da9cbfe2ef2f7a31b2fde8b0d12368972" 373 | dependencies: 374 | iconv-lite "^0.4.17" 375 | jschardet "^1.4.2" 376 | tmp "^0.0.31" 377 | 378 | fast-deep-equal@^1.0.0: 379 | version "1.0.0" 380 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" 381 | 382 | fast-json-stable-stringify@^2.0.0: 383 | version "2.0.0" 384 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" 385 | 386 | fast-levenshtein@~2.0.4: 387 | version "2.0.6" 388 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 389 | 390 | figures@^2.0.0: 391 | version "2.0.0" 392 | resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" 393 | dependencies: 394 | escape-string-regexp "^1.0.5" 395 | 396 | file-entry-cache@^2.0.0: 397 | version "2.0.0" 398 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" 399 | dependencies: 400 | flat-cache "^1.2.1" 401 | object-assign "^4.0.1" 402 | 403 | flat-cache@^1.2.1: 404 | version "1.2.2" 405 | resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" 406 | dependencies: 407 | circular-json "^0.3.1" 408 | del "^2.0.2" 409 | graceful-fs "^4.1.2" 410 | write "^0.2.1" 411 | 412 | formatio@1.2.0, formatio@^1.2.0: 413 | version "1.2.0" 414 | resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb" 415 | dependencies: 416 | samsam "1.x" 417 | 418 | fs.realpath@^1.0.0: 419 | version "1.0.0" 420 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 421 | 422 | functional-red-black-tree@^1.0.1: 423 | version "1.0.1" 424 | resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" 425 | 426 | get-func-name@^2.0.0: 427 | version "2.0.0" 428 | resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 429 | 430 | glob@7.1.2, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: 431 | version "7.1.2" 432 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 433 | dependencies: 434 | fs.realpath "^1.0.0" 435 | inflight "^1.0.4" 436 | inherits "2" 437 | minimatch "^3.0.4" 438 | once "^1.3.0" 439 | path-is-absolute "^1.0.0" 440 | 441 | globals@^11.0.1: 442 | version "11.1.0" 443 | resolved "https://registry.yarnpkg.com/globals/-/globals-11.1.0.tgz#632644457f5f0e3ae711807183700ebf2e4633e4" 444 | 445 | globby@^5.0.0: 446 | version "5.0.0" 447 | resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" 448 | dependencies: 449 | array-union "^1.0.1" 450 | arrify "^1.0.0" 451 | glob "^7.0.3" 452 | object-assign "^4.0.1" 453 | pify "^2.0.0" 454 | pinkie-promise "^2.0.0" 455 | 456 | graceful-fs@^4.1.2: 457 | version "4.1.11" 458 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 459 | 460 | growl@1.10.3: 461 | version "1.10.3" 462 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" 463 | 464 | has-ansi@^2.0.0: 465 | version "2.0.0" 466 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 467 | dependencies: 468 | ansi-regex "^2.0.0" 469 | 470 | has-flag@^2.0.0: 471 | version "2.0.0" 472 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" 473 | 474 | he@1.1.1: 475 | version "1.1.1" 476 | resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" 477 | 478 | iconv-lite@^0.4.17: 479 | version "0.4.18" 480 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" 481 | 482 | ignore@^3.3.3: 483 | version "3.3.3" 484 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d" 485 | 486 | imurmurhash@^0.1.4: 487 | version "0.1.4" 488 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 489 | 490 | inflight@^1.0.4: 491 | version "1.0.6" 492 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 493 | dependencies: 494 | once "^1.3.0" 495 | wrappy "1" 496 | 497 | inherits@2, inherits@^2.0.3, inherits@~2.0.1: 498 | version "2.0.3" 499 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 500 | 501 | inquirer@^3.0.6: 502 | version "3.2.1" 503 | resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.2.1.tgz#06ceb0f540f45ca548c17d6840959878265fa175" 504 | dependencies: 505 | ansi-escapes "^2.0.0" 506 | chalk "^2.0.0" 507 | cli-cursor "^2.1.0" 508 | cli-width "^2.0.0" 509 | external-editor "^2.0.4" 510 | figures "^2.0.0" 511 | lodash "^4.3.0" 512 | mute-stream "0.0.7" 513 | run-async "^2.2.0" 514 | rx-lite "^4.0.8" 515 | rx-lite-aggregates "^4.0.8" 516 | string-width "^2.1.0" 517 | strip-ansi "^4.0.0" 518 | through "^2.3.6" 519 | 520 | is-fullwidth-code-point@^2.0.0: 521 | version "2.0.0" 522 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 523 | 524 | is-path-cwd@^1.0.0: 525 | version "1.0.0" 526 | resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" 527 | 528 | is-path-in-cwd@^1.0.0: 529 | version "1.0.0" 530 | resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" 531 | dependencies: 532 | is-path-inside "^1.0.0" 533 | 534 | is-path-inside@^1.0.0: 535 | version "1.0.0" 536 | resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" 537 | dependencies: 538 | path-is-inside "^1.0.1" 539 | 540 | is-promise@^2.1.0: 541 | version "2.1.0" 542 | resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" 543 | 544 | is-resolvable@^1.0.0: 545 | version "1.0.0" 546 | resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" 547 | dependencies: 548 | tryit "^1.0.1" 549 | 550 | isarray@0.0.1: 551 | version "0.0.1" 552 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 553 | 554 | isarray@~1.0.0: 555 | version "1.0.0" 556 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 557 | 558 | isexe@^2.0.0: 559 | version "2.0.0" 560 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 561 | 562 | js-tokens@^3.0.0: 563 | version "3.0.1" 564 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" 565 | 566 | js-yaml@^3.9.1: 567 | version "3.10.0" 568 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" 569 | dependencies: 570 | argparse "^1.0.7" 571 | esprima "^4.0.0" 572 | 573 | jschardet@^1.4.2: 574 | version "1.5.0" 575 | resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.0.tgz#a61f310306a5a71188e1b1acd08add3cfbb08b1e" 576 | 577 | json-schema-traverse@^0.3.0: 578 | version "0.3.1" 579 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" 580 | 581 | json-stable-stringify-without-jsonify@^1.0.1: 582 | version "1.0.1" 583 | resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" 584 | 585 | json-stable-stringify@^1.0.1: 586 | version "1.0.1" 587 | resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" 588 | dependencies: 589 | jsonify "~0.0.0" 590 | 591 | jsonify@~0.0.0: 592 | version "0.0.0" 593 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 594 | 595 | just-extend@^1.1.26: 596 | version "1.1.27" 597 | resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905" 598 | 599 | levn@^0.3.0, levn@~0.3.0: 600 | version "0.3.0" 601 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" 602 | dependencies: 603 | prelude-ls "~1.1.2" 604 | type-check "~0.3.2" 605 | 606 | lodash.get@^4.4.2: 607 | version "4.4.2" 608 | resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" 609 | 610 | lodash@^4.0.0, lodash@^4.17.4, lodash@^4.3.0: 611 | version "4.17.4" 612 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 613 | 614 | lolex@^1.6.0: 615 | version "1.6.0" 616 | resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" 617 | 618 | lolex@^2.2.0: 619 | version "2.3.1" 620 | resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.1.tgz#3d2319894471ea0950ef64692ead2a5318cff362" 621 | 622 | lru-cache@^4.0.1: 623 | version "4.1.1" 624 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" 625 | dependencies: 626 | pseudomap "^1.0.2" 627 | yallist "^2.1.2" 628 | 629 | mimic-fn@^1.0.0: 630 | version "1.1.0" 631 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" 632 | 633 | minimatch@^3.0.2: 634 | version "3.0.3" 635 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 636 | dependencies: 637 | brace-expansion "^1.0.0" 638 | 639 | minimatch@^3.0.4: 640 | version "3.0.4" 641 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 642 | dependencies: 643 | brace-expansion "^1.1.7" 644 | 645 | minimist@0.0.8: 646 | version "0.0.8" 647 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 648 | 649 | mkdirp@0.5.1, mkdirp@^0.5.1: 650 | version "0.5.1" 651 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 652 | dependencies: 653 | minimist "0.0.8" 654 | 655 | mocha@^4.0.1: 656 | version "4.0.1" 657 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.0.1.tgz#0aee5a95cf69a4618820f5e51fa31717117daf1b" 658 | dependencies: 659 | browser-stdout "1.3.0" 660 | commander "2.11.0" 661 | debug "3.1.0" 662 | diff "3.3.1" 663 | escape-string-regexp "1.0.5" 664 | glob "7.1.2" 665 | growl "1.10.3" 666 | he "1.1.1" 667 | mkdirp "0.5.1" 668 | supports-color "4.4.0" 669 | 670 | ms@2.0.0: 671 | version "2.0.0" 672 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 673 | 674 | mute-stream@0.0.7: 675 | version "0.0.7" 676 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" 677 | 678 | natural-compare@^1.4.0: 679 | version "1.4.0" 680 | resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 681 | 682 | nise@^1.2.0: 683 | version "1.2.0" 684 | resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.0.tgz#079d6cadbbcb12ba30e38f1c999f36ad4d6baa53" 685 | dependencies: 686 | formatio "^1.2.0" 687 | just-extend "^1.1.26" 688 | lolex "^1.6.0" 689 | path-to-regexp "^1.7.0" 690 | text-encoding "^0.6.4" 691 | 692 | object-assign@^4.0.1: 693 | version "4.1.1" 694 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 695 | 696 | once@^1.3.0: 697 | version "1.4.0" 698 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 699 | dependencies: 700 | wrappy "1" 701 | 702 | onetime@^2.0.0: 703 | version "2.0.1" 704 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" 705 | dependencies: 706 | mimic-fn "^1.0.0" 707 | 708 | optionator@^0.8.2: 709 | version "0.8.2" 710 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" 711 | dependencies: 712 | deep-is "~0.1.3" 713 | fast-levenshtein "~2.0.4" 714 | levn "~0.3.0" 715 | prelude-ls "~1.1.2" 716 | type-check "~0.3.2" 717 | wordwrap "~1.0.0" 718 | 719 | os-tmpdir@~1.0.1: 720 | version "1.0.2" 721 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 722 | 723 | path-is-absolute@^1.0.0: 724 | version "1.0.1" 725 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 726 | 727 | path-is-inside@^1.0.1, path-is-inside@^1.0.2: 728 | version "1.0.2" 729 | resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 730 | 731 | path-to-regexp@^1.7.0: 732 | version "1.7.0" 733 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" 734 | dependencies: 735 | isarray "0.0.1" 736 | 737 | pathval@^1.0.0: 738 | version "1.1.0" 739 | resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" 740 | 741 | pify@^2.0.0: 742 | version "2.3.0" 743 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 744 | 745 | pinkie-promise@^2.0.0: 746 | version "2.0.1" 747 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 748 | dependencies: 749 | pinkie "^2.0.0" 750 | 751 | pinkie@^2.0.0: 752 | version "2.0.4" 753 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 754 | 755 | pluralize@^7.0.0: 756 | version "7.0.0" 757 | resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" 758 | 759 | prelude-ls@~1.1.2: 760 | version "1.1.2" 761 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" 762 | 763 | process-nextick-args@~1.0.6: 764 | version "1.0.7" 765 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 766 | 767 | progress@^2.0.0: 768 | version "2.0.0" 769 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" 770 | 771 | pseudomap@^1.0.2: 772 | version "1.0.2" 773 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 774 | 775 | qs@^6.5.1: 776 | version "6.5.1" 777 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" 778 | 779 | readable-stream@^2.2.2: 780 | version "2.2.9" 781 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.9.tgz#cf78ec6f4a6d1eb43d26488cac97f042e74b7fc8" 782 | dependencies: 783 | buffer-shims "~1.0.0" 784 | core-util-is "~1.0.0" 785 | inherits "~2.0.1" 786 | isarray "~1.0.0" 787 | process-nextick-args "~1.0.6" 788 | string_decoder "~1.0.0" 789 | util-deprecate "~1.0.1" 790 | 791 | require-uncached@^1.0.3: 792 | version "1.0.3" 793 | resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" 794 | dependencies: 795 | caller-path "^0.1.0" 796 | resolve-from "^1.0.0" 797 | 798 | resolve-from@^1.0.0: 799 | version "1.0.1" 800 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" 801 | 802 | restore-cursor@^2.0.0: 803 | version "2.0.0" 804 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" 805 | dependencies: 806 | onetime "^2.0.0" 807 | signal-exit "^3.0.2" 808 | 809 | rimraf@^2.2.8: 810 | version "2.6.1" 811 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" 812 | dependencies: 813 | glob "^7.0.5" 814 | 815 | run-async@^2.2.0: 816 | version "2.3.0" 817 | resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" 818 | dependencies: 819 | is-promise "^2.1.0" 820 | 821 | rx-lite-aggregates@^4.0.8: 822 | version "4.0.8" 823 | resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" 824 | dependencies: 825 | rx-lite "*" 826 | 827 | rx-lite@*, rx-lite@^4.0.8: 828 | version "4.0.8" 829 | resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" 830 | 831 | samsam@1.x: 832 | version "1.2.1" 833 | resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.2.1.tgz#edd39093a3184370cb859243b2bdf255e7d8ea67" 834 | 835 | semver@^5.3.0: 836 | version "5.4.1" 837 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" 838 | 839 | shebang-command@^1.2.0: 840 | version "1.2.0" 841 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 842 | dependencies: 843 | shebang-regex "^1.0.0" 844 | 845 | shebang-regex@^1.0.0: 846 | version "1.0.0" 847 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 848 | 849 | signal-exit@^3.0.2: 850 | version "3.0.2" 851 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 852 | 853 | sinon-chai@^2.14.0: 854 | version "2.14.0" 855 | resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.14.0.tgz#da7dd4cc83cd6a260b67cca0f7a9fdae26a1205d" 856 | 857 | sinon@^4.1.3: 858 | version "4.1.3" 859 | resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.1.3.tgz#fc599eda47ed9f1a694ce774b94ab44260bd7ac5" 860 | dependencies: 861 | diff "^3.1.0" 862 | formatio "1.2.0" 863 | lodash.get "^4.4.2" 864 | lolex "^2.2.0" 865 | nise "^1.2.0" 866 | supports-color "^4.4.0" 867 | type-detect "^4.0.5" 868 | 869 | slice-ansi@0.0.4: 870 | version "0.0.4" 871 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" 872 | 873 | sprintf-js@~1.0.2: 874 | version "1.0.3" 875 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 876 | 877 | string-width@^2.0.0, string-width@^2.1.0: 878 | version "2.1.1" 879 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 880 | dependencies: 881 | is-fullwidth-code-point "^2.0.0" 882 | strip-ansi "^4.0.0" 883 | 884 | string_decoder@~1.0.0: 885 | version "1.0.0" 886 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.0.tgz#f06f41157b664d86069f84bdbdc9b0d8ab281667" 887 | dependencies: 888 | buffer-shims "~1.0.0" 889 | 890 | strip-ansi@^3.0.0: 891 | version "3.0.1" 892 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 893 | dependencies: 894 | ansi-regex "^2.0.0" 895 | 896 | strip-ansi@^4.0.0: 897 | version "4.0.0" 898 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 899 | dependencies: 900 | ansi-regex "^3.0.0" 901 | 902 | strip-json-comments@~2.0.1: 903 | version "2.0.1" 904 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 905 | 906 | supports-color@4.4.0: 907 | version "4.4.0" 908 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" 909 | dependencies: 910 | has-flag "^2.0.0" 911 | 912 | supports-color@^2.0.0: 913 | version "2.0.0" 914 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 915 | 916 | supports-color@^4.0.0: 917 | version "4.2.1" 918 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836" 919 | dependencies: 920 | has-flag "^2.0.0" 921 | 922 | supports-color@^4.4.0: 923 | version "4.5.0" 924 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" 925 | dependencies: 926 | has-flag "^2.0.0" 927 | 928 | table@^4.0.1: 929 | version "4.0.1" 930 | resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435" 931 | dependencies: 932 | ajv "^4.7.0" 933 | ajv-keywords "^1.0.0" 934 | chalk "^1.1.1" 935 | lodash "^4.0.0" 936 | slice-ansi "0.0.4" 937 | string-width "^2.0.0" 938 | 939 | text-encoding@^0.6.4: 940 | version "0.6.4" 941 | resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" 942 | 943 | text-table@~0.2.0: 944 | version "0.2.0" 945 | resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 946 | 947 | through@^2.3.6: 948 | version "2.3.8" 949 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 950 | 951 | tmp@^0.0.31: 952 | version "0.0.31" 953 | resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" 954 | dependencies: 955 | os-tmpdir "~1.0.1" 956 | 957 | tryit@^1.0.1: 958 | version "1.0.3" 959 | resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" 960 | 961 | type-check@~0.3.2: 962 | version "0.3.2" 963 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" 964 | dependencies: 965 | prelude-ls "~1.1.2" 966 | 967 | type-detect@^4.0.0: 968 | version "4.0.3" 969 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.3.tgz#0e3f2670b44099b0b46c284d136a7ef49c74c2ea" 970 | 971 | type-detect@^4.0.5: 972 | version "4.0.5" 973 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" 974 | 975 | typedarray@^0.0.6: 976 | version "0.0.6" 977 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 978 | 979 | util-deprecate@~1.0.1: 980 | version "1.0.2" 981 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 982 | 983 | which@^1.2.9: 984 | version "1.2.14" 985 | resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" 986 | dependencies: 987 | isexe "^2.0.0" 988 | 989 | wordwrap@~1.0.0: 990 | version "1.0.0" 991 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 992 | 993 | wrappy@1: 994 | version "1.0.2" 995 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 996 | 997 | write@^0.2.1: 998 | version "0.2.1" 999 | resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" 1000 | dependencies: 1001 | mkdirp "^0.5.1" 1002 | 1003 | yallist@^2.1.2: 1004 | version "2.1.2" 1005 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 1006 | --------------------------------------------------------------------------------