├── .eslintrc ├── CHANGELOG.md ├── index.js ├── package-lock.json ├── package.json ├── readme.md └── test ├── github-named.test.js ├── github.test.js ├── passthrough-named.test.js ├── passthrough.test.js └── run.sh /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "plugins": [ 8 | "node" 9 | ], 10 | "rules": { 11 | "arrow-parens": ["error", "always"], 12 | "no-var": "error", 13 | "prefer-const": "error", 14 | "array-bracket-spacing": ["error", "never"], 15 | "comma-dangle": ["error", "never"], 16 | "computed-property-spacing": ["error", "never"], 17 | "eol-last": "error", 18 | "eqeqeq": ["error", "smart"], 19 | "indent": ["error", 2, { "SwitchCase": 1 }], 20 | "no-confusing-arrow": ["error", {"allowParens": false}], 21 | "no-extend-native": "error", 22 | "no-mixed-spaces-and-tabs": "error", 23 | "no-spaced-func": "error", 24 | "no-trailing-spaces": "error", 25 | "no-unused-vars": "error", 26 | "no-use-before-define": ["error", "nofunc"], 27 | "object-curly-spacing": ["error", "always"], 28 | "prefer-arrow-callback": "error", 29 | "quotes": ["error", "single", "avoid-escape"], 30 | "semi": ["error", "always"], 31 | "space-infix-ops": "error", 32 | "spaced-comment": ["error", "always"], 33 | "keyword-spacing": ["error", { before: true, after: true }], 34 | "template-curly-spacing": ["error", "never"], 35 | "semi-spacing": "error", 36 | "strict": "error", 37 | 38 | "node/no-unsupported-features": ["error", {"version": 6}], 39 | "node/no-missing-require": "error" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 5.0.1 4 | 5 | - Allow lowercase CORS headers from browsers. 6 | 7 | ## 5.0.0 8 | 9 | - Passthrough functions handle CORS. 10 | 11 | ## 4.0.0 12 | 13 | ## 3.1.0 14 | 15 | - Secrets are stored with Api Gateway Keys instead of IAM users. ([#15](https://github.com/mapbox/hookshot/pull/15)) 16 | - NB: Upgrading to this version will generate a **new webhook secret** that will need to be updated in your application. 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | const cf = require('@mapbox/cloudfriend'); 5 | 6 | const random = crypto.randomBytes(4).toString('hex'); 7 | 8 | const Topic = (lambda) => ({ 9 | Type: 'AWS::SNS::Topic', 10 | Properties: { 11 | Subscription: [ 12 | { 13 | Protocol: 'lambda', 14 | Endpoint: cf.getAtt(lambda, 'Arn') 15 | } 16 | ] 17 | } 18 | }); 19 | 20 | const SnsPermission = (lambda, prefix) => ({ 21 | Type: 'AWS::Lambda::Permission', 22 | Properties: { 23 | Action: 'lambda:invokeFunction', 24 | FunctionName: cf.ref(lambda), 25 | Principal: 'sns.amazonaws.com', 26 | SourceArn: cf.ref(`${prefix}InvocationTopic`) 27 | } 28 | }); 29 | 30 | const Secret = () => ({ 31 | Type: 'AWS::ApiGateway::ApiKey', 32 | Properties: { 33 | Enabled: false 34 | } 35 | }); 36 | 37 | const Api = () => ({ 38 | Type: 'AWS::ApiGateway::RestApi', 39 | Properties: { 40 | Name: cf.sub('${AWS::StackName}-webhook'), 41 | FailOnWarnings: true 42 | } 43 | }); 44 | 45 | const Stage = (prefix) => ({ 46 | Type: 'AWS::ApiGateway::Stage', 47 | Properties: { 48 | DeploymentId: cf.ref(`${prefix}Deployment${random}`), 49 | StageName: 'hookshot', 50 | RestApiId: cf.ref(`${prefix}Api`), 51 | MethodSettings: [ 52 | { 53 | HttpMethod: '*', 54 | ResourcePath: '/*', 55 | ThrottlingBurstLimit: 20, 56 | ThrottlingRateLimit: 5 57 | } 58 | ] 59 | } 60 | }); 61 | 62 | const Deployment = (prefix) => ({ 63 | Type: 'AWS::ApiGateway::Deployment', 64 | DependsOn: `${prefix}Method`, 65 | Properties: { 66 | RestApiId: cf.ref(`${prefix}Api`), 67 | StageName: 'unused' 68 | } 69 | }); 70 | 71 | const Method = (prefix) => ({ 72 | Type: 'AWS::ApiGateway::Method', 73 | Properties: { 74 | RestApiId: cf.ref(`${prefix}Api`), 75 | ResourceId: cf.ref(`${prefix}Resource`), 76 | ApiKeyRequired: false, 77 | AuthorizationType: 'None', 78 | HttpMethod: 'POST', 79 | Integration: { 80 | Type: 'AWS', 81 | IntegrationHttpMethod: 'POST', 82 | IntegrationResponses: [ 83 | { 84 | StatusCode: 200 85 | }, 86 | { 87 | StatusCode: 500, 88 | SelectionPattern: '^error.*' 89 | }, 90 | { 91 | StatusCode: 403, 92 | SelectionPattern: '^invalid.*' 93 | } 94 | ], 95 | Uri: cf.sub(`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${prefix}Function.Arn}/invocations`), 96 | RequestTemplates: { 97 | 'application/json': "{\"signature\":\"$input.params('X-Hub-Signature')\",\"body\":$input.json('$')}" 98 | } 99 | }, 100 | MethodResponses: [ 101 | { 102 | StatusCode: '200', 103 | ResponseModels: { 104 | 'application/json': 'Empty' 105 | } 106 | }, 107 | { 108 | StatusCode: '500', 109 | ResponseModels: { 110 | 'application/json': 'Empty' 111 | } 112 | }, 113 | { 114 | StatusCode: '403', 115 | ResponseModels: { 116 | 'application/json': 'Empty' 117 | } 118 | } 119 | ] 120 | } 121 | }); 122 | 123 | const PassthroughFunction = (lambda, prefix) => ({ 124 | Type: 'AWS::Lambda::Function', 125 | Properties: { 126 | Code: { 127 | ZipFile: cf.join('\n', [ 128 | '"use strict";', 129 | 'const AWS = require("aws-sdk");', 130 | cf.sub('const lambda = new AWS.Lambda({ region: "${AWS::Region}" });'), 131 | 'module.exports.lambda = (event, context, callback) => {', 132 | ' if (event.httpMethod === "OPTIONS") {', 133 | ' const requestHeaders = event.headers["Access-Control-Request-Headers"]', 134 | ' || event.headers["access-control-request-headers"];', 135 | ' const response = {', 136 | ' statusCode: 200,', 137 | ' body: "",', 138 | ' headers: {', 139 | ' "Access-Control-Allow-Headers": requestHeaders,', 140 | ' "Access-Control-Allow-Methods": "POST, OPTIONS",', 141 | ' "Access-Control-Allow-Origin": "*"', 142 | ' }', 143 | ' };', 144 | ' callback(null, response);', 145 | ' return;', 146 | ' }', 147 | ' const lambdaParams = {', 148 | cf.sub(` FunctionName: "\${${lambda}}",`), 149 | ' Payload: JSON.stringify(event)', 150 | ' };', 151 | ' lambda.invoke(lambdaParams, (err, response) => {', 152 | ' if (err) return callback(err);', 153 | ' if (!response || !response.Payload) {', 154 | ` callback(new Error("Your Lambda function ${lambda} did not provide a payload"));`, 155 | ' return;', 156 | ' }', 157 | ' var payload = JSON.parse(response.Payload);', 158 | ' payload.headers = payload.headers || {};', 159 | ' payload.headers["Access-Control-Allow-Origin"] = "*";', 160 | ' callback(null, payload)', 161 | ' });', 162 | '};' 163 | ]) 164 | }, 165 | Role: cf.getAtt(`${prefix}PassthroughFunctionRole`, 'Arn'), 166 | Description: cf.sub('Passthrough function for ${AWS::StackName}'), 167 | Handler: 'index.lambda', 168 | Runtime: 'nodejs6.10', 169 | Timeout: 30, 170 | MemorySize: 128 171 | } 172 | }); 173 | 174 | const PassthroughMethod = (lambda, prefix) => ({ 175 | Type: 'AWS::ApiGateway::Method', 176 | Properties: { 177 | RestApiId: cf.ref(`${prefix}Api`), 178 | ResourceId: cf.ref(`${prefix}Resource`), 179 | ApiKeyRequired: false, 180 | AuthorizationType: 'None', 181 | HttpMethod: 'POST', 182 | Integration: { 183 | Type: 'AWS_PROXY', 184 | IntegrationHttpMethod: 'POST', 185 | Uri: cf.sub(`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${prefix}PassthroughFunction.Arn}/invocations`) 186 | } 187 | } 188 | }); 189 | 190 | const Resource = (prefix) => ({ 191 | Type: 'AWS::ApiGateway::Resource', 192 | Properties: { 193 | ParentId: cf.getAtt(`${prefix}Api`, 'RootResourceId'), 194 | RestApiId: cf.ref(`${prefix}Api`), 195 | PathPart: 'webhook' 196 | } 197 | }); 198 | 199 | const OptionsMethod = (prefix) => ({ 200 | Type: 'AWS::ApiGateway::Method', 201 | Properties: { 202 | RestApiId: cf.ref(`${prefix}Api`), 203 | ResourceId: cf.ref(`${prefix}Resource`), 204 | ApiKeyRequired: false, 205 | AuthorizationType: 'None', 206 | HttpMethod: 'OPTIONS', 207 | Integration: { 208 | Type: 'AWS_PROXY', 209 | IntegrationHttpMethod: 'POST', 210 | Uri: cf.sub(`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${prefix}PassthroughFunction.Arn}/invocations`) 211 | } 212 | } 213 | }); 214 | 215 | const PassthroughFunctionRole = (lambda, prefix) => ({ 216 | Type: 'AWS::IAM::Role', 217 | Properties: { 218 | AssumeRolePolicyDocument: { 219 | Statement: [ 220 | { 221 | Sid: 'passthroughrole', 222 | Effect: 'Allow', 223 | Principal: { 224 | Service: 'lambda.amazonaws.com' 225 | }, 226 | Action: 'sts:AssumeRole' 227 | } 228 | ] 229 | }, 230 | Policies: [ 231 | { 232 | PolicyName: `${prefix}PassthroughPolicy`, 233 | PolicyDocument: { 234 | Statement: [ 235 | { 236 | Effect: 'Allow', 237 | Action: [ 238 | 'logs:*' 239 | ], 240 | Resource: [ 241 | 'arn:aws:logs:*:*:*' 242 | ] 243 | }, 244 | { 245 | Effect: 'Allow', 246 | Action: [ 247 | 'lambda:InvokeFunction' 248 | ], 249 | Resource: cf.getAtt(lambda, 'Arn') 250 | } 251 | ] 252 | } 253 | } 254 | ] 255 | } 256 | }); 257 | 258 | const FunctionRole = (prefix) => ({ 259 | Type: 'AWS::IAM::Role', 260 | Properties: { 261 | AssumeRolePolicyDocument: { 262 | Statement: [ 263 | { 264 | Sid: 'webhookrole', 265 | Effect: 'Allow', 266 | Principal: { 267 | Service: 'lambda.amazonaws.com' 268 | }, 269 | Action: 'sts:AssumeRole' 270 | } 271 | ] 272 | }, 273 | Policies: [ 274 | { 275 | PolicyName: `${prefix}Policy`, 276 | PolicyDocument: { 277 | Statement: [ 278 | { 279 | Effect: 'Allow', 280 | Action: [ 281 | 'logs:*' 282 | ], 283 | Resource: [ 284 | 'arn:aws:logs:*:*:*' 285 | ] 286 | }, 287 | { 288 | Effect: 'Allow', 289 | Action: [ 290 | 'sns:Publish' 291 | ], 292 | Resource: [ 293 | cf.ref(`${prefix}InvocationTopic`) 294 | ] 295 | } 296 | ] 297 | } 298 | } 299 | ] 300 | } 301 | }); 302 | 303 | const LambdaFunction = (prefix) => ({ 304 | Type: 'AWS::Lambda::Function', 305 | Properties: { 306 | Code: { 307 | ZipFile: cf.join('\n', [ 308 | 'var AWS = require("aws-sdk");', 309 | cf.sub('var sns = new AWS.SNS({ region: "${AWS::Region}" });'), 310 | cf.sub(`var topic = "\${${prefix}InvocationTopic}";`), 311 | cf.sub(`var secret = "\${${prefix}Secret}";`), 312 | 'var crypto = require("crypto");', 313 | 'module.exports.webhooks = function(event, context) {', 314 | ' var body = event.body', 315 | ' var hash = "sha1=" + crypto.createHmac("sha1", secret).update(new Buffer(JSON.stringify(body))).digest("hex");', 316 | ' if (event.signature !== hash) return context.done("invalid: signature does not match");', 317 | ' if (body.zen) return context.done(null, "ignored ping request");', 318 | ' var push;', 319 | ' try {', 320 | ' push = {', 321 | ' ref: event.body.ref,', 322 | ' after: event.body.after,', 323 | ' before: event.body.before,', 324 | ' deleted: event.body.deleted,', 325 | ' repository: {', 326 | ' name: event.body.repository.name,', 327 | ' owner: { name: event.body.repository.owner.name }', 328 | ' },', 329 | ' pusher: { name: event.body.pusher.name }', 330 | ' };', 331 | ' } catch (err) {', 332 | ' return context.done(null, "Ignored unparseable event from Github");', 333 | ' }', 334 | ' var params = {', 335 | ' TopicArn: topic,', 336 | ' Subject: "webhook",', 337 | ' Message: JSON.stringify(push)', 338 | ' };', 339 | ' sns.publish(params, function(err) {', 340 | ' if (err) return context.done("error: " + err.message);', 341 | ' context.done(null, "success");', 342 | ' });', 343 | '};' 344 | ]) 345 | }, 346 | Role: cf.getAtt(`${prefix}FunctionRole`, 'Arn'), 347 | Description: cf.sub('Github webhook for ${AWS::StackName}'), 348 | Handler: 'index.webhooks', 349 | Runtime: 'nodejs6.10', 350 | Timeout: 30, 351 | MemorySize: 128 352 | } 353 | }); 354 | 355 | const Permission = (prefix) => ({ 356 | Type: 'AWS::Lambda::Permission', 357 | Properties: { 358 | FunctionName: cf.ref(`${prefix}Function`), 359 | Action: 'lambda:InvokeFunction', 360 | Principal: 'apigateway.amazonaws.com', 361 | SourceArn: cf.sub(`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${prefix}Api}/*`) 362 | } 363 | }); 364 | 365 | const PassthroughFunctionPermission = (lambda, prefix) => ({ 366 | Type: 'AWS::Lambda::Permission', 367 | Properties: { 368 | FunctionName: cf.ref(`${prefix}PassthroughFunction`), 369 | Action: 'lambda:InvokeFunction', 370 | Principal: 'apigateway.amazonaws.com', 371 | SourceArn: cf.sub(`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${prefix}Api}/*`) 372 | } 373 | }); 374 | 375 | const EndpointOuput = (prefix) => ({ 376 | Description: 'The HTTPS endpoint used to send github webhooks', 377 | Value: cf.sub(`https://\${${prefix}Api}.execute-api.\${AWS::Region}.amazonaws.com/hookshot/webhook`) 378 | }); 379 | 380 | const SecretOutput = (prefix) => ({ 381 | Description: 'A secret key to give Github to use when signing webhook requests', 382 | Value: cf.ref(`${prefix}Secret`) 383 | }); 384 | 385 | const github = (lambda, prefix = 'Webhook') => { 386 | const Resources = {}; 387 | const Outputs = {}; 388 | 389 | Resources[`${prefix}InvocationTopic`] = Topic(lambda, prefix); 390 | Resources[`${prefix}InvocationPermission`] = SnsPermission(lambda, prefix); 391 | Resources[`${prefix}Secret`] = Secret(); 392 | Resources[`${prefix}Api`] = Api(prefix); 393 | Resources[`${prefix}Stage`] = Stage(prefix); 394 | Resources[`${prefix}Method`] = Method(prefix); 395 | Resources[`${prefix}Resource`] = Resource(prefix); 396 | Resources[`${prefix}FunctionRole`] = FunctionRole(prefix); 397 | Resources[`${prefix}Function`] = LambdaFunction(prefix); 398 | Resources[`${prefix}Permission`] = Permission(prefix); 399 | Resources[`${prefix}Deployment${random}`] = Deployment(prefix); 400 | 401 | Outputs[`${prefix}EndpointOutput`] = EndpointOuput(prefix); 402 | Outputs[`${prefix}SecretOutput`] = SecretOutput(prefix); 403 | 404 | return { Resources, Outputs }; 405 | }; 406 | 407 | const passthrough = (lambda, prefix = 'Webhook') => { 408 | const Resources = {}; 409 | const Outputs = {}; 410 | 411 | Resources[`${prefix}InvocationTopic`] = Topic(lambda, prefix); 412 | Resources[`${prefix}Api`] = Api(prefix); 413 | Resources[`${prefix}Stage`] = Stage(prefix); 414 | Resources[`${prefix}Method`] = PassthroughMethod(lambda, prefix); 415 | Resources[`${prefix}PassthroughFunction`] = PassthroughFunction(lambda, prefix); 416 | Resources[`${prefix}OptionsMethod`] = OptionsMethod(prefix); 417 | Resources[`${prefix}Resource`] = Resource(prefix); 418 | Resources[`${prefix}PassthroughFunctionRole`] = PassthroughFunctionRole(lambda, prefix); 419 | Resources[`${prefix}PassthroughFunctionPermission`] = PassthroughFunctionPermission(lambda, prefix); 420 | Resources[`${prefix}Deployment${random}`] = Deployment(prefix); 421 | Resources[`${prefix}Secret`] = Secret(); 422 | 423 | Outputs[`${prefix}EndpointOutput`] = EndpointOuput(prefix); 424 | Outputs[`${prefix}SecretOutput`] = SecretOutput(prefix); 425 | 426 | return { Resources, Outputs }; 427 | }; 428 | 429 | module.exports = { github, passthrough }; 430 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/hookshot", 3 | "version": "4.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@mapbox/cloudfriend": { 8 | "version": "1.9.0", 9 | "resolved": "https://registry.npmjs.org/@mapbox/cloudfriend/-/cloudfriend-1.9.0.tgz", 10 | "integrity": "sha512-7N5U8KqWqIQcYpFg39d35e3zTT//tr32VlHh7+FBy6uTxaCWPDKWQkHEiAYr/0tSk1qDzdy60gKNMU9BR/1VhQ==", 11 | "requires": { 12 | "aws-sdk": "2.108.0" 13 | } 14 | }, 15 | "acorn": { 16 | "version": "5.1.2", 17 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", 18 | "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==", 19 | "dev": true 20 | }, 21 | "acorn-jsx": { 22 | "version": "3.0.1", 23 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", 24 | "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", 25 | "dev": true, 26 | "requires": { 27 | "acorn": "3.3.0" 28 | }, 29 | "dependencies": { 30 | "acorn": { 31 | "version": "3.3.0", 32 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", 33 | "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", 34 | "dev": true 35 | } 36 | } 37 | }, 38 | "ajv": { 39 | "version": "4.11.8", 40 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", 41 | "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", 42 | "dev": true, 43 | "requires": { 44 | "co": "4.6.0", 45 | "json-stable-stringify": "1.0.1" 46 | } 47 | }, 48 | "ajv-keywords": { 49 | "version": "1.5.1", 50 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", 51 | "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", 52 | "dev": true 53 | }, 54 | "ansi-escapes": { 55 | "version": "1.4.0", 56 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", 57 | "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", 58 | "dev": true 59 | }, 60 | "ansi-regex": { 61 | "version": "2.1.1", 62 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 63 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 64 | "dev": true 65 | }, 66 | "ansi-styles": { 67 | "version": "2.2.1", 68 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 69 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 70 | "dev": true 71 | }, 72 | "argparse": { 73 | "version": "1.0.9", 74 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", 75 | "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", 76 | "dev": true, 77 | "requires": { 78 | "sprintf-js": "1.0.3" 79 | } 80 | }, 81 | "array-union": { 82 | "version": "1.0.2", 83 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", 84 | "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", 85 | "dev": true, 86 | "requires": { 87 | "array-uniq": "1.0.3" 88 | } 89 | }, 90 | "array-uniq": { 91 | "version": "1.0.3", 92 | "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", 93 | "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", 94 | "dev": true 95 | }, 96 | "arrify": { 97 | "version": "1.0.1", 98 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 99 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", 100 | "dev": true 101 | }, 102 | "aws-sdk": { 103 | "version": "2.108.0", 104 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.108.0.tgz", 105 | "integrity": "sha1-i29u2Gkr/GysF4LQQk+YZ9G6pb0=", 106 | "requires": { 107 | "buffer": "4.9.1", 108 | "crypto-browserify": "1.0.9", 109 | "events": "1.1.1", 110 | "jmespath": "0.15.0", 111 | "querystring": "0.2.0", 112 | "sax": "1.2.1", 113 | "url": "0.10.3", 114 | "uuid": "3.0.1", 115 | "xml2js": "0.4.17", 116 | "xmlbuilder": "4.2.1" 117 | } 118 | }, 119 | "babel-code-frame": { 120 | "version": "6.26.0", 121 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", 122 | "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", 123 | "dev": true, 124 | "requires": { 125 | "chalk": "1.1.3", 126 | "esutils": "2.0.2", 127 | "js-tokens": "3.0.2" 128 | } 129 | }, 130 | "balanced-match": { 131 | "version": "1.0.0", 132 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 133 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 134 | "dev": true 135 | }, 136 | "base64-js": { 137 | "version": "1.2.1", 138 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", 139 | "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" 140 | }, 141 | "brace-expansion": { 142 | "version": "1.1.8", 143 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 144 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 145 | "dev": true, 146 | "requires": { 147 | "balanced-match": "1.0.0", 148 | "concat-map": "0.0.1" 149 | } 150 | }, 151 | "buffer": { 152 | "version": "4.9.1", 153 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 154 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 155 | "requires": { 156 | "base64-js": "1.2.1", 157 | "ieee754": "1.1.8", 158 | "isarray": "1.0.0" 159 | } 160 | }, 161 | "caller-path": { 162 | "version": "0.1.0", 163 | "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", 164 | "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", 165 | "dev": true, 166 | "requires": { 167 | "callsites": "0.2.0" 168 | } 169 | }, 170 | "callsites": { 171 | "version": "0.2.0", 172 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", 173 | "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", 174 | "dev": true 175 | }, 176 | "chalk": { 177 | "version": "1.1.3", 178 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 179 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 180 | "dev": true, 181 | "requires": { 182 | "ansi-styles": "2.2.1", 183 | "escape-string-regexp": "1.0.5", 184 | "has-ansi": "2.0.0", 185 | "strip-ansi": "3.0.1", 186 | "supports-color": "2.0.0" 187 | } 188 | }, 189 | "circular-json": { 190 | "version": "0.3.3", 191 | "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", 192 | "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", 193 | "dev": true 194 | }, 195 | "cli-cursor": { 196 | "version": "1.0.2", 197 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", 198 | "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", 199 | "dev": true, 200 | "requires": { 201 | "restore-cursor": "1.0.1" 202 | } 203 | }, 204 | "cli-width": { 205 | "version": "2.2.0", 206 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", 207 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", 208 | "dev": true 209 | }, 210 | "co": { 211 | "version": "4.6.0", 212 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 213 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", 214 | "dev": true 215 | }, 216 | "code-point-at": { 217 | "version": "1.1.0", 218 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 219 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", 220 | "dev": true 221 | }, 222 | "concat-map": { 223 | "version": "0.0.1", 224 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 225 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 226 | "dev": true 227 | }, 228 | "concat-stream": { 229 | "version": "1.6.0", 230 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", 231 | "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", 232 | "dev": true, 233 | "requires": { 234 | "inherits": "2.0.3", 235 | "readable-stream": "2.3.3", 236 | "typedarray": "0.0.6" 237 | } 238 | }, 239 | "core-util-is": { 240 | "version": "1.0.2", 241 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 242 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 243 | "dev": true 244 | }, 245 | "crypto-browserify": { 246 | "version": "1.0.9", 247 | "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz", 248 | "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=" 249 | }, 250 | "d": { 251 | "version": "1.0.0", 252 | "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", 253 | "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", 254 | "dev": true, 255 | "requires": { 256 | "es5-ext": "0.10.30" 257 | } 258 | }, 259 | "debug": { 260 | "version": "2.6.8", 261 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 262 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 263 | "dev": true, 264 | "requires": { 265 | "ms": "2.0.0" 266 | } 267 | }, 268 | "deep-is": { 269 | "version": "0.1.3", 270 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 271 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 272 | "dev": true 273 | }, 274 | "del": { 275 | "version": "2.2.2", 276 | "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", 277 | "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", 278 | "dev": true, 279 | "requires": { 280 | "globby": "5.0.0", 281 | "is-path-cwd": "1.0.0", 282 | "is-path-in-cwd": "1.0.0", 283 | "object-assign": "4.1.1", 284 | "pify": "2.3.0", 285 | "pinkie-promise": "2.0.1", 286 | "rimraf": "2.6.1" 287 | } 288 | }, 289 | "doctrine": { 290 | "version": "2.0.0", 291 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", 292 | "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", 293 | "dev": true, 294 | "requires": { 295 | "esutils": "2.0.2", 296 | "isarray": "1.0.0" 297 | } 298 | }, 299 | "es5-ext": { 300 | "version": "0.10.30", 301 | "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.30.tgz", 302 | "integrity": "sha1-cUGhaDZpfbq/qq7uQUlc4p9SyTk=", 303 | "dev": true, 304 | "requires": { 305 | "es6-iterator": "2.0.1", 306 | "es6-symbol": "3.1.1" 307 | } 308 | }, 309 | "es6-iterator": { 310 | "version": "2.0.1", 311 | "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", 312 | "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=", 313 | "dev": true, 314 | "requires": { 315 | "d": "1.0.0", 316 | "es5-ext": "0.10.30", 317 | "es6-symbol": "3.1.1" 318 | } 319 | }, 320 | "es6-map": { 321 | "version": "0.1.5", 322 | "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", 323 | "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", 324 | "dev": true, 325 | "requires": { 326 | "d": "1.0.0", 327 | "es5-ext": "0.10.30", 328 | "es6-iterator": "2.0.1", 329 | "es6-set": "0.1.5", 330 | "es6-symbol": "3.1.1", 331 | "event-emitter": "0.3.5" 332 | } 333 | }, 334 | "es6-set": { 335 | "version": "0.1.5", 336 | "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", 337 | "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", 338 | "dev": true, 339 | "requires": { 340 | "d": "1.0.0", 341 | "es5-ext": "0.10.30", 342 | "es6-iterator": "2.0.1", 343 | "es6-symbol": "3.1.1", 344 | "event-emitter": "0.3.5" 345 | } 346 | }, 347 | "es6-symbol": { 348 | "version": "3.1.1", 349 | "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", 350 | "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", 351 | "dev": true, 352 | "requires": { 353 | "d": "1.0.0", 354 | "es5-ext": "0.10.30" 355 | } 356 | }, 357 | "es6-weak-map": { 358 | "version": "2.0.2", 359 | "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", 360 | "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", 361 | "dev": true, 362 | "requires": { 363 | "d": "1.0.0", 364 | "es5-ext": "0.10.30", 365 | "es6-iterator": "2.0.1", 366 | "es6-symbol": "3.1.1" 367 | } 368 | }, 369 | "escape-string-regexp": { 370 | "version": "1.0.5", 371 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 372 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 373 | "dev": true 374 | }, 375 | "escope": { 376 | "version": "3.6.0", 377 | "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", 378 | "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", 379 | "dev": true, 380 | "requires": { 381 | "es6-map": "0.1.5", 382 | "es6-weak-map": "2.0.2", 383 | "esrecurse": "4.2.0", 384 | "estraverse": "4.2.0" 385 | } 386 | }, 387 | "eslint": { 388 | "version": "3.19.0", 389 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", 390 | "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", 391 | "dev": true, 392 | "requires": { 393 | "babel-code-frame": "6.26.0", 394 | "chalk": "1.1.3", 395 | "concat-stream": "1.6.0", 396 | "debug": "2.6.8", 397 | "doctrine": "2.0.0", 398 | "escope": "3.6.0", 399 | "espree": "3.5.0", 400 | "esquery": "1.0.0", 401 | "estraverse": "4.2.0", 402 | "esutils": "2.0.2", 403 | "file-entry-cache": "2.0.0", 404 | "glob": "7.1.2", 405 | "globals": "9.18.0", 406 | "ignore": "3.3.5", 407 | "imurmurhash": "0.1.4", 408 | "inquirer": "0.12.0", 409 | "is-my-json-valid": "2.16.1", 410 | "is-resolvable": "1.0.0", 411 | "js-yaml": "3.9.1", 412 | "json-stable-stringify": "1.0.1", 413 | "levn": "0.3.0", 414 | "lodash": "4.17.4", 415 | "mkdirp": "0.5.1", 416 | "natural-compare": "1.4.0", 417 | "optionator": "0.8.2", 418 | "path-is-inside": "1.0.2", 419 | "pluralize": "1.2.1", 420 | "progress": "1.1.8", 421 | "require-uncached": "1.0.3", 422 | "shelljs": "0.7.8", 423 | "strip-bom": "3.0.0", 424 | "strip-json-comments": "2.0.1", 425 | "table": "3.8.3", 426 | "text-table": "0.2.0", 427 | "user-home": "2.0.0" 428 | } 429 | }, 430 | "eslint-plugin-node": { 431 | "version": "5.1.1", 432 | "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-5.1.1.tgz", 433 | "integrity": "sha512-3xdoEbPyyQNyGhhqttjgSO3cU/non8QDBJF8ttGaHM2h8CaY5zFIngtqW6ZbLEIvhpoFPDVwiQg61b8zanx5zQ==", 434 | "dev": true, 435 | "requires": { 436 | "ignore": "3.3.5", 437 | "minimatch": "3.0.4", 438 | "resolve": "1.4.0", 439 | "semver": "5.3.0" 440 | } 441 | }, 442 | "espree": { 443 | "version": "3.5.0", 444 | "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.0.tgz", 445 | "integrity": "sha1-mDWGJb3QVYYeon4oZ+pyn69GPY0=", 446 | "dev": true, 447 | "requires": { 448 | "acorn": "5.1.2", 449 | "acorn-jsx": "3.0.1" 450 | } 451 | }, 452 | "esprima": { 453 | "version": "4.0.0", 454 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", 455 | "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", 456 | "dev": true 457 | }, 458 | "esquery": { 459 | "version": "1.0.0", 460 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", 461 | "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", 462 | "dev": true, 463 | "requires": { 464 | "estraverse": "4.2.0" 465 | } 466 | }, 467 | "esrecurse": { 468 | "version": "4.2.0", 469 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", 470 | "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", 471 | "dev": true, 472 | "requires": { 473 | "estraverse": "4.2.0", 474 | "object-assign": "4.1.1" 475 | } 476 | }, 477 | "estraverse": { 478 | "version": "4.2.0", 479 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 480 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 481 | "dev": true 482 | }, 483 | "esutils": { 484 | "version": "2.0.2", 485 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 486 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 487 | "dev": true 488 | }, 489 | "event-emitter": { 490 | "version": "0.3.5", 491 | "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", 492 | "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", 493 | "dev": true, 494 | "requires": { 495 | "d": "1.0.0", 496 | "es5-ext": "0.10.30" 497 | } 498 | }, 499 | "events": { 500 | "version": "1.1.1", 501 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 502 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 503 | }, 504 | "exit-hook": { 505 | "version": "1.1.1", 506 | "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", 507 | "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", 508 | "dev": true 509 | }, 510 | "fast-levenshtein": { 511 | "version": "2.0.6", 512 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 513 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 514 | "dev": true 515 | }, 516 | "figures": { 517 | "version": "1.7.0", 518 | "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", 519 | "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", 520 | "dev": true, 521 | "requires": { 522 | "escape-string-regexp": "1.0.5", 523 | "object-assign": "4.1.1" 524 | } 525 | }, 526 | "file-entry-cache": { 527 | "version": "2.0.0", 528 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", 529 | "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", 530 | "dev": true, 531 | "requires": { 532 | "flat-cache": "1.2.2", 533 | "object-assign": "4.1.1" 534 | } 535 | }, 536 | "flat-cache": { 537 | "version": "1.2.2", 538 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", 539 | "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", 540 | "dev": true, 541 | "requires": { 542 | "circular-json": "0.3.3", 543 | "del": "2.2.2", 544 | "graceful-fs": "4.1.11", 545 | "write": "0.2.1" 546 | } 547 | }, 548 | "fs.realpath": { 549 | "version": "1.0.0", 550 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 551 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 552 | "dev": true 553 | }, 554 | "generate-function": { 555 | "version": "2.0.0", 556 | "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", 557 | "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", 558 | "dev": true 559 | }, 560 | "generate-object-property": { 561 | "version": "1.2.0", 562 | "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", 563 | "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", 564 | "dev": true, 565 | "requires": { 566 | "is-property": "1.0.2" 567 | } 568 | }, 569 | "glob": { 570 | "version": "7.1.2", 571 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 572 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 573 | "dev": true, 574 | "requires": { 575 | "fs.realpath": "1.0.0", 576 | "inflight": "1.0.6", 577 | "inherits": "2.0.3", 578 | "minimatch": "3.0.4", 579 | "once": "1.4.0", 580 | "path-is-absolute": "1.0.1" 581 | } 582 | }, 583 | "globals": { 584 | "version": "9.18.0", 585 | "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", 586 | "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", 587 | "dev": true 588 | }, 589 | "globby": { 590 | "version": "5.0.0", 591 | "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", 592 | "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", 593 | "dev": true, 594 | "requires": { 595 | "array-union": "1.0.2", 596 | "arrify": "1.0.1", 597 | "glob": "7.1.2", 598 | "object-assign": "4.1.1", 599 | "pify": "2.3.0", 600 | "pinkie-promise": "2.0.1" 601 | } 602 | }, 603 | "graceful-fs": { 604 | "version": "4.1.11", 605 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 606 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", 607 | "dev": true 608 | }, 609 | "has-ansi": { 610 | "version": "2.0.0", 611 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 612 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 613 | "dev": true, 614 | "requires": { 615 | "ansi-regex": "2.1.1" 616 | } 617 | }, 618 | "ieee754": { 619 | "version": "1.1.8", 620 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 621 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 622 | }, 623 | "ignore": { 624 | "version": "3.3.5", 625 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.5.tgz", 626 | "integrity": "sha512-JLH93mL8amZQhh/p6mfQgVBH3M6epNq3DfsXsTSuSrInVjwyYlFE1nv2AgfRCC8PoOhM0jwQ5v8s9LgbK7yGDw==", 627 | "dev": true 628 | }, 629 | "imurmurhash": { 630 | "version": "0.1.4", 631 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 632 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 633 | "dev": true 634 | }, 635 | "inflight": { 636 | "version": "1.0.6", 637 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 638 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 639 | "dev": true, 640 | "requires": { 641 | "once": "1.4.0", 642 | "wrappy": "1.0.2" 643 | } 644 | }, 645 | "inherits": { 646 | "version": "2.0.3", 647 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 648 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 649 | "dev": true 650 | }, 651 | "inquirer": { 652 | "version": "0.12.0", 653 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", 654 | "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", 655 | "dev": true, 656 | "requires": { 657 | "ansi-escapes": "1.4.0", 658 | "ansi-regex": "2.1.1", 659 | "chalk": "1.1.3", 660 | "cli-cursor": "1.0.2", 661 | "cli-width": "2.2.0", 662 | "figures": "1.7.0", 663 | "lodash": "4.17.4", 664 | "readline2": "1.0.1", 665 | "run-async": "0.1.0", 666 | "rx-lite": "3.1.2", 667 | "string-width": "1.0.2", 668 | "strip-ansi": "3.0.1", 669 | "through": "2.3.8" 670 | } 671 | }, 672 | "interpret": { 673 | "version": "1.0.3", 674 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", 675 | "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", 676 | "dev": true 677 | }, 678 | "is-fullwidth-code-point": { 679 | "version": "1.0.0", 680 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 681 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 682 | "dev": true, 683 | "requires": { 684 | "number-is-nan": "1.0.1" 685 | } 686 | }, 687 | "is-my-json-valid": { 688 | "version": "2.16.1", 689 | "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", 690 | "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", 691 | "dev": true, 692 | "requires": { 693 | "generate-function": "2.0.0", 694 | "generate-object-property": "1.2.0", 695 | "jsonpointer": "4.0.1", 696 | "xtend": "4.0.1" 697 | } 698 | }, 699 | "is-path-cwd": { 700 | "version": "1.0.0", 701 | "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", 702 | "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", 703 | "dev": true 704 | }, 705 | "is-path-in-cwd": { 706 | "version": "1.0.0", 707 | "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", 708 | "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", 709 | "dev": true, 710 | "requires": { 711 | "is-path-inside": "1.0.0" 712 | } 713 | }, 714 | "is-path-inside": { 715 | "version": "1.0.0", 716 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", 717 | "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", 718 | "dev": true, 719 | "requires": { 720 | "path-is-inside": "1.0.2" 721 | } 722 | }, 723 | "is-property": { 724 | "version": "1.0.2", 725 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", 726 | "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", 727 | "dev": true 728 | }, 729 | "is-resolvable": { 730 | "version": "1.0.0", 731 | "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", 732 | "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", 733 | "dev": true, 734 | "requires": { 735 | "tryit": "1.0.3" 736 | } 737 | }, 738 | "isarray": { 739 | "version": "1.0.0", 740 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 741 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 742 | }, 743 | "jmespath": { 744 | "version": "0.15.0", 745 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 746 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 747 | }, 748 | "js-tokens": { 749 | "version": "3.0.2", 750 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 751 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 752 | "dev": true 753 | }, 754 | "js-yaml": { 755 | "version": "3.9.1", 756 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.1.tgz", 757 | "integrity": "sha512-CbcG379L1e+mWBnLvHWWeLs8GyV/EMw862uLI3c+GxVyDHWZcjZinwuBd3iW2pgxgIlksW/1vNJa4to+RvDOww==", 758 | "dev": true, 759 | "requires": { 760 | "argparse": "1.0.9", 761 | "esprima": "4.0.0" 762 | } 763 | }, 764 | "json-stable-stringify": { 765 | "version": "1.0.1", 766 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 767 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", 768 | "dev": true, 769 | "requires": { 770 | "jsonify": "0.0.0" 771 | } 772 | }, 773 | "jsonify": { 774 | "version": "0.0.0", 775 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 776 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", 777 | "dev": true 778 | }, 779 | "jsonpointer": { 780 | "version": "4.0.1", 781 | "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", 782 | "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", 783 | "dev": true 784 | }, 785 | "levn": { 786 | "version": "0.3.0", 787 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 788 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 789 | "dev": true, 790 | "requires": { 791 | "prelude-ls": "1.1.2", 792 | "type-check": "0.3.2" 793 | } 794 | }, 795 | "lodash": { 796 | "version": "4.17.4", 797 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 798 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 799 | }, 800 | "minimatch": { 801 | "version": "3.0.4", 802 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 803 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 804 | "dev": true, 805 | "requires": { 806 | "brace-expansion": "1.1.8" 807 | } 808 | }, 809 | "minimist": { 810 | "version": "0.0.8", 811 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 812 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 813 | "dev": true 814 | }, 815 | "mkdirp": { 816 | "version": "0.5.1", 817 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 818 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 819 | "dev": true, 820 | "requires": { 821 | "minimist": "0.0.8" 822 | } 823 | }, 824 | "ms": { 825 | "version": "2.0.0", 826 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 827 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 828 | "dev": true 829 | }, 830 | "mute-stream": { 831 | "version": "0.0.5", 832 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", 833 | "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", 834 | "dev": true 835 | }, 836 | "natural-compare": { 837 | "version": "1.4.0", 838 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 839 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 840 | "dev": true 841 | }, 842 | "number-is-nan": { 843 | "version": "1.0.1", 844 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 845 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 846 | "dev": true 847 | }, 848 | "object-assign": { 849 | "version": "4.1.1", 850 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 851 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 852 | "dev": true 853 | }, 854 | "once": { 855 | "version": "1.4.0", 856 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 857 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 858 | "dev": true, 859 | "requires": { 860 | "wrappy": "1.0.2" 861 | } 862 | }, 863 | "onetime": { 864 | "version": "1.1.0", 865 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", 866 | "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", 867 | "dev": true 868 | }, 869 | "optionator": { 870 | "version": "0.8.2", 871 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", 872 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", 873 | "dev": true, 874 | "requires": { 875 | "deep-is": "0.1.3", 876 | "fast-levenshtein": "2.0.6", 877 | "levn": "0.3.0", 878 | "prelude-ls": "1.1.2", 879 | "type-check": "0.3.2", 880 | "wordwrap": "1.0.0" 881 | } 882 | }, 883 | "os-homedir": { 884 | "version": "1.0.2", 885 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 886 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", 887 | "dev": true 888 | }, 889 | "path-is-absolute": { 890 | "version": "1.0.1", 891 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 892 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 893 | "dev": true 894 | }, 895 | "path-is-inside": { 896 | "version": "1.0.2", 897 | "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", 898 | "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", 899 | "dev": true 900 | }, 901 | "path-parse": { 902 | "version": "1.0.5", 903 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", 904 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", 905 | "dev": true 906 | }, 907 | "pify": { 908 | "version": "2.3.0", 909 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 910 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 911 | "dev": true 912 | }, 913 | "pinkie": { 914 | "version": "2.0.4", 915 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 916 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 917 | "dev": true 918 | }, 919 | "pinkie-promise": { 920 | "version": "2.0.1", 921 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 922 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 923 | "dev": true, 924 | "requires": { 925 | "pinkie": "2.0.4" 926 | } 927 | }, 928 | "pluralize": { 929 | "version": "1.2.1", 930 | "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", 931 | "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", 932 | "dev": true 933 | }, 934 | "prelude-ls": { 935 | "version": "1.1.2", 936 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 937 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 938 | "dev": true 939 | }, 940 | "process-nextick-args": { 941 | "version": "1.0.7", 942 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 943 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", 944 | "dev": true 945 | }, 946 | "progress": { 947 | "version": "1.1.8", 948 | "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", 949 | "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", 950 | "dev": true 951 | }, 952 | "punycode": { 953 | "version": "1.3.2", 954 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 955 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 956 | }, 957 | "querystring": { 958 | "version": "0.2.0", 959 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 960 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 961 | }, 962 | "readable-stream": { 963 | "version": "2.3.3", 964 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 965 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 966 | "dev": true, 967 | "requires": { 968 | "core-util-is": "1.0.2", 969 | "inherits": "2.0.3", 970 | "isarray": "1.0.0", 971 | "process-nextick-args": "1.0.7", 972 | "safe-buffer": "5.1.1", 973 | "string_decoder": "1.0.3", 974 | "util-deprecate": "1.0.2" 975 | } 976 | }, 977 | "readline2": { 978 | "version": "1.0.1", 979 | "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", 980 | "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", 981 | "dev": true, 982 | "requires": { 983 | "code-point-at": "1.1.0", 984 | "is-fullwidth-code-point": "1.0.0", 985 | "mute-stream": "0.0.5" 986 | } 987 | }, 988 | "rechoir": { 989 | "version": "0.6.2", 990 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 991 | "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", 992 | "dev": true, 993 | "requires": { 994 | "resolve": "1.4.0" 995 | } 996 | }, 997 | "require-uncached": { 998 | "version": "1.0.3", 999 | "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", 1000 | "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", 1001 | "dev": true, 1002 | "requires": { 1003 | "caller-path": "0.1.0", 1004 | "resolve-from": "1.0.1" 1005 | } 1006 | }, 1007 | "resolve": { 1008 | "version": "1.4.0", 1009 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", 1010 | "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", 1011 | "dev": true, 1012 | "requires": { 1013 | "path-parse": "1.0.5" 1014 | } 1015 | }, 1016 | "resolve-from": { 1017 | "version": "1.0.1", 1018 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", 1019 | "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", 1020 | "dev": true 1021 | }, 1022 | "restore-cursor": { 1023 | "version": "1.0.1", 1024 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", 1025 | "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", 1026 | "dev": true, 1027 | "requires": { 1028 | "exit-hook": "1.1.1", 1029 | "onetime": "1.1.0" 1030 | } 1031 | }, 1032 | "rimraf": { 1033 | "version": "2.6.1", 1034 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", 1035 | "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", 1036 | "dev": true, 1037 | "requires": { 1038 | "glob": "7.1.2" 1039 | } 1040 | }, 1041 | "run-async": { 1042 | "version": "0.1.0", 1043 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", 1044 | "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", 1045 | "dev": true, 1046 | "requires": { 1047 | "once": "1.4.0" 1048 | } 1049 | }, 1050 | "rx-lite": { 1051 | "version": "3.1.2", 1052 | "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", 1053 | "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", 1054 | "dev": true 1055 | }, 1056 | "safe-buffer": { 1057 | "version": "5.1.1", 1058 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 1059 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", 1060 | "dev": true 1061 | }, 1062 | "sax": { 1063 | "version": "1.2.1", 1064 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 1065 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 1066 | }, 1067 | "semver": { 1068 | "version": "5.3.0", 1069 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", 1070 | "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", 1071 | "dev": true 1072 | }, 1073 | "shelljs": { 1074 | "version": "0.7.8", 1075 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", 1076 | "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", 1077 | "dev": true, 1078 | "requires": { 1079 | "glob": "7.1.2", 1080 | "interpret": "1.0.3", 1081 | "rechoir": "0.6.2" 1082 | } 1083 | }, 1084 | "slice-ansi": { 1085 | "version": "0.0.4", 1086 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", 1087 | "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", 1088 | "dev": true 1089 | }, 1090 | "sprintf-js": { 1091 | "version": "1.0.3", 1092 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1093 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1094 | "dev": true 1095 | }, 1096 | "string_decoder": { 1097 | "version": "1.0.3", 1098 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 1099 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 1100 | "dev": true, 1101 | "requires": { 1102 | "safe-buffer": "5.1.1" 1103 | } 1104 | }, 1105 | "string-width": { 1106 | "version": "1.0.2", 1107 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1108 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1109 | "dev": true, 1110 | "requires": { 1111 | "code-point-at": "1.1.0", 1112 | "is-fullwidth-code-point": "1.0.0", 1113 | "strip-ansi": "3.0.1" 1114 | } 1115 | }, 1116 | "strip-ansi": { 1117 | "version": "3.0.1", 1118 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1119 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1120 | "dev": true, 1121 | "requires": { 1122 | "ansi-regex": "2.1.1" 1123 | } 1124 | }, 1125 | "strip-bom": { 1126 | "version": "3.0.0", 1127 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1128 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 1129 | "dev": true 1130 | }, 1131 | "strip-json-comments": { 1132 | "version": "2.0.1", 1133 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1134 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 1135 | "dev": true 1136 | }, 1137 | "supports-color": { 1138 | "version": "2.0.0", 1139 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1140 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1141 | "dev": true 1142 | }, 1143 | "table": { 1144 | "version": "3.8.3", 1145 | "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", 1146 | "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", 1147 | "dev": true, 1148 | "requires": { 1149 | "ajv": "4.11.8", 1150 | "ajv-keywords": "1.5.1", 1151 | "chalk": "1.1.3", 1152 | "lodash": "4.17.4", 1153 | "slice-ansi": "0.0.4", 1154 | "string-width": "2.1.1" 1155 | }, 1156 | "dependencies": { 1157 | "ansi-regex": { 1158 | "version": "3.0.0", 1159 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 1160 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 1161 | "dev": true 1162 | }, 1163 | "is-fullwidth-code-point": { 1164 | "version": "2.0.0", 1165 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 1166 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 1167 | "dev": true 1168 | }, 1169 | "string-width": { 1170 | "version": "2.1.1", 1171 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 1172 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 1173 | "dev": true, 1174 | "requires": { 1175 | "is-fullwidth-code-point": "2.0.0", 1176 | "strip-ansi": "4.0.0" 1177 | } 1178 | }, 1179 | "strip-ansi": { 1180 | "version": "4.0.0", 1181 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 1182 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 1183 | "dev": true, 1184 | "requires": { 1185 | "ansi-regex": "3.0.0" 1186 | } 1187 | } 1188 | } 1189 | }, 1190 | "text-table": { 1191 | "version": "0.2.0", 1192 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1193 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1194 | "dev": true 1195 | }, 1196 | "through": { 1197 | "version": "2.3.8", 1198 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1199 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1200 | "dev": true 1201 | }, 1202 | "tryit": { 1203 | "version": "1.0.3", 1204 | "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", 1205 | "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", 1206 | "dev": true 1207 | }, 1208 | "type-check": { 1209 | "version": "0.3.2", 1210 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 1211 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 1212 | "dev": true, 1213 | "requires": { 1214 | "prelude-ls": "1.1.2" 1215 | } 1216 | }, 1217 | "typedarray": { 1218 | "version": "0.0.6", 1219 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1220 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", 1221 | "dev": true 1222 | }, 1223 | "url": { 1224 | "version": "0.10.3", 1225 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 1226 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 1227 | "requires": { 1228 | "punycode": "1.3.2", 1229 | "querystring": "0.2.0" 1230 | } 1231 | }, 1232 | "user-home": { 1233 | "version": "2.0.0", 1234 | "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", 1235 | "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", 1236 | "dev": true, 1237 | "requires": { 1238 | "os-homedir": "1.0.2" 1239 | } 1240 | }, 1241 | "util-deprecate": { 1242 | "version": "1.0.2", 1243 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1244 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 1245 | "dev": true 1246 | }, 1247 | "uuid": { 1248 | "version": "3.0.1", 1249 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", 1250 | "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" 1251 | }, 1252 | "wordwrap": { 1253 | "version": "1.0.0", 1254 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 1255 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", 1256 | "dev": true 1257 | }, 1258 | "wrappy": { 1259 | "version": "1.0.2", 1260 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1261 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1262 | "dev": true 1263 | }, 1264 | "write": { 1265 | "version": "0.2.1", 1266 | "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", 1267 | "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", 1268 | "dev": true, 1269 | "requires": { 1270 | "mkdirp": "0.5.1" 1271 | } 1272 | }, 1273 | "xml2js": { 1274 | "version": "0.4.17", 1275 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", 1276 | "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", 1277 | "requires": { 1278 | "sax": "1.2.1", 1279 | "xmlbuilder": "4.2.1" 1280 | } 1281 | }, 1282 | "xmlbuilder": { 1283 | "version": "4.2.1", 1284 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", 1285 | "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", 1286 | "requires": { 1287 | "lodash": "4.17.4" 1288 | } 1289 | }, 1290 | "xtend": { 1291 | "version": "4.0.1", 1292 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 1293 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", 1294 | "dev": true 1295 | } 1296 | } 1297 | } 1298 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/hookshot", 3 | "version": "5.0.1", 4 | "description": "Create an API Gateway endpoint to receive Github (or any other) webhooks", 5 | "main": "index.js", 6 | "repository": "git@github.com:mapbox/hookshot", 7 | "author": "Mapbox", 8 | "license": "MIT", 9 | "scripts": { 10 | "pretest": "eslint test index.js", 11 | "test": "./test/run.sh", 12 | "example": "build-template test/github.test.js" 13 | }, 14 | "devDependencies": { 15 | "eslint": "^3.19.0", 16 | "eslint-plugin-node": "^5.1.1" 17 | }, 18 | "dependencies": { 19 | "@mapbox/cloudfriend": "^1.8.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # deprecated-hookshot 2 | 3 | ⚠️ _This repo has been deprecated. Please use [@mapbox/cloudfriend](https://github.com/mapbox/cloudfriend) shortcuts instead: [hookshot shortcuts](https://github.com/mapbox/cloudfriend/blob/7f3652feeb8ee7437e1e526560940d3093343bf3/lib/shortcuts/readme.md#available-shortcuts)._ ⚠️ 4 | 5 | ![hookshot](https://cloud.githubusercontent.com/assets/515424/25831605/3671112e-341a-11e7-8865-13ef8afc67fc.gif) 6 | 7 | A simple helper to build a connection between 3rd-party service webhooks and your AWS Lambda functions. 8 | 9 | ## Respond to Github push events 10 | 11 | ```js 12 | const hookshot = require('@mapbox/hookshot'); 13 | const webhook = hookshot.github('lambda function logical name'); 14 | ``` 15 | 16 | You want to write a Lambda function and you want it to be triggered every time a push is made to a Github repository. 17 | 18 | Hookshot helps you create a CloudFormation template that creates an API Gateway HTTPS endpoint. You define your Lambda function in the same template, and launch a CloudFormation stack. This provides you with a URL and a secret key that you can provide to a Github webhook integration. 19 | 20 | Hookshot takes care of authenticating incoming requests. Any requests that did not come from Github or were not properly encrypted using your secret key are rejected, and will never make it to your Lambda function. 21 | 22 | Your Lambda function will receive a shortened version of a [Github push event](https://developer.github.com/v3/activity/events/types/#pushevent). Here's an example: 23 | 24 | ```json 25 | { 26 | "ref": "refs/head/changes", 27 | "after": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", 28 | "before": "9049f1265b7d61be4a8904a9a27120d2064dab3b", 29 | "deleted": false, 30 | "repository": { 31 | "name": "public-repo", 32 | "owner": { 33 | "name": "baxterthehacker" 34 | } 35 | }, 36 | "pusher": { 37 | "name": "baxterthehacker" 38 | } 39 | } 40 | ``` 41 | 42 | However that data will be "wrapped" a few levels deep. To access the data as a JavaScript object, you will want your Lambda function's code to parse the incoming event as follows: 43 | 44 | ```js 45 | module.exports.handler = (event, context, callback) => { 46 | const message = JSON.parse(event.Records[0].Sns.Message); 47 | } 48 | ``` 49 | 50 | ## Respond to arbitrary POST requests 51 | 52 | ```js 53 | const hookshot = require('@mapbox/hookshot'); 54 | const webhook = hookshot.passthrough('lambda function logical name'); 55 | ``` 56 | 57 | If you simply need to be able to invoke a Lambda function through a straightforward POST request, hookshot has you covered here as well. 58 | 59 | Note that in this case, your Lambda function will receive every HTTP POST request that arrives at the API Gateway URL that hookshot helped you create. You are responsible for any authentication that should be performed against incoming requests. 60 | 61 | Your Lambda function will receive an event object which includes the request method, headers, and body, as well as other data specific to the API Gateway endpoint created by hookshot. See [AWS documentation here](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) for a full description of the incoming data. 62 | 63 | In order to work properly, **your lambda function must return a data object matching in a specific JSON format**. Again, see [AWS documentation for a full description](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format). 64 | 65 | ### CORS handling 66 | 67 | Your API Gateway endpoint will be set up to allow cross-origin resource sharing (CORS) required by requests from any web page. Preflight `OPTIONS` requests will receive a `200` response with CORS headers. And the response you return from your Lambda function will be modified to include CORS headers. 68 | 69 | ## How to use this module 70 | 71 | 1. Create a CloudFormation template (in JavaScript) that defines 72 | - a Lambda function that you've designed to process incoming requests, and 73 | - the IAM role that your Lambda function will need in order to function. 74 | 75 | 2. Use the JavaScript function exported by this module to create the rest of the resources required. Merge those resources with your own using [cloudfriend](https://github.com/mapbox/cloudfriend). 76 | 77 | 3. Create the CloudFormation stack by deploying your template using [cfn-config](https://github.com/mapbox/cfn-config), or by using [cloudfriend's `build-template` command](https://github.com/mapbox/cloudfriend#cli-tools) to produce the template as a JSON document and launch it in the AWS console. 78 | 79 | 4. Your stack will output the URL for your webhook's endpoint, and a secret token. If generating a Github webhook, provide these values to Github as a new webhook (see `settings/hooks/new` for your repository). Make sure to specify the Content type as `application/json`. 80 | 81 | There are a few examples of very simple github and passthrough templates in this repositories' `/test/` directory. 82 | -------------------------------------------------------------------------------- /test/github-named.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const cf = require('@mapbox/cloudfriend'); 4 | const buildWebhook = require('..').github; 5 | 6 | const myTemplate = { 7 | Resources: { 8 | MyLambda: { 9 | Type: 'AWS::Lambda::Function', 10 | Properties: { 11 | Code: { 12 | S3Bucket: 'my-bucket', 13 | S3Key: 'my-code.zip' 14 | }, 15 | FunctionName: 'MyGithubWebhook', 16 | Handler: 'index.handler', 17 | MemorySize: 256, 18 | Runtime: 'nodejs6.10', 19 | Timeout: 300, 20 | Role: cf.getAtt('LambdaRole', 'Arn') 21 | } 22 | }, 23 | LambdaRole: { 24 | Type: 'AWS::IAM::Role', 25 | Properties: { 26 | AssumeRolePolicyDocument: { 27 | Statement: [ 28 | { 29 | Effect: 'Allow', 30 | Principal: { Service: 'lambda.amazonaws.com' }, 31 | Action: 'sts:AssumeRole' 32 | } 33 | ] 34 | }, 35 | Policies: [ 36 | { 37 | PolicyName: 'write-logs', 38 | PolicyDocument: { 39 | Statement: [ 40 | { 41 | Effect: 'Allow', 42 | Action: 'logs:*', 43 | Resource: 'arn:aws:logs:*' 44 | } 45 | ] 46 | } 47 | } 48 | ] 49 | } 50 | } 51 | } 52 | }; 53 | 54 | const webhook = buildWebhook('MyLambda', 'MyPrefix'); 55 | 56 | module.exports = cf.merge(myTemplate, webhook); 57 | -------------------------------------------------------------------------------- /test/github.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const cf = require('@mapbox/cloudfriend'); 4 | const buildWebhook = require('..').github; 5 | 6 | const myTemplate = { 7 | Resources: { 8 | MyLambda: { 9 | Type: 'AWS::Lambda::Function', 10 | Properties: { 11 | Code: { 12 | S3Bucket: 'my-bucket', 13 | S3Key: 'my-code.zip' 14 | }, 15 | FunctionName: 'MyGithubWebhook', 16 | Handler: 'index.handler', 17 | MemorySize: 256, 18 | Runtime: 'nodejs6.10', 19 | Timeout: 300, 20 | Role: cf.getAtt('LambdaRole', 'Arn') 21 | } 22 | }, 23 | LambdaRole: { 24 | Type: 'AWS::IAM::Role', 25 | Properties: { 26 | AssumeRolePolicyDocument: { 27 | Statement: [ 28 | { 29 | Effect: 'Allow', 30 | Principal: { Service: 'lambda.amazonaws.com' }, 31 | Action: 'sts:AssumeRole' 32 | } 33 | ] 34 | }, 35 | Policies: [ 36 | { 37 | PolicyName: 'write-logs', 38 | PolicyDocument: { 39 | Statement: [ 40 | { 41 | Effect: 'Allow', 42 | Action: 'logs:*', 43 | Resource: 'arn:aws:logs:*' 44 | } 45 | ] 46 | } 47 | } 48 | ] 49 | } 50 | } 51 | } 52 | }; 53 | 54 | const webhook = buildWebhook('MyLambda'); 55 | 56 | module.exports = cf.merge(myTemplate, webhook); 57 | -------------------------------------------------------------------------------- /test/passthrough-named.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const cf = require('@mapbox/cloudfriend'); 4 | const buildWebhook = require('..').passthrough; 5 | 6 | const myTemplate = { 7 | Resources: { 8 | MyLogs: { 9 | Type: 'AWS::Logs::LogGroup', 10 | Properties: { 11 | LogGroupName: cf.sub('/aws/lambda/${AWS::StackName}'), 12 | RetentionInDays: 14 13 | } 14 | }, 15 | MyLambda: { 16 | Type: 'AWS::Lambda::Function', 17 | Properties: { 18 | Code: { 19 | ZipFile: cf.join('\n', [ 20 | 'module.exports.handler = (event, context, callback) => {', 21 | ' console.log(event);', 22 | ' callback(null, {', 23 | ' statusCode: 200,', 24 | ' body: "hello",', 25 | ' headers: {"Content-type": "text/plain"}', 26 | ' });', 27 | '};' 28 | ]) 29 | }, 30 | FunctionName: cf.stackName, 31 | Handler: 'index.handler', 32 | MemorySize: 128, 33 | Runtime: 'nodejs6.10', 34 | Timeout: 300, 35 | Role: cf.getAtt('LambdaRole', 'Arn') 36 | } 37 | }, 38 | LambdaRole: { 39 | Type: 'AWS::IAM::Role', 40 | Properties: { 41 | AssumeRolePolicyDocument: { 42 | Statement: [ 43 | { 44 | Effect: 'Allow', 45 | Principal: { Service: 'lambda.amazonaws.com' }, 46 | Action: 'sts:AssumeRole' 47 | } 48 | ] 49 | }, 50 | Policies: [ 51 | { 52 | PolicyName: 'write-logs', 53 | PolicyDocument: { 54 | Statement: [ 55 | { 56 | Effect: 'Allow', 57 | Action: 'logs:*', 58 | Resource: cf.getAtt('MyLogs', 'Arn') 59 | } 60 | ] 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | } 67 | }; 68 | 69 | const webhook = buildWebhook('MyLambda', 'MyPrefix'); 70 | 71 | module.exports = cf.merge(myTemplate, webhook); 72 | -------------------------------------------------------------------------------- /test/passthrough.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const cf = require('@mapbox/cloudfriend'); 4 | const buildWebhook = require('..').passthrough; 5 | 6 | const myTemplate = { 7 | Resources: { 8 | MyLogs: { 9 | Type: 'AWS::Logs::LogGroup', 10 | Properties: { 11 | LogGroupName: cf.sub('/aws/lambda/${AWS::StackName}'), 12 | RetentionInDays: 14 13 | } 14 | }, 15 | MyLambda: { 16 | Type: 'AWS::Lambda::Function', 17 | Properties: { 18 | Code: { 19 | ZipFile: cf.join('\n', [ 20 | 'module.exports.handler = (event, context, callback) => {', 21 | ' console.log(event);', 22 | ' callback(null, {', 23 | ' statusCode: 200,', 24 | ' body: "hello",', 25 | ' headers: {"Content-type": "text/plain"}', 26 | ' });', 27 | '};' 28 | ]) 29 | }, 30 | FunctionName: cf.stackName, 31 | Handler: 'index.handler', 32 | MemorySize: 128, 33 | Runtime: 'nodejs6.10', 34 | Timeout: 300, 35 | Role: cf.getAtt('LambdaRole', 'Arn') 36 | } 37 | }, 38 | LambdaRole: { 39 | Type: 'AWS::IAM::Role', 40 | Properties: { 41 | AssumeRolePolicyDocument: { 42 | Statement: [ 43 | { 44 | Effect: 'Allow', 45 | Principal: { Service: 'lambda.amazonaws.com' }, 46 | Action: 'sts:AssumeRole' 47 | } 48 | ] 49 | }, 50 | Policies: [ 51 | { 52 | PolicyName: 'write-logs', 53 | PolicyDocument: { 54 | Statement: [ 55 | { 56 | Effect: 'Allow', 57 | Action: 'logs:*', 58 | Resource: cf.getAtt('MyLogs', 'Arn') 59 | } 60 | ] 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | } 67 | }; 68 | 69 | const webhook = buildWebhook('MyLambda'); 70 | 71 | module.exports = cf.merge(myTemplate, webhook); 72 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | validate="./node_modules/.bin/validate-template" 4 | 5 | echo "github:" 6 | ${validate} test/github.test.js 7 | 8 | echo "github-named:" 9 | ${validate} test/github-named.test.js 10 | 11 | echo "passthrough:" 12 | ${validate} test/passthrough.test.js 13 | 14 | echo "passthrough-named:" 15 | ${validate} test/passthrough-named.test.js 16 | --------------------------------------------------------------------------------