From 6d3be0f31fd13bbcaa9b083d669518a9bf9d4ab6 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Mon, 10 Jun 2019 07:58:14 -0600 Subject: [PATCH 1/7] feat: Azure Functions offline plugin --- .babelrc | 11 + .eslintrc.json | 10 +- .travis.yml | 23 +- .vscode/settings.json | 9 + jest.config.js | 33 +- package-lock.json | 3907 +++++++++++------ package.json | 18 +- src/index.test.ts | 2 +- src/index.ts | 2 + src/models/apiManagement.ts | 12 + src/models/functionApp.ts | 7 + src/models/generic.ts | 3 + src/plugins/apim/apimFunctionPlugin.ts | 5 +- src/plugins/apim/apimServicePlugin.ts | 5 +- src/plugins/deploy/azureDeployPlugin.test.ts | 16 +- src/plugins/func/azureFunc.test.ts | 4 +- src/plugins/func/azureFunc.ts | 6 +- src/plugins/func/funcUtils.ts | 4 +- src/plugins/login/loginPlugin.test.ts | 8 +- .../offline/azureOfflinePlugin.test.ts | 29 + src/plugins/offline/azureOfflinePlugin.ts | 43 + src/plugins/package/azurePackage.test.ts | 28 +- src/plugins/package/azurePackage.ts | 2 +- src/services/apimService.test.ts | 380 ++ src/services/apimService.ts | 167 +- src/services/baseService.test.ts | 88 + src/services/baseService.ts | 33 +- src/services/functionAppService.ts | 136 +- src/services/loginService.test.ts | 20 +- src/services/loginService.ts | 15 +- src/services/offlineService.test.ts | 28 + src/services/offlineService.ts | 29 + src/services/resourceService.test.ts | 50 +- src/shared/bindings.ts | 10 +- src/shared/guard.test.ts | 61 + src/shared/guard.ts | 41 + src/shared/utils.ts | 62 +- src/test/mockFactory.ts | 319 +- src/test/responses/apim-get-api-200.json | 25 + src/test/responses/apim-get-api-404.json | 7 + src/test/responses/apim-get-service-200.json | 53 + src/test/responses/apim-get-service-404.json | 6 + src/test/responses/apim-put-api-201.json | 25 + src/test/responses/apim-put-backend-201.json | 11 + src/test/responses/apim-put-property-201.json | 11 + src/test/utils.ts | 24 +- tsconfig.json | 70 +- 47 files changed, 4168 insertions(+), 1690 deletions(-) create mode 100644 .babelrc create mode 100644 .vscode/settings.json create mode 100644 src/models/apiManagement.ts create mode 100644 src/models/functionApp.ts create mode 100644 src/models/generic.ts create mode 100644 src/plugins/offline/azureOfflinePlugin.test.ts create mode 100644 src/plugins/offline/azureOfflinePlugin.ts create mode 100644 src/services/apimService.test.ts create mode 100644 src/services/baseService.test.ts create mode 100644 src/services/offlineService.test.ts create mode 100644 src/services/offlineService.ts create mode 100644 src/shared/guard.test.ts create mode 100644 src/shared/guard.ts create mode 100644 src/test/responses/apim-get-api-200.json create mode 100644 src/test/responses/apim-get-api-404.json create mode 100644 src/test/responses/apim-get-service-200.json create mode 100644 src/test/responses/apim-get-service-404.json create mode 100644 src/test/responses/apim-put-api-201.json create mode 100644 src/test/responses/apim-put-backend-201.json create mode 100644 src/test/responses/apim-put-property-201.json diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..c312ef53 --- /dev/null +++ b/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "react-app", + { + "flow": false, + "typescript": true + } + ] + ] +} \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index eb1be605..d44894db 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,14 +1,20 @@ { "parser": "@typescript-eslint/parser", - "extends": ["plugin:@typescript-eslint/recommended"], + "parserOptions": { + "project": "./tsconfig.json" + }, "plugins": ["@typescript-eslint"], + "extends": ["plugin:@typescript-eslint/recommended"], "rules": { + "quotes": ["error", "double"], "@typescript-eslint/indent": [ "error", 2 ], "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/explicit-function-return-type": 0, - "@typescript-eslint/no-parameter-properties": 0 + "@typescript-eslint/no-parameter-properties": 0, + "@typescript-eslint/no-use-before-define": 0, + "@typescript-eslint/no-object-literal-type-assertion": 0 } } \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b7979fe3..ba9b137a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,17 +3,26 @@ language: node_js os: linux matrix: - include: - - node_js: '8' - - node_js: '10' + include: + - node_js: '8' + - node_js: '10' sudo: false install: - - npm install - +- npm install script: - - npm test + - npm run test:ci after_success: - - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage +- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage + +deploy: + provider: npm + email: $NPM_EMAIL_ACCOUNT + api_key: + secure: Iu5Q2uuiF+jRWkNRtX77YElMwyEopzKD+iHQQkT7HheH12AwDAW/PZiwLRhBTif8kvHmIIwPhjey6i7QwmJFUmpb433299WF+tinRhy5mBFntdaNmgS2Wvv/hOFJza2XdOeIsKA+s+zPAGArfLonmvQX5QKVmJI7/W+hyqJT0FjtTMqxPyJ00ENuDeZdUEcsaoDAwu1XTA6bj5ILnguR+smeGoZscpp8nwTv27fA+GWLFGf08pPAyuHSwc1/lRxSbLlw/f0ZqnCclGU+SF2EV4/9+xxcPWqtJdJfURR/LaOkg3Q5o4aILRjNJNCbpRkrc5lEcPEvBPLrI6e7F9ssg08aHniaMvdD5B9TsgGqV6sxZX/KvMIHWfzBRLuCzY2bPE8Wbw+ZI/yi9+89NZdRikAMyWocvg4tx1OsQRcrRltsXqQrZyw45IR5oSv0kLx54xrslXu8jjP08jQEpnBncJp883DX6DmO42TRP4QYHsQ8BB5hHQ2qmVoKMydkLy0Kdvq7e+9v/pZHEMsR/HOwz1S5xYsSeqwqoq4u/H+hPFWu3GVJXbsVLHZuaDU3fBRMtJOcjujPszrvN/cNt2JryTT1EYPJotmpURqu/n9vPLMzoi1wIKPrZiAcEzh8MLlM93MyG/yuPeHZOvMuwCVtOXd3KKH8sEJvbTVUtKIvQXw= + on: + tags: true + repo: serverless/serverless-azure-functions + branch: master diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5f1eb0ce --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "eslint.options": { + "configFile": "./.eslintrc.json" + }, + "eslint.validate": [ + "javascript", + "typescript" + ] +} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 1d854c50..6b84680f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,13 +1,26 @@ module.exports = { - preset: 'ts-jest', - transform: { - ".(ts|tsx)": "ts-jest" + "collectCoverageFrom": [ + "src/**/*.{js,jsx,ts,tsx}", + "!src/**/*.d.ts" + ], + "testEnvironment": "node", + "transform": { + "^.+\\.(js|jsx|ts|tsx)$": "babel-jest" }, - testRegex: "((\\.|/)(test|spec))\\.ts$", - moduleFileExtensions: ["ts", "tsx", "js"], - testPathIgnorePatterns: [ - "/lib/", - "/node_modules/" + "testPathIgnorePatterns": [ + "./lib", + "./node_modules" + ], + "transformIgnorePatterns": [ + "./lib", + "./node_modules" ], - collectCoverage: true -}; \ No newline at end of file + "moduleFileExtensions": [ + "js", + "ts", + "tsx", + "json", + "jsx", + "node" + ] +}; diff --git a/package-lock.json b/package-lock.json index 9795f4d5..9e5127fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,237 +40,988 @@ "integrity": "sha512-l7z0DPCi2Hp88w12JhDTtx5d0Y3+vhfE7JKJb9O7sEz71Cwp053N8piTtTnnk/tUor9oZHgEKi/p3tQQmLPjvA==" }, "@azure/ms-rest-azure-js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-js/-/ms-rest-azure-js-1.3.4.tgz", - "integrity": "sha512-aRYOK1xHR7nRfFVKXinM8DXtprsRpBkivKcUNKbJYzYS+Px+95uI2eYBheO8oe4yYhDZ0htN8oRBua+DuOpYyg==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-js/-/ms-rest-azure-js-1.3.5.tgz", + "integrity": "sha512-gk6RdpSEblvYEJhK+WjDDJnMLVNrjb/53qINyXXbgY5bs0UbRNFCJwRKsfRGoUB8WgHkQj5mtztR4V1PNAB9Eg==", + "requires": { + "@azure/ms-rest-js": "^1.8.1", + "tslib": "^1.9.2" + } + }, + "@azure/ms-rest-js": { + "version": "1.8.7", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-1.8.7.tgz", + "integrity": "sha512-JTVf4hu+/TnOQW1bprK+wxCBgJdZR6aC3oeAW35CfbxL4NVVgwEQ4+c1JOG8ph0WHGEL8bfyDF2vIjV/RuHkdg==", + "requires": { + "@types/tunnel": "0.0.0", + "axios": "^0.18.0", + "form-data": "^2.3.2", + "tough-cookie": "^2.4.3", + "tslib": "^1.9.2", + "tunnel": "0.0.6", + "uuid": "^3.2.1", + "xml2js": "^0.4.19" + } + }, + "@azure/ms-rest-nodeauth": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-1.1.1.tgz", + "integrity": "sha512-maESfl2vixP3+zjbfuC8XsSRjf3chDp7SL9nWaURc7D9j8xjg/8ajSv7r9OsJSfgcuO1HaSkjIjDaL+Yz18vLQ==", + "requires": { + "@azure/ms-rest-azure-env": "^1.1.2", + "@azure/ms-rest-js": "^1.8.6", + "adal-node": "^0.1.28" + } + }, + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.5.tgz", + "integrity": "sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helpers": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.5", + "@babel/types": "^7.4.4", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", + "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz", + "integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0", + "esutils": "^2.0.0" + } + }, + "@babel/helper-call-delegate": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", + "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz", + "integrity": "sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.4.4", + "@babel/helper-split-export-declaration": "^7.4.4" + } + }, + "@babel/helper-define-map": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz", + "integrity": "sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.4.4", + "lodash": "^4.17.11" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", + "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz", + "integrity": "sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/template": "^7.4.4", + "@babel/types": "^7.4.4", + "lodash": "^4.17.11" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.4.tgz", + "integrity": "sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz", + "integrity": "sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "@babel/helpers": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", + "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "dev": true, + "requires": { + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", + "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.0.tgz", + "integrity": "sha512-t2ECPNOXsIeK1JxJNKmgbzQtoG27KIlVE61vTqX0DKR9E9sZlVVxWUtEW9D5FlZ8b8j7SBNCHY47GgPKCKlpPg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.4.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-proposal-decorators": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.0.tgz", + "integrity": "sha512-d08TLmXeK/XbgCo7ZeZ+JaeZDtDai/2ctapTRsWWkkmy7G/cqz8DQN/HlWG7RR4YmfXxmExsbU3SuCjlM7AtUg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.4.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-decorators": "^7.2.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.3.tgz", + "integrity": "sha512-xC//6DNSSHVjq8O2ge0dyYlhshsH4T7XdCVoxbi5HzLYWfsC5ooFlJjrXk8RcAT+hjHAK9UjBXdylzSoDK3t4g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", + "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-decorators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz", + "integrity": "sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", + "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-flow": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.2.0.tgz", + "integrity": "sha512-r6YMuZDWLtLlu0kqIim5o/3TNRAlWb073HwT3e2nKf9I8IIvOggPrnILYPsrrKilmn/mYEMCf/Z07w3yQJF6dg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz", + "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", + "integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz", + "integrity": "sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz", + "integrity": "sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.11" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.3.tgz", + "integrity": "sha512-PUaIKyFUDtG6jF5DUJOfkBdwAS/kFFV3XFk7Nn0a6vR7ZT8jYw5cGtIlat77wcnd0C6ViGqo/wyNf4ZHytF/nQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.4.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.4.0", + "@babel/helper-split-export-declaration": "^7.4.0", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.3.tgz", + "integrity": "sha512-rVTLLZpydDFDyN4qnXdzwoVpk1oaXHIvPEOkOLyr88o7oHxVc/LyrnDx+amuBWGOwUb7D1s/uLsKBNTx08htZg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz", + "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", + "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-flow-strip-types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.4.0.tgz", + "integrity": "sha512-C4ZVNejHnfB22vI2TYN4RUp2oCmq6cSEAg4RygSvYZUECRqUu9O4PMEMNJ4wsemaRGg27BbgYctG4BZh+AgIHw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.2.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", + "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", + "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", + "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", + "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz", + "integrity": "sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz", + "integrity": "sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz", + "integrity": "sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==", + "dev": true, + "requires": { + "regexp-tree": "^0.1.6" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", + "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", + "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", + "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "dev": true, + "requires": { + "@babel/helper-call-delegate": "^7.4.4", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", + "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-react-constant-elements": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.2.0.tgz", + "integrity": "sha512-YYQFg6giRFMsZPKUM9v+VcHOdfSQdz9jHCx3akAi3UYgyjndmdYGSXylQ/V+HswQt4fL8IklchD9HTsaOCrWQQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", + "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", + "dev": true, "requires": { - "@azure/ms-rest-js": "^1.8.1", - "tslib": "^1.9.2" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@azure/ms-rest-js": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-1.8.6.tgz", - "integrity": "sha512-PjAshR9q56XSNKHMljbOzkbR5dFaKlkG1fVkTY9+1eJ/31lF7RnslAqj909hRguLzDpkHR0VgW65lhCMAq9Juw==", + "@babel/plugin-transform-react-jsx": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", + "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==", + "dev": true, "requires": { - "@types/tunnel": "0.0.0", - "axios": "^0.18.0", - "form-data": "^2.3.2", - "tough-cookie": "^2.4.3", - "tslib": "^1.9.2", - "tunnel": "0.0.6", - "uuid": "^3.2.1", - "xml2js": "^0.4.19" - }, - "dependencies": { - "tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" - } + "@babel/helper-builder-react-jsx": "^7.3.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0" } }, - "@azure/ms-rest-nodeauth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-1.0.1.tgz", - "integrity": "sha512-ga/E4lb/jElK3EQrWg/0lb2K+sC3AGyqkVjE0ofb9XqxE9CoAOy/i66ZRfOs8hk57oA27TPlsw7R8sISslqzKw==", + "@babel/plugin-transform-react-jsx-self": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz", + "integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==", + "dev": true, "requires": { - "@azure/ms-rest-azure-env": "^1.1.2", - "@azure/ms-rest-js": "^1.8.2", - "adal-node": "^0.1.28" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0" } }, - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "@babel/plugin-transform-react-jsx-source": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.2.0.tgz", + "integrity": "sha512-A32OkKTp4i5U6aE88GwwcuV4HAprUgHcTq0sSafLxjr6AW0QahrCRCjxogkbbcdtpbXkuTOlgpjophCxb6sh5g==", "dev": true, "requires": { - "@babel/highlight": "^7.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0" } }, - "@babel/core": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.4.tgz", - "integrity": "sha512-lQgGX3FPRgbz2SKmhMtYgJvVzGZrmjaF4apZ2bLwofAKiSjxU0drPh4S/VasyYXwaTs+A1gvQ45BN8SQJzHsQQ==", + "@babel/plugin-transform-regenerator": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", + "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.4", - "@babel/helpers": "^7.4.4", - "@babel/parser": "^7.4.4", - "@babel/template": "^7.4.4", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4", - "convert-source-map": "^1.1.0", - "debug": "^4.1.0", - "json5": "^2.1.0", - "lodash": "^4.17.11", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "regenerator-transform": "^0.14.0" } }, - "@babel/generator": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", - "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "@babel/plugin-transform-reserved-words": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", + "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", "dev": true, "requires": { - "@babel/types": "^7.4.4", - "jsesc": "^2.5.1", - "lodash": "^4.17.11", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "@babel/plugin-transform-runtime": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.3.tgz", + "integrity": "sha512-7Q61bU+uEI7bCUFReT1NKn7/X6sDQsZ7wL1sJ9IYMAO7cI+eg6x9re1cEw2fCRMbbTVyoeUKWSV1M6azEfKCfg==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "resolve": "^1.8.1", + "semver": "^5.5.1" } }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/helper-plugin-utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", - "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", - "dev": true + "@babel/plugin-transform-spread": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "@babel/helper-split-export-declaration": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", - "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", "dev": true, "requires": { - "@babel/types": "^7.4.4" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" } }, - "@babel/helpers": { + "@babel/plugin-transform-template-literals": { "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", - "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", + "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", "dev": true, "requires": { - "@babel/template": "^7.4.4", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", "dev": true, "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/parser": { + "@babel/plugin-transform-typescript": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.4.5.tgz", + "integrity": "sha512-RPB/YeGr4ZrFKNwfuQRlMf2lxoCUaU01MTw39/OFE/RiL8HDjtn68BwEPft1P7JN4akyEmjGWAMNldOV7o9V2g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-typescript": "^7.2.0" + } + }, + "@babel/plugin-transform-unicode-regex": { "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz", - "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==", - "dev": true + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", + "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.5.4" + } + }, + "@babel/preset-env": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.3.tgz", + "integrity": "sha512-FYbZdV12yHdJU5Z70cEg0f6lvtpZ8jFSDakTm7WXeJbLXh4R0ztGEu/SW7G1nJ2ZvKwDhz8YrbA84eYyprmGqw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.4.3", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.0", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.4.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.4.0", + "@babel/plugin-transform-classes": "^7.4.3", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.4.3", + "@babel/plugin-transform-dotall-regex": "^7.4.3", + "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.4.3", + "@babel/plugin-transform-function-name": "^7.4.3", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-member-expression-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.4.3", + "@babel/plugin-transform-modules-systemjs": "^7.4.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.2", + "@babel/plugin-transform-new-target": "^7.4.0", + "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-parameters": "^7.4.3", + "@babel/plugin-transform-property-literals": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.4.3", + "@babel/plugin-transform-reserved-words": "^7.2.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.2.0", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.4.3", + "@babel/types": "^7.4.0", + "browserslist": "^4.5.2", + "core-js-compat": "^3.0.0", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.5.0" + } }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", - "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "@babel/preset-react": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", + "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0" + } + }, + "@babel/preset-typescript": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz", + "integrity": "sha512-mzMVuIP4lqtn4du2ynEfdO0+RYcslwrZiJHXu4MGaC1ctJiW2fyaeDrtjJGs7R/KebZ1sgowcIoWf4uRpEfKEg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.3.2" + } + }, + "@babel/runtime": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", + "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" } }, "@babel/template": { @@ -285,16 +1036,16 @@ } }, "@babel/traverse": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.4.tgz", - "integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==", + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz", + "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/generator": "^7.4.4", "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.4.4", + "@babel/parser": "^7.4.5", "@babel/types": "^7.4.4", "debug": "^4.1.0", "globals": "^11.1.0", @@ -309,18 +1060,6 @@ "requires": { "ms": "^2.1.1" } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true } } }, @@ -552,6 +1291,110 @@ "@types/yargs": "^12.0.9" } }, + "@serverless/enterprise-plugin": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.5.tgz", + "integrity": "sha512-4n15uV5ka/PQWh0jUKg6s8qGmqkL4gB9QsDdIRO3ENEaOwKkO2CVa0FdsVZ1HmKDbDknnYflwm2jv3tDI+VGKA==", + "dev": true, + "requires": { + "@serverless/event-mocks": "^1.1.1", + "@serverless/platform-sdk": "^1.0.0", + "chalk": "^2.4.2", + "flat": "^4.1.0", + "fs-extra": "^7.0.1", + "iso8601-duration": "^1.1.7", + "jsonata": "^1.6.4", + "jszip": "^3.2.1", + "lodash": "^4.17.11", + "moment": "^2.24.0", + "node-dir": "^0.1.17", + "node-fetch": "^2.3.0", + "regenerator-runtime": "^0.13.1", + "semver": "^5.6.0", + "yamljs": "^0.3.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true + } + } + }, + "@serverless/event-mocks": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@serverless/event-mocks/-/event-mocks-1.1.1.tgz", + "integrity": "sha512-YAV5V/y+XIOfd+HEVeXfPWZb8C6QLruFk9tBivoX2roQLWVq145s4uxf8D0QioCueuRzkukHUS4JIj+KVoS34A==", + "dev": true, + "requires": { + "@types/lodash": "^4.14.123", + "lodash": "^4.17.11" + } + }, + "@serverless/platform-sdk": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@serverless/platform-sdk/-/platform-sdk-1.0.1.tgz", + "integrity": "sha512-x7DWlvAEOdwur7pYLPHkqsEJ4+1xgmOHjSqpv3krthqQoUuh1+IBexL+GhtYqpN1t3N//a4mXDREb2Vd5eqIhA==", + "dev": true, + "requires": { + "body-parser": "^1.19.0", + "chalk": "^2.4.1", + "cors": "^2.8.4", + "express": "^4.16.3", + "is-docker": "^1.1.0", + "isomorphic-fetch": "^2.2.1", + "jwt-decode": "^2.2.0", + "opn": "^5.5.0", + "querystring": "^0.2.0", + "ramda": "^0.25.0", + "rc": "^1.2.8", + "regenerator-runtime": "^0.13.1", + "source-map-support": "^0.5.12", + "uuid": "^3.3.2", + "write-file-atomic": "^2.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } + } + }, "@types/babel__core": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", @@ -649,15 +1492,15 @@ "dev": true }, "@types/lodash": { - "version": "4.14.130", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.130.tgz", - "integrity": "sha512-H++wk0tbneBsRVfLkgAAd0IIpmpVr2Bj4T0HncoOsQf3/xrJexRYQK2Tqo0Ej3pFslM8GkMgdis9bu6xIb1ycw==", + "version": "4.14.133", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.133.tgz", + "integrity": "sha512-/3JqnvPnY58GLzG3Y7fpphOhATV1DDZ/Ak3DQufjlRK5E4u+s0CfClfNFtAGBabw+jDGtRFbOZe+Z02ZMWCBNQ==", "dev": true }, "@types/node": { - "version": "8.10.48", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.48.tgz", - "integrity": "sha512-c35YEBTkL4rzXY2ucpSKy+UYHjUBIIkuJbWYbsGIrKLEWU5dgJMmLkkIb3qeC3O3Tpb1ZQCwecscvJTDjDjkRw==" + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.4.tgz", + "integrity": "sha512-j8YL2C0fXq7IONwl/Ud5Kt0PeXw22zGERt+HSSnwbKOJVsAGkEz3sFCYwaF9IOuoG1HOtE0vKCj6sXF7Q0+Vaw==" }, "@types/open": { "version": "6.1.0", @@ -725,17 +1568,6 @@ "regexpp": "^2.0.1", "requireindex": "^1.2.0", "tsutils": "^3.7.0" - }, - "dependencies": { - "tsutils": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.10.0.tgz", - "integrity": "sha512-q20XSMq7jutbGB8luhKKsQldRKWvyBO2BGqni3p4yq8Ys9bEP/xQw3KepKmMRt9gJ4lvQSScrihJrcKdKoSU7Q==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } } }, "@typescript-eslint/experimental-utils": { @@ -788,6 +1620,16 @@ "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", "dev": true }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, "acorn": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", @@ -854,6 +1696,13 @@ "uuid": "^3.1.0", "xmldom": ">= 0.1.x", "xpath.js": "~1.1.0" + }, + "dependencies": { + "@types/node": { + "version": "8.10.49", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.49.tgz", + "integrity": "sha512-YX30JVx0PvSmJ3Eqr74fYLGeBxD+C7vIL20ek+GGGLJeUbVYRUW3EzyAXpIRA0K8c8o0UWqR/GwEFYiFoz1T8w==" + } } }, "agent-base": { @@ -876,10 +1725,17 @@ "uri-js": "^4.2.2" } }, + "ajv-keywords": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", + "optional": true + }, "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -916,7 +1772,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -931,10 +1786,10 @@ } }, "archiver": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", - "integrity": "sha1-TyGU1tj5nfP1MeaIHxTxXVX6ryI=", - "dev": true, + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", + "integrity": "sha1-/2YrSnggFJSj7lRNOjP+dJZQnrw=", + "optional": true, "requires": { "archiver-utils": "^1.3.0", "async": "^2.0.0", @@ -943,8 +1798,18 @@ "lodash": "^4.8.0", "readable-stream": "^2.0.0", "tar-stream": "^1.5.0", - "walkdir": "^0.0.11", - "zip-stream": "^1.1.0" + "zip-stream": "^1.2.0" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "optional": true, + "requires": { + "lodash": "^4.17.11" + } + } } }, "archiver-utils": { @@ -958,29 +1823,6 @@ "lodash": "^4.8.0", "normalize-path": "^2.0.0", "readable-stream": "^2.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "requires": { - "readable-stream": "^2.0.5" - } - } } }, "are-we-there-yet": { @@ -1022,6 +1864,12 @@ "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -1060,6 +1908,7 @@ "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "optional": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -1110,12 +1959,9 @@ "dev": true }, "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "requires": { - "lodash": "^4.14.0" - } + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.0.1.tgz", + "integrity": "sha512-ZswD8vwPtmBZzbn9xyi8XBQWXH3AvOQ43Za1KWYq7JeycrZuUYzx01KvHcVbXltjqH4y0MWrQ33008uLTqXuDw==" }, "async-each": { "version": "1.0.3", @@ -1140,9 +1986,9 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "aws-sdk": { - "version": "2.461.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.461.0.tgz", - "integrity": "sha512-nqRqlOaM92P6BTx/huq8FuowWNPiRRcpEKHvAQ2XTWTQUADx9HIP9KtbEzLpauxE4Er2reM0UYz9Kbtyke/3EQ==", + "version": "2.466.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.466.0.tgz", + "integrity": "sha512-dWFpz774ONjP1Cb19VkLOfQSVTu5p5/uncZGovAe71NOfPGDSvrQKXOsKcuI1/k4oJyKW9z/GATF8ht8DkDWGg==", "dev": true, "requires": { "buffer": "4.9.1", @@ -1206,8 +2052,7 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.8.0", @@ -1238,6 +2083,15 @@ "slash": "^2.0.0" } }, + "babel-plugin-dynamic-import-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.2.0.tgz", + "integrity": "sha512-fP899ELUnTaBcIzmrW7nniyqqdYWrWuJUyPWHxFa/c7r7hS6KC8FscNfLlBNIoPSc55kYMGEEKjPjJGCLbE1qA==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, "babel-plugin-istanbul": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.4.tgz", @@ -1303,6 +2157,23 @@ "@types/babel__traverse": "^7.0.6" } }, + "babel-plugin-macros": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.5.1.tgz", + "integrity": "sha512-xN3KhAxPzsJ6OQTktCanNpIFnnMsCV+t8OloKxIL72D6+SUZYFn9qfklPgef5HyyDtzYZqqb+fs1S12+gQY82Q==", + "dev": true, + "requires": { + "@babel/runtime": "^7.4.2", + "cosmiconfig": "^5.2.0", + "resolve": "^1.10.0" + } + }, + "babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "dev": true + }, "babel-preset-jest": { "version": "24.6.0", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz", @@ -1313,6 +2184,89 @@ "babel-plugin-jest-hoist": "^24.6.0" } }, + "babel-preset-react-app": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-9.0.0.tgz", + "integrity": "sha512-YVsDA8HpAKklhFLJtl9+AgaxrDaor8gGvDFlsg1ByOS0IPGUovumdv4/gJiAnLcDmZmKlH6+9sVOz4NVW7emAg==", + "dev": true, + "requires": { + "@babel/core": "7.4.3", + "@babel/plugin-proposal-class-properties": "7.4.0", + "@babel/plugin-proposal-decorators": "7.4.0", + "@babel/plugin-proposal-object-rest-spread": "7.4.3", + "@babel/plugin-syntax-dynamic-import": "7.2.0", + "@babel/plugin-transform-classes": "7.4.3", + "@babel/plugin-transform-destructuring": "7.4.3", + "@babel/plugin-transform-flow-strip-types": "7.4.0", + "@babel/plugin-transform-react-constant-elements": "7.2.0", + "@babel/plugin-transform-react-display-name": "7.2.0", + "@babel/plugin-transform-runtime": "7.4.3", + "@babel/preset-env": "7.4.3", + "@babel/preset-react": "7.0.0", + "@babel/preset-typescript": "7.3.3", + "@babel/runtime": "7.4.3", + "babel-plugin-dynamic-import-node": "2.2.0", + "babel-plugin-macros": "2.5.1", + "babel-plugin-transform-react-remove-prop-types": "0.4.24" + }, + "dependencies": { + "@babel/core": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.3.tgz", + "integrity": "sha512-oDpASqKFlbspQfzAE7yaeTmdljSH2ADIvBlb0RwbStltTuWa0+7CCI1fYVINNv9saHPa1W7oaKeuNuKj+RQCvA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helpers": "^7.4.3", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/runtime": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.3.tgz", + "integrity": "sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1408,21 +2362,55 @@ } }, "bluebird": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", - "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==" + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" }, "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "optional": true }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "2.x.x" + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } } }, "boxen": { @@ -1487,7 +2475,8 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "optional": true }, "browser-process-hrtime": { "version": "0.1.3", @@ -1516,6 +2505,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "optional": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -1552,6 +2542,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "optional": true, "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" @@ -1581,13 +2572,15 @@ "pako": "~1.0.5" } }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "browserslist": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.1.tgz", + "integrity": "sha512-1MC18ooMPRG2UuVFJTHFIAkk6mpByJfxCrnUyvSlu/hyQSFHMrlhM02SzNuCV+quTP4CKmqtOMAIjrifrpBJXQ==", "dev": true, "requires": { - "fast-json-stable-stringify": "2.x" + "caniuse-lite": "^1.0.30000971", + "electron-to-chromium": "^1.3.137", + "node-releases": "^1.1.21" } }, "bser": { @@ -1646,7 +2639,8 @@ "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "optional": true }, "builtin-modules": { "version": "1.1.1", @@ -1660,6 +2654,12 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "optional": true }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -1682,6 +2682,32 @@ "integrity": "sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ==", "dev": true }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1694,6 +2720,12 @@ "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", "optional": true }, + "caniuse-lite": { + "version": "1.0.30000971", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000971.tgz", + "integrity": "sha512-TQFYFhRS0O5rdsmSbF1Wn+16latXYsQJat66f7S7lizXW1PVpWJeZw9wqqVLIjuxDRz7s7xRUj13QCfd8hKn6g==", + "dev": true + }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -1740,17 +2772,22 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "chokidar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", - "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", "optional": true, "requires": { "anymatch": "^2.0.0", @@ -1785,6 +2822,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "optional": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -1859,7 +2897,8 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true }, "code-point-at": { "version": "1.1.0", @@ -1916,7 +2955,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", - "dev": true, "requires": { "buffer-crc32": "^0.2.1", "crc32-stream": "^2.0.0", @@ -1997,6 +3035,21 @@ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", "optional": true }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, "convert-source-map": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", @@ -2007,9 +3060,15 @@ } }, "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, "cookiejar": { @@ -2023,11 +3082,70 @@ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, + "core-js-compat": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.3.tgz", + "integrity": "sha512-EP018pVhgwsKHz3YoN1hTq49aRe+h017Kjz0NQz3nXV0cCRMvH3fLQl+vEPGr4r4J5sk4sU3tUC7U1aqTCeJeA==", + "dev": true, + "requires": { + "browserslist": "^4.6.0", + "core-js-pure": "3.1.3", + "semver": "^6.1.0" + }, + "dependencies": { + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", + "dev": true + } + } + }, + "core-js-pure": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.3.tgz", + "integrity": "sha512-k3JWTrcQBKqjkjI0bkfXS0lbpWPxYuHWfMMjC1VDmzU4Q58IwSbuXSo99YO/hUHlw/EB4AlfA2PVxOGkrIq6dA==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, "coveralls": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.3.tgz", @@ -2040,36 +3158,6 @@ "log-driver": "^1.2.7", "minimist": "^1.2.0", "request": "^2.86.0" - }, - "dependencies": { - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - } } }, "crc": { @@ -2084,7 +3172,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=", - "dev": true, "requires": { "crc": "^3.4.4", "readable-stream": "^2.0.0" @@ -2113,6 +3200,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "optional": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -2125,6 +3213,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "optional": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -2142,25 +3231,6 @@ "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", "which": "^1.2.9" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - } - } - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "requires": { - "boom": "2.x.x" } }, "crypto-browserify": { @@ -2255,11 +3325,11 @@ "integrity": "sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q=" }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "decamelize": { @@ -2453,6 +3523,12 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", @@ -2463,6 +3539,12 @@ "minimalistic-assert": "^1.0.0" } }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -2568,10 +3650,23 @@ "safe-buffer": "^5.0.1" } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.142", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.142.tgz", + "integrity": "sha512-GLOB/wAA2g9l5Hwg1XrPqd6br2WNOPIY8xl/q+g5zZdv3b5fB69oFOooxKxc0DfDfDS1RqaF6hKjwt6v4fuFUw==", + "dev": true + }, "elliptic": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "optional": true, "requires": { "bn.js": "^4.4.0", "brorand": "^1.0.1", @@ -2594,6 +3689,12 @@ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", "optional": true }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, "encoding": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", @@ -2627,6 +3728,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "optional": true, "requires": { "prr": "~1.0.1" } @@ -2747,6 +3849,12 @@ "es6-symbol": "^3.1.1" } }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2834,15 +3942,9 @@ }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "cross-spawn": { @@ -2867,53 +3969,20 @@ "ms": "^2.1.1" } }, - "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "inquirer": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", - "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.11", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "dependencies": { - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" } }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "strip-ansi": { @@ -2923,14 +3992,6 @@ "dev": true, "requires": { "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } } } } @@ -3031,10 +4092,17 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "optional": true, "requires": { "d": "1", "es5-ext": "~0.10.14" @@ -3050,6 +4118,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "optional": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -3101,6 +4170,14 @@ "to-regex": "^3.0.1" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -3116,6 +4193,11 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -3133,6 +4215,67 @@ "jest-regex-util": "^24.3.0" } }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3158,25 +4301,14 @@ } }, "external-editor": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", - "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "extend": "^3.0.0", - "spawn-sync": "^1.0.15", - "tmp": "^0.0.29" - }, - "dependencies": { - "tmp": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", - "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - } + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" } }, "extglob": { @@ -3349,6 +4481,38 @@ } } }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -3358,6 +4522,23 @@ "locate-path": "^2.0.0" } }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + } + } + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -3381,21 +4562,6 @@ "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", "requires": { "debug": "^3.2.6" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } } }, "for-in": { @@ -3424,6 +4590,12 @@ "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", "dev": true }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -3432,22 +4604,26 @@ "map-cache": "^0.2.2" } }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", - "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "optional": true, "requires": { "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "fs.realpath": { @@ -3472,7 +4648,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -3490,13 +4667,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3509,18 +4684,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -3623,8 +4795,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -3634,7 +4805,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3647,20 +4817,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3677,7 +4844,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3750,8 +4916,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -3761,7 +4926,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3837,7 +5001,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3867,7 +5032,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3885,6 +5049,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3923,11 +5088,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, @@ -4126,9 +5293,9 @@ "dev": true }, "uglify-js": { - "version": "3.5.15", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.15.tgz", - "integrity": "sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", + "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", "dev": true, "optional": true, "requires": { @@ -4141,14 +5308,12 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -4237,6 +5402,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "optional": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -4246,37 +5412,23 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "optional": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "requires": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" - } - }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "optional": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -4300,11 +5452,23 @@ "whatwg-encoding": "^1.0.1" } }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -4325,23 +5489,6 @@ "requires": { "agent-base": "^4.1.0", "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } } }, "iconv-lite": { @@ -4371,13 +5518,13 @@ "dev": true }, "import-fresh": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", - "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "dev": true, "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" } }, "import-lazy": { @@ -4429,118 +5576,40 @@ "dev": true }, "inquirer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", - "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", + "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", "dev": true, "requires": { - "ansi-escapes": "^1.1.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^1.1.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "mute-stream": "0.0.6", - "pinkie-promise": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", + "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rx": "^4.1.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", "through": "^2.3.6" }, "dependencies": { - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "mute-stream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", - "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=", - "dev": true - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "ansi-regex": "^4.1.0" } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true } } }, @@ -4565,6 +5634,12 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "optional": true }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "dev": true + }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -4647,6 +5722,12 @@ } } }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, "is-docker": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-1.1.0.tgz", @@ -4661,12 +5742,16 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "optional": true }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } }, "is-generator-fn": { "version": "2.1.0", @@ -4808,350 +5893,142 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, + "iso8601-duration": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/iso8601-duration/-/iso8601-duration-1.2.0.tgz", + "integrity": "sha512-ErTBd++b17E8nmWII1K1uZtBgD1E8RjyvwmxlCjPHNqHMD7gmcMHOw0E8Ro/6+QT4PhHRSnnMo7bxa1vFPkwhg==", + "dev": true + }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", - "dev": true, - "requires": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", - "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" - }, - "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", - "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", - "dev": true, - "requires": { - "handlebars": "^4.1.2" - } - }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "dev": true, - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, - "jest": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-24.8.0.tgz", - "integrity": "sha512-o0HM90RKFRNWmAWvlyV8i5jGZ97pFwkeVoGvPW1EtLTgJc2+jcuqcbbqcSZLE/3f2S5pt0y2ZBETuhpWNl1Reg==", - "dev": true, - "requires": { - "import-local": "^2.0.0", - "jest-cli": "^24.8.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "jest-cli": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.8.0.tgz", - "integrity": "sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA==", - "dev": true, - "requires": { - "@jest/core": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "import-local": "^2.0.0", - "is-ci": "^2.0.0", - "jest-config": "^24.8.0", - "jest-util": "^24.8.0", - "jest-validate": "^24.8.0", - "prompts": "^2.0.1", - "realpath-native": "^1.1.0", - "yargs": "^12.0.2" - } - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "dev": true, + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", "dev": true - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + } + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "has-flag": "^3.0.0" } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "ms": "^2.1.1" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, + "istanbul-reports": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "dev": true, + "requires": { + "handlebars": "^4.1.2" + } + }, + "isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "dev": true, + "requires": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + } + }, + "jest": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.8.0.tgz", + "integrity": "sha512-o0HM90RKFRNWmAWvlyV8i5jGZ97pFwkeVoGvPW1EtLTgJc2+jcuqcbbqcSZLE/3f2S5pt0y2ZBETuhpWNl1Reg==", + "dev": true, + "requires": { + "import-local": "^2.0.0", + "jest-cli": "^24.8.0" + } + }, "jest-changed-files": { "version": "24.8.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.8.0.tgz", @@ -5199,16 +6076,6 @@ "requires": { "pump": "^3.0.0" } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } } } }, @@ -5860,16 +6727,6 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -6060,6 +6917,12 @@ "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", "dev": true }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6146,34 +7009,6 @@ "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", "dev": true }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6249,14 +7084,6 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "~0.0.0" - } - }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -6274,20 +7101,20 @@ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "optional": true }, + "jsonata": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-1.6.4.tgz", + "integrity": "sha512-3MWTH77OHLf3muMknZJS4GnDhGPMITyF9D84hpRQrjt1Hk3pBtTiyZcqodHUDSaDq8VDy9YyIbanRI+3RoW3FA==", + "dev": true + }, "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { "graceful-fs": "^4.1.6" } }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, "jsonpath": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.0.1.tgz", @@ -6392,6 +7219,14 @@ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", "optional": true }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "^2.0.5" + } + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -6548,7 +7383,8 @@ "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "optional": true }, "loose-envify": { "version": "1.4.0", @@ -6565,6 +7401,15 @@ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, "lsmod": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", @@ -6592,7 +7437,8 @@ "make-error": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==" + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "optional": true }, "makeerror": { "version": "1.0.11", @@ -6629,12 +7475,19 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "optional": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" } }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, "mem": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", @@ -6648,11 +7501,18 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "optional": true, "requires": { "errno": "^0.1.3", "readable-stream": "^2.0.1" } }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, "merge-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", @@ -6732,12 +7592,14 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "optional": true }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "optional": true }, "minimatch": { "version": "3.0.4", @@ -6799,9 +7661,9 @@ "dev": true }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "mute-stream": { "version": "0.0.7", @@ -6852,6 +7714,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, "neo-async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", @@ -6868,6 +7736,15 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU=", + "dev": true, + "requires": { + "minimatch": "^3.0.2" + } + }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", @@ -6953,6 +7830,15 @@ "which": "^1.3.0" } }, + "node-releases": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.22.tgz", + "integrity": "sha512-O6XpteBuntW1j86mw6LlovBIwTe+sO2+7vi9avQffNeIW4upgnaCVm6xrBWH+KATz7mNNRNNeEpuWB7dT6Cr3w==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, "nomnom": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz", @@ -7039,8 +7925,7 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", @@ -7087,6 +7972,18 @@ "isobject": "^3.0.0" } }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -7105,6 +8002,15 @@ "isobject": "^3.0.1" } }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7130,6 +8036,15 @@ "is-wsl": "^1.1.0" } }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", @@ -7282,6 +8197,7 @@ "version": "5.1.4", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "optional": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", @@ -7312,6 +8228,12 @@ "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", "dev": true }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -7365,6 +8287,12 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", @@ -7378,6 +8306,7 @@ "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "optional": true, "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -7395,8 +8324,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { "version": "2.3.0", @@ -7523,6 +8451,12 @@ } } }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -7562,10 +8496,21 @@ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", "dev": true }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "optional": true }, "pseudomap": { "version": "1.0.2", @@ -7573,9 +8518,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + "version": "1.1.32", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", + "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" }, "public-encrypt": { "version": "4.0.3", @@ -7609,8 +8554,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystring": { "version": "0.2.0", @@ -7623,10 +8567,17 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "optional": true }, + "ramda": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", + "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "optional": true, "requires": { "safe-buffer": "^5.1.0" } @@ -7641,6 +8592,12 @@ "safe-buffer": "^5.1.0" } }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, "raven": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", @@ -7654,6 +8611,12 @@ "uuid": "3.0.0" }, "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, "uuid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", @@ -7662,6 +8625,18 @@ } } }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -7735,6 +8710,36 @@ "util.promisify": "^1.0.0" } }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==", + "dev": true + }, + "regenerator-transform": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.0.tgz", + "integrity": "sha512-rtOelq4Cawlbmq9xuMR5gdFmv7ku/sFoB7sRiywx7aq53bc52b4j6zvH7Te1Vt/X2YveDKnCGUbioieU7FEL3w==", + "dev": true, + "requires": { + "private": "^0.1.6" + } + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -7744,12 +8749,32 @@ "safe-regex": "^1.1.0" } }, + "regexp-tree": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.10.tgz", + "integrity": "sha512-K1qVSbcedffwuIslMwpe6vGlj+ZXRnGkvjAtFHfDZZZuEdA/h0dxljAPu9vhUo6Rrx2U2AwJ+nSQ6hK+lrP5MQ==", + "dev": true + }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, + "regexpu-core": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", + "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.0.2", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, "registry-auth-token": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", @@ -7769,6 +8794,29 @@ "rc": "^1.0.1" } }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", + "dev": true + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -7791,112 +8839,43 @@ "dev": true }, "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.1.1", - "har-validator": "~4.2.1", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "oauth-sign": "~0.8.1", - "performance-now": "^0.2.0", - "qs": "~6.4.0", - "safe-buffer": "^5.0.1", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.0.0" + "uuid": "^3.3.2" }, "dependencies": { - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.12" - } - }, - "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" - }, - "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", - "requires": { - "ajv": "^4.9.1", - "har-schema": "^1.0.5" - } - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "requires": { - "assert-plus": "^0.2.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" - }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, - "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" - }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" } } @@ -7939,9 +8918,9 @@ "dev": true }, "resolve": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", - "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", "requires": { "path-parse": "^1.0.6" } @@ -7953,20 +8932,12 @@ "dev": true, "requires": { "resolve-from": "^3.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - } } }, "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, "resolve-url": { @@ -8010,6 +8981,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "optional": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -8116,16 +9088,6 @@ "requires": { "pump": "^3.0.0" } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } } } }, @@ -8174,12 +9136,65 @@ "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=", "dev": true }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, "serverless": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/serverless/-/serverless-1.43.0.tgz", - "integrity": "sha512-3fQM7vwlUkJZbKpKFFKfDJYNkD1c5UxiAA+JWHY2JiD4Rkn5RVX89ToeR5+rzsT9+ewr2WJ9TqTVM+pd9WjOvA==", + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/serverless/-/serverless-1.44.1.tgz", + "integrity": "sha512-xbAtKobCPb67ovst5FsNt0eBlymESiISjJ/KOS4KrJCfwOJNJfDHIG07VwiKC1aJ5aQZX2V1Kf++DDrzbBxBng==", "dev": true, "requires": { + "@serverless/enterprise-plugin": "^1.0.3", "archiver": "^1.1.0", "async": "^1.5.2", "aws-sdk": "^2.430.0", @@ -8222,6 +9237,34 @@ "yaml-ast-parser": "0.0.34" }, "dependencies": { + "archiver": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", + "integrity": "sha1-TyGU1tj5nfP1MeaIHxTxXVX6ryI=", + "dev": true, + "requires": { + "archiver-utils": "^1.3.0", + "async": "^2.0.0", + "buffer-crc32": "^0.2.1", + "glob": "^7.0.0", + "lodash": "^4.8.0", + "readable-stream": "^2.0.0", + "tar-stream": "^1.5.0", + "walkdir": "^0.0.11", + "zip-stream": "^1.1.0" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + } + } + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -8234,6 +9277,28 @@ "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", "dev": true }, + "fs-extra": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", + "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "uuid": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", @@ -8256,91 +9321,6 @@ "lodash": "^4.17.4", "semver": "^5.4.1", "ts-node": "^3.2.0" - }, - "dependencies": { - "archiver": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", - "integrity": "sha1-/2YrSnggFJSj7lRNOjP+dJZQnrw=", - "optional": true, - "requires": { - "archiver-utils": "^1.3.0", - "async": "^2.0.0", - "buffer-crc32": "^0.2.1", - "glob": "^7.0.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0", - "tar-stream": "^1.5.0", - "zip-stream": "^1.2.0" - } - }, - "compress-commons": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", - "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", - "optional": true, - "requires": { - "buffer-crc32": "^0.2.1", - "crc32-stream": "^2.0.0", - "normalize-path": "^2.0.0", - "readable-stream": "^2.0.0" - } - }, - "crc32-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", - "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=", - "optional": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^2.0.0" - } - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "optional": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "optional": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "zip-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", - "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=", - "optional": true, - "requires": { - "archiver-utils": "^1.3.0", - "compress-commons": "^1.2.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0" - } - } } }, "set-blocking": { @@ -8381,10 +9361,17 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", "optional": true }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "optional": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -8435,6 +9422,14 @@ "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } } }, "snapdragon": { @@ -8452,6 +9447,14 @@ "use": "^3.1.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -8468,6 +9471,11 @@ "is-extendable": "^0.1.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -8534,18 +9542,11 @@ "kind-of": "^3.2.0" } }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "requires": { - "hoek": "2.x.x" - } - }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "optional": true }, "source-map": { "version": "0.7.3", @@ -8723,6 +9724,12 @@ } } }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", @@ -8793,6 +9800,11 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -8811,11 +9823,6 @@ "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -8865,38 +9872,20 @@ "requires": { "component-emitter": "^1.2.0", "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" } }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -8908,9 +9897,9 @@ "dev": true }, "table": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/table/-/table-5.3.3.tgz", - "integrity": "sha512-3wUNCgdWX6PNpOe3amTTPWPuF6VGvgzjKCaO1snFj0z7Y3mUPWf5+zDtxUVGispJkDECPmR29wbzh6bVMOHbcw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.0.tgz", + "integrity": "sha512-nHFDrxmbrkU7JAFKqKbDJXfzrX2UBsWmrieXFTGxiI5e4ncg3VqsZeI4EzNmX0ncp4XNGVeoxIWJXfCIXwrsvw==", "dev": true, "requires": { "ajv": "^6.9.1", @@ -8925,6 +9914,12 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -8961,12 +9956,155 @@ "mkdirp": "^0.5.1", "npmlog": "^2.0.3", "object-assign": "^4.1.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "external-editor": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", + "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "spawn-sync": "^1.0.15", + "tmp": "^0.0.29" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "inquirer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", + "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", + "dev": true, + "requires": { + "ansi-escapes": "^1.1.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "external-editor": "^1.1.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "mute-stream": "0.0.6", + "pinkie-promise": "^2.0.0", + "run-async": "^2.2.0", + "rx": "^4.1.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", + "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=", + "dev": true + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "tmp": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", + "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + } } }, "tapable": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", - "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==" + "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", + "optional": true }, "tar-stream": { "version": "1.6.2", @@ -9205,20 +10343,19 @@ "repeat-string": "^1.6.1" } }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "tr46": { @@ -9245,49 +10382,6 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, - "ts-jest": { - "version": "24.0.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.0.2.tgz", - "integrity": "sha512-h6ZCZiA1EQgjczxq+uGLXQlNgeg02WWJBbeT8j6nyIBRQdglqbvzDoHahTEIiS6Eor6x8mK6PfZ7brQ9Q6tzHw==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "json5": "2.x", - "make-error": "1.x", - "mkdirp": "0.x", - "resolve": "1.x", - "semver": "^5.5", - "yargs-parser": "10.x" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, "ts-node": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-3.3.0.tgz", @@ -9304,37 +10398,6 @@ "tsconfig": "^6.0.0", "v8flags": "^3.0.0", "yn": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "optional": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "optional": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "optional": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "tsconfig": { @@ -9352,12 +10415,26 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, + "tsutils": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.14.0.tgz", + "integrity": "sha512-SmzGbB0l+8I0QwsPgjooFRaRvHLBLNYM8SeQ0k6rtNDru5sCGeLJcZdwilNndN+GysuFjF5EIYgN8GfFG6UeUw==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "optional": true }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -9379,6 +10456,16 @@ "prelude-ls": "~1.1.2" } }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -9386,9 +10473,9 @@ "dev": true }, "typescript": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", - "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", + "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", "dev": true }, "uglify-js": { @@ -9462,6 +10549,34 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "dev": true + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -9506,8 +10621,13 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "optional": true + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true }, "unset-value": { "version": "1.0.0", @@ -9673,6 +10793,12 @@ "object.getownpropertydescriptors": "^2.0.3" } }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -9696,6 +10822,12 @@ "spdx-expression-parse": "^3.0.0" } }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -9786,11 +10918,14 @@ "yargs": "^8.0.2" }, "dependencies": { - "ajv-keywords": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", - "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", - "optional": true + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "optional": true, + "requires": { + "lodash": "^4.17.11" + } }, "has-flag": { "version": "2.0.0", @@ -9819,6 +10954,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "optional": true, "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" @@ -9827,7 +10963,8 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true } } }, @@ -9840,6 +10977,12 @@ "iconv-lite": "0.4.24" } }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==", + "dev": true + }, "whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", @@ -9899,14 +11042,6 @@ "strip-ansi": "^3.0.1" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -10010,6 +11145,16 @@ "integrity": "sha1-0A88+ddztyQUCa6SpnQNHbGfSeY=", "dev": true }, + "yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + } + }, "yargs": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", @@ -10060,15 +11205,6 @@ } } } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } } } }, @@ -10109,7 +11245,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=", - "dev": true, "requires": { "archiver-utils": "^1.3.0", "compress-commons": "^1.2.0", diff --git a/package.json b/package.json index b038e446..47c7dba8 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,17 @@ "description": "Provider plugin for the Serverless Framework v1.x which adds support for Azure Functions.", "license": "MIT", "main": "./lib/index.js", + "typings": "./lib/index.d.ts", "author": "Azure Functions", "scripts": { + "start": "npm run test -- --watch", "lint": "eslint src/**/*.ts", - "lintfix": "eslint src/**/*.ts --fix", + "lint:fix": "eslint src/**/*.ts --fix", "pretest": "npm run lint", - "test": "jest --config jest.config.js", - "prebuild": "rm -rf lib/ && npm test", + "test": "jest", + "test:ci": "npm run test -- --ci", + "test:coverage": "npm run test -- --coverage", + "prebuild": "rm lib/ -rf", "build": "tsc" }, "repository": { @@ -41,6 +45,7 @@ "rimraf": "^2.6.3" }, "devDependencies": { + "@babel/runtime": "^7.4.5", "@types/jest": "^24.0.13", "@types/lodash": "^4.14.130", "@types/open": "^6.1.0", @@ -48,13 +53,14 @@ "@types/serverless": "^1.18.2", "@typescript-eslint/eslint-plugin": "^1.9.0", "@typescript-eslint/parser": "^1.9.0", + "babel-jest": "^24.8.0", + "babel-preset-react-app": "^9.0.0", "coveralls": "^3.0.3", "eslint": "^5.16.0", "jest": "^24.8.0", "jest-cli": "^24.8.0", "mock-fs": "^4.10.0", - "serverless": "^1.43.0", - "ts-jest": "^24.0.2", + "serverless": "^1.44.1", "typescript": "^3.4.5" }, "optionalDependencies": { @@ -64,4 +70,4 @@ "engines": { "node": ">= 6.5.0" } -} +} \ No newline at end of file diff --git a/src/index.test.ts b/src/index.test.ts index 0547521a..66f8d1d2 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -14,7 +14,7 @@ describe("Azure Index", () => { it("contains all registered plugins", () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); - const index = new AzureIndex(sls, options); + new AzureIndex(sls, options); sls.setProvider = jest.fn(); expect(sls.setProvider).toBeCalledWith("azure", new AzureProvider(sls)); diff --git a/src/index.ts b/src/index.ts index d4550730..42c35e96 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ import { AzureLoginPlugin } from "./plugins/login/loginPlugin"; import { AzureApimServicePlugin } from "./plugins/apim/apimServicePlugin"; import { AzureApimFunctionPlugin } from "./plugins/apim/apimFunctionPlugin"; import { AzureFuncPlugin } from "./plugins/func/azureFunc"; +import { AzureOfflinePlugin } from "./plugins/offline/azureOfflinePlugin" export default class AzureIndex { @@ -32,6 +33,7 @@ export default class AzureIndex { this.serverless.pluginManager.addPlugin(AzureApimServicePlugin); this.serverless.pluginManager.addPlugin(AzureApimFunctionPlugin); this.serverless.pluginManager.addPlugin(AzureFuncPlugin); + this.serverless.pluginManager.addPlugin(AzureOfflinePlugin); } } diff --git a/src/models/apiManagement.ts b/src/models/apiManagement.ts new file mode 100644 index 00000000..0ba19750 --- /dev/null +++ b/src/models/apiManagement.ts @@ -0,0 +1,12 @@ +import { OperationContract, ApiContract, BackendContract } from "@azure/arm-apimanagement/esm/models"; + +export interface ApiManagementConfig { + name: string; + api: ApiContract; + backend?: BackendContract; +} + +export interface ApiOperationOptions { + function: string; + operation: OperationContract; +} \ No newline at end of file diff --git a/src/models/functionApp.ts b/src/models/functionApp.ts new file mode 100644 index 00000000..712f65f4 --- /dev/null +++ b/src/models/functionApp.ts @@ -0,0 +1,7 @@ +export interface FunctionAppHttpTriggerConfig { + authLevel: string; + methods: string[]; + route: string; + name: string; + url: string; +} diff --git a/src/models/generic.ts b/src/models/generic.ts new file mode 100644 index 00000000..d187d7c9 --- /dev/null +++ b/src/models/generic.ts @@ -0,0 +1,3 @@ +export interface Logger { + log: (message: string) => void; +} \ No newline at end of file diff --git a/src/plugins/apim/apimFunctionPlugin.ts b/src/plugins/apim/apimFunctionPlugin.ts index 8c5c9ee5..6427e60e 100644 --- a/src/plugins/apim/apimFunctionPlugin.ts +++ b/src/plugins/apim/apimFunctionPlugin.ts @@ -14,7 +14,10 @@ export class AzureApimFunctionPlugin { this.serverless.cli.log("Starting APIM function deployment"); const apimService = new ApimService(this.serverless, this.options); - await apimService.deployFunction(this.options); + const service = await apimService.get(); + const api = await apimService.getApi(); + + await apimService.deployFunction(service, api, this.options); this.serverless.cli.log("Finished APIM function deployment"); } diff --git a/src/plugins/apim/apimServicePlugin.ts b/src/plugins/apim/apimServicePlugin.ts index 2f13aef4..55c72e30 100644 --- a/src/plugins/apim/apimServicePlugin.ts +++ b/src/plugins/apim/apimServicePlugin.ts @@ -19,8 +19,9 @@ export class AzureApimServicePlugin { this.serverless.cli.log("Starting APIM service deployment"); const apimService = new ApimService(this.serverless, this.options); - await apimService.deployApi(); - await apimService.deployFunctions(); + const service = await apimService.get(); + const api = await apimService.deployApi(); + await apimService.deployFunctions(service, api); this.serverless.cli.log("Finished APIM service deployment"); } diff --git a/src/plugins/deploy/azureDeployPlugin.test.ts b/src/plugins/deploy/azureDeployPlugin.test.ts index 468947dc..2818890a 100644 --- a/src/plugins/deploy/azureDeployPlugin.test.ts +++ b/src/plugins/deploy/azureDeployPlugin.test.ts @@ -7,27 +7,27 @@ import { FunctionAppService } from "../../services/functionAppService"; jest.mock("../../services/resourceService"); import { ResourceService } from "../../services/resourceService"; +import { Site } from "@azure/arm-appservice/esm/models"; describe("Deploy plugin", () => { - it("calls deploy hook", async () => { const deployResourceGroup = jest.fn(); - const functionAppStub = "Function App Stub"; + const functionAppStub: Site = MockFactory.createTestSite(); const deploy = jest.fn(() => Promise.resolve(functionAppStub)); const uploadFunctions = jest.fn(); - ResourceService.prototype.deployResourceGroup = deployResourceGroup - FunctionAppService.prototype.deploy = deploy - FunctionAppService.prototype.uploadFunctions = uploadFunctions + ResourceService.prototype.deployResourceGroup = deployResourceGroup; + FunctionAppService.prototype.deploy = deploy; + FunctionAppService.prototype.uploadFunctions = uploadFunctions; const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureDeployPlugin(sls, options); - + await invokeHook(plugin, "deploy:deploy"); - + expect(deployResourceGroup).toBeCalled(); expect(deploy).toBeCalled(); expect(uploadFunctions).toBeCalledWith(functionAppStub); }); -}); \ No newline at end of file +}); diff --git a/src/plugins/func/azureFunc.test.ts b/src/plugins/func/azureFunc.test.ts index 2ff32525..163becbe 100644 --- a/src/plugins/func/azureFunc.test.ts +++ b/src/plugins/func/azureFunc.test.ts @@ -46,7 +46,7 @@ describe("Azure Func Plugin", () => { options["name"] = "myExistingFunction"; const plugin = new AzureFuncPlugin(sls, options); await invokeHook(plugin, "func:add:add"); - expect(sls.cli.log).toBeCalledWith(`Function myExistingFunction already exists`); + expect(sls.cli.log).toBeCalledWith("Function myExistingFunction already exists"); }); it("creates function directory and updates serverless.yml", async () => { @@ -97,7 +97,7 @@ describe("Azure Func Plugin", () => { options["name"] = "myNonExistingFunction"; const plugin = new AzureFuncPlugin(sls, options); await invokeHook(plugin, "func:remove:remove"); - expect(sls.cli.log).toBeCalledWith(`Function myNonExistingFunction does not exist`); + expect(sls.cli.log).toBeCalledWith("Function myNonExistingFunction does not exist"); }); it("deletes directory and updates serverless.yml", async () => { diff --git a/src/plugins/func/azureFunc.ts b/src/plugins/func/azureFunc.ts index 00d1a212..86c448fe 100644 --- a/src/plugins/func/azureFunc.ts +++ b/src/plugins/func/azureFunc.ts @@ -7,7 +7,7 @@ import { FuncPluginUtils } from "./funcUtils"; export class AzureFuncPlugin { public hooks: { [eventName: string]: Promise }; public commands: any; - + public constructor(private serverless: Serverless, private options: Serverless.Options) { this.hooks = { @@ -79,7 +79,7 @@ export class AzureFuncPlugin { this.serverless.cli.log(`Error making directory ${e}`); } this.serverless.utils.writeFileSync(path.join(name, "index.js"), FuncPluginUtils.getFunctionHandler(name)); - this.serverless.utils.writeFileSync(path.join(name, "function.json"), FuncPluginUtils.getFunctionJsonString(name, this.options)) + this.serverless.utils.writeFileSync(path.join(name, "function.json"), FuncPluginUtils.getFunctionJsonString(name, this.options)); } private addToServerlessYml(name: string) { @@ -87,7 +87,7 @@ export class AzureFuncPlugin { const functionYml = FuncPluginUtils.getFunctionsYml(this.serverless); functionYml[name] = FuncPluginUtils.getFunctionSlsObject(name, this.options); FuncPluginUtils.updateFunctionsYml(this.serverless, functionYml); - } + } private async remove() { if (!("name" in this.options)) { diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index 7d025d59..cebeeb2b 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -55,7 +55,7 @@ module.exports.handler = async function (context, req) { handler: "index.handler", events: FuncPluginUtils.httpEvents() } - } + } private static httpEvents() { return [ @@ -73,5 +73,5 @@ module.exports.handler = async function (context, req) { } }, ] - } + } } \ No newline at end of file diff --git a/src/plugins/login/loginPlugin.test.ts b/src/plugins/login/loginPlugin.test.ts index 14445512..f8cbced8 100644 --- a/src/plugins/login/loginPlugin.test.ts +++ b/src/plugins/login/loginPlugin.test.ts @@ -33,7 +33,7 @@ describe("Login Plugin", () => { AzureLoginService.servicePrincipalLogin = servicePrincipalLogin; const sls = MockFactory.createTestServerless(); - delete sls.variables['azureCredentials'] + delete sls.variables["azureCredentials"] const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureLoginPlugin(sls, options); @@ -57,7 +57,7 @@ describe("Login Plugin", () => { AzureLoginService.servicePrincipalLogin = servicePrincipalLogin; const sls = MockFactory.createTestServerless(); - delete sls.variables['azureCredentials'] + delete sls.variables["azureCredentials"] const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureLoginPlugin(sls, options); await invokeHook(plugin, "before:package:initialize"); @@ -85,7 +85,7 @@ describe("Login Plugin", () => { AzureLoginService.servicePrincipalLogin = servicePrincipalLogin; const sls = MockFactory.createTestServerless(); - delete sls.variables['azureCredentials'] + delete sls.variables["azureCredentials"] const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureLoginPlugin(sls, options); await invokeHook(plugin, "before:package:initialize"); @@ -112,7 +112,7 @@ describe("Login Plugin", () => { AzureLoginService.servicePrincipalLogin = servicePrincipalLogin; const sls = MockFactory.createTestServerless(); - delete sls.variables['azureCredentials'] + delete sls.variables["azureCredentials"] const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureLoginPlugin(sls, options); await invokeHook(plugin, "before:package:initialize"); diff --git a/src/plugins/offline/azureOfflinePlugin.test.ts b/src/plugins/offline/azureOfflinePlugin.test.ts new file mode 100644 index 00000000..4aac97ab --- /dev/null +++ b/src/plugins/offline/azureOfflinePlugin.test.ts @@ -0,0 +1,29 @@ +import path from "path"; +import Serverless from "serverless"; +import { MockFactory } from "../../test/mockFactory"; +import { invokeHook } from "../../test/utils"; +import { AzureOfflinePlugin } from "./azureOfflinePlugin"; + +describe("Azure Offline Plugin", () => { + + function createPlugin(sls?: Serverless, options?: Serverless.Options) { + return new AzureOfflinePlugin( + sls || MockFactory.createTestServerless(), + options || MockFactory.createTestServerlessOptions(), + ) + } + + it("invokes build hook", async () => { + const sls = MockFactory.createTestServerless(); + const plugin = createPlugin(sls); + await invokeHook(plugin, "offline:build:build"); + const calls = (sls.utils.writeFileSync as any).mock.calls; + const functionNames = sls.service.getAllFunctions(); + const expectedFunctionJson = MockFactory.createTestBindingsObject(); + for (let i = 0; i < calls.length; i++) { + const name = functionNames[i]; + expect(calls[i][0]).toEqual(`${name}${path.sep}function.json`) + expect(JSON.parse(calls[i][1])).toEqual(expectedFunctionJson); + } + }); +}); \ No newline at end of file diff --git a/src/plugins/offline/azureOfflinePlugin.ts b/src/plugins/offline/azureOfflinePlugin.ts new file mode 100644 index 00000000..fd0ec3e9 --- /dev/null +++ b/src/plugins/offline/azureOfflinePlugin.ts @@ -0,0 +1,43 @@ +import Serverless from "serverless"; +import { OfflineService } from "../../services/offlineService"; + +export class AzureOfflinePlugin { + public hooks: { [eventName: string]: Promise }; + public commands: any; + private offlineService: OfflineService; + + public constructor(private serverless: Serverless, private options: Serverless.Options) { + this.offlineService = new OfflineService(this.serverless, this.options); + + this.hooks = { + "before:offline:offline": this.azureOfflineBuild.bind(this), + "offline:build:build": this.azureOfflineBuild.bind(this), + "offline:offline": this.azureOfflineStart.bind(this), + }; + + this.commands = { + offline: { + usage: "Start Azure Function App offline", + lifecycleEvents: [ + "offline", + ], + commands: { + build: { + usage: "Build necessary files for running Azure Function App offline", + lifecycleEvents: [ + "build", + ] + } + } + } + } + } + + private async azureOfflineBuild(){ + this.offlineService.build(); + } + + private async azureOfflineStart(){ + this.offlineService.start(); + } +} \ No newline at end of file diff --git a/src/plugins/package/azurePackage.test.ts b/src/plugins/package/azurePackage.test.ts index bf8acca2..2710849e 100644 --- a/src/plugins/package/azurePackage.test.ts +++ b/src/plugins/package/azurePackage.test.ts @@ -1,31 +1,37 @@ import { MockFactory } from "../../test/mockFactory"; import { invokeHook } from "../../test/utils"; -import { AzurePackage } from "./azurePackage" +import { AzurePackage } from "./azurePackage"; jest.mock("../../shared/bindings"); import { BindingUtils } from "../../shared/bindings"; jest.mock("../../shared/utils"); -import { Utils, FunctionMetadata } from "../../shared/utils"; +import { Utils } from "../../shared/utils"; describe("Azure Package Plugin", () => { it("sets up provider configuration", async () => { - const metadata = "metadata"; - const functionName = "function1"; + const slsFunctionConfig = MockFactory.createTestSlsFunctionConfig(); + const sls = MockFactory.createTestServerless(); + Object.assign(sls.service, { + functions: slsFunctionConfig + }); - const getFunctionMetaDataFn = jest.fn(() => metadata as any as FunctionMetadata); - const createEventsBindingsFn = jest.fn(); + const functionConfig = Object.keys(slsFunctionConfig).map((funcName) => { + return { + name: funcName, + config: slsFunctionConfig[funcName], + }; + }); - Utils.getFunctionMetaData = getFunctionMetaDataFn - BindingUtils.createEventsBindings = createEventsBindingsFn + Utils.getFunctionMetaData = jest.fn((funcName) => slsFunctionConfig[funcName]); + BindingUtils.createEventsBindings = jest.fn(); - const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); const plugin = new AzurePackage(sls, options); await invokeHook(plugin, "package:setupProviderConfiguration"); expect(sls.cli.log).toBeCalledWith("Building Azure Events Hooks"); - expect(getFunctionMetaDataFn).toBeCalledWith(functionName, sls); - expect(createEventsBindingsFn).toBeCalledWith(sls.config.servicePath, functionName, metadata); + expect(Utils.getFunctionMetaData).toBeCalledWith(functionConfig[0].name, sls); + expect(BindingUtils.createEventsBindings).toBeCalledWith(sls, functionConfig[0].name, functionConfig[0].config); }); }); \ No newline at end of file diff --git a/src/plugins/package/azurePackage.ts b/src/plugins/package/azurePackage.ts index 903effad..a9af2101 100644 --- a/src/plugins/package/azurePackage.ts +++ b/src/plugins/package/azurePackage.ts @@ -21,7 +21,7 @@ export class AzurePackage { .map((functionName) => { const metaData = Utils.getFunctionMetaData(functionName, this.serverless); - return BindingUtils.createEventsBindings(this.serverless.config.servicePath, functionName, metaData); + return BindingUtils.createEventsBindings(this.serverless, functionName, metaData); }); return Promise.all(createEventsPromises); diff --git a/src/services/apimService.test.ts b/src/services/apimService.test.ts new file mode 100644 index 00000000..e8ae9957 --- /dev/null +++ b/src/services/apimService.test.ts @@ -0,0 +1,380 @@ +import Serverless from "serverless"; +import _ from "lodash"; +import { MockFactory } from "../test/mockFactory"; +import { ApiManagementConfig } from "../models/apiManagement"; +import { ApimService } from "./apimService"; +import { interpolateJson } from "../test/utils"; +import axios from "axios"; +import { Api, Backend, Property, ApiOperation, ApiOperationPolicy, ApiManagementService } from "@azure/arm-apimanagement"; +import apimGetService404 from "../test/responses/apim-get-service-404.json"; +import apimGetService200 from "../test/responses/apim-get-service-200.json"; +import apimGetApi200 from "../test/responses/apim-get-api-200.json"; +import apimGetApi404 from "../test/responses/apim-get-api-404.json"; +import { FunctionAppService } from "./functionAppService"; +import { Site } from "@azure/arm-appservice/esm/models"; +import { + PropertyContract, BackendContract, BackendCreateOrUpdateResponse, + ApiCreateOrUpdateResponse, PropertyCreateOrUpdateResponse, ApiContract, + ApiOperationCreateOrUpdateResponse, ApiManagementServiceResource, ApiGetResponse, + ApiManagementServiceGetResponse, + OperationContract, +} from "@azure/arm-apimanagement/esm/models"; + +describe("APIM Service", () => { + const apimConfig: ApiManagementConfig = { + name: "test-apim-resource", + api: { + name: "test-apim-api1", + subscriptionRequired: false, + displayName: "API 1", + description: "description of api 1", + protocols: ["https"], + path: "test-api1", + }, + }; + + let serverless: Serverless; + + beforeEach(() => { + const slsConfig: any = { + service: "test-sls", + provider: { + name: "azure", + resourceGroup: "test-sls-rg", + location: "West US", + apim: apimConfig, + }, + functions: MockFactory.createTestSlsFunctionConfig(), + }; + + serverless = MockFactory.createTestServerless(); + Object.assign(serverless.service, slsConfig); + + serverless.variables = { + ...serverless.variables, + azureCredentials: MockFactory.createTestAzureCredentials(), + subscriptionId: "ABC123", + }; + }); + + it("is defined", () => { + expect(ApimService).toBeDefined(); + }); + + it("can be instantiated", () => { + const service = new ApimService(serverless); + expect(service).not.toBeNull(); + }); + + describe("Get service reference", () => { + it("returns null when service doesn't exist", async () => { + axios.request = jest.fn((requestConfig) => MockFactory.createTestAxiosResponse(requestConfig, apimGetService404, 404)); + + const service = new ApimService(serverless); + const resource = await service.get(); + + expect(resource).toBeNull(); + }); + + it("returns null when APIM is not configured", async () => { + serverless.service.provider["apim"] = null; + + const service = new ApimService(serverless); + const resource = await service.get(); + + expect(resource).toBeNull(); + }); + + it("returns instance of service resource", async () => { + const expectedResponse = interpolateJson(apimGetService200, { + resourceGroup: { + name: serverless.service.provider["resourceGroup"], + location: serverless.service.provider["location"], + }, + resource: { + name: apimConfig.name, + }, + }); + + axios.request = jest.fn((requestConfig) => MockFactory.createTestAxiosResponse(requestConfig, expectedResponse)); + + const service = new ApimService(serverless); + const resource = await service.get(); + + expect(resource).not.toBeNull(); + expect(resource).toMatchObject({ + name: apimConfig.name, + location: serverless.service.provider["location"], + }); + }); + }); + + describe("Get API reference", () => { + it("returns null when API doesn't exist", async () => { + axios.request = jest.fn((requestConfig) => MockFactory.createTestAxiosResponse(requestConfig, apimGetApi404, 404)); + + const service = new ApimService(serverless); + const api = await service.getApi(); + expect(api).toBeNull(); + }); + + it("returns null when APIM config does not exist", async () => { + serverless.service.provider["apim"] = null; + + const service = new ApimService(serverless); + const api = await service.getApi(); + + expect(api).toBeNull(); + }); + + it("returns the API reference", async () => { + const expectedResponse = interpolateJson(apimGetApi200, { + resourceGroup: { + name: serverless.service.provider["resourceGroup"], + location: serverless.service.provider["location"], + }, + service: { + name: apimConfig.name, + }, + resource: { + name: apimConfig.api.name, + displayName: apimConfig.api.displayName, + description: apimConfig.api.description, + path: apimConfig.api.path, + }, + }); + + axios.request = jest.fn((requestConfig) => MockFactory.createTestAxiosResponse(requestConfig, expectedResponse)); + + const service = new ApimService(serverless); + const api = await service.getApi(); + + expect(api).not.toBeNull(); + expect(api).toMatchObject({ + displayName: apimConfig.api.displayName, + description: apimConfig.api.description, + path: apimConfig.api.path, + }); + }); + }); + + describe("Deploying API", () => { + let backendConfig: BackendContract; + let resourceGroupName: string; + let appName: string; + let serviceName: string; + let apiName: string; + let backendName: string; + let functionApp: Site; + let masterKey: string; + let expectedApi: ApiContract; + let expectedApiResult: ApiContract; + let expectedBackend: BackendContract; + let expectedProperty: PropertyContract; + + beforeEach(() => { + backendConfig = apimConfig.backend || {} as BackendContract; + resourceGroupName = serverless.service.provider["resourceGroup"]; + appName = serverless.service["service"]; + serviceName = apimConfig.name; + apiName = apimConfig.api.name; + backendName = backendConfig.name || appName; + + functionApp = { + id: "/testapp1", + name: "Test Site", + location: "West US", + defaultHostName: "testsite.azurewebsites.net", + }; + masterKey = "ABC123"; + + FunctionAppService.prototype.get = jest.fn(() => Promise.resolve(functionApp)); + FunctionAppService.prototype.getMasterKey = jest.fn(() => Promise.resolve(masterKey)); + + expectedApi = { + isCurrent: true, + subscriptionRequired: apimConfig.api.subscriptionRequired, + displayName: apimConfig.api.displayName, + description: apimConfig.api.description, + path: apimConfig.api.path, + protocols: apimConfig.api.protocols, + }; + + expectedApiResult = { + id: apimConfig.api.name, + name: apimConfig.api.name, + isCurrent: true, + subscriptionRequired: apimConfig.api.subscriptionRequired, + displayName: apimConfig.api.displayName, + description: apimConfig.api.description, + path: apimConfig.api.path, + protocols: apimConfig.api.protocols, + }; + + expectedBackend = { + credentials: { + header: { + "x-functions-key": [`{{${serverless.service["service"]}-key}}`], + }, + }, + title: backendConfig.title || functionApp.name, + tls: backendConfig.tls, + proxy: backendConfig.proxy, + description: backendConfig.description, + protocol: backendConfig.protocol || "http", + resourceId: `https://management.azure.com${functionApp.id}`, + url: `https://${functionApp.defaultHostName}/api`, + }; + + expectedProperty = { + displayName: `${serverless.service["service"]}-key`, + secret: true, + value: masterKey, + }; + }); + + it("ensures API, backend and keys have all been set", async () => { + + Api.prototype.createOrUpdate = + jest.fn(() => MockFactory.createTestArmSdkResponse(expectedApiResult, 201)); + Backend.prototype.createOrUpdate = + jest.fn(() => MockFactory.createTestArmSdkResponse(expectedBackend, 201)); + Property.prototype.createOrUpdate = + jest.fn(() => MockFactory.createTestArmSdkResponse(expectedProperty, 201)); + + const apimService = new ApimService(serverless); + const result = await apimService.deployApi(); + + expect(result).toMatchObject(expectedApiResult); + expect(Api.prototype.createOrUpdate).toBeCalledWith( + resourceGroupName, + serviceName, + apiName, + expectedApi, + ); + + expect(Backend.prototype.createOrUpdate).toBeCalledWith( + resourceGroupName, + serviceName, + backendName, + expectedBackend, + ); + + expect(Property.prototype.createOrUpdate).toBeCalledWith( + resourceGroupName, + serviceName, + expectedProperty.displayName, + expectedProperty, + ); + }); + + it("returns null when APIM is not configured", async () => { + serverless.service.provider["apim"] = null; + + const service = new ApimService(serverless); + const api = await service.deployApi(); + + expect(api).toBeNull(); + }); + + it("fails when API deployment fails", async () => { + const apiError = "Error creating API"; + Api.prototype.createOrUpdate = jest.fn(() => Promise.reject(apiError)); + + const apimService = new ApimService(serverless); + await expect(apimService.deployApi()).rejects.toEqual(apiError); + }); + + it("fails when Backend deployment fails", async () => { + const apiError = "Error creating Backend"; + + Api.prototype.createOrUpdate = + jest.fn(() => MockFactory.createTestArmSdkResponse(expectedApiResult, 201)); + Backend.prototype.createOrUpdate = jest.fn(() => Promise.reject(apiError)); + + const apimService = new ApimService(serverless); + await expect(apimService.deployApi()).rejects.toEqual(apiError); + }); + + it("fails when Property deployment fails", async () => { + const apiError = "Error creating Property"; + + Api.prototype.createOrUpdate = + jest.fn(() => MockFactory.createTestArmSdkResponse(expectedApiResult, 201)); + Backend.prototype.createOrUpdate = + jest.fn(() => MockFactory.createTestArmSdkResponse(expectedBackend, 201)); + Property.prototype.createOrUpdate = jest.fn(() => Promise.reject(apiError)); + + const apimService = new ApimService(serverless); + await expect(apimService.deployApi()).rejects.toEqual(apiError); + }); + }); + + describe("Deploying Functions", () => { + it("performs a noop when APIM config has not been configured", async () => { + serverless.service.provider["apim"] = null; + + const apimService = new ApimService(serverless); + const deploySpy = jest.spyOn(apimService, "deployFunction"); + + const serviceResource: ApiManagementServiceResource = MockFactory.createTestApimService(); + const api: ApiContract = MockFactory.createTestApimApi(); + + await apimService.deployFunctions(serviceResource, api); + + expect(deploySpy).not.toBeCalled(); + }); + + it("ensures all serverless functions have been deployed into specified API", async () => { + const slsFunctions = _.values(serverless.service["functions"]); + + const apimResource: ApiManagementServiceResource = { + name: apimConfig.name, + location: "West US", + publisherEmail: "someone@example.com", + publisherName: "Someone", + sku: { + capacity: 1, + name: "Consumption", + }, + }; + + const apiContract = apimConfig.api; + ApiManagementService.prototype.get = jest.fn(() => MockFactory.createTestArmSdkResponse(apimResource, 200)); + Api.prototype.get = jest.fn(() => MockFactory.createTestArmSdkResponse(apiContract, 200)); + + ApiOperation.prototype.createOrUpdate = + jest.fn((resourceGroup, serviceName, apiName, operationName, operationContract) => { + const response = MockFactory.createTestArmSdkResponse(operationContract, 201); + return Promise.resolve(response); + }); + + ApiOperationPolicy.prototype.createOrUpdate = jest.fn(() => Promise.resolve(null)); + + const deployFunctionSpy = jest.spyOn(ApimService.prototype, "deployFunction"); + + const service = new ApimService(serverless); + const apimInstance = await service.get(); + const api = await service.getApi(); + + await service.deployFunctions(apimInstance, api); + + expect(deployFunctionSpy).toBeCalledTimes(slsFunctions.length); + + const createOperationCall = ApiOperation.prototype.createOrUpdate as jest.Mock; + createOperationCall.mock.calls.forEach((args, index) => { + const expected = slsFunctions[index].apim.operations[0] as OperationContract; + const actual = args[4] as OperationContract; + + expect(actual).toMatchObject({ + displayName: expected.displayName, + description: expected.description || "", + method: expected.method, + urlTemplate: expected.urlTemplate, + templateParameters: expected.templateParameters || [], + responses: expected.responses || [], + }); + }); + }); + }); +}); diff --git a/src/services/apimService.ts b/src/services/apimService.ts index 7516cf34..c7a389d4 100644 --- a/src/services/apimService.ts +++ b/src/services/apimService.ts @@ -2,6 +2,13 @@ import Serverless from "serverless"; import { ApiManagementClient } from "@azure/arm-apimanagement"; import { FunctionAppService } from "./functionAppService"; import { BaseService } from "./baseService"; +import { ApiManagementConfig, ApiOperationOptions } from "../models/apiManagement"; +import { + ApiContract, BackendContract, OperationContract, + PropertyContract, ApiManagementServiceResource, +} from "@azure/arm-apimanagement/esm/models"; +import { Site } from "@azure/arm-appservice/esm/models"; +import { Guard } from "../shared/guard"; /** * APIM Service handles deployment and integration with Azure API Management @@ -9,55 +16,107 @@ import { BaseService } from "./baseService"; export class ApimService extends BaseService { private apimClient: ApiManagementClient; private functionAppService: FunctionAppService; - private config: any; - - public constructor(serverless: Serverless, options: Serverless.Options) { + private config: ApiManagementConfig; + + public constructor(serverless: Serverless, options?: Serverless.Options) { super(serverless, options); this.config = this.serverless.service.provider["apim"]; + if (!this.config) { + return; + } + + if (!this.config.backend) { + this.config.backend = {} as any; + } + this.apimClient = new ApiManagementClient(this.credentials, this.subscriptionId); this.functionAppService = new FunctionAppService(serverless, options); } + /** + * Gets the configured APIM resource + */ + public async get(): Promise { + if (!(this.config && this.config.name)) { + return null; + } + + try { + return await this.apimClient.apiManagementService.get(this.resourceGroup, this.config.name); + } catch (err) { + return null; + } + } + + public async getApi(): Promise { + if (!(this.config && this.config.api && this.config.api.name)) { + return null; + } + + try { + return await this.apimClient.api.get(this.resourceGroup, this.config.name, this.config.api.name); + } catch (err) { + return null; + } + } + /** * Deploys the APIM top level api */ public async deployApi() { + if (!(this.config && this.config.name)) { + return null; + } + const functionApp = await this.functionAppService.get(); - await this.ensureApi(); + const api = await this.ensureApi(); await this.ensureFunctionAppKeys(functionApp); await this.ensureBackend(functionApp); + + return api; } /** * Deploys all the functions of the serverless service to APIM */ - public async deployFunctions() { + public async deployFunctions(service: ApiManagementServiceResource, api: ApiContract) { + Guard.null(service); + Guard.null(api); + + if (!(this.config && this.config.name)) { + return null; + } + this.serverless.cli.log("-> Deploying API Operations"); const deployApiTasks = this.serverless.service .getAllFunctions() - .map((functionName) => this.deployFunction({ function: functionName })); + .map((functionName) => this.deployFunction(service, api, { function: functionName })); return Promise.all(deployApiTasks); } /** * Deploys the specified serverless function to APIM - * @param options + * @param options */ - public async deployFunction(options) { + public async deployFunction(service: ApiManagementServiceResource, api: ApiContract, options) { + Guard.null(service); + Guard.null(api); + Guard.null(options); + const functionConfig = this.serverless.service["functions"][options.function]; - if (!functionConfig.apim) { + if (!(functionConfig && functionConfig.apim)) { return; } const tasks = functionConfig.apim.operations.map((operation) => { - return this.deployOperation({ + return this.deployOperation(service, api, { function: options.function, - operation: operation + operation, }); }); @@ -67,22 +126,21 @@ export class ApimService extends BaseService { /** * Deploys the APIM API referenced by the serverless service */ - private async ensureApi() { - this.serverless.cli.log("-> Deploying API") + private async ensureApi(): Promise { + this.serverless.cli.log("-> Deploying API"); try { - await this.apimClient.api.createOrUpdate(this.resourceGroup, this.config.resourceId, this.config.name, { + return await this.apimClient.api.createOrUpdate(this.resourceGroup, this.config.name, this.config.api.name, { isCurrent: true, - displayName: this.config.displayName, - description: this.config.description, - path: this.config.urlSuffix, - protocols: [ - this.config.urlScheme - ] + subscriptionRequired: this.config.api.subscriptionRequired, + displayName: this.config.api.displayName, + description: this.config.api.description, + path: this.config.api.path, + protocols: this.config.api.protocols, }); } catch (e) { this.serverless.cli.log("Error creating APIM API"); - this.serverless.cli.log(JSON.stringify(e.body, null, 4)); + throw e; } } @@ -90,50 +148,67 @@ export class ApimService extends BaseService { * Deploys the APIM Backend referenced by the serverless service * @param functionAppUrl The host name for the deployed function app */ - private async ensureBackend(functionApp) { - this.serverless.cli.log("-> Deploying API Backend") + private async ensureBackend(functionApp: Site): Promise { + const backendUrl = `https://${functionApp.defaultHostName}/api`; + + this.serverless.cli.log(`-> Deploying API Backend ${functionApp.name} = ${backendUrl}`); try { const functionAppResourceId = `https://management.azure.com${functionApp.id}`; - await this.apimClient.backend.createOrUpdate(this.resourceGroup, this.config.resourceId, this.serviceName, { + return await this.apimClient.backend.createOrUpdate(this.resourceGroup, this.config.name, this.serviceName, { credentials: { header: { "x-functions-key": [`{{${this.serviceName}-key}}`], - } + }, }, - description: this.serviceName, - protocol: "http", + title: this.config.backend.title || functionApp.name, + tls: this.config.backend.tls, + proxy: this.config.backend.proxy, + description: this.config.backend.description, + protocol: this.config.backend.protocol || "http", resourceId: functionAppResourceId, - url: `https://${functionApp.defaultHostName}/api` + url: backendUrl, }); } catch (e) { this.serverless.cli.log("Error creating APIM Backend"); - this.serverless.cli.log(JSON.stringify(e.body, null, 4)); + throw e; } } /** * Deploys a single APIM api operation for the specified function - * @param serverless The serverless framework + * @param serverless The serverless framework * @param options The plugin options */ - private async deployOperation(options) { - this.serverless.cli.log(`--> Deploying API operation ${options.function}`); - + private async deployOperation( + service: ApiManagementServiceResource, + api: ApiContract, + options: ApiOperationOptions, + ): Promise { try { const client = new ApiManagementClient(this.credentials, this.subscriptionId); - const operationConfig = { + const operationConfig: OperationContract = { displayName: options.operation.displayName || options.function, description: options.operation.description || "", - urlTemplate: options.operation.path, method: options.operation.method, + urlTemplate: options.operation.urlTemplate, templateParameters: options.operation.templateParameters || [], responses: options.operation.responses || [], }; - await client.apiOperation.createOrUpdate(this.resourceGroup, this.config.resourceId, this.config.name, options.function, operationConfig); - await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.config.resourceId, this.config.name, options.function, { + const operationUrl = `${service.gatewayUrl}/${api.path}${operationConfig.urlTemplate}`; + this.serverless.cli.log(`--> Deploying API operation ${options.function}: ${operationConfig.method.toUpperCase()} ${operationUrl}`); + + const operation = await client.apiOperation.createOrUpdate( + this.resourceGroup, + this.config.name, + this.config.api.name, + options.function, + operationConfig, + ); + + await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.config.name, this.config.api.name, options.function, { format: "rawxml", value: ` @@ -150,11 +225,13 @@ export class ApimService extends BaseService { - ` + `, }); + + return operation; } catch (e) { this.serverless.cli.log(`Error deploying API operation ${options.function}`); - this.serverless.cli.log(JSON.stringify(e, null, 4)); + this.serverless.cli.log(JSON.stringify(e.body, null, 4)); } } @@ -162,20 +239,20 @@ export class ApimService extends BaseService { * Gets the master key for the function app and stores a reference in the APIM instance * @param functionAppUrl The host name for the Azure function app */ - private async ensureFunctionAppKeys(functionApp) { - this.serverless.cli.log("-> Deploying API keys") + private async ensureFunctionAppKeys(functionApp): Promise { + this.serverless.cli.log("-> Deploying API keys"); try { const masterKey = await this.functionAppService.getMasterKey(functionApp); const keyName = `${this.serviceName}-key`; - this.apimClient.property.createOrUpdate(this.resourceGroup, this.config.resourceId, keyName, { + return await this.apimClient.property.createOrUpdate(this.resourceGroup, this.config.name, keyName, { displayName: keyName, secret: true, - value: masterKey + value: masterKey, }); } catch (e) { this.serverless.cli.log("Error creating APIM Property"); - this.serverless.cli.log(JSON.stringify(e, null, 4)); + throw e; } } -} +} \ No newline at end of file diff --git a/src/services/baseService.test.ts b/src/services/baseService.test.ts new file mode 100644 index 00000000..d1107891 --- /dev/null +++ b/src/services/baseService.test.ts @@ -0,0 +1,88 @@ +import Serverless from "serverless"; +import { BaseService } from "./baseService"; +import { MockFactory } from "../test/mockFactory"; +jest.mock("axios", () => jest.fn()); +import axios from "axios"; + +class TestService extends BaseService { + public constructor(serverless: Serverless, options?: Serverless.Options) { + super(serverless, options); + } + + public axiosRequest(method, url, options) { + return this.sendApiRequest(method, url, options); + } + + public postFile(requestOptions, filePath) { + return this.sendFile(requestOptions, filePath); + } + + public getProperties() { + return { + baseUrl: this.baseUrl, + serviceName: this.serviceName, + credentials: this.credentials, + subscriptionId: this.subscriptionId, + resourceGroup: this.resourceGroup, + deploymentName: this.deploymentName, + }; + } +} + +describe("Base Service", () => { + let service: TestService; + let sls: Serverless; + + const slsConfig = { + service: "My custom service", + provider: { + resourceGroup: "My-Resource-Group", + deploymentName: "My-Deployment", + }, + }; + + beforeEach(() => { + sls = MockFactory.createTestServerless(); + sls.variables["azureCredentials"] = MockFactory.createTestAzureCredentials(); + sls.variables["subscriptionId"] = "ABC123"; + Object.assign(sls.service, slsConfig); + service = new TestService(sls); + }); + + it("Initializes common service properties", () => { + const props = service.getProperties(); + expect(props.baseUrl).toEqual("https://management.azure.com"); + expect(props.credentials).toEqual(sls.variables["azureCredentials"]); + expect(props.subscriptionId).toEqual(sls.variables["subscriptionId"]); + expect(props.serviceName).toEqual(slsConfig.service); + expect(props.resourceGroup).toEqual(slsConfig.provider.resourceGroup); + expect(props.deploymentName).toEqual(slsConfig.provider.deploymentName); + }); + + it("Fails if credentials have not been set in serverless config", () => { + sls.variables["azureCredentials"] = null; + expect(() => new TestService(sls)).toThrow() + }); + + it("Makes HTTP request via axios", async () => { + const method = "GET"; + const url = "https://api.service.com/foo/bar"; + const axiosMock = (axios as any) as jest.Mock; + const options = { + headers: { + "x-custom": "value", + } + }; + await service.axiosRequest(method, url, options); + + const expectedHeaders = { + ...options.headers, + Authorization: expect.any(String), + } + + expect(axiosMock).toBeCalledWith(url, { + method, + headers: expectedHeaders, + }); + }) +}); \ No newline at end of file diff --git a/src/services/baseService.ts b/src/services/baseService.ts index b920b6d6..a1fa90d9 100644 --- a/src/services/baseService.ts +++ b/src/services/baseService.ts @@ -1,7 +1,8 @@ -import Serverless from "serverless"; import axios from "axios"; -import request from "request" import fs from "fs"; +import request from "request"; +import Serverless from "serverless"; +import { Guard } from "../shared/guard"; export abstract class BaseService { protected baseUrl: string; @@ -11,7 +12,9 @@ export abstract class BaseService { protected resourceGroup: string; protected deploymentName: string; - public constructor(protected serverless: Serverless, protected options: Serverless.Options) { + protected constructor(protected serverless: Serverless, protected options?: Serverless.Options, authenticate = true) { + Guard.null(serverless); + this.baseUrl = "https://management.azure.com"; this.serviceName = serverless.service["service"]; this.credentials = serverless.variables["azureCredentials"]; @@ -19,14 +22,14 @@ export abstract class BaseService { this.resourceGroup = serverless.service.provider["resourceGroup"] || `${this.serviceName}-rg`; this.deploymentName = serverless.service.provider["deploymentName"] || `${this.resourceGroup}-deployment`; - if (!this.credentials) { + if (!this.credentials && authenticate) { throw new Error(`Azure Credentials has not been set in ${this.constructor.name}`); } } protected async sendApiRequest(method: string, relativeUrl: string, options: any = {}) { const defaultHeaders = { - "Authorization": `Bearer ${this.credentials.tokenCache._entries[0].accessToken}` + Authorization: `Bearer ${this.credentials.tokenCache._entries[0].accessToken}`, }; const allHeaders = { @@ -36,7 +39,7 @@ export abstract class BaseService { const requestOptions = { ...options, - method: method, + method, headers: allHeaders, }; @@ -67,10 +70,10 @@ export abstract class BaseService { } /** - * Uploads the specified file via HTTP request - * @param requestOptions The HTTP request options - * @param filePath The local file path - */ + * Uploads the specified file via HTTP request + * @param requestOptions The HTTP request options + * @param filePath The local file path + */ protected sendFile(requestOptions, filePath) { return new Promise((resolve, reject) => { fs.createReadStream(filePath) @@ -83,4 +86,12 @@ export abstract class BaseService { })); }); } -} \ No newline at end of file + + protected log(message: string) { + this.serverless.cli.log(message); + } + + protected slsFunctions() { + return this.serverless.service["functions"]; + } +} diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts index ef02e69e..145d165e 100644 --- a/src/services/functionAppService.ts +++ b/src/services/functionAppService.ts @@ -7,7 +7,8 @@ import jsonpath from "jsonpath"; import _ from "lodash"; import Serverless from "serverless"; import { BaseService } from "./baseService"; -import { constants } from "../config"; +import { FunctionAppHttpTriggerConfig } from "../models/functionApp"; +import { Site, FunctionEnvelope } from "@azure/arm-appservice/esm/models"; export class FunctionAppService extends BaseService { private resourceClient: ResourceManagementClient; @@ -20,7 +21,7 @@ export class FunctionAppService extends BaseService { this.webClient = new WebSiteManagementClient(this.credentials, this.subscriptionId); } - public async get() { + public async get(): Promise { const response: any = await this.webClient.webApps.get(this.resourceGroup, this.serviceName); if (response.error && (response.error.code === "ResourceNotFound" || response.error.code === "ResourceGroupNotFound")) { return null; @@ -44,19 +45,21 @@ export class FunctionAppService extends BaseService { return response.data.value; } - public async deleteFunction(functionName) { + public async deleteFunction(functionApp: Site, functionName: string) { this.serverless.cli.log(`-> Deleting function: ${functionName}`); - return await this.webClient.webApps.deleteFunction(this.resourceGroup, this.serviceName, functionName); + const deleteFunctionUrl = `${this.baseUrl}${functionApp.id}/functions/${functionName}?api-version=2016-08-01`; + + return await this.sendApiRequest("DELETE", deleteFunctionUrl); } - public async syncTriggers(functionApp) { + public async syncTriggers(functionApp: Site) { this.serverless.cli.log("Syncing function triggers"); const syncTriggersUrl = `${this.baseUrl}${functionApp.id}/syncfunctiontriggers?api-version=2016-08-01`; await this.sendApiRequest("POST", syncTriggersUrl); } - public async cleanUp(functionApp) { + public async cleanUp(functionApp: Site) { this.serverless.cli.log("Cleaning up existing functions"); const deleteTasks = []; @@ -65,58 +68,37 @@ export class FunctionAppService extends BaseService { deployedFunctions.forEach((func) => { if (serviceFunctions.includes(func.name)) { - this.serverless.cli.log(`-> Deleting function '${func.name}'`); - deleteTasks.push(this.deleteFunction(func.name)); + deleteTasks.push(this.deleteFunction(functionApp, func.name)); } }); return await Promise.all(deleteTasks); } - public async listFunctions(functionApp) { + public async listFunctions(functionApp: Site): Promise { const getTokenUrl = `${this.baseUrl}${functionApp.id}/functions?api-version=2016-08-01`; const response = await this.sendApiRequest("GET", getTokenUrl); - return response.data.value || []; - } + if (response.status !== 200) { + return []; + } - public async uploadFunctions(functionApp) { - await this.zipDeploy(functionApp); + return response.data.value.map((functionConfig) => functionConfig.properties); } - private async zipDeploy(functionApp) { - const functionAppName = functionApp.name; - this.serverless.cli.log(`Deploying zip file to function app: ${functionAppName}`); + public async getFunction(functionApp: Site, functionName: string): Promise { + const getFunctionUrl = `${this.baseUrl}${functionApp.id}/functions/${functionName}?api-version=2016-08-01`; + const response = await this.sendApiRequest("GET", getFunctionUrl); - // Upload function artifact if it exists, otherwise the full service is handled in 'uploadFunctions' method - const functionZipFile = this.serverless.service["artifact"]; - if (!functionZipFile) { - throw new Error("No zip file found for function app"); + if (response.status !== 200) { + return null; } - this.serverless.cli.log(`-> Uploading ${functionZipFile}`); - - const uploadUrl = `https://${functionAppName}${constants.scmDomain}${constants.scmZipDeployApiPath}`; - this.serverless.cli.log(`-> Upload url: ${uploadUrl}`); - - // https://github.com/projectkudu/kudu/wiki/Deploying-from-a-zip-file-or-url - const requestOptions = { - method: "POST", - uri: uploadUrl, - json: true, - headers: { - Authorization: `Bearer ${this.credentials.tokenCache._entries[0].accessToken}`, - Accept: "*/*", - ContentType: "application/octet-stream", - } - }; + return response.data.properties; + } - try { - await this.sendFile(requestOptions, functionZipFile); - this.serverless.cli.log("-> Function package uploaded successfully"); - } catch (e) { - throw new Error(`Error uploading zip file:\n --> ${e}`); - } + public async uploadFunctions(functionApp: Site): Promise { + await this.zipDeploy(functionApp); } /** @@ -191,7 +173,73 @@ export class FunctionAppService extends BaseService { return await this.get(); } - private async runKuduCommand(functionApp, command) { + private async zipDeploy(functionApp) { + const functionAppName = functionApp.name; + const scmDomain = functionApp.enabledHostNames[0]; + + this.serverless.cli.log(`Deploying zip file to function app: ${functionAppName}`); + + // Upload function artifact if it exists, otherwise the full service is handled in 'uploadFunctions' method + const functionZipFile = this.serverless.service["artifact"]; + if (!functionZipFile) { + throw new Error("No zip file found for function app"); + } + + this.serverless.cli.log(`-> Deploying service package @ ${functionZipFile}`); + + // https://github.com/projectkudu/kudu/wiki/Deploying-from-a-zip-file-or-url + const requestOptions = { + method: "POST", + uri: `https://${scmDomain}/api/zipdeploy/`, + json: true, + headers: { + Authorization: `Bearer ${this.credentials.tokenCache._entries[0].accessToken}`, + Accept: "*/*", + ContentType: "application/octet-stream", + } + }; + + await this.sendFile(requestOptions, functionZipFile); + this.serverless.cli.log("-> Function package uploaded successfully"); + const serverlessFunctions = this.serverless.service.getAllFunctions(); + const deployedFunctions = await this.listFunctions(functionApp); + + this.serverless.cli.log("Deployed serverless functions:") + deployedFunctions.forEach((functionConfig) => { + // List functions that are part of the serverless yaml config + if (serverlessFunctions.includes(functionConfig.name)) { + const httpConfig = this.getFunctionHttpTriggerConfig(functionApp, functionConfig); + + if (httpConfig) { + const method = httpConfig.methods[0].toUpperCase(); + this.serverless.cli.log(`-> ${functionConfig.name}: ${method} ${httpConfig.url}`); + } + } + }); + } + + private getFunctionHttpTriggerConfig(functionApp: Site, functionConfig: FunctionEnvelope): FunctionAppHttpTriggerConfig { + const httpTrigger = functionConfig.config.bindings.find((binding) => { + return binding.type === "httpTrigger"; + }); + + if (!httpTrigger) { + return; + } + + const route = httpTrigger.route || functionConfig.name; + const url = `${functionApp.defaultHostName}/api/${route}`; + + return { + authLevel: httpTrigger.authLevel, + methods: httpTrigger.methods || ["*"], + url: url, + route: httpTrigger.route, + name: functionConfig.name, + }; + } + + private async runKuduCommand(functionApp: Site, command: string) { this.serverless.cli.log(`-> Running Kudu command ${command}...`); const scmDomain = functionApp.enabledHostNames[0]; @@ -217,7 +265,7 @@ export class FunctionAppService extends BaseService { /** * Gets a short lived admin token used to retrieve function keys */ - private async getAuthKey(functionApp) { + private async getAuthKey(functionApp: Site) { const adminTokenUrl = `${this.baseUrl}${functionApp.id}/functions/admin/token?api-version=2016-08-01`; const response = await this.sendApiRequest("GET", adminTokenUrl); diff --git a/src/services/loginService.test.ts b/src/services/loginService.test.ts index 2e7d8c56..2e8d23c9 100644 --- a/src/services/loginService.test.ts +++ b/src/services/loginService.test.ts @@ -5,9 +5,9 @@ import open from "open"; jest.mock("@azure/ms-rest-nodeauth") import { interactiveLoginWithAuthResponse, loginWithServicePrincipalSecretWithAuthResponse } from "@azure/ms-rest-nodeauth"; -describe('Login Service', () => { +describe("Login Service", () => { - it('logs in interactively', async () => { + it("logs in interactively", async () => { // Ensure env variables are not set delete process.env.azureSubId; delete process.env.azureServicePrincipalClientId; @@ -19,18 +19,18 @@ describe('Login Service', () => { expect(interactiveLoginWithAuthResponse).toBeCalled(); }); - it('logs in with a service principal', async () => { + it("logs in with a service principal", async () => { // Set environment variables - process.env.azureSubId = 'azureSubId'; - process.env.azureServicePrincipalClientId = 'azureServicePrincipalClientId'; - process.env.azureServicePrincipalPassword = 'azureServicePrincipalPassword'; - process.env.azureServicePrincipalTenantId = 'azureServicePrincipalTenantId'; + process.env.azureSubId = "azureSubId"; + process.env.azureServicePrincipalClientId = "azureServicePrincipalClientId"; + process.env.azureServicePrincipalPassword = "azureServicePrincipalPassword"; + process.env.azureServicePrincipalTenantId = "azureServicePrincipalTenantId"; await AzureLoginService.login(); expect(loginWithServicePrincipalSecretWithAuthResponse).toBeCalledWith( - 'azureServicePrincipalClientId', - 'azureServicePrincipalPassword', - 'azureServicePrincipalTenantId' + "azureServicePrincipalClientId", + "azureServicePrincipalPassword", + "azureServicePrincipalTenantId" ); }); }); \ No newline at end of file diff --git a/src/services/loginService.ts b/src/services/loginService.ts index 6eda1e9f..b6afa60b 100644 --- a/src/services/loginService.ts +++ b/src/services/loginService.ts @@ -1,9 +1,12 @@ -import { interactiveLoginWithAuthResponse, loginWithServicePrincipalSecretWithAuthResponse } from "@azure/ms-rest-nodeauth"; import open from "open"; +import { + interactiveLoginWithAuthResponse, + loginWithServicePrincipalSecretWithAuthResponse, + AuthResponse, +} from "@azure/ms-rest-nodeauth"; export class AzureLoginService { - - public static async login() { + public static async login(): Promise { const subscriptionId = process.env.azureSubId; const clientId = process.env.azureServicePrincipalClientId; const secret = process.env.azureServicePrincipalPassword; @@ -16,12 +19,12 @@ export class AzureLoginService { } } - public static async interactiveLogin() { + public static async interactiveLogin(): Promise { await open("https://microsoft.com/devicelogin"); return await interactiveLoginWithAuthResponse(); } - public static async servicePrincipalLogin(clientId: string, secret: string, tenantId: string) { + public static async servicePrincipalLogin(clientId: string, secret: string, tenantId: string): Promise { return loginWithServicePrincipalSecretWithAuthResponse(clientId, secret, tenantId); } -} \ No newline at end of file +} diff --git a/src/services/offlineService.test.ts b/src/services/offlineService.test.ts new file mode 100644 index 00000000..3fd48fb9 --- /dev/null +++ b/src/services/offlineService.test.ts @@ -0,0 +1,28 @@ +import path from "path"; +import Serverless from "serverless"; +import { MockFactory } from "../test/mockFactory"; +import { OfflineService } from "./offlineService"; + +describe("Offline Service", () => { + + function createService(sls?: Serverless) { + return new OfflineService( + sls || MockFactory.createTestServerless(), + MockFactory.createTestServerlessOptions(), + ) + } + + it("builds required files for offline execution", async () => { + const sls = MockFactory.createTestServerless(); + const service = createService(sls); + await service.build(); + const calls = (sls.utils.writeFileSync as any).mock.calls; + const functionNames = sls.service.getAllFunctions(); + const expectedFunctionJson = MockFactory.createTestBindingsObject(); + for (let i = 0; i < calls.length; i++) { + const name = functionNames[i]; + expect(calls[i][0]).toEqual(`${name}${path.sep}function.json`) + expect(JSON.parse(calls[i][1])).toEqual(expectedFunctionJson); + } + }); +}); \ No newline at end of file diff --git a/src/services/offlineService.ts b/src/services/offlineService.ts new file mode 100644 index 00000000..ba929456 --- /dev/null +++ b/src/services/offlineService.ts @@ -0,0 +1,29 @@ +import Serverless from "serverless"; +import { BindingUtils } from "../shared/bindings"; +import { Utils } from "../shared/utils"; +import { BaseService } from "./baseService"; + +export class OfflineService extends BaseService { + + public constructor(serverless: Serverless, options: Serverless.Options) { + super(serverless, options, false); + } + + public async build() { + this.log("Building offline service"); + const createEventsPromises = this.serverless.service.getAllFunctions() + .map((functionName) => { + this.log(`Building function ${functionName}`); + const metaData = Utils.getFunctionMetaData(functionName, this.serverless); + return BindingUtils.createEventsBindings(this.serverless, functionName, metaData); + }); + await Promise.all(createEventsPromises); + this.log("Finished building offline service"); + } + + public start() { + this.log("Run 'npm start' or 'func host start' to run service locally"); + this.log("Make sure you have Azure Functions Core Tools installed"); + this.log("If not installed run 'npm i azure-functions-core-tools -g") + } +} \ No newline at end of file diff --git a/src/services/resourceService.test.ts b/src/services/resourceService.test.ts index fc8777a4..3ce6df36 100644 --- a/src/services/resourceService.test.ts +++ b/src/services/resourceService.test.ts @@ -1,11 +1,11 @@ -import { MockFactory } from '../test/mockFactory'; -import { ResourceService } from './resourceService'; +import { MockFactory } from "../test/mockFactory"; +import { ResourceService } from "./resourceService"; -jest.mock('@azure/arm-resources') -import { ResourceManagementClient } from '@azure/arm-resources'; +jest.mock("@azure/arm-resources") +import { ResourceManagementClient } from "@azure/arm-resources"; -describe('Resource Service', () => { +describe("Resource Service", () => { beforeAll(() => { ResourceManagementClient.prototype.resourceGroups = { @@ -18,26 +18,26 @@ describe('Resource Service', () => { } as any; }); - it('throws error with empty credentials', () => { + it("throws error with empty credentials", () => { const sls = MockFactory.createTestServerless(); - delete sls.variables['azureCredentials'] + delete sls.variables["azureCredentials"] const options = MockFactory.createTestServerlessOptions(); - expect(() => new ResourceService(sls, options)).toThrowError('Azure Credentials has not been set in ResourceService') + expect(() => new ResourceService(sls, options)).toThrowError("Azure Credentials has not been set in ResourceService") }); - it('initializes a resource service', () => { + it("initializes a resource service", () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); expect(() => new ResourceService(sls, options)).not.toThrowError(); }); - it('deploys a resource group', () => { + it("deploys a resource group", () => { const sls = MockFactory.createTestServerless(); - const resourceGroup = 'myResourceGroup' - const location = 'West Us'; - sls.service.provider['resourceGroup'] = resourceGroup - sls.service.provider['location'] = location; - sls.variables['azureCredentials'] = 'fake credentials' + const resourceGroup = "myResourceGroup" + const location = "West Us"; + sls.service.provider["resourceGroup"] = resourceGroup + sls.service.provider["location"] = location; + sls.variables["azureCredentials"] = "fake credentials" const options = MockFactory.createTestServerlessOptions(); const service = new ResourceService(sls, options); service.deployResourceGroup(); @@ -45,13 +45,13 @@ describe('Resource Service', () => { .toBeCalledWith(resourceGroup, { location }); }); - it('deletes a deployment', () => { + it("deletes a deployment", () => { const sls = MockFactory.createTestServerless(); - const resourceGroup = 'myResourceGroup'; - const deploymentName = 'myDeployment'; - sls.service.provider['resourceGroup'] = resourceGroup - sls.service.provider['deploymentName'] = deploymentName; - sls.variables['azureCredentials'] = 'fake credentials' + const resourceGroup = "myResourceGroup"; + const deploymentName = "myDeployment"; + sls.service.provider["resourceGroup"] = resourceGroup + sls.service.provider["deploymentName"] = deploymentName; + sls.variables["azureCredentials"] = "fake credentials" const options = MockFactory.createTestServerlessOptions(); const service = new ResourceService(sls, options); service.deleteDeployment(); @@ -59,11 +59,11 @@ describe('Resource Service', () => { .toBeCalledWith(resourceGroup, deploymentName); }); - it('deletes a resource group', () => { + it("deletes a resource group", () => { const sls = MockFactory.createTestServerless(); - const resourceGroup = 'myResourceGroup'; - sls.service.provider['resourceGroup'] = resourceGroup - sls.variables['azureCredentials'] = 'fake credentials' + const resourceGroup = "myResourceGroup"; + sls.service.provider["resourceGroup"] = resourceGroup + sls.variables["azureCredentials"] = "fake credentials" const options = MockFactory.createTestServerlessOptions(); const service = new ResourceService(sls, options); service.deleteResourceGroup(); diff --git a/src/shared/bindings.ts b/src/shared/bindings.ts index e53314b6..69ceabfa 100644 --- a/src/shared/bindings.ts +++ b/src/shared/bindings.ts @@ -1,8 +1,7 @@ -import { writeFileSync } from "fs"; import { join } from "path"; import Serverless from "serverless"; -import { FunctionMetadata } from "./utils"; import { constants } from "./constants"; +import { FunctionMetadata } from "./utils"; const bindingsJson = require("./bindings.json"); // eslint-disable-line @typescript-eslint/no-var-requires @@ -37,11 +36,14 @@ export class BindingUtils { }; } - public static createEventsBindings(servicePath: string, functionName: string, functionMetadata: FunctionMetadata): Promise { + public static createEventsBindings(serverless: Serverless, functionName: string, functionMetadata: FunctionMetadata): Promise { const functionJSON = functionMetadata.params.functionsJson; functionJSON.entryPoint = functionMetadata.entryPoint; functionJSON.scriptFile = functionMetadata.handlerPath; - writeFileSync(join(servicePath, functionName, "function.json"), JSON.stringify(functionJSON, null, 4)); + serverless.utils.writeFileSync( + join(serverless.config.servicePath, functionName, "function.json"), + JSON.stringify(functionJSON, null, 4) + ); return Promise.resolve(); } diff --git a/src/shared/guard.test.ts b/src/shared/guard.test.ts new file mode 100644 index 00000000..0962b4b3 --- /dev/null +++ b/src/shared/guard.test.ts @@ -0,0 +1,61 @@ +import { Guard } from "./guard"; + +describe("Guard", () => { + function methodWithRequiredName(name: string) { + Guard.empty(name); + } + + function methodWithRequiredNameWithParam(name: string) { + Guard.empty(name, "name", "Name is required"); + } + + function methodWithRequiredObject(options: any) { + Guard.null(options); + } + + function methodWithRequiredExpression(value: number) { + Guard.expression(value, (num) => num > 0 && num < 100); + } + + describe("empty", () => { + it("throws error on null value", () => { + expect(() => methodWithRequiredName(null)).toThrowError(); + }); + + it("throws error on empty value", () => { + expect(() => methodWithRequiredName("")).toThrowError(); + }); + + it("throw error on whitespace", () => { + expect(() => methodWithRequiredName(" ")).toThrowError(); + }); + + it("does not throw error on valid value", () => { + expect(() => methodWithRequiredName("valid")).not.toThrowError(); + }); + + it("throws specific error message", () => { + expect(() => methodWithRequiredNameWithParam(null)).toThrowError("Name is required"); + }); + }); + + describe("null", () => { + it("throws error on null value", () => { + expect(() => methodWithRequiredObject(null)).toThrowError(); + }); + + it("does not throw error on valid value", () => { + expect(() => methodWithRequiredObject({})).not.toThrowError(); + }); + }); + + describe("expression", () => { + it("throws error on invalide value", () => { + expect(() => methodWithRequiredExpression(0)).toThrowError(); + }); + + it("does not throw error on valid value", () => { + expect(() => methodWithRequiredExpression(1)).not.toThrowError(); + }); + }); +}); diff --git a/src/shared/guard.ts b/src/shared/guard.ts new file mode 100644 index 00000000..ae46c1b0 --- /dev/null +++ b/src/shared/guard.ts @@ -0,0 +1,41 @@ +export class Guard { + /** + * Validates the string express is not null or empty, otherwise throws an exception + * @param value - The value to validate + * @param paramName - The name of the parameter to validate + * @param message - The error message to return on invalid value + */ + public static empty(value: string, paramName?: string, message?: string) { + if ((!!value === false || value.trim().length === 0)) { + message = message || (`'${paramName || "value"}' cannot be null or empty`); + throw new Error(message); + } + } + + /** + * Validates the value is not null, otherwise throw an exception + * @param value - The value to validate + * @param paramName - The name of the parameter to validate + * @param message - The error message to return on invalid value + */ + public static null(value: any, paramName?: string, message?: string) { + if ((!!value === false)) { + message = message || (`'${paramName || "value"}' cannot be null or undefined`); + throw new Error(message); + } + } + + /** + * Validates the value meets the specified expectation, otherwise throws an exception + * @param value - The value to validate + * @param predicate - The predicate used for validation + * @param paramName - The name of the parameter to validate + * @param message - The error message to return on invalid value + */ + public static expression(value: T, predicate: (value: T) => boolean, paramName?: string, message?: string) { + if (!!value === false || !predicate(value)) { + message = message || (`'${paramName || "value"}' is not a valid value`); + throw new Error(message); + } + } +} diff --git a/src/shared/utils.ts b/src/shared/utils.ts index ee4e3cfd..3e5bed53 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -9,7 +9,7 @@ export interface FunctionMetadata { } export class Utils { - public static getFunctionMetaData(functionName: string, serverless): FunctionMetadata { + public static getFunctionMetaData(functionName: string, serverless: Serverless): FunctionMetadata { const bindings = []; let bindingSettingsNames = []; let bindingSettings = []; @@ -18,97 +18,99 @@ export class Utils { const functionsJson = { disabled: false, bindings: [] }; const functionObject = serverless.service.getFunction(functionName); const handler = functionObject.handler; - const events = functionObject.events; + const events = functionObject["events"]; const params: any = { functionJson: null }; - + const parsedBindings = BindingUtils.getBindingsMetaData(serverless); - + const bindingTypes = parsedBindings.bindingTypes; const bindingDisplayNames = parsedBindings.bindingDisplayNames; - + for (let eventsIndex = 0; eventsIndex < events.length; eventsIndex++) { - bindingType = Object.keys(functionObject.events[eventsIndex])[0]; - + bindingType = Object.keys(functionObject["events"][eventsIndex])[0]; + if (eventsIndex === 0) { bindingType += constants.trigger; } - + const index = bindingTypes.indexOf(bindingType); - + if (index < 0) { throw new Error(`Binding ${bindingType} not supported`); } - + serverless.cli.log(`Building binding for function: ${functionName} event: ${bindingType}`); - + bindingUserSettings = {}; const azureSettings = events[eventsIndex][constants.xAzureSettings]; let bindingTypeIndex = bindingTypes.indexOf(bindingType); const bindingUserSettingsMetaData = BindingUtils.getBindingUserSettingsMetaData(azureSettings, bindingType, bindingTypeIndex, bindingDisplayNames); - + bindingTypeIndex = bindingUserSettingsMetaData.index; bindingUserSettings = bindingUserSettingsMetaData.userSettings; - - if (bindingType.includes(constants.queue) && functionObject.events[eventsIndex].queue) { - bindingUserSettings[constants.queueName] = functionObject.events[eventsIndex].queue; + + if (bindingType.includes(constants.queue) && functionObject["events"][eventsIndex].queue) { + bindingUserSettings[constants.queueName] = functionObject["events"][eventsIndex].queue; } - + if (bindingTypeIndex < 0) { throw new Error("Binding not supported"); } - + bindingSettings = parsedBindings.bindingSettings[bindingTypeIndex]; bindingSettingsNames = parsedBindings.bindingSettingsNames[bindingTypeIndex]; - + if (azureSettings) { for (let azureSettingKeyIndex = 0; azureSettingKeyIndex < Object.keys(azureSettings).length; azureSettingKeyIndex++) { const key = Object.keys(azureSettings)[azureSettingKeyIndex]; - + if (bindingSettingsNames.indexOf(key) >= 0) { bindingUserSettings[key] = azureSettings[key]; } } } - + bindings.push(BindingUtils.getBinding(bindingType, bindingSettings, bindingUserSettings)); } - + if (bindingType === constants.httpTrigger) { bindings.push(BindingUtils.getHttpOutBinding(bindingUserSettings)); } - + functionsJson.bindings = bindings; params.functionsJson = functionsJson; - + const entryPointAndHandlerPath = Utils.getEntryPointAndHandlerPath(handler); - if (functionObject.scriptFile) { - entryPointAndHandlerPath.handlerPath = functionObject.scriptFile; + if (functionObject["scriptFile"]) { + entryPointAndHandlerPath.handlerPath = functionObject["scriptFile"]; } const metaData = { entryPoint: entryPointAndHandlerPath[constants.entryPoint], handlerPath: entryPointAndHandlerPath.handlerPath, params: params }; - + return metaData; } - public static getEntryPointAndHandlerPath(handler) { + public static getEntryPointAndHandlerPath(handler: string) { let handlerPath = "handler.js"; let entryPoint = handler; const handlerSplit = handler.split("."); - + const slashIndex = handler.lastIndexOf("/"); + if (handlerSplit.length > 1) { entryPoint = handlerSplit[handlerSplit.length - 1]; - handlerPath = `${handler.substring(0, handler.lastIndexOf("."))}.js`; + handlerPath = `${handler.substring(slashIndex + 1, handler.lastIndexOf("."))}.js`; } + const metaData = { entryPoint: entryPoint, handlerPath: handlerPath }; - + return metaData; } diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index 5e3ad1a1..35e99c3c 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -1,9 +1,14 @@ +import { ApiContract, ApiManagementServiceResource } from "@azure/arm-apimanagement/esm/models"; +import { Site } from "@azure/arm-appservice/esm/models"; +import { HttpHeaders, HttpOperationResponse, HttpResponse, WebResource } from "@azure/ms-rest-js"; import { AuthResponse, LinkedSubscription, TokenCredentialsBase } from "@azure/ms-rest-nodeauth"; +import { TokenClientCredentials, TokenResponse } from "@azure/ms-rest-nodeauth/dist/lib/credentials/tokenClientCredentials"; +import { AxiosRequestConfig, AxiosResponse } from "axios"; import yaml from "js-yaml"; import Serverless from "serverless"; import Service from "serverless/classes/Service"; -import Utils = require("serverless/classes/Utils"); -import PluginManager = require("serverless/classes/PluginManager"); +import Utils from "serverless/classes/Utils"; +import PluginManager from "serverless/lib/classes/PluginManager"; function getAttribute(object: any, prop: string, defaultValue: any): any { if (object && object[prop]) { @@ -15,14 +20,45 @@ function getAttribute(object: any, prop: string, defaultValue: any): any { export class MockFactory { public static createTestServerless(config?: any): Serverless { const sls = new Serverless(config); - sls.service = getAttribute(config, "service", MockFactory.createTestService()); sls.utils = getAttribute(config, "utils", MockFactory.createTestUtils()); sls.cli = getAttribute(config, "cli", MockFactory.createTestCli()); sls.pluginManager = getAttribute(config, "pluginManager", MockFactory.createTestPluginManager()); sls.variables = getAttribute(config, "variables", MockFactory.createTestVariables()); + sls.service = getAttribute(config, "service", MockFactory.createTestService()); + sls.service.getAllFunctions = jest.fn(() => { + return Object.keys(sls.service["functions"]); + }) + sls.service.getAllFunctionsNames = sls.service.getAllFunctions; + sls.service.getServiceName = jest.fn(() => sls.service["service"]); + sls.config.servicePath = ""; return sls; } + public static createTestService(functions?): Service { + if (!functions) { + functions = MockFactory.createTestSlsFunctionConfig() + } + const serviceName = "serviceName"; + return { + getAllFunctions: jest.fn(() => Object.keys(functions)), + getFunction: jest.fn((name: string) => functions[name]), + getAllEventsInFunction: jest.fn(), + getAllFunctionsNames: jest.fn(() => Object.keys(functions)), + getEventInFunction: jest.fn(), + getServiceName: jest.fn(() => serviceName), + load: jest.fn(), + mergeResourceArrays: jest.fn(), + setFunctionNames: jest.fn(), + update: jest.fn(), + validate: jest.fn(), + custom: null, + provider: MockFactory.createTestAzureServiceProvider(), + service: serviceName, + artifact: "app.zip", + functions + } as any as Service; + } + public static createTestServerlessOptions(): Serverless.Options { return { extraServicePath: null, @@ -30,8 +66,23 @@ export class MockFactory { noDeploy: null, region: null, stage: null, - watch: null - } + watch: null, + }; + } + + public static createTestArmSdkResponse(model: any, statusCode: number): Promise { + const response: HttpResponse = { + headers: new HttpHeaders(), + request: null, + status: statusCode, + }; + + const result: R = { + ...model, + _response: response, + }; + + return Promise.resolve(result); } public static createTestAuthResponse(): AuthResponse { @@ -40,11 +91,91 @@ export class MockFactory { subscriptions: [ { id: "azureSubId", + }, + ] as any as LinkedSubscription[], + }; + } + + public static createTestFunctions(functionCount = 3) { + const functions = [] + for (let i = 0; i < functionCount; i++) { + functions.push(MockFactory.createTestFunction(`function${i + 1}`)); + } + return functions; + } + + public static createTestFunction(name: string = "TestFunction") { + return { + properties: { + name, + config: { + bindings: MockFactory.createTestBindings() } - ] as any as LinkedSubscription[] + } } } + public static createTestAzureCredentials(): TokenClientCredentials { + const credentials = { + getToken: jest.fn(() => { + const token: TokenResponse = { + tokenType: "Bearer", + accessToken: "ABC123", + }; + + return Promise.resolve(token); + }), + signRequest: jest.fn((resource) => Promise.resolve(resource)), + }; + + // TODO: Reduce usage on tokenCache._entries[0] + credentials["tokenCache"] = { + _entries: [{ accessToken: "ABC123" }] + }; + + return credentials; + } + + public static createTestAxiosResponse( + config: AxiosRequestConfig, + responseJson: T, + statusCode: number = 200, + ): Promise { + let statusText; + switch (statusCode) { + case 200: + statusText = "OK"; + break; + case 404: + statusText = "NotFound"; + break; + } + + const response: AxiosResponse = { + config, + data: JSON.stringify(responseJson), + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + status: statusCode, + statusText, + }; + + return Promise.resolve(response); + } + + public static createTestAzureClientResponse(responseJson: T, statusCode: number = 200): Promise { + const response: HttpOperationResponse = { + request: new WebResource(), + parsedBody: responseJson, + bodyAsText: JSON.stringify(responseJson), + headers: new HttpHeaders(), + status: statusCode, + }; + + return Promise.resolve(response); + } + public static createTestServerlessYml(asYaml = false, functionMetadata?) { const data = { "provider": { @@ -57,38 +188,42 @@ export class MockFactory { "functions": functionMetadata || MockFactory.createTestFunctionsMetadata(2, false), } return (asYaml) ? yaml.dump(data) : data; - } + } public static createTestFunctionsMetadata(functionCount = 2, wrap = false) { const data = {}; for (let i = 0; i < functionCount; i++) { - const functionName = `function${i+1}`; + const functionName = `function${i + 1}`; data[functionName] = MockFactory.createTestFunctionMetadata() } - return (wrap) ? {"functions": data } : data; + return (wrap) ? { "functions": data } : data; } public static createTestFunctionMetadata() { return { "handler": "index.handler", - "events": [ - { - "http": true, - "x-azure-settings": { - "authLevel": "anonymous" - } - }, - { - "http": true, - "x-azure-settings": { - "direction": "out", - "name": "res" - } - } - ] + "events": MockFactory.createTestFunctionEvents(), } } + public static createTestFunctionEvents() { + return [ + { + "http": true, + "x-azure-settings": { + "authLevel": "anonymous" + } + }, + { + "http": true, + "x-azure-settings": { + "direction": "out", + "name": "res" + } + } + ] + } + public static createTestFunctionApp() { return { id: "App Id", @@ -97,26 +232,6 @@ export class MockFactory { } } - public static createTestService(): Service { - return { - getAllFunctions: jest.fn(() => ["function1"]), - getFunction: jest.fn(), - getAllEventsInFunction: jest.fn(), - getAllFunctionsNames: jest.fn(), - getEventInFunction: jest.fn(), - getServiceName: jest.fn(), - load: jest.fn(), - mergeResourceArrays: jest.fn(), - setFunctionNames: jest.fn(), - update: jest.fn(), - validate: jest.fn(), - custom: null, - provider: MockFactory.createTestAzureServiceProvider(), - service: "serviceName", - artifact: "app.zip", - } as any as Service; - } - public static createTestAzureServiceProvider() { return { resourceGroup: "myResourceGroup", @@ -131,8 +246,104 @@ export class MockFactory { } } - private getConfig(config: any, prop: string, defaultValue: any) { + public static createTestSite(name: string = "Test"): Site { + return { + name: name, + location: "West US", + }; + } + + public static createTestBindings(bindingCount = 3) { + const bindings = []; + for (let i = 0; i < bindingCount; i++) { + bindings.push(MockFactory.createTestBinding()); + } + return bindings; + } + + public static createTestBinding() { + // Only supporting HTTP for now, could support others + return MockFactory.createTestHttpBinding(); + } + + public static createTestHttpBinding(direction: string = "in") { + if (direction === "in") { + return { + authLevel: "anonymous", + type: "httpTrigger", + direction, + name: "req", + } + } else { + return { + type: "http", + direction, + name: "res" + } + } + } + public static createTestBindingsObject() { + return { + scriptFile: "index.js", + entryPoint: "handler", + disabled: false, + bindings: [ + MockFactory.createTestHttpBinding("in"), + MockFactory.createTestHttpBinding("out") + ] + } + } + + public static createTestSlsFunctionConfig() { + return { + hello: { + handler: "index.handler", + apim: { + operations: [ + { + method: "get", + urlTemplate: "hello", + displayName: "Hello", + }, + ], + }, + events: MockFactory.createTestFunctionEvents(), + }, + goodbye: { + handler: "index.handler", + apim: { + operations: [ + { + method: "get", + urlTemplate: "goodbye", + displayName: "Goodbye", + }, + ], + }, + events: MockFactory.createTestFunctionEvents(), + }, + }; + } + + public static createTestApimService(): ApiManagementServiceResource { + return { + name: "APIM Service Instance", + location: "West US", + publisherName: "Somebody", + publisherEmail: "somebody@example.com", + sku: { + capacity: 0, + name: "Consumption", + } + }; + } + + public static createTestApimApi(): ApiContract { + return { + name: "Api1", + path: "/api1", + }; } private static createTestUtils(): Utils { @@ -154,18 +365,18 @@ export class MockFactory { walkDirSync: jest.fn(), writeFile: jest.fn(), writeFileDir: jest.fn(), - writeFileSync: jest.fn() - } + writeFileSync: jest.fn(), + }; } - private static createTestCli(){ + private static createTestCli() { return { - log: jest.fn() - } + log: jest.fn(), + }; } private static createTestPluginManager(): PluginManager { - return { + return { addPlugin: jest.fn(), cliCommands: null, cliOptions: null, @@ -182,7 +393,7 @@ export class MockFactory { serverless: null, setCliCommands: jest.fn(), setCliOptions: jest.fn(), - spawn: jest.fn() - } + spawn: jest.fn(), + }; } -} \ No newline at end of file +} diff --git a/src/test/responses/apim-get-api-200.json b/src/test/responses/apim-get-api-200.json new file mode 100644 index 00000000..6aadaffc --- /dev/null +++ b/src/test/responses/apim-get-api-200.json @@ -0,0 +1,25 @@ +{ + "id": "/subscriptions/d36d0808-a967-4f73-9fdc-32ea232fc81d/resourceGroups/${resourceGroup.name}/providers/Microsoft.ApiManagement/service/${service.name}/apis/${resource.name}", + "type": "Microsoft.ApiManagement/service/apis", + "name": "${resource.name}", + "properties": { + "displayName": "${resource.displayName}", + "apiRevision": "1", + "description": "${resource.description}", + "subscriptionRequired": false, + "serviceUrl": null, + "path": "${resource.path}", + "protocols": [ + "https" + ], + "authenticationSettings": { + "oAuth2": null, + "openid": null + }, + "subscriptionKeyParameterNames": { + "header": "Ocp-Apim-Subscription-Key", + "query": "subscription-key" + }, + "isCurrent": true + } +} \ No newline at end of file diff --git a/src/test/responses/apim-get-api-404.json b/src/test/responses/apim-get-api-404.json new file mode 100644 index 00000000..31f69cf0 --- /dev/null +++ b/src/test/responses/apim-get-api-404.json @@ -0,0 +1,7 @@ +{ + "error": { + "code": "ResourceNotFound", + "message": "Api not found.", + "details": null + } +} \ No newline at end of file diff --git a/src/test/responses/apim-get-service-200.json b/src/test/responses/apim-get-service-200.json new file mode 100644 index 00000000..d83a9534 --- /dev/null +++ b/src/test/responses/apim-get-service-200.json @@ -0,0 +1,53 @@ +{ + "id": "/subscriptions/d36d0808-a967-4f73-9fdc-32ea232fc81d/resourceGroups/${resourceGroup.name}/providers/Microsoft.ApiManagement/service/${resource.name}", + "name": "${resource.name}", + "type": "Microsoft.ApiManagement/service", + "tags": {}, + "location": "${resourceGroup.location}", + "etag": "AAAAAAAAcns=", + "properties": { + "publisherEmail": "wabrez@microsoft.com", + "publisherName": "Microsoft", + "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com", + "provisioningState": "Succeeded", + "targetProvisioningState": "", + "createdAtUtc": "2019-05-22T15:27:07.8117982Z", + "gatewayUrl": "https://${resource.name}.azure-api.net", + "gatewayRegionalUrl": null, + "portalUrl": null, + "managementApiUrl": null, + "scmUrl": null, + "hostnameConfigurations": [ + { + "type": "Proxy", + "hostName": "${resource.name}.azure-api.net", + "encodedCertificate": null, + "keyVaultId": null, + "certificatePassword": null, + "negotiateClientCertificate": false, + "certificate": null, + "defaultSslBinding": true + } + ], + "publicIPAddresses": null, + "privateIPAddresses": null, + "additionalLocations": null, + "virtualNetworkConfiguration": null, + "customProperties": { + "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10": "False", + "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11": "False", + "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10": "False", + "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11": "False", + "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30": "False", + "Microsoft.WindowsAzure.ApiManagement.Gateway.Protocols.Server.Http2": "False" + }, + "virtualNetworkType": "None", + "certificates": null, + "enableClientCertificate": false + }, + "sku": { + "name": "Consumption", + "capacity": 0 + }, + "identity": null +} \ No newline at end of file diff --git a/src/test/responses/apim-get-service-404.json b/src/test/responses/apim-get-service-404.json new file mode 100644 index 00000000..58d0172b --- /dev/null +++ b/src/test/responses/apim-get-service-404.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "ResourceNotFound", + "message": "The Resource 'Microsoft.ApiManagement/service/foobar' under resource group 'wabrez-711-westus-rg' was not found." + } +} \ No newline at end of file diff --git a/src/test/responses/apim-put-api-201.json b/src/test/responses/apim-put-api-201.json new file mode 100644 index 00000000..22d026e9 --- /dev/null +++ b/src/test/responses/apim-put-api-201.json @@ -0,0 +1,25 @@ +{ + "id": "/subscriptions/d36d0808-a967-4f73-9fdc-32ea232fc81d/resourceGroups/${resourceGroup.name}/providers/Microsoft.ApiManagement/service/wabrez-711-westus-apim/apis/${resource.name}", + "type": "Microsoft.ApiManagement/service/apis", + "name": "${resource.name}", + "properties": { + "displayName": "${resource.displayName}", + "apiRevision": "1", + "description": null, + "subscriptionRequired": false, + "serviceUrl": null, + "path": "${resource.name}", + "protocols": [ + "https" + ], + "authenticationSettings": { + "oAuth2": null, + "openid": null + }, + "subscriptionKeyParameterNames": { + "header": "Ocp-Apim-Subscription-Key", + "query": "subscription-key" + }, + "isCurrent": true + } +} \ No newline at end of file diff --git a/src/test/responses/apim-put-backend-201.json b/src/test/responses/apim-put-backend-201.json new file mode 100644 index 00000000..24942837 --- /dev/null +++ b/src/test/responses/apim-put-backend-201.json @@ -0,0 +1,11 @@ +{ + "id": "/subscriptions/d36d0808-a967-4f73-9fdc-32ea232fc81d/resourceGroups/${resourceGroup.name}/providers/Microsoft.ApiManagement/service/wabrez-711-westus-apim/backends/sls-api34", + "type": "Microsoft.ApiManagement/service/backends", + "name": "${resource.name}", + "properties": { + "title": "${resource.title}", + "description": "${resource.description}", + "url": "${resource.url}", + "protocol": "${resource.protocol}" + } +} \ No newline at end of file diff --git a/src/test/responses/apim-put-property-201.json b/src/test/responses/apim-put-property-201.json new file mode 100644 index 00000000..5155a546 --- /dev/null +++ b/src/test/responses/apim-put-property-201.json @@ -0,0 +1,11 @@ +{ + "id": "/subscriptions/d36d0808-a967-4f73-9fdc-32ea232fc81d/resourceGroups/${resourceGroup.name}/providers/Microsoft.ApiManagement/service/wabrez-711-westus-apim/properties/prop1", + "type": "Microsoft.ApiManagement/service/properties", + "name": "${resource.name}", + "properties": { + "displayName": "${resource.displayName}", + "value": "${resource.value}", + "tags": null, + "secret": "${resource.secret}" + } +} \ No newline at end of file diff --git a/src/test/utils.ts b/src/test/utils.ts index 3210ab3e..23a6d56f 100644 --- a/src/test/utils.ts +++ b/src/test/utils.ts @@ -1,3 +1,25 @@ -export async function invokeHook(plugin: { hooks: { [eventName: string]: Promise }}, hook: string) { +export async function invokeHook(plugin: { hooks: { [eventName: string]: Promise } }, hook: string) { return await (plugin.hooks[hook] as any)(); +} + +/** + * Stringifies the JSON and substitutes values from params + * @param json JSON object + * @param params Parameters for substitution + */ +export function interpolateJson(json: any, params: any) { + const template = JSON.stringify(json); + const outputJson = interpolate(template, params); + return JSON.parse(outputJson); +} + +/** + * Makes substitution of values in string + * @param template String containing variables + * @param params Params containing substitution values + */ +export function interpolate(template: string, params: any) { + const names = Object.keys(params); + const vals = Object["values"](params); + return new Function(...names, `return \`${template}\`;`)(...vals); } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index d4d9522e..47a642b3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,20 +1,62 @@ { "compilerOptions": { - "target": "es6", - "module": "commonjs", - "declaration": true, - "outDir": "./lib", - "strict": false, - "resolveJsonModule": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "types": [ - "jest", - "node" - ] + /* Basic Options */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./lib", /* Redirect output structure to the directory. */ + "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "incremental": true, /* Enable incremental compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": false, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "resolveJsonModule": true }, "include": [ - "src", - "src/**/*.json" + "./src", + "./src/**/*.json" ] } \ No newline at end of file From 8edcb89578ec9c99ccf018bf7fce0089951df1f0 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 14 Jun 2019 12:11:39 -0700 Subject: [PATCH 2/7] Restructure with new packaging changes - fix func plugin with changes --- README.md | 29 ++ package-lock.json | 394 +++++------------- package.json | 7 +- src/models/azureProvider.ts | 25 ++ src/models/serverless.ts | 8 + src/plugins/func/azureFunc.test.ts | 78 ++-- src/plugins/func/azureFunc.ts | 59 +-- src/plugins/func/funcUtils.test.ts | 32 -- src/plugins/func/funcUtils.ts | 77 ---- src/plugins/login/loginPlugin.test.ts | 145 +++---- .../offline/azureOfflinePlugin.test.ts | 38 ++ src/plugins/offline/azureOfflinePlugin.ts | 11 + src/plugins/package/azurePackage.test.ts | 67 +-- src/plugins/package/azurePackage.ts | 41 +- src/services/apimService.test.ts | 7 +- src/services/apimService.ts | 20 +- src/services/baseService.test.ts | 43 +- src/services/baseService.ts | 33 +- src/services/funcService.ts | 132 ++++++ src/services/functionAppService.test.ts | 164 ++++++++ src/services/functionAppService.ts | 14 +- src/services/offlineService.test.ts | 46 +- src/services/offlineService.ts | 18 +- src/services/packageService.test.ts | 166 ++++++++ src/services/packageService.ts | 95 +++++ src/services/resourceService.ts | 6 +- src/shared/binding.test.ts | 17 +- src/shared/bindings.ts | 54 +-- src/shared/utils.test.ts | 67 +++ src/shared/utils.ts | 14 +- src/test/mockFactory.ts | 153 ++++--- src/test/utils.ts | 14 + 32 files changed, 1292 insertions(+), 782 deletions(-) create mode 100644 src/models/azureProvider.ts create mode 100644 src/models/serverless.ts delete mode 100644 src/plugins/func/funcUtils.test.ts delete mode 100644 src/plugins/func/funcUtils.ts create mode 100644 src/services/funcService.ts create mode 100644 src/services/functionAppService.test.ts create mode 100644 src/services/packageService.test.ts create mode 100644 src/services/packageService.ts create mode 100644 src/shared/utils.test.ts diff --git a/README.md b/README.md index 1535a58a..0cfb1515 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,35 @@ Please create issues in this repo for any problems or questions you find. Before We're still in the process of getting everying running 100%, but please refer to the [Serverless contributing guidlines](https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md) for information on how to contribute and code of conduct. +#### Local dev + +1. Clone this repository to your local machine +2. Navigate to the cloned folder +3. Run `npm install` +4. Run `npm run-script build` +5. Navigate to a folder where you created a new Serverless project, run `npm install`, and then run `npm link {path to serverless-azure-functions folder}`. Running `npm install` after the link command may override the link. +6. The npm modules should now contain your local version of this plugin. + +#### Signing commits + +All commits in your Pull Request will need to be signed. When looking at the commits in the pull request, you will see a green 'verified' icon next to your commit. Commit signature verification is discussed [here](https://help.github.com/en/articles/about-commit-signature-verification) + +Follow the directions [here](https://help.github.com/en/articles/signing-commits) to configure commit signing. + +If any of your commits are not signed, your pull request will not be able to be merged into the base branch. You can fix this by squashing any unsigned commits into signed commits using an interactive rebase, and force pushing your new commit history. More detail [here](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) + +When using Windows you may also encounter an error when trying to sign a commit, stating that a security key could not be found. Ensure that you have set the path the gpg in the git config: `git config --global gpg.program "C:\Program Files (x86)\GnuPG\bin\gpg.exe"` + +#### Signing commits + +All commits in your Pull Request will need to be signed. When looking at the commits in the pull request, you will see a green 'verified' icon next to your commit. Commit signature verification is discussed [here](https://help.github.com/en/articles/about-commit-signature-verification) + +Follow the directions [here](https://help.github.com/en/articles/signing-commits) to configure commit signing. + +If any of your commits are not signed, your pull request will not be able to be merged into the base branch. You can fix this by squashing any unsigned commits into signed commits using an interactive rebase, and force pushing your new commit history. More detail [here](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) + +When using Windows you may also encounter an error when trying to sign a commit, stating that a security key could not be found. Ensure that you have set the path the gpg in the git config: `git config --global gpg.program "C:\Program Files (x86)\GnuPG\bin\gpg.exe"` + ## License [MIT](LICENSE) diff --git a/package-lock.json b/package-lock.json index fe447a90..6839d73f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -784,7 +784,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", "dev": true, -<<<<<<< HEAD "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -804,13 +803,10 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", "dev": true, -======= ->>>>>>> 9bdd648dc9faa97b9c56a94ebc3606b408fecb2a "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, -<<<<<<< HEAD "@babel/plugin-transform-react-jsx": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", @@ -838,39 +834,10 @@ "integrity": "sha512-A32OkKTp4i5U6aE88GwwcuV4HAprUgHcTq0sSafLxjr6AW0QahrCRCjxogkbbcdtpbXkuTOlgpjophCxb6sh5g==", "dev": true, "requires": { -======= - "@babel/plugin-transform-react-constant-elements": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.2.0.tgz", - "integrity": "sha512-YYQFg6giRFMsZPKUM9v+VcHOdfSQdz9jHCx3akAi3UYgyjndmdYGSXylQ/V+HswQt4fL8IklchD9HTsaOCrWQQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", - "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", - "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==", - "dev": true, - "requires": { - "@babel/helper-builder-react-jsx": "^7.3.0", ->>>>>>> 9bdd648dc9faa97b9c56a94ebc3606b408fecb2a "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-syntax-jsx": "^7.2.0" } }, -<<<<<<< HEAD "@babel/plugin-transform-regenerator": { "version": "7.4.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", @@ -905,47 +872,11 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", -======= - "@babel/plugin-transform-react-jsx-self": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz", - "integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.2.0" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.2.0.tgz", - "integrity": "sha512-A32OkKTp4i5U6aE88GwwcuV4HAprUgHcTq0sSafLxjr6AW0QahrCRCjxogkbbcdtpbXkuTOlgpjophCxb6sh5g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.2.0" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", - "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", - "dev": true, - "requires": { - "regenerator-transform": "^0.14.0" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", - "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", ->>>>>>> 9bdd648dc9faa97b9c56a94ebc3606b408fecb2a "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, -<<<<<<< HEAD "@babel/plugin-transform-spread": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", @@ -1065,171 +996,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", -======= - "@babel/plugin-transform-runtime": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.3.tgz", - "integrity": "sha512-7Q61bU+uEI7bCUFReT1NKn7/X6sDQsZ7wL1sJ9IYMAO7cI+eg6x9re1cEw2fCRMbbTVyoeUKWSV1M6azEfKCfg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "resolve": "^1.8.1", - "semver": "^5.5.1" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", - "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", - "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", - "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.0.0" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", - "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", - "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", ->>>>>>> 9bdd648dc9faa97b9c56a94ebc3606b408fecb2a - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0" - } - }, - "@babel/preset-typescript": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz", - "integrity": "sha512-mzMVuIP4lqtn4du2ynEfdO0+RYcslwrZiJHXu4MGaC1ctJiW2fyaeDrtjJGs7R/KebZ1sgowcIoWf4uRpEfKEg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.3.2" - } - }, - "@babel/runtime": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", - "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.2" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.4.5.tgz", - "integrity": "sha512-RPB/YeGr4ZrFKNwfuQRlMf2lxoCUaU01MTw39/OFE/RiL8HDjtn68BwEPft1P7JN4akyEmjGWAMNldOV7o9V2g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-typescript": "^7.2.0" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", - "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" - } - }, - "@babel/preset-env": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.3.tgz", - "integrity": "sha512-FYbZdV12yHdJU5Z70cEg0f6lvtpZ8jFSDakTm7WXeJbLXh4R0ztGEu/SW7G1nJ2ZvKwDhz8YrbA84eYyprmGqw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.2.0", - "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.4.3", - "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.0", - "@babel/plugin-syntax-async-generators": "^7.2.0", - "@babel/plugin-syntax-json-strings": "^7.2.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", - "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.4.0", - "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.4.0", - "@babel/plugin-transform-classes": "^7.4.3", - "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.4.3", - "@babel/plugin-transform-dotall-regex": "^7.4.3", - "@babel/plugin-transform-duplicate-keys": "^7.2.0", - "@babel/plugin-transform-exponentiation-operator": "^7.2.0", - "@babel/plugin-transform-for-of": "^7.4.3", - "@babel/plugin-transform-function-name": "^7.4.3", - "@babel/plugin-transform-literals": "^7.2.0", - "@babel/plugin-transform-member-expression-literals": "^7.2.0", - "@babel/plugin-transform-modules-amd": "^7.2.0", - "@babel/plugin-transform-modules-commonjs": "^7.4.3", - "@babel/plugin-transform-modules-systemjs": "^7.4.0", - "@babel/plugin-transform-modules-umd": "^7.2.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.2", - "@babel/plugin-transform-new-target": "^7.4.0", - "@babel/plugin-transform-object-super": "^7.2.0", - "@babel/plugin-transform-parameters": "^7.4.3", - "@babel/plugin-transform-property-literals": "^7.2.0", - "@babel/plugin-transform-regenerator": "^7.4.3", - "@babel/plugin-transform-reserved-words": "^7.2.0", - "@babel/plugin-transform-shorthand-properties": "^7.2.0", - "@babel/plugin-transform-spread": "^7.2.0", - "@babel/plugin-transform-sticky-regex": "^7.2.0", - "@babel/plugin-transform-template-literals": "^7.2.0", - "@babel/plugin-transform-typeof-symbol": "^7.2.0", - "@babel/plugin-transform-unicode-regex": "^7.4.3", - "@babel/types": "^7.4.0", - "browserslist": "^4.5.2", - "core-js-compat": "^3.0.0", - "invariant": "^2.2.2", - "js-levenshtein": "^1.1.3", - "semver": "^5.5.0" - } - }, - "@babel/preset-react": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", - "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1731,6 +1497,15 @@ "integrity": "sha512-/3JqnvPnY58GLzG3Y7fpphOhATV1DDZ/Ak3DQufjlRK5E4u+s0CfClfNFtAGBabw+jDGtRFbOZe+Z02ZMWCBNQ==", "dev": true }, + "@types/mock-fs": { + "version": "3.6.30", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-3.6.30.tgz", + "integrity": "sha1-TYElQeh7I1dyYaWqlfcE3T0B5BA=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "12.0.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.4.tgz", @@ -1969,7 +1744,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -2142,7 +1916,6 @@ "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "optional": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -2302,6 +2075,15 @@ "is-buffer": "^1.1.5" } }, + "axios-mock-adapter": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.16.0.tgz", + "integrity": "sha512-m2D8ngMTQ5p4zZNBsPKoENgwz5rDfd0pZmXI/spdE2eeeKIcR3jquk+NRiBVFtb9UJlciBYplNzSUmgQ6X385Q==", + "dev": true, + "requires": { + "deep-equal": "^1.0.1" + } + }, "babel-jest": { "version": "24.8.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.8.0.tgz", @@ -2603,8 +2385,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "optional": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, "body-parser": { "version": "1.19.0", @@ -2709,8 +2490,7 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "optional": true + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, "browser-process-hrtime": { "version": "0.1.3", @@ -2739,7 +2519,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "optional": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -2776,7 +2555,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "optional": true, "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" @@ -2873,8 +2651,7 @@ "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "optional": true + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, "builtin-modules": { "version": "1.1.1", @@ -3056,7 +2833,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "optional": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -3434,7 +3210,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "optional": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -3447,7 +3222,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "optional": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -3684,6 +3458,12 @@ } } }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -3900,7 +3680,6 @@ "version": "6.4.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", - "optional": true, "requires": { "bn.js": "^4.4.0", "brorand": "^1.0.1", @@ -3962,7 +3741,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "optional": true, "requires": { "prr": "~1.0.1" } @@ -4034,6 +3812,12 @@ "event-emitter": "~0.3.5" } }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", + "dev": true + }, "es6-promise": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", @@ -4336,7 +4120,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "optional": true, "requires": { "d": "1", "es5-ext": "~0.10.14" @@ -4352,7 +4135,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "optional": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -4882,8 +4664,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -4901,11 +4682,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4918,15 +4701,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5029,7 +4815,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5039,6 +4826,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5051,17 +4839,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5078,6 +4869,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5150,7 +4942,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5160,6 +4953,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5235,8 +5029,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -5266,6 +5059,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5283,7 +5077,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5322,13 +5115,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -5636,7 +5427,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "optional": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -5646,7 +5436,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "optional": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -5656,7 +5445,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "optional": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -5850,8 +5638,7 @@ "interpret": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", - "optional": true + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" }, "invariant": { "version": "2.2.4", @@ -5976,8 +5763,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "optional": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -7617,8 +7403,7 @@ "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "optional": true + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" }, "loose-envify": { "version": "1.4.0", @@ -7709,7 +7494,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "optional": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -7735,7 +7519,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "optional": true, "requires": { "errno": "^0.1.3", "readable-stream": "^2.0.1" @@ -7826,14 +7609,12 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "optional": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "optional": true + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "minimatch": { "version": "3.0.4", @@ -8431,7 +8212,6 @@ "version": "5.1.4", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", - "optional": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", @@ -8540,7 +8320,6 @@ "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", - "optional": true, "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -8743,8 +8522,7 @@ "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "optional": true + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, "pseudomap": { "version": "1.0.2", @@ -8811,7 +8589,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "optional": true, "requires": { "safe-buffer": "^5.1.0" } @@ -8944,6 +8721,15 @@ "util.promisify": "^1.0.0" } }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -9215,7 +9001,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "optional": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -9605,7 +9390,6 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "optional": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -9624,12 +9408,34 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, + "shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", "dev": true }, + "shx": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.2.tgz", + "integrity": "sha512-aS0mWtW3T2sHAenrSrip2XGv39O9dXIFUqxAEWHEOS1ePtGIBavdPJY1kE2IHl14V/4iCbUiNDPGdyYTtmhSoA==", + "dev": true, + "requires": { + "es6-object-assign": "^1.0.3", + "minimist": "^1.2.0", + "shelljs": "^0.8.1" + } + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -9779,8 +9585,7 @@ "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "optional": true + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" }, "source-map": { "version": "0.7.3", @@ -10337,8 +10142,7 @@ "tapable": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", - "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", - "optional": true + "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==" }, "tar-stream": { "version": "1.6.2", @@ -11188,7 +10992,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", - "optional": true, "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" @@ -11197,8 +11000,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, diff --git a/package.json b/package.json index 47c7dba8..ef09617e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "test": "jest", "test:ci": "npm run test -- --ci", "test:coverage": "npm run test -- --coverage", - "prebuild": "rm lib/ -rf", + "prebuild": "shx rm -rf lib/ && npm run test", "build": "tsc" }, "repository": { @@ -48,11 +48,13 @@ "@babel/runtime": "^7.4.5", "@types/jest": "^24.0.13", "@types/lodash": "^4.14.130", + "@types/mock-fs": "^3.6.30", "@types/open": "^6.1.0", "@types/request": "^2.48.1", "@types/serverless": "^1.18.2", "@typescript-eslint/eslint-plugin": "^1.9.0", "@typescript-eslint/parser": "^1.9.0", + "axios-mock-adapter": "^1.16.0", "babel-jest": "^24.8.0", "babel-preset-react-app": "^9.0.0", "coveralls": "^3.0.3", @@ -61,6 +63,7 @@ "jest-cli": "^24.8.0", "mock-fs": "^4.10.0", "serverless": "^1.44.1", + "shx": "^0.3.2", "typescript": "^3.4.5" }, "optionalDependencies": { @@ -70,4 +73,4 @@ "engines": { "node": ">= 6.5.0" } -} \ No newline at end of file +} diff --git a/src/models/azureProvider.ts b/src/models/azureProvider.ts new file mode 100644 index 00000000..9b293ae6 --- /dev/null +++ b/src/models/azureProvider.ts @@ -0,0 +1,25 @@ +export interface FunctionEvent { + http?: boolean; + "x-azure-settings"?: { + authLevel?: string; + direction?: string; + name?: string; + }; +} + +export interface ServicePrincipalEnvVariables { + azureSubId: string; + azureServicePrincipalClientId: string; + azureServicePrincipalPassword: string; + azureServicePrincipalTenantId: string; +} + +export interface FunctionMetadata { + handler: string; + events: FunctionEvent[]; +} + +export interface AzureServiceProvider { + resourceGroup: string; + deploymentName: string; +} diff --git a/src/models/serverless.ts b/src/models/serverless.ts new file mode 100644 index 00000000..debd6df5 --- /dev/null +++ b/src/models/serverless.ts @@ -0,0 +1,8 @@ +export interface ServerlessAzureConfig { + provider: { + name: string; + location: string; + }; + plugins: string[]; + functions: any; +} diff --git a/src/plugins/func/azureFunc.test.ts b/src/plugins/func/azureFunc.test.ts index 163becbe..26b55ab8 100644 --- a/src/plugins/func/azureFunc.test.ts +++ b/src/plugins/func/azureFunc.test.ts @@ -1,18 +1,19 @@ import fs from "fs"; import mockFs from "mock-fs"; -import path from "path"; +import rimraf from "rimraf"; import { MockFactory } from "../../test/mockFactory"; import { invokeHook } from "../../test/utils"; import { AzureFuncPlugin } from "./azureFunc"; -import rimraf from "rimraf"; describe("Azure Func Plugin", () => { - + it("displays a help message", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncPlugin(sls, options); await invokeHook(plugin, "func:func"); + expect(sls.cli.log).toBeCalledWith("Use the func plugin to add or remove functions within Function App"); }) @@ -24,65 +25,74 @@ describe("Azure Func Plugin", () => { "index.js": "contents", "function.json": "contents", }, - "serverless.yml": MockFactory.createTestServerlessYml(true), - }, {createCwd: true, createTmp: true}) + "serverless.yml": MockFactory.createTestServerlessYml(true) as any, + }); }); - + afterAll(() => { mockFs.restore(); }); + afterEach(() => { + jest.clearAllMocks(); + }) + it("returns with missing name", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncPlugin(sls, options); await invokeHook(plugin, "func:add:add"); + expect(sls.cli.log).toBeCalledWith("Need to provide a name of function to add") }); - + it("returns with pre-existing function", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); - options["name"] = "myExistingFunction"; + options["name"] = "hello"; + const plugin = new AzureFuncPlugin(sls, options); await invokeHook(plugin, "func:add:add"); - expect(sls.cli.log).toBeCalledWith("Function myExistingFunction already exists"); + + expect(sls.cli.log).toBeCalledWith("Function hello already exists"); }); - - it("creates function directory and updates serverless.yml", async () => { + + it("creates function handler and updates serverless.yml", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); const functionName = "myFunction"; options["name"] = functionName; + const expectedFunctionsYml = MockFactory.createTestSlsFunctionConfig(); + expectedFunctionsYml[functionName] = MockFactory.createTestFunctionMetadata(functionName); + const plugin = new AzureFuncPlugin(sls, options); - const mkdirSpy = jest.spyOn(fs, "mkdirSync"); + await invokeHook(plugin, "func:add:add"); - expect(mkdirSpy).toBeCalledWith(functionName); - const calls = (sls.utils.writeFileSync as any).mock.calls; - expect(calls[0][0]).toBe(path.join(functionName, "index.js")); - expect(calls[1][0]).toBe(path.join(functionName, "function.json")); - const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); - expectedFunctionsYml[functionName] = MockFactory.createTestFunctionMetadata(); - expect(calls[2][0]).toBe("serverless.yml"); - expect(calls[2][1]).toBe(MockFactory.createTestServerlessYml(true, expectedFunctionsYml)); + + const writeFileCalls = (sls.utils.writeFileSync as any).mock.calls; + expect(writeFileCalls[0][0]).toBe(`./${functionName}.js`); + + expect(writeFileCalls[1][0]).toBe("serverless.yml"); + expect(writeFileCalls[1][1]).toBe(MockFactory.createTestServerlessYml(true, expectedFunctionsYml)); }); }); - describe("Remove command", () => { - + describe("Remove command", () => { + beforeAll(() => { mockFs({ + "index.js": "contents", "function1": { - "index.js": "contents", "function.json": "contents", }, - }, {createCwd: true, createTmp: true}); + }); }); - + afterAll(() => { mockFs.restore(); }); - + it("returns with missing name", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); @@ -90,7 +100,7 @@ describe("Azure Func Plugin", () => { await invokeHook(plugin, "func:remove:remove"); expect(sls.cli.log).toBeCalledWith("Need to provide a name of function to remove") }); - + it("returns with non-existing function", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); @@ -99,17 +109,25 @@ describe("Azure Func Plugin", () => { await invokeHook(plugin, "func:remove:remove"); expect(sls.cli.log).toBeCalledWith("Function myNonExistingFunction does not exist"); }); - + it("deletes directory and updates serverless.yml", async () => { + mockFs({ + "hello.js": "contents", + hello: { + "function.json": "contents", + } + }) const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureFuncPlugin(sls, options); - const functionName = "function1"; + const functionName = "hello"; options["name"] = functionName; + const unlinkSpy = jest.spyOn(fs, "unlinkSync"); const rimrafSpy = jest.spyOn(rimraf, "sync"); await invokeHook(plugin, "func:remove:remove"); + expect(unlinkSpy).toBeCalledWith(`${functionName}.js`) expect(rimrafSpy).toBeCalledWith(functionName); - const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); + const expectedFunctionsYml = MockFactory.createTestSlsFunctionConfig(); delete expectedFunctionsYml[functionName]; expect(sls.utils.writeFileSync).toBeCalledWith("serverless.yml", MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) }); diff --git a/src/plugins/func/azureFunc.ts b/src/plugins/func/azureFunc.ts index 86c448fe..09a82014 100644 --- a/src/plugins/func/azureFunc.ts +++ b/src/plugins/func/azureFunc.ts @@ -1,12 +1,10 @@ -import fs from "fs"; -import path from "path"; -import rimraf from "rimraf"; import Serverless from "serverless"; -import { FuncPluginUtils } from "./funcUtils"; +import { FuncService } from "../../services/funcService"; export class AzureFuncPlugin { public hooks: { [eventName: string]: Promise }; public commands: any; + private service: FuncService; public constructor(private serverless: Serverless, private options: Serverless.Options) { @@ -50,6 +48,8 @@ export class AzureFuncPlugin { } } } + + this.service = new FuncService(serverless, options); } private async func() { @@ -57,57 +57,10 @@ export class AzureFuncPlugin { } private async add() { - if (!("name" in this.options)) { - this.serverless.cli.log("Need to provide a name of function to add"); - return; - } - const funcToAdd = this.options["name"] - const exists = fs.existsSync(funcToAdd); - if (exists) { - this.serverless.cli.log(`Function ${funcToAdd} already exists`); - return; - } - this.createFunctionDir(funcToAdd); - this.addToServerlessYml(funcToAdd); - } - - private createFunctionDir(name: string) { - this.serverless.cli.log("Creating function dir"); - try { - fs.mkdirSync(name); - } catch (e) { - this.serverless.cli.log(`Error making directory ${e}`); - } - this.serverless.utils.writeFileSync(path.join(name, "index.js"), FuncPluginUtils.getFunctionHandler(name)); - this.serverless.utils.writeFileSync(path.join(name, "function.json"), FuncPluginUtils.getFunctionJsonString(name, this.options)); - } - - private addToServerlessYml(name: string) { - this.serverless.cli.log("Adding to serverless.yml"); - const functionYml = FuncPluginUtils.getFunctionsYml(this.serverless); - functionYml[name] = FuncPluginUtils.getFunctionSlsObject(name, this.options); - FuncPluginUtils.updateFunctionsYml(this.serverless, functionYml); + this.service.add(); } private async remove() { - if (!("name" in this.options)) { - this.serverless.cli.log("Need to provide a name of function to remove"); - return; - } - const funcToRemove = this.options["name"]; - const exists = fs.existsSync(funcToRemove); - if (!exists) { - this.serverless.cli.log(`Function ${funcToRemove} does not exist`); - return; - } - this.serverless.cli.log(`Removing ${funcToRemove}`); - rimraf.sync(funcToRemove); - await this.removeFromServerlessYml(funcToRemove); - } - - private async removeFromServerlessYml(name: string) { - const functionYml = FuncPluginUtils.getFunctionsYml(this.serverless); - delete functionYml[name]; - FuncPluginUtils.updateFunctionsYml(this.serverless, functionYml) + this.service.remove(); } } \ No newline at end of file diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts deleted file mode 100644 index 83480457..00000000 --- a/src/plugins/func/funcUtils.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { MockFactory } from "../../test/mockFactory"; -import { FuncPluginUtils } from "./funcUtils"; - -describe("Func Utils", () => { - - it("gets functions yml", () => { - const sls = MockFactory.createTestServerless(); - const funcYaml = FuncPluginUtils.getFunctionsYml(sls); - expect(funcYaml).toEqual(MockFactory.createTestFunctionsMetadata()); - }); - - it("updates functions yml", () => { - const updatedFunctions = MockFactory.createTestFunctionsMetadata(3); - const originalSls = MockFactory.createTestServerlessYml(false, 2); - const sls = MockFactory.createTestServerless(); - FuncPluginUtils.updateFunctionsYml(sls, updatedFunctions, originalSls); - const calls = (sls.utils.writeFileSync as any).mock.calls[0] - expect(calls[0]).toBe("serverless.yml"); - const expected = MockFactory.createTestServerlessYml( - true, - MockFactory.createTestFunctionsMetadata(3) - ); - expect(calls[1]).toBe(expected); - }); - - it("adds new function name to function handler", () => { - const name = "This is my function name" - const handler = FuncPluginUtils.getFunctionHandler(name); - expect(handler) - .toContain(`body: "${name} " + (req.query.name || req.body.name)`); - }); -}); \ No newline at end of file diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts deleted file mode 100644 index cebeeb2b..00000000 --- a/src/plugins/func/funcUtils.ts +++ /dev/null @@ -1,77 +0,0 @@ -import yaml from "js-yaml"; -import Serverless from "serverless"; -import httpBinding from "./bindingTemplates/http.json" - -export class FuncPluginUtils { - - public static getServerlessYml(sls: Serverless) { - return sls.utils.readFileSync("serverless.yml"); - } - - public static getFunctionsYml(sls: Serverless, serverlessYml?: any) { - serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(sls); - return serverlessYml["functions"]; - } - - public static updateFunctionsYml(sls: Serverless, functionYml: any, serverlessYml?: any) { - serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(sls); - serverlessYml["functions"] = functionYml; - sls.utils.writeFileSync("serverless.yml", yaml.dump(serverlessYml)); - } - - public static getFunctionHandler(name: string) { - return `"use strict"; - -module.exports.handler = async function (context, req) { - context.log("JavaScript HTTP trigger function processed a request."); - - if (req.query.name || (req.body && req.body.name)) { - context.res = { - // status: 200, /* Defaults to 200 */ - body: "${name} " + (req.query.name || req.body.name) - }; - } - else { - context.res = { - status: 400, - body: "Please pass a name on the query string or in the request body" - }; - } -};` - } - - public static getFunctionJsonString(name: string, options: any) { - // TODO: This is where we would just generate function JSON from SLS object - // using getFunctionSlsObject(name, options). Currently defaulting to http in and out - return JSON.stringify(httpBinding, null, 2); - } - - public static getFunctionSlsObject(name: string, options: any) { - return FuncPluginUtils.defaultFunctionSlsObject(name); - } - - private static defaultFunctionSlsObject(name: string) { - return { - handler: "index.handler", - events: FuncPluginUtils.httpEvents() - } - } - - private static httpEvents() { - return [ - { - http: true, - "x-azure-settings": { - authLevel: "anonymous" - } - }, - { - http: true, - "x-azure-settings": { - direction: "out", - name: "res" - } - }, - ] - } -} \ No newline at end of file diff --git a/src/plugins/login/loginPlugin.test.ts b/src/plugins/login/loginPlugin.test.ts index f8cbced8..6bf138ed 100644 --- a/src/plugins/login/loginPlugin.test.ts +++ b/src/plugins/login/loginPlugin.test.ts @@ -1,127 +1,92 @@ +import Serverless from "serverless"; +import { AzureLoginService } from "../../services/loginService"; import { MockFactory } from "../../test/mockFactory"; -import { invokeHook } from "../../test/utils"; +import { invokeHook, setEnvVariables, unsetEnvVariables } from "../../test/utils"; import { AzureLoginPlugin } from "./loginPlugin"; -import { AzureLoginService } from "../../services/loginService"; describe("Login Plugin", () => { const authResponse = MockFactory.createTestAuthResponse(); + const envVariables = MockFactory.createTestServicePrincipalEnvVariables() + const credentials = MockFactory.createTestVariables().azureCredentials; + + function createPlugin(hasCreds = false, serverless?: Serverless): AzureLoginPlugin { + const sls = serverless || MockFactory.createTestServerless(); + if (!hasCreds) { + delete sls.variables["azureCredentials"]; + } + const options = MockFactory.createTestServerlessOptions(); + return new AzureLoginPlugin(sls, options); + } - it("returns if azure credentials are set", async () => { - const interactiveLogin = jest.fn(() => Promise.resolve(authResponse)); - const servicePrincipalLogin = jest.fn(() => Promise.resolve(authResponse)); + function createMockLoginFunction() { + return jest.fn(() => Promise.resolve(authResponse)); + } - AzureLoginService.interactiveLogin = interactiveLogin; - AzureLoginService.servicePrincipalLogin = servicePrincipalLogin; + function setServicePrincipalEnvVariables() { + setEnvVariables(envVariables); + } - const sls = MockFactory.createTestServerless(); - sls.variables["azureCredentials"] = "credentials"; - const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzureLoginPlugin(sls, options); + function unsetServicePrincipalEnvVariables() { + unsetEnvVariables(envVariables); + } + async function invokeLoginHook(hasCreds = false, serverless?: Serverless) { + const plugin = createPlugin(hasCreds, serverless); await invokeHook(plugin, "before:package:initialize"); + } - expect(interactiveLogin).not.toBeCalled(); - expect(servicePrincipalLogin).not.toBeCalled(); + beforeEach(() => { + AzureLoginService.interactiveLogin = createMockLoginFunction(); + AzureLoginService.servicePrincipalLogin = createMockLoginFunction(); }); - it("calls login if azure credentials are not set", async () => { - const interactiveLogin = jest.fn(() => Promise.resolve(authResponse)); - const servicePrincipalLogin = jest.fn(() => Promise.resolve(authResponse)); - - AzureLoginService.interactiveLogin = interactiveLogin; - AzureLoginService.servicePrincipalLogin = servicePrincipalLogin; - - const sls = MockFactory.createTestServerless(); - delete sls.variables["azureCredentials"] - const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzureLoginPlugin(sls, options); - - await invokeHook(plugin, "before:package:initialize"); + it("returns if azure credentials are set", async () => { + await invokeLoginHook(true); + expect(AzureLoginService.interactiveLogin).not.toBeCalled(); + expect(AzureLoginService.servicePrincipalLogin).not.toBeCalled(); + }); - expect(interactiveLogin).toBeCalled(); - expect(servicePrincipalLogin).not.toBeCalled(); + it("calls login if azure credentials are not set", async () => { + await invokeLoginHook(); + expect(AzureLoginService.interactiveLogin).toBeCalled(); + expect(AzureLoginService.servicePrincipalLogin).not.toBeCalled(); }); it("calls service principal login if environment variables are set", async () => { - - process.env.azureSubId = "azureSubId"; - process.env.azureServicePrincipalClientId = "azureServicePrincipalClientId"; - process.env.azureServicePrincipalPassword = "azureServicePrincipalPassword"; - process.env.azureServicePrincipalTenantId = "azureServicePrincipalTenantId"; - - const interactiveLogin = jest.fn(() => Promise.resolve(authResponse)); - const servicePrincipalLogin = jest.fn(() => Promise.resolve(authResponse)); - - AzureLoginService.interactiveLogin = interactiveLogin; - AzureLoginService.servicePrincipalLogin = servicePrincipalLogin; - + setServicePrincipalEnvVariables(); const sls = MockFactory.createTestServerless(); - delete sls.variables["azureCredentials"] - const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzureLoginPlugin(sls, options); - await invokeHook(plugin, "before:package:initialize"); - expect(servicePrincipalLogin).toBeCalledWith( + await invokeLoginHook(false, sls); + expect(AzureLoginService.servicePrincipalLogin).toBeCalledWith( "azureServicePrincipalClientId", "azureServicePrincipalPassword", "azureServicePrincipalTenantId" ) - expect(interactiveLogin).not.toBeCalled(); - - expect(sls.variables["azureCredentials"]).toEqual(authResponse.credentials); + expect(AzureLoginService.interactiveLogin).not.toBeCalled(); + expect(sls.variables["azureCredentials"]).toEqual(credentials); expect(sls.variables["subscriptionId"]).toEqual("azureSubId"); }); it("calls interactive login if environment variables are not set", async () => { - delete process.env.azureSubId; - delete process.env.azureServicePrincipalClientId; - delete process.env.azureServicePrincipalPassword; - delete process.env.azureServicePrincipalTenantId; - - const interactiveLogin = jest.fn(() => Promise.resolve(authResponse)); - const servicePrincipalLogin = jest.fn(() => Promise.resolve(authResponse)); - - AzureLoginService.interactiveLogin = interactiveLogin; - AzureLoginService.servicePrincipalLogin = servicePrincipalLogin; - + unsetServicePrincipalEnvVariables(); const sls = MockFactory.createTestServerless(); - delete sls.variables["azureCredentials"] - const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzureLoginPlugin(sls, options); - await invokeHook(plugin, "before:package:initialize"); - expect(servicePrincipalLogin).not.toBeCalled(); - expect(interactiveLogin).toBeCalled(); - - expect(sls.variables["azureCredentials"]).toEqual(authResponse.credentials); + await invokeLoginHook(false, sls); + expect(AzureLoginService.servicePrincipalLogin).not.toBeCalled(); + expect(AzureLoginService.interactiveLogin).toBeCalled(); + expect(sls.variables["azureCredentials"]).toEqual(credentials); expect(sls.variables["subscriptionId"]).toEqual("azureSubId"); }); it("logs an error from authentication", async () => { - process.env.azureSubId = "azureSubId"; - process.env.azureServicePrincipalClientId = "azureServicePrincipalClientId"; - process.env.azureServicePrincipalPassword = "azureServicePrincipalPassword"; - process.env.azureServicePrincipalTenantId = "azureServicePrincipalTenantId"; - - const interactiveLogin = jest.fn(() => Promise.resolve(authResponse)); + unsetServicePrincipalEnvVariables(); const errorMessage = "This is my error message"; - const servicePrincipalLogin = jest.fn(() => { + AzureLoginService.interactiveLogin = jest.fn(() => { throw new Error(errorMessage); - }); - - AzureLoginService.interactiveLogin = interactiveLogin; - AzureLoginService.servicePrincipalLogin = servicePrincipalLogin; - + }); const sls = MockFactory.createTestServerless(); - delete sls.variables["azureCredentials"] - const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzureLoginPlugin(sls, options); - await invokeHook(plugin, "before:package:initialize"); - expect(servicePrincipalLogin).toBeCalledWith( - "azureServicePrincipalClientId", - "azureServicePrincipalPassword", - "azureServicePrincipalTenantId" - ) - expect(interactiveLogin).not.toBeCalled(); + await invokeLoginHook(false, sls); + expect(AzureLoginService.interactiveLogin).toBeCalled() + expect(AzureLoginService.servicePrincipalLogin).not.toBeCalled(); expect(sls.cli.log).lastCalledWith(`Error: ${errorMessage}`) }); -}) \ No newline at end of file +}) diff --git a/src/plugins/offline/azureOfflinePlugin.test.ts b/src/plugins/offline/azureOfflinePlugin.test.ts index 4aac97ab..fbed65da 100644 --- a/src/plugins/offline/azureOfflinePlugin.test.ts +++ b/src/plugins/offline/azureOfflinePlugin.test.ts @@ -1,3 +1,5 @@ +import fs from "fs"; +import mockFs from "mock-fs"; import path from "path"; import Serverless from "serverless"; import { MockFactory } from "../../test/mockFactory"; @@ -13,6 +15,18 @@ describe("Azure Offline Plugin", () => { ) } + beforeAll(() => { + mockFs({}) + }); + + afterAll(() => { + mockFs.restore(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }) + it("invokes build hook", async () => { const sls = MockFactory.createTestServerless(); const plugin = createPlugin(sls); @@ -26,4 +40,28 @@ describe("Azure Offline Plugin", () => { expect(JSON.parse(calls[i][1])).toEqual(expectedFunctionJson); } }); + + it("invokes offline hook", async () => { + const sls = MockFactory.createTestServerless(); + const plugin = createPlugin(sls); + await invokeHook(plugin, "offline:offline"); + // Trivial test for now. In the future, this process + // may spawn the start process itself rather than telling + // the user how to do it. + expect(sls.cli.log).toBeCalledTimes(3); + }); + + it("invokes cleanup hook", async () => { + const unlinkSpy = jest.spyOn(fs, "unlinkSync"); + const rmdirSpy = jest.spyOn(fs, "rmdirSync") + const sls = MockFactory.createTestServerless(); + const plugin = createPlugin(sls); + await invokeHook(plugin, "offline:cleanup:cleanup"); + const unlinkCalls = unlinkSpy.mock.calls; + expect(unlinkCalls[0][0]).toBe(`hello${path.sep}function.json`); + expect(unlinkCalls[1][0]).toBe(`goodbye${path.sep}function.json`); + const rmdirCalls = rmdirSpy.mock.calls; + expect(rmdirCalls[0][0]).toBe("hello"); + expect(rmdirCalls[1][0]).toBe("goodbye"); + }); }); \ No newline at end of file diff --git a/src/plugins/offline/azureOfflinePlugin.ts b/src/plugins/offline/azureOfflinePlugin.ts index fd0ec3e9..2a96789e 100644 --- a/src/plugins/offline/azureOfflinePlugin.ts +++ b/src/plugins/offline/azureOfflinePlugin.ts @@ -13,6 +13,7 @@ export class AzureOfflinePlugin { "before:offline:offline": this.azureOfflineBuild.bind(this), "offline:build:build": this.azureOfflineBuild.bind(this), "offline:offline": this.azureOfflineStart.bind(this), + "offline:cleanup:cleanup": this.azureOfflineCleanup.bind(this), }; this.commands = { @@ -27,6 +28,12 @@ export class AzureOfflinePlugin { lifecycleEvents: [ "build", ] + }, + cleanup: { + usage: "Clean up files from offline development", + lifecycleEvents: [ + "cleanup" + ] } } } @@ -40,4 +47,8 @@ export class AzureOfflinePlugin { private async azureOfflineStart(){ this.offlineService.start(); } + + private async azureOfflineCleanup(){ + this.offlineService.cleanup(); + } } \ No newline at end of file diff --git a/src/plugins/package/azurePackage.test.ts b/src/plugins/package/azurePackage.test.ts index 2710849e..398a24d9 100644 --- a/src/plugins/package/azurePackage.test.ts +++ b/src/plugins/package/azurePackage.test.ts @@ -1,37 +1,42 @@ +import Serverless from "serverless"; import { MockFactory } from "../../test/mockFactory"; import { invokeHook } from "../../test/utils"; import { AzurePackage } from "./azurePackage"; - -jest.mock("../../shared/bindings"); -import { BindingUtils } from "../../shared/bindings"; -jest.mock("../../shared/utils"); -import { Utils } from "../../shared/utils"; +jest.mock("../../services/packageService"); +import { PackageService } from "../../services/packageService"; describe("Azure Package Plugin", () => { - it("sets up provider configuration", async () => { - const slsFunctionConfig = MockFactory.createTestSlsFunctionConfig(); - const sls = MockFactory.createTestServerless(); - Object.assign(sls.service, { - functions: slsFunctionConfig - }); - - const functionConfig = Object.keys(slsFunctionConfig).map((funcName) => { - return { - name: funcName, - config: slsFunctionConfig[funcName], - }; - }); - - Utils.getFunctionMetaData = jest.fn((funcName) => slsFunctionConfig[funcName]); - BindingUtils.createEventsBindings = jest.fn(); - - const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzurePackage(sls, options); - - await invokeHook(plugin, "package:setupProviderConfiguration"); - - expect(sls.cli.log).toBeCalledWith("Building Azure Events Hooks"); - expect(Utils.getFunctionMetaData).toBeCalledWith(functionConfig[0].name, sls); - expect(BindingUtils.createEventsBindings).toBeCalledWith(sls, functionConfig[0].name, functionConfig[0].config); + let sls: Serverless; + let plugin: AzurePackage; + + beforeEach(() => { + jest.resetAllMocks(); + sls = MockFactory.createTestServerless(); + + plugin = new AzurePackage(sls); }); -}); \ No newline at end of file + + it("sets creates function bindings before package:setupProviderConfiguration life cycle event", async () => { + await invokeHook(plugin, "before:package:setupProviderConfiguration"); + expect(PackageService.prototype.createBindings).toBeCalled(); + }); + + it("prepares the package for webpack before webpack:package:packageModules life cycle event", async () => { + await invokeHook(plugin, "before:webpack:package:packageModules"); + expect(PackageService.prototype.createBindings).toBeCalled(); + expect(PackageService.prototype.prepareWebpack).toBeCalled(); + }); + + it("only calls create bindings 1 time throughout full package life cycle", async () => { + await invokeHook(plugin, "before:package:setupProviderConfiguration"); + await invokeHook(plugin, "before:webpack:package:packageModules"); + + expect(PackageService.prototype.createBindings).toBeCalledTimes(1); + expect(PackageService.prototype.prepareWebpack).toBeCalledTimes(1); + }); + + it("cleans up package after package:finalize", async () => { + await invokeHook(plugin, "after:package:finalize"); + expect(PackageService.prototype.cleanUp).toBeCalled(); + }) +}); diff --git a/src/plugins/package/azurePackage.ts b/src/plugins/package/azurePackage.ts index a9af2101..37b58811 100644 --- a/src/plugins/package/azurePackage.ts +++ b/src/plugins/package/azurePackage.ts @@ -1,29 +1,44 @@ import Serverless from "serverless"; import AzureProvider from "../../provider/azureProvider"; -import { BindingUtils } from "../../shared/bindings"; -import { Utils } from "../../shared/utils"; +import { PackageService } from "../../services/packageService"; export class AzurePackage { - public provider: AzureProvider + private bindingsCreated: boolean = false; + private packageService: PackageService; + public provider: AzureProvider; public hooks: { [eventName: string]: Promise }; - public constructor(private serverless: Serverless, private options: Serverless.Options) { + public constructor(private serverless: Serverless) { this.hooks = { - "package:setupProviderConfiguration": this.setupProviderConfiguration.bind(this), + "before:package:setupProviderConfiguration": this.setupProviderConfiguration.bind(this), + "before:webpack:package:packageModules": this.webpack.bind(this), + "after:package:finalize": this.finalize.bind(this), }; + + this.packageService = new PackageService(this.serverless); } - private async setupProviderConfiguration() { - this.serverless.cli.log("Building Azure Events Hooks"); + private async setupProviderConfiguration(): Promise { + await this.packageService.createBindings(); + this.bindingsCreated = true; - const createEventsPromises = this.serverless.service.getAllFunctions() - .map((functionName) => { - const metaData = Utils.getFunctionMetaData(functionName, this.serverless); + return Promise.resolve(); + } - return BindingUtils.createEventsBindings(this.serverless, functionName, metaData); - }); + private async webpack(): Promise { + if (!this.bindingsCreated) { + await this.setupProviderConfiguration(); + } - return Promise.all(createEventsPromises); + await this.packageService.prepareWebpack(); + } + + /** + * Cleans up generated folders & files after packaging is complete + */ + private async finalize(): Promise { + await this.packageService.cleanUp(); } } + diff --git a/src/services/apimService.test.ts b/src/services/apimService.test.ts index e8ae9957..51671acb 100644 --- a/src/services/apimService.test.ts +++ b/src/services/apimService.test.ts @@ -37,6 +37,7 @@ describe("APIM Service", () => { beforeEach(() => { const slsConfig: any = { + ...MockFactory.createTestService(MockFactory.createTestSlsFunctionConfig()), service: "test-sls", provider: { name: "azure", @@ -44,11 +45,11 @@ describe("APIM Service", () => { location: "West US", apim: apimConfig, }, - functions: MockFactory.createTestSlsFunctionConfig(), }; - serverless = MockFactory.createTestServerless(); - Object.assign(serverless.service, slsConfig); + serverless = MockFactory.createTestServerless({ + service: slsConfig + }); serverless.variables = { ...serverless.variables, diff --git a/src/services/apimService.ts b/src/services/apimService.ts index c7a389d4..55e54e85 100644 --- a/src/services/apimService.ts +++ b/src/services/apimService.ts @@ -89,7 +89,7 @@ export class ApimService extends BaseService { return null; } - this.serverless.cli.log("-> Deploying API Operations"); + this.log("-> Deploying API Operations"); const deployApiTasks = this.serverless.service .getAllFunctions() @@ -127,7 +127,7 @@ export class ApimService extends BaseService { * Deploys the APIM API referenced by the serverless service */ private async ensureApi(): Promise { - this.serverless.cli.log("-> Deploying API"); + this.log("-> Deploying API"); try { return await this.apimClient.api.createOrUpdate(this.resourceGroup, this.config.name, this.config.api.name, { @@ -139,7 +139,7 @@ export class ApimService extends BaseService { protocols: this.config.api.protocols, }); } catch (e) { - this.serverless.cli.log("Error creating APIM API"); + this.log("Error creating APIM API"); throw e; } } @@ -151,7 +151,7 @@ export class ApimService extends BaseService { private async ensureBackend(functionApp: Site): Promise { const backendUrl = `https://${functionApp.defaultHostName}/api`; - this.serverless.cli.log(`-> Deploying API Backend ${functionApp.name} = ${backendUrl}`); + this.log(`-> Deploying API Backend ${functionApp.name} = ${backendUrl}`); try { const functionAppResourceId = `https://management.azure.com${functionApp.id}`; @@ -170,7 +170,7 @@ export class ApimService extends BaseService { url: backendUrl, }); } catch (e) { - this.serverless.cli.log("Error creating APIM Backend"); + this.log("Error creating APIM Backend"); throw e; } } @@ -198,7 +198,7 @@ export class ApimService extends BaseService { }; const operationUrl = `${service.gatewayUrl}/${api.path}${operationConfig.urlTemplate}`; - this.serverless.cli.log(`--> Deploying API operation ${options.function}: ${operationConfig.method.toUpperCase()} ${operationUrl}`); + this.log(`--> Deploying API operation ${options.function}: ${operationConfig.method.toUpperCase()} ${operationUrl}`); const operation = await client.apiOperation.createOrUpdate( this.resourceGroup, @@ -230,8 +230,8 @@ export class ApimService extends BaseService { return operation; } catch (e) { - this.serverless.cli.log(`Error deploying API operation ${options.function}`); - this.serverless.cli.log(JSON.stringify(e.body, null, 4)); + this.log(`Error deploying API operation ${options.function}`); + this.log(JSON.stringify(e.body, null, 4)); } } @@ -240,7 +240,7 @@ export class ApimService extends BaseService { * @param functionAppUrl The host name for the Azure function app */ private async ensureFunctionAppKeys(functionApp): Promise { - this.serverless.cli.log("-> Deploying API keys"); + this.log("-> Deploying API keys"); try { const masterKey = await this.functionAppService.getMasterKey(functionApp); const keyName = `${this.serviceName}-key`; @@ -251,7 +251,7 @@ export class ApimService extends BaseService { value: masterKey, }); } catch (e) { - this.serverless.cli.log("Error creating APIM Property"); + this.log("Error creating APIM Property"); throw e; } } diff --git a/src/services/baseService.test.ts b/src/services/baseService.test.ts index d1107891..a7e0225a 100644 --- a/src/services/baseService.test.ts +++ b/src/services/baseService.test.ts @@ -1,8 +1,12 @@ import Serverless from "serverless"; -import { BaseService } from "./baseService"; -import { MockFactory } from "../test/mockFactory"; jest.mock("axios", () => jest.fn()); import axios from "axios"; +import mockFs from "mock-fs"; +import { MockFactory } from "../test/mockFactory"; +jest.mock("request", () => MockFactory.createTestMockRequestFactory()); +import request from "request"; +import fs from "fs"; +import { BaseService } from "./baseService"; class TestService extends BaseService { public constructor(serverless: Serverless, options?: Serverless.Options) { @@ -41,6 +45,10 @@ describe("Base Service", () => { }, }; + afterAll(() => { + mockFs.restore(); + }); + beforeEach(() => { sls = MockFactory.createTestServerless(); sls.variables["azureCredentials"] = MockFactory.createTestAzureCredentials(); @@ -84,5 +92,34 @@ describe("Base Service", () => { method, headers: expectedHeaders, }); - }) + }); + + it("POST a file to a HTTP endpoint", async () => { + mockFs({ + ".serverless": { + "project.zip": "contents", + }, + }); + + const readStreamSpy = jest.spyOn(fs, "createReadStream"); + + const filePath = ".serverless/project.zip"; + const requestOptions = { + method: "POST", + uri: "https://myCustomSite.scm.azurewebsites.net/api/zipdeploy/", + json: true, + headers: { + Authorization: "Bearer ABC123", + Accept: "*/*", + ContentType: "application/octet-stream", + } + }; + + await service.postFile(requestOptions, filePath); + + expect(readStreamSpy).toBeCalledWith(filePath); + expect(request).toBeCalledWith(requestOptions, expect.anything()); + + readStreamSpy.mockRestore(); + }); }); \ No newline at end of file diff --git a/src/services/baseService.ts b/src/services/baseService.ts index a1fa90d9..68aada6b 100644 --- a/src/services/baseService.ts +++ b/src/services/baseService.ts @@ -26,7 +26,13 @@ export abstract class BaseService { throw new Error(`Azure Credentials has not been set in ${this.constructor.name}`); } } - + + /** + * Sends an API request using axios HTTP library + * @param method The HTTP method + * @param relativeUrl The relative url + * @param options Additional HTTP options including headers, etc + */ protected async sendApiRequest(method: string, relativeUrl: string, options: any = {}) { const defaultHeaders = { Authorization: `Bearer ${this.credentials.tokenCache._entries[0].accessToken}`, @@ -46,29 +52,6 @@ export abstract class BaseService { return await axios(relativeUrl, requestOptions); } - protected wait(timeout: number) { - return new Promise((resolve) => setTimeout(resolve, timeout)); - } - - protected waitForCondition(predicate: () => boolean, interval: number = 2000) { - return new Promise((resolve, reject) => { - let retries = 0; - const id = setInterval(async () => { - if (retries >= 20) { - clearInterval(id); - return reject("Failed conditional check 20 times"); - } - - retries++; - const result = await predicate(); - if (result) { - clearInterval(id); - resolve(result); - } - }, interval); - }); - } - /** * Uploads the specified file via HTTP request * @param requestOptions The HTTP request options @@ -79,7 +62,7 @@ export abstract class BaseService { fs.createReadStream(filePath) .pipe(request(requestOptions, (err, response) => { if (err) { - this.serverless.cli.log(JSON.stringify(err, null, 4)); + this.log(JSON.stringify(err, null, 4)); return reject(err); } resolve(response); diff --git a/src/services/funcService.ts b/src/services/funcService.ts new file mode 100644 index 00000000..513ad490 --- /dev/null +++ b/src/services/funcService.ts @@ -0,0 +1,132 @@ +import yaml from "js-yaml"; +import rimraf from "rimraf"; +import Serverless from "serverless"; +import httpBinding from "../plugins/func/bindingTemplates/http.json"; +import { BaseService } from "./baseService"; +import fs from "fs"; + +export class FuncService extends BaseService { + public constructor(serverless: Serverless, options: Serverless.Options){ + super(serverless, options, false); + } + + public add() { + const functionName = this.options["name"]; + if (!functionName) { + this.log("Need to provide a name of function to add"); + return; + } + if (this.exists(functionName)) { + this.serverless.cli.log(`Function ${functionName} already exists`); + return; + } + this.createHandler(functionName); + this.addToServerlessYml(functionName); + } + + public remove() { + const functionName = this.options["name"]; + if (!functionName) { + this.log("Need to provide a name of function to remove"); + return; + } + if (!this.exists(functionName)) { + this.log(`Function ${functionName} does not exist`); + return; + } + const fileName = `${functionName}.js`; + if (fs.existsSync(fileName)) { + fs.unlinkSync(fileName); + } + if (fs.existsSync(functionName)) { + rimraf.sync(functionName); + } + this.removeFromServerlessYml(functionName); + } + + private exists(functionName: string) { + return (functionName in this.slsFunctions()); + } + + private createHandler(functionName: string) { + this.serverless.utils.writeFileSync(`./${functionName}.js`, this.getFunctionHandler(functionName)) + } + + private addToServerlessYml(functionName: string) { + const functions = this.slsFunctions(); + functions[functionName] = this.getFunctionSlsObject(functionName) + this.updateFunctionsYml(functions) + } + + private removeFromServerlessYml(functionName: string) { + const functions = this.slsFunctions(); + delete functions[functionName]; + this.updateFunctionsYml(functions) + } + + private getServerlessYml() { + return this.serverless.utils.readFileSync("serverless.yml"); + } + + private updateFunctionsYml(functionYml: any) { + const serverlessYml = this.getServerlessYml(); + serverlessYml["functions"] = functionYml; + this.serverless.utils.writeFileSync("serverless.yml", yaml.dump(serverlessYml)); + } + + private getFunctionHandler(name: string) { + return `"use strict"; + +module.exports.handler = async function (context, req) { + context.log("JavaScript HTTP trigger function processed a request."); + + if (req.query.name || (req.body && req.body.name)) { + context.res = { + // status: 200, /* Defaults to 200 */ + body: "${name} " + (req.query.name || req.body.name) + }; + } + else { + context.res = { + status: 400, + body: "Please pass a name on the query string or in the request body" + }; + } +};` + } + + private getFunctionJsonString(name: string, options: any) { + // TODO: This is where we would just generate function JSON from SLS object + // using getFunctionSlsObject(name, options). Currently defaulting to http in and out + return JSON.stringify(httpBinding, null, 2); + } + + private getFunctionSlsObject(name: string) { + return this.defaultFunctionSlsObject(name); + } + + private defaultFunctionSlsObject(name: string) { + return { + handler: `${name}.handler`, + events: this.httpEvents() + } + } + + private httpEvents() { + return [ + { + http: true, + "x-azure-settings": { + authLevel: "anonymous" + } + }, + { + http: true, + "x-azure-settings": { + direction: "out", + name: "res" + } + }, + ] + } +} \ No newline at end of file diff --git a/src/services/functionAppService.test.ts b/src/services/functionAppService.test.ts new file mode 100644 index 00000000..d87f2c9a --- /dev/null +++ b/src/services/functionAppService.test.ts @@ -0,0 +1,164 @@ +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import mockFs from "mock-fs"; +import Serverless from "serverless"; +import { constants } from "../config"; +import { MockFactory } from "../test/mockFactory"; +import { FunctionAppService } from "./functionAppService"; + +jest.mock("@azure/arm-appservice") +import { WebSiteManagementClient } from "@azure/arm-appservice"; +jest.mock("@azure/arm-resources") + +describe("Function App Service", () => { + + const app = MockFactory.createTestSite(); + const slsService = MockFactory.createTestService(); + const variables = MockFactory.createTestVariables(); + const provider = MockFactory.createTestAzureServiceProvider(); + + const masterKey = "masterKey"; + const authKey = "authKey"; + const syncTriggersMessage = "sync triggers success"; + const deleteFunctionMessage = "delete function success"; + const functions = MockFactory.createTestSlsFunctionConfig(); + const functionsResponse = MockFactory.createTestFunctionsResponse(functions); + + const baseUrl = "https://management.azure.com" + const masterKeyUrl = `https://${app.defaultHostName}/admin/host/systemkeys/_master`; + const authKeyUrl = `${baseUrl}${app.id}/functions/admin/token?api-version=2016-08-01`; + const syncTriggersUrl = `${baseUrl}${app.id}/syncfunctiontriggers?api-version=2016-08-01`; + const listFunctionsUrl = `${baseUrl}${app.id}/functions?api-version=2016-08-01`; + const uploadUrl = `https://${app.enabledHostNames[0]}${constants.scmZipDeployApiPath}/` + + beforeAll(() => { + + // TODO: How to spy on defaul exported function? + const axiosMock = new MockAdapter(axios); + + // Master Key + axiosMock.onGet(masterKeyUrl).reply(200, { value: masterKey }); + // Auth Key + axiosMock.onGet(authKeyUrl).reply(200, authKey); + // Sync Triggers + axiosMock.onPost(syncTriggersUrl).reply(200, syncTriggersMessage); + // List Functions + axiosMock.onGet(listFunctionsUrl).reply(200, { value: functionsResponse }); + // Delete Function + for (const funcName of Object.keys(functions)) { + axiosMock.onDelete(`${baseUrl}${app.id}/functions/${funcName}?api-version=2016-08-01`) + .reply(200, deleteFunctionMessage); + } + + mockFs({ + "app.zip": "contents", + }, {createCwd: true, createTmp: true}); + }); + + beforeEach(() => { + + WebSiteManagementClient.prototype.webApps = { + get: jest.fn(() => app), + deleteFunction: jest.fn(), + } as any; + (FunctionAppService.prototype as any).sendFile = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }) + + afterAll(() => { + mockFs.restore(); + }); + + function createService(sls?: Serverless, options?: Serverless.Options) { + return new FunctionAppService( + sls || MockFactory.createTestServerless({ + service: slsService, + variables: variables, + }), + options || MockFactory.createTestServerlessOptions() + ) + } + + it("get returns function app", async () => { + const service = createService(); + const result = await service.get(); + expect(WebSiteManagementClient.prototype.webApps.get) + .toBeCalledWith(provider.resourceGroup, slsService["service"]); + expect(result).toEqual(app) + }); + + it("get returns null if error occurred", async () => { + const service = createService(); + WebSiteManagementClient.prototype.webApps = { + get: jest.fn(() => { return { error: { code: "ResourceNotFound"}}}), + deleteFunction: jest.fn(), + } as any; + const result = await service.get(); + expect(WebSiteManagementClient.prototype.webApps.get) + .toBeCalledWith(provider.resourceGroup, slsService["service"]); + expect(result).toBeNull(); + }); + + it("gets master key", async () => { + const service = createService(); + const masterKey = await service.getMasterKey(); + expect(masterKey).toEqual(masterKey); + + }); + + it("deletes function", async () => { + const service = createService(); + const response = await service.deleteFunction(app, Object.keys(functions)[0]); + expect(response.data).toEqual(deleteFunctionMessage); + + }); + + it("syncs triggers", async () => { + const service = createService(); + const result = await service.syncTriggers(app); + expect(result.data).toEqual(syncTriggersMessage) + }); + + it("cleans up", async () => { + const sls = MockFactory.createTestServerless(); + const service = createService(sls); + const result = await service.cleanUp(app); + const functionNames = Object.keys(functions); + expect(result).toHaveLength(functionNames.length); + const logCalls = (sls.cli.log as any).mock.calls as any[]; + for (let i = 0; i < functionNames.length; i++) { + const functionName = functionNames[i]; + expect(logCalls[i + 1][0]).toEqual(`-> Deleting function: ${functionName}`); + } + }); + + it("lists functions", async () => { + const service = createService(); + expect(await service.listFunctions(app)).toEqual(functionsResponse.map((f) => f.properties)); + }); + + it("uploads functions", async () => { + const service = createService(); + await service.uploadFunctions(app); + expect((FunctionAppService.prototype as any).sendFile).toBeCalledWith({ + method: "POST", + uri: uploadUrl, + json: true, + headers: { + Authorization: `Bearer ${variables["azureCredentials"].tokenCache._entries[0].accessToken}`, + Accept: "*/*", + ContentType: "application/octet-stream", + } + }, slsService["artifact"]) + }); + + it("throws an error with no zip file", async () => { + const sls = MockFactory.createTestServerless(); + delete sls.service["artifact"]; + const service = createService(sls); + await expect(service.uploadFunctions(app)).rejects.not.toBeNull() + }); +}); \ No newline at end of file diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts index 145d165e..5ea7c7d3 100644 --- a/src/services/functionAppService.ts +++ b/src/services/functionAppService.ts @@ -30,7 +30,7 @@ export class FunctionAppService extends BaseService { return response; } - public async getMasterKey(functionApp) { + public async getMasterKey(functionApp?: Site) { functionApp = functionApp || await this.get(); const adminToken = await this.getAuthKey(functionApp); const keyUrl = `https://${functionApp.defaultHostName}/admin/host/systemkeys/_master`; @@ -56,7 +56,7 @@ export class FunctionAppService extends BaseService { this.serverless.cli.log("Syncing function triggers"); const syncTriggersUrl = `${this.baseUrl}${functionApp.id}/syncfunctiontriggers?api-version=2016-08-01`; - await this.sendApiRequest("POST", syncTriggersUrl); + return await this.sendApiRequest("POST", syncTriggersUrl); } public async cleanUp(functionApp: Site) { @@ -106,7 +106,7 @@ export class FunctionAppService extends BaseService { * resource-group, storage account, app service plan, and app service at the minimum */ public async deploy() { - this.serverless.cli.log(`Creating function app: ${this.serviceName}`); + this.log(`Creating function app: ${this.serviceName}`); let parameters: any = { functionAppName: { value: this.serviceName } }; const gitUrl = this.serverless.service.provider["gitUrl"]; @@ -125,7 +125,7 @@ export class FunctionAppService extends BaseService { } if (this.serverless.service.provider["armTemplate"]) { - this.serverless.cli.log(`-> Deploying custom ARM template: ${this.serverless.service.provider["armTemplate"].file}`); + this.log(`-> Deploying custom ARM template: ${this.serverless.service.provider["armTemplate"].file}`); templateFilePath = path.join(this.serverless.config.servicePath, this.serverless.service.provider["armTemplate"].file); const userParameters = this.serverless.service.provider["armTemplate"].parameters; const userParametersKeys = Object.keys(userParameters); @@ -180,8 +180,12 @@ export class FunctionAppService extends BaseService { this.serverless.cli.log(`Deploying zip file to function app: ${functionAppName}`); // Upload function artifact if it exists, otherwise the full service is handled in 'uploadFunctions' method - const functionZipFile = this.serverless.service["artifact"]; + let functionZipFile = this.serverless.service["artifact"]; if (!functionZipFile) { + functionZipFile = path.join(this.serverless.config.servicePath, ".serverless", `${this.serverless.service.getServiceName()}.zip`); + } + + if (!(functionZipFile && fs.existsSync(functionZipFile))) { throw new Error("No zip file found for function app"); } diff --git a/src/services/offlineService.test.ts b/src/services/offlineService.test.ts index 3fd48fb9..4c6727cd 100644 --- a/src/services/offlineService.test.ts +++ b/src/services/offlineService.test.ts @@ -1,3 +1,5 @@ +import fs from "fs"; +import mockFs from "mock-fs"; import path from "path"; import Serverless from "serverless"; import { MockFactory } from "../test/mockFactory"; @@ -5,13 +7,23 @@ import { OfflineService } from "./offlineService"; describe("Offline Service", () => { - function createService(sls?: Serverless) { + function createService(sls?: Serverless): OfflineService { return new OfflineService( sls || MockFactory.createTestServerless(), MockFactory.createTestServerlessOptions(), ) } + beforeEach(() => { + // Mocking the file system so that files are not created in project directory + mockFs({}) + }); + + afterEach(() => { + mockFs.restore(); + jest.clearAllMocks(); + }) + it("builds required files for offline execution", async () => { const sls = MockFactory.createTestServerless(); const service = createService(sls); @@ -25,4 +37,36 @@ describe("Offline Service", () => { expect(JSON.parse(calls[i][1])).toEqual(expectedFunctionJson); } }); + + it("cleans up functions files", async () => { + mockFs({ + hello: { + "function.json": "contents" + }, + goodbye: { + "function.json": "contents" + } + }) + const sls = MockFactory.createTestServerless(); + const service = createService(sls); + const unlinkSpy = jest.spyOn(fs, "unlinkSync"); + const rmdirSpy = jest.spyOn(fs, "rmdirSync") + await service.cleanup(); + const unlinkCalls = unlinkSpy.mock.calls; + expect(unlinkCalls[0][0]).toBe(`hello${path.sep}function.json`); + expect(unlinkCalls[1][0]).toBe(`goodbye${path.sep}function.json`); + const rmdirCalls = rmdirSpy.mock.calls; + expect(rmdirCalls[0][0]).toBe("hello"); + expect(rmdirCalls[1][0]).toBe("goodbye"); + }); + + it("instructs users how to run locally", async () => { + const sls = MockFactory.createTestServerless(); + const service = createService(sls); + await service.start(); + // Trivial test for now. In the future, this process + // may spawn the start process itself rather than telling + // the user how to do it. + expect(sls.cli.log).toBeCalledTimes(3); + }); }); \ No newline at end of file diff --git a/src/services/offlineService.ts b/src/services/offlineService.ts index ba929456..708d7f0d 100644 --- a/src/services/offlineService.ts +++ b/src/services/offlineService.ts @@ -1,26 +1,26 @@ import Serverless from "serverless"; -import { BindingUtils } from "../shared/bindings"; -import { Utils } from "../shared/utils"; import { BaseService } from "./baseService"; +import { PackageService } from "./packageService"; export class OfflineService extends BaseService { + + private packageService: PackageService; public constructor(serverless: Serverless, options: Serverless.Options) { super(serverless, options, false); + this.packageService = new PackageService(serverless); } public async build() { this.log("Building offline service"); - const createEventsPromises = this.serverless.service.getAllFunctions() - .map((functionName) => { - this.log(`Building function ${functionName}`); - const metaData = Utils.getFunctionMetaData(functionName, this.serverless); - return BindingUtils.createEventsBindings(this.serverless, functionName, metaData); - }); - await Promise.all(createEventsPromises); + await this.packageService.createBindings(); this.log("Finished building offline service"); } + public async cleanup() { + await this.packageService.cleanUp(); + } + public start() { this.log("Run 'npm start' or 'func host start' to run service locally"); this.log("Make sure you have Azure Functions Core Tools installed"); diff --git a/src/services/packageService.test.ts b/src/services/packageService.test.ts new file mode 100644 index 00000000..1b95c1f1 --- /dev/null +++ b/src/services/packageService.test.ts @@ -0,0 +1,166 @@ +import Serverless from "serverless"; +import mockFs from "mock-fs"; +import fs from "fs"; +import path from "path"; +import { PackageService } from "./packageService"; +import { MockFactory } from "../test/mockFactory"; +import { FunctionMetadata } from "../shared/utils"; + +describe("Package Service", () => { + let sls: Serverless; + let packageService: PackageService; + + beforeEach(() => { + sls = MockFactory.createTestServerless(); + sls.config.servicePath = process.cwd(); + + packageService = new PackageService(sls); + }); + + afterEach(() => { + mockFs.restore(); + jest.clearAllMocks(); + }); + + it("cleans up function.json and function folders", async () => { + const fsConfig = {}; + const functionNames = sls.service.getAllFunctions(); + + functionNames.forEach((functionName) => { + fsConfig[functionName] = { + "function.json": "contents", + }; + }); + + mockFs(fsConfig); + + const unlinkSpy = jest.spyOn(fs, "unlinkSync"); + const rmdirSpy = jest.spyOn(fs, "rmdirSync"); + + packageService.cleanUp(); + + expect(unlinkSpy).toBeCalledTimes(functionNames.length); + expect(rmdirSpy).toBeCalledTimes(functionNames.length); + + unlinkSpy.mockRestore(); + rmdirSpy.mockRestore(); + }); + + it("cleans up function.json but does not delete function folder", async () => { + const fsConfig = {}; + + const functionNames = sls.service.getAllFunctions(); + functionNames.forEach((functionName) => { + fsConfig[functionName] = { + "function.json": "contents", + "index.js": "contents", + }; + }); + + mockFs(fsConfig); + + const unlinkSpy = jest.spyOn(fs, "unlinkSync"); + const rmdirSpy = jest.spyOn(fs, "rmdirSync"); + + packageService.cleanUp(); + + expect(unlinkSpy).toBeCalledTimes(functionNames.length); + expect(rmdirSpy).not.toBeCalled(); + + unlinkSpy.mockRestore(); + rmdirSpy.mockRestore(); + }); + + it("createBinding writes function.json files into function folder", async () => { + const functionName = "helloWorld"; + const functionMetadata: FunctionMetadata = { + entryPoint: "handler", + handlerPath: "src/handlers/hello.js", + params: { + functionsJson: {}, + }, + }; + + const expectedFolderPath = path.join(sls.config.servicePath, functionName); + const expectedFilePath = path.join(expectedFolderPath, "function.json"); + + mockFs({}); + + const mkdirSpy = jest.spyOn(fs, "mkdirSync"); + const writeFileSpy = jest.spyOn(fs, "writeFileSync"); + + await packageService.createBinding(functionName, functionMetadata); + + expect(mkdirSpy).toBeCalledWith(expectedFolderPath); + expect(writeFileSpy).toBeCalledWith(expectedFilePath, expect.any(String)); + + mkdirSpy.mockRestore(); + writeFileSpy.mockRestore(); + }); + + it("createBinding does not need to create directory if function folder already exists", async () => { + const functionName = "helloWorld"; + const functionMetadata: FunctionMetadata = { + entryPoint: "handler", + handlerPath: "src/handlers/hello.js", + params: { + functionsJson: {}, + }, + }; + + const expectedFolderPath = path.join(sls.config.servicePath, functionName); + const expectedFilePath = path.join(expectedFolderPath, "function.json"); + + mockFs({ + "helloWorld": { + "index.js": "contents", + }, + }); + + sls.utils.dirExistsSync = jest.fn(() => true); + const mkdirSpy = jest.spyOn(fs, "mkdirSync"); + const writeFileSpy = jest.spyOn(fs, "writeFileSync"); + + await packageService.createBinding(functionName, functionMetadata); + + expect(mkdirSpy).not.toBeCalledWith(expectedFolderPath); + expect(writeFileSpy).toBeCalledWith(expectedFilePath, expect.any(String)); + + mkdirSpy.mockRestore(); + writeFileSpy.mockRestore(); + }); + + it("webpack copies required", async () => { + mockFs({ + // Generated by webpack plugin + ".webpack": { + "serivce": {} + }, + // Generated by azure package plugin + "host.json": "contents", + "hello": { + "function.json": "contents", + }, + "goodbye": { + "function.json": "contents", + }, + }); + + const destinationPath = path.join(".webpack", "service"); + + const mkDirSpy = jest.spyOn(fs, "mkdirSync"); + const copyFileSpy = jest.spyOn(fs, "copyFileSync"); + + await packageService.prepareWebpack(); + + expect(copyFileSpy).toBeCalledWith("host.json", path.join(destinationPath, "host.json")); + + sls.service.getAllFunctions().forEach((functionName) => { + const functionJsonFilePath = path.join(functionName, "function.json"); + expect(copyFileSpy).toBeCalledWith(functionJsonFilePath, path.join(destinationPath, functionJsonFilePath)); + }); + + mkDirSpy.mockRestore(); + copyFileSpy.mockRestore(); + }); +}); \ No newline at end of file diff --git a/src/services/packageService.ts b/src/services/packageService.ts new file mode 100644 index 00000000..1bcd9431 --- /dev/null +++ b/src/services/packageService.ts @@ -0,0 +1,95 @@ +import Serverless from "serverless"; +import path from "path"; +import fs from "fs"; +import { Utils, FunctionMetadata } from "../shared/utils"; + +/** + * Adds service packing support + */ +export class PackageService { + public constructor(private serverless: Serverless) { + } + + /** + * Creates the function.json binding files required for the serverless service + */ + public createBindings(): Promise { + const createEventsPromises = this.serverless.service.getAllFunctions() + .map((functionName) => { + const metaData = Utils.getFunctionMetaData(functionName, this.serverless); + return this.createBinding(functionName, metaData); + }); + + return Promise.all(createEventsPromises); + } + + /** + * Prepares a serverless project for webpack and copies required files including + * host.json and function.json files + */ + public prepareWebpack() { + const filesToCopy: string[] = []; + if (fs.existsSync("host.json")) { + filesToCopy.push("host.json"); + } + + this.serverless.service.getAllFunctions().forEach((functionName) => { + const functionJsonPath = path.join(functionName, "function.json"); + if (fs.existsSync(functionJsonPath)) { + filesToCopy.push(functionJsonPath); + } + }); + + this.serverless.cli.log("Copying files for webpack"); + filesToCopy.forEach((filePath) => { + const destinationPath = path.join(".webpack", "service", filePath); + const destinationDirectory = path.dirname(destinationPath); + if (!fs.existsSync(destinationDirectory)) { + fs.mkdirSync(destinationDirectory); + } + fs.copyFileSync(filePath, destinationPath); + this.serverless.cli.log(`-> ${destinationPath}`); + }); + + return Promise.resolve(); + } + + /** + * Cleans up generated function.json files after packaging has completed + */ + public cleanUp() { + this.serverless.service.getAllFunctions().map((functionName) => { + // Delete function.json if exists in function folder + const filePath = path.join(functionName, "function.json"); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + + // Delete function folder if empty + const items = fs.readdirSync(functionName); + if (items.length === 0) { + fs.rmdirSync(functionName); + } + }); + + return Promise.resolve(); + } + + /** + * Creates the function.json for for the specified function + */ + public createBinding(functionName: string, functionMetadata: FunctionMetadata) { + const functionJSON = functionMetadata.params.functionsJson; + functionJSON.entryPoint = functionMetadata.entryPoint; + functionJSON.scriptFile = functionMetadata.handlerPath; + + const functionDirPath = path.join(this.serverless.config.servicePath, functionName); + if (!fs.existsSync(functionDirPath)) { + fs.mkdirSync(functionDirPath); + } + + fs.writeFileSync(path.join(functionDirPath, "function.json"), JSON.stringify(functionJSON, null, 2)); + + return Promise.resolve(); + } +} \ No newline at end of file diff --git a/src/services/resourceService.ts b/src/services/resourceService.ts index 7ef589c1..7bd46530 100644 --- a/src/services/resourceService.ts +++ b/src/services/resourceService.ts @@ -12,7 +12,7 @@ export class ResourceService extends BaseService { } public async deployResourceGroup() { - this.serverless.cli.log(`Creating resource group: ${this.resourceGroup}`); + this.log(`Creating resource group: ${this.resourceGroup}`); const groupParameters = { location: this.serverless.service.provider["location"] @@ -22,12 +22,12 @@ export class ResourceService extends BaseService { } public async deleteDeployment() { - this.serverless.cli.log(`Deleting deployment: ${this.deploymentName}`); + this.log(`Deleting deployment: ${this.deploymentName}`); return await this.resourceClient.deployments.deleteMethod(this.resourceGroup, this.deploymentName); } public async deleteResourceGroup() { - this.serverless.cli.log(`Deleting resource group: ${this.resourceGroup}`); + this.log(`Deleting resource group: ${this.resourceGroup}`); return await this.resourceClient.resourceGroups.deleteMethod(this.resourceGroup); } } \ No newline at end of file diff --git a/src/shared/binding.test.ts b/src/shared/binding.test.ts index 1e8fcea5..870385d8 100644 --- a/src/shared/binding.test.ts +++ b/src/shared/binding.test.ts @@ -1,11 +1,24 @@ +import Serverless from "serverless"; import { MockFactory } from "../test/mockFactory"; import { BindingUtils } from "./bindings"; +import mockFs from "mock-fs"; describe("Bindings", () => { + let sls: Serverless; + + afterEach(() => { + mockFs.restore(); + }); + + beforeEach(() => { + sls = MockFactory.createTestServerless(); + sls.config.servicePath = process.cwd(); + jest.clearAllMocks(); + }); + it("should get bindings metadata from serverless", () => { - const sls = MockFactory.createTestServerless(); expect(sls).not.toBeNull(); BindingUtils.getBindingsMetaData(sls); expect(sls.cli.log).toBeCalledWith("Parsing Azure Functions Bindings.json..."); }); -}); \ No newline at end of file +}); diff --git a/src/shared/bindings.ts b/src/shared/bindings.ts index 69ceabfa..7022b72a 100644 --- a/src/shared/bindings.ts +++ b/src/shared/bindings.ts @@ -1,7 +1,5 @@ -import { join } from "path"; import Serverless from "serverless"; import { constants } from "./constants"; -import { FunctionMetadata } from "./utils"; const bindingsJson = require("./bindings.json"); // eslint-disable-line @typescript-eslint/no-var-requires @@ -11,23 +9,23 @@ export class BindingUtils { const bindingTypes = []; const bindingSettings = []; const bindingSettingsNames = []; - + serverless.cli.log("Parsing Azure Functions Bindings.json..."); - + for (let bindingsIndex = 0; bindingsIndex < bindingsJson[constants.bindings].length; bindingsIndex++) { const settingsNames = []; - + bindingTypes.push(bindingsJson[constants.bindings][bindingsIndex][constants.type]); bindingDisplayNames.push(bindingsJson[constants.bindings][bindingsIndex][constants.displayName].toLowerCase()); bindingSettings[bindingsIndex] = bindingsJson[constants.bindings][bindingsIndex][constants.settings]; - + for (let bindingSettingsIndex = 0; bindingSettingsIndex < bindingSettings[bindingsIndex].length; bindingSettingsIndex++) { settingsNames.push(bindingSettings[bindingsIndex][bindingSettingsIndex][constants.name]); } - + bindingSettingsNames[bindingsIndex] = settingsNames; } - + return { bindingDisplayNames: bindingDisplayNames, bindingTypes: bindingTypes, @@ -36,28 +34,17 @@ export class BindingUtils { }; } - public static createEventsBindings(serverless: Serverless, functionName: string, functionMetadata: FunctionMetadata): Promise { - const functionJSON = functionMetadata.params.functionsJson; - functionJSON.entryPoint = functionMetadata.entryPoint; - functionJSON.scriptFile = functionMetadata.handlerPath; - serverless.utils.writeFileSync( - join(serverless.config.servicePath, functionName, "function.json"), - JSON.stringify(functionJSON, null, 4) - ); - return Promise.resolve(); - } - public static getBindingUserSettingsMetaData(azureSettings, bindingType, bindingTypeIndex, bindingDisplayNames) { let bindingDisplayNamesIndex = bindingTypeIndex; const bindingUserSettings = {}; - + if (azureSettings) { const directionIndex = Object.keys(azureSettings).indexOf(constants.direction); - + if (directionIndex >= 0) { const key = Object.keys(azureSettings)[directionIndex]; const displayName = `$${bindingType}${azureSettings[key]}_displayName`; - + bindingDisplayNamesIndex = bindingDisplayNames.indexOf(displayName.toLowerCase()); bindingUserSettings[constants.direction] = azureSettings[key]; } @@ -66,26 +53,26 @@ export class BindingUtils { index: bindingDisplayNamesIndex, userSettings: bindingUserSettings }; - + return bindingUserSettingsMetaData; } public static getHttpOutBinding(bindingUserSettings) { const binding = {}; - + binding[constants.type] = "http"; binding[constants.direction] = constants.outDirection; binding[constants.name] = "$return"; if (bindingUserSettings[constants.webHookType]) { binding[constants.name] = "res"; } - + return binding; } public static getBinding(bindingType, bindingSettings, bindingUserSettings) { const binding = {}; - + binding[constants.type] = bindingType; if (bindingUserSettings && bindingUserSettings[constants.direction]) { binding[constants.direction] = bindingUserSettings[constants.direction]; @@ -94,10 +81,10 @@ export class BindingUtils { } else { binding[constants.direction] = constants.outDirection; } - + for (let bindingSettingsIndex = 0; bindingSettingsIndex < bindingSettings.length; bindingSettingsIndex++) { const name = bindingSettings[bindingSettingsIndex][constants.name]; - + if (bindingUserSettings && bindingUserSettings[name] !== undefined && bindingUserSettings[name] !== null) { binding[name] = bindingUserSettings[name]; continue; @@ -105,10 +92,10 @@ export class BindingUtils { const value = bindingSettings[bindingSettingsIndex][constants.value]; const required = bindingSettings[bindingSettingsIndex][constants.required]; const resource = bindingSettings[bindingSettingsIndex][constants.resource]; - + if (required) { const defaultValue = bindingSettings[bindingSettingsIndex][constants.defaultValue]; - + if (defaultValue) { binding[name] = defaultValue; } else if (name === constants.connection && resource.toLowerCase() === constants.storage) { @@ -117,15 +104,14 @@ export class BindingUtils { throw new Error(`Required property ${name} is missing for binding:${bindingType}`); } } - + if (value === constants.enum && name !== constants.webHookType) { const enumValues = bindingSettings[bindingSettingsIndex][constants.enum]; - + binding[name] = enumValues[0][constants.value]; } } - + return binding; } - } diff --git a/src/shared/utils.test.ts b/src/shared/utils.test.ts new file mode 100644 index 00000000..c75044e0 --- /dev/null +++ b/src/shared/utils.test.ts @@ -0,0 +1,67 @@ +import Serverless from "serverless"; +import path, { relative } from "path"; +import { MockFactory } from "../test/mockFactory"; +import { Utils, FunctionMetadata } from "./utils"; + +describe("utils", () => { + let sls: Serverless; + + beforeEach(() => { + const slsConfig = { + service: "My test service", + provider: "azure", + functions: MockFactory.createTestSlsFunctionConfig(), + }; + + sls = MockFactory.createTestServerless(); + Object.assign(sls.service, slsConfig); + }); + + it("resolves handler when handler code is outside function folders", () => { + sls.service["functions"].hello.handler = "src/handlers/hello.handler"; + MockFactory.updateService(sls); + + const functions = sls.service.getAllFunctions(); + const metadata = Utils.getFunctionMetaData(functions[0], sls); + + const expectedMetadata: FunctionMetadata = { + entryPoint: "handler", + handlerPath: path.normalize("../src/handlers/hello.js"), + params: expect.anything(), + }; + + expect(metadata).toEqual(expectedMetadata); + }); + + it("resolves handler when code is in function folder", () => { + sls.service["functions"].hello.handler = "hello/index.handler"; + MockFactory.updateService(sls); + + const functions = sls.service.getAllFunctions(); + const metadata = Utils.getFunctionMetaData(functions[0], sls); + + const expectedMetadata: FunctionMetadata = { + entryPoint: "handler", + handlerPath: path.normalize("index.js"), + params: expect.anything(), + }; + + expect(metadata).toEqual(expectedMetadata); + }); + + it("resolves handler when code is at the project root", () => { + sls.service["functions"].hello.handler = "hello.handler"; + MockFactory.updateService(sls); + + const functions = sls.service.getAllFunctions(); + const metadata = Utils.getFunctionMetaData(functions[0], sls); + + const expectedMetadata: FunctionMetadata = { + entryPoint: "handler", + handlerPath: path.normalize("../hello.js"), + params: expect.anything(), + }; + + expect(metadata).toEqual(expectedMetadata); + }); +}); \ No newline at end of file diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 3e5bed53..ed26a295 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -1,4 +1,5 @@ import Serverless from "serverless"; +import { relative } from "path"; import { BindingUtils } from "./bindings"; import { constants } from "./constants"; @@ -86,24 +87,23 @@ export class Utils { if (functionObject["scriptFile"]) { entryPointAndHandlerPath.handlerPath = functionObject["scriptFile"]; } - const metaData = { - entryPoint: entryPointAndHandlerPath[constants.entryPoint], - handlerPath: entryPointAndHandlerPath.handlerPath, + let { handlerPath, entryPoint } = entryPointAndHandlerPath; + + return { + entryPoint, + handlerPath: relative(functionName, handlerPath), params: params }; - - return metaData; } public static getEntryPointAndHandlerPath(handler: string) { let handlerPath = "handler.js"; let entryPoint = handler; const handlerSplit = handler.split("."); - const slashIndex = handler.lastIndexOf("/"); if (handlerSplit.length > 1) { entryPoint = handlerSplit[handlerSplit.length - 1]; - handlerPath = `${handler.substring(slashIndex + 1, handler.lastIndexOf("."))}.js`; + handlerPath = `${handler.substring(0, handler.lastIndexOf("."))}.js`; } const metaData = { diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index 35e99c3c..25ac9bf8 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -1,5 +1,5 @@ import { ApiContract, ApiManagementServiceResource } from "@azure/arm-apimanagement/esm/models"; -import { Site } from "@azure/arm-appservice/esm/models"; +import { Site, FunctionEnvelope } from "@azure/arm-appservice/esm/models"; import { HttpHeaders, HttpOperationResponse, HttpResponse, WebResource } from "@azure/ms-rest-js"; import { AuthResponse, LinkedSubscription, TokenCredentialsBase } from "@azure/ms-rest-nodeauth"; import { TokenClientCredentials, TokenResponse } from "@azure/ms-rest-nodeauth/dist/lib/credentials/tokenClientCredentials"; @@ -9,6 +9,9 @@ import Serverless from "serverless"; import Service from "serverless/classes/Service"; import Utils from "serverless/classes/Utils"; import PluginManager from "serverless/lib/classes/PluginManager"; +import { ServerlessAzureConfig } from "../models/serverless"; +import { AzureServiceProvider, ServicePrincipalEnvVariables } from "../models/azureProvider" +import { Logger } from "../models/generic"; function getAttribute(object: any, prop: string, defaultValue: any): any { if (object && object[prop]) { @@ -25,11 +28,6 @@ export class MockFactory { sls.pluginManager = getAttribute(config, "pluginManager", MockFactory.createTestPluginManager()); sls.variables = getAttribute(config, "variables", MockFactory.createTestVariables()); sls.service = getAttribute(config, "service", MockFactory.createTestService()); - sls.service.getAllFunctions = jest.fn(() => { - return Object.keys(sls.service["functions"]); - }) - sls.service.getAllFunctionsNames = sls.service.getAllFunctions; - sls.service.getServiceName = jest.fn(() => sls.service["service"]); sls.config.servicePath = ""; return sls; } @@ -59,6 +57,10 @@ export class MockFactory { } as any as Service; } + public static updateService(sls: Serverless) { + sls.service = MockFactory.createTestService(sls.service["functions"]); + } + public static createTestServerlessOptions(): Serverless.Options { return { extraServicePath: null, @@ -87,7 +89,8 @@ export class MockFactory { public static createTestAuthResponse(): AuthResponse { return { - credentials: "credentials" as any as TokenCredentialsBase, + credentials: MockFactory.createTestVariables() + .azureCredentials as any as TokenCredentialsBase, subscriptions: [ { id: "azureSubId", @@ -176,32 +179,37 @@ export class MockFactory { return Promise.resolve(response); } - public static createTestServerlessYml(asYaml = false, functionMetadata?) { + public static createTestServerlessYml(asYaml = false, functionMetadata?): ServerlessAzureConfig { const data = { - "provider": { - "name": "azure", - "location": "West US 2" + provider: { + name: "azure", + location: "West US 2" }, - "plugins": [ + plugins: [ "serverless-azure-functions" ], - "functions": functionMetadata || MockFactory.createTestFunctionsMetadata(2, false), + functions: functionMetadata || MockFactory.createTestSlsFunctionConfig(), } return (asYaml) ? yaml.dump(data) : data; } - public static createTestFunctionsMetadata(functionCount = 2, wrap = false) { - const data = {}; - for (let i = 0; i < functionCount; i++) { - const functionName = `function${i + 1}`; - data[functionName] = MockFactory.createTestFunctionMetadata() - } - return (wrap) ? { "functions": data } : data; + public static createTestFunctionApimConfig(name: string) { + return { + apim: { + operations: [ + { + method: "get", + urlTemplate: name, + displayName: name, + }, + ], + }, + }; } - public static createTestFunctionMetadata() { + public static createTestFunctionMetadata(name: string) { return { - "handler": "index.handler", + "handler": `${name}.handler`, "events": MockFactory.createTestFunctionEvents(), } } @@ -224,35 +232,67 @@ export class MockFactory { ] } - public static createTestFunctionApp() { - return { - id: "App Id", - name: "App Name", - defaultHostName: "My Host Name" + public static createTestFunctionsResponse(functions?) { + const result = [] + functions = functions || MockFactory.createTestSlsFunctionConfig(); + for (const name of Object.keys(functions)) { + result.push({ properties: MockFactory.createTestFunctionEnvelope(name)}); } + return result; } - public static createTestAzureServiceProvider() { + public static createTestAzureServiceProvider(): AzureServiceProvider { return { resourceGroup: "myResourceGroup", deploymentName: "myDeploymentName", } } - public static createTestVariables() { + public static createTestServicePrincipalEnvVariables(): ServicePrincipalEnvVariables { return { - azureCredentials: "credentials", - subscriptionId: "subId", + azureSubId: "azureSubId", + azureServicePrincipalClientId: "azureServicePrincipalClientId", + azureServicePrincipalPassword: "azureServicePrincipalPassword", + azureServicePrincipalTenantId: "azureServicePrincipalTenantId", } } + public static createTestVariables() { + return { + azureCredentials: { + tokenCache: { + _entries: [ + { + accessToken: "token" + } + ] + } + }, + subscriptionId: "azureSubId", + } + } + public static createTestSite(name: string = "Test"): Site { return { + id: "appId", name: name, location: "West US", + defaultHostName: "myHostName", + enabledHostNames: [ + "myHostName" + ] }; } + public static createTestFunctionEnvelope(name: string = "TestFunction"): FunctionEnvelope { + return { + name, + config: { + bindings: MockFactory.createTestBindings() + } + } + } + public static createTestBindings(bindingCount = 3) { const bindings = []; for (let i = 0; i < bindingCount; i++) { @@ -298,30 +338,12 @@ export class MockFactory { public static createTestSlsFunctionConfig() { return { hello: { - handler: "index.handler", - apim: { - operations: [ - { - method: "get", - urlTemplate: "hello", - displayName: "Hello", - }, - ], - }, - events: MockFactory.createTestFunctionEvents(), + ...MockFactory.createTestFunctionMetadata("hello"), + ...MockFactory.createTestFunctionApimConfig("hello"), }, goodbye: { - handler: "index.handler", - apim: { - operations: [ - { - method: "get", - urlTemplate: "goodbye", - displayName: "Goodbye", - }, - ], - }, - events: MockFactory.createTestFunctionEvents(), + ...MockFactory.createTestFunctionMetadata("goodbye"), + ...MockFactory.createTestFunctionApimConfig("goodbye"), }, }; } @@ -350,8 +372,8 @@ export class MockFactory { return { appendFileSync: jest.fn(), copyDirContentsSync: jest.fn(), - dirExistsSync: jest.fn(), - fileExistsSync: jest.fn(), + dirExistsSync: jest.fn(() => false), + fileExistsSync: jest.fn(() => false), findServicePath: jest.fn(), generateShortId: jest.fn(), getVersion: jest.fn(), @@ -369,7 +391,26 @@ export class MockFactory { }; } - private static createTestCli() { + /** + * Create a mock "request" module factory used to mock request objects that support piping + * @param response The expected HTTP response + */ + public static createTestMockRequestFactory(response: any = {}) { + return jest.fn((options, callback) => { + setImmediate(() => callback(null, response)); + + // Required interface for .pipe() + return { + on: jest.fn(), + once: jest.fn(), + emit: jest.fn(), + write: jest.fn(), + end: jest.fn(), + }; + }); + } + + private static createTestCli(): Logger { return { log: jest.fn(), }; diff --git a/src/test/utils.ts b/src/test/utils.ts index 23a6d56f..abebb1c9 100644 --- a/src/test/utils.ts +++ b/src/test/utils.ts @@ -2,6 +2,20 @@ export async function invokeHook(plugin: { hooks: { [eventName: string]: Promise return await (plugin.hooks[hook] as any)(); } +export function setEnvVariables(variables: any) { + const keys = Object.keys(variables); + for (const key of keys) { + process.env[key] = variables[key]; + } +} + +export function unsetEnvVariables(variables: any) { + const keys = Object.keys(variables); + for (const key of keys) { + delete process.env[key]; + } +} + /** * Stringifies the JSON and substitutes values from params * @param json JSON object From 529d2a2c0fec48dcd3403f01cd509e484a04b792 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 14 Jun 2019 12:49:41 -0700 Subject: [PATCH 3/7] Add documentation for offline --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 0cfb1515..f545fbb2 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,33 @@ This will remove the `{functionName}` directory and remove the function from `se *Note: Add & remove currently only support HTTP triggered functions. For other triggers, you will need to update `serverless.yml` manually +### Running Function App Locally (`offline` plugin) + +In order to run a Azure Function App locally, the `azure-functions-core-tools` package needs to be installed from NPM. Since it is only used for local development, we did not include it in the `devDependencies` of `package.json`. To install globally, run: + +```bash +npm i azure-functions-core-tools -g +``` + +Then, at the root of your project directory, run: + +```bash +# Builds necessary function bindings files +sls offline +# Starts the function app +npm start +``` + +The build process will generate a directory for each of your functions, which will contain a file titled `function.json`. This will contain a relative reference to your handler file & exported function from that file as long as they are referenced correctly in `serverless.yml`. + +The `npm start` script just runs `func host start`, but we included the `npm` script for ease of use. + +To clean up files generated from the build, you can simply run: + +```bash +sls offline cleanup +``` + ### Deploy, test, and diagnose your Azure service 1. Deploy your new service to Azure! The first time you do this, you will be asked to authenticate with your Azure account, so the `serverless` CLI can manage Functions on your behalf. Simply follow the provided instructions, and the deployment will continue as soon as the authentication process is completed. From c6e46b4e002bf4bc3fb5416c23d3d5dc464fd998 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 14 Jun 2019 13:37:40 -0700 Subject: [PATCH 4/7] Restore mocks after usage --- src/plugins/func/azureFunc.test.ts | 2 ++ src/plugins/offline/azureOfflinePlugin.test.ts | 2 ++ src/services/offlineService.test.ts | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/plugins/func/azureFunc.test.ts b/src/plugins/func/azureFunc.test.ts index 26b55ab8..9c490c1a 100644 --- a/src/plugins/func/azureFunc.test.ts +++ b/src/plugins/func/azureFunc.test.ts @@ -127,6 +127,8 @@ describe("Azure Func Plugin", () => { await invokeHook(plugin, "func:remove:remove"); expect(unlinkSpy).toBeCalledWith(`${functionName}.js`) expect(rimrafSpy).toBeCalledWith(functionName); + unlinkSpy.mockRestore(); + rimrafSpy.mockRestore(); const expectedFunctionsYml = MockFactory.createTestSlsFunctionConfig(); delete expectedFunctionsYml[functionName]; expect(sls.utils.writeFileSync).toBeCalledWith("serverless.yml", MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) diff --git a/src/plugins/offline/azureOfflinePlugin.test.ts b/src/plugins/offline/azureOfflinePlugin.test.ts index fbed65da..8a236f2e 100644 --- a/src/plugins/offline/azureOfflinePlugin.test.ts +++ b/src/plugins/offline/azureOfflinePlugin.test.ts @@ -63,5 +63,7 @@ describe("Azure Offline Plugin", () => { const rmdirCalls = rmdirSpy.mock.calls; expect(rmdirCalls[0][0]).toBe("hello"); expect(rmdirCalls[1][0]).toBe("goodbye"); + unlinkSpy.mockRestore(); + rmdirSpy.mockRestore(); }); }); \ No newline at end of file diff --git a/src/services/offlineService.test.ts b/src/services/offlineService.test.ts index 4c6727cd..4c4e3c46 100644 --- a/src/services/offlineService.test.ts +++ b/src/services/offlineService.test.ts @@ -58,6 +58,8 @@ describe("Offline Service", () => { const rmdirCalls = rmdirSpy.mock.calls; expect(rmdirCalls[0][0]).toBe("hello"); expect(rmdirCalls[1][0]).toBe("goodbye"); + unlinkSpy.mockRestore(); + rmdirSpy.mockRestore(); }); it("instructs users how to run locally", async () => { From 8eeca1d42d608d3e9661abfb2ba385f523082b68 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 14 Jun 2019 17:00:05 -0700 Subject: [PATCH 5/7] Generating local.settings.json if it doesn't exist --- .../offline/azureOfflinePlugin.test.ts | 33 +++++++++++++------ src/services/offlineService.test.ts | 20 +++++++---- src/services/offlineService.ts | 29 ++++++++++++++++ src/shared/constants.ts | 2 +- src/test/mockFactory.ts | 4 +-- 5 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/plugins/offline/azureOfflinePlugin.test.ts b/src/plugins/offline/azureOfflinePlugin.test.ts index 8a236f2e..f786b78c 100644 --- a/src/plugins/offline/azureOfflinePlugin.test.ts +++ b/src/plugins/offline/azureOfflinePlugin.test.ts @@ -15,30 +15,33 @@ describe("Azure Offline Plugin", () => { ) } - beforeAll(() => { + beforeEach(() => { mockFs({}) }); - afterAll(() => { - mockFs.restore(); - }); - afterEach(() => { - jest.clearAllMocks(); + mockFs.restore(); }) it("invokes build hook", async () => { const sls = MockFactory.createTestServerless(); const plugin = createPlugin(sls); + const writeFileSpy = jest.spyOn(fs, "writeFileSync"); await invokeHook(plugin, "offline:build:build"); - const calls = (sls.utils.writeFileSync as any).mock.calls; + const calls = writeFileSpy.mock.calls; const functionNames = sls.service.getAllFunctions(); - const expectedFunctionJson = MockFactory.createTestBindingsObject(); - for (let i = 0; i < calls.length; i++) { + expect(calls).toHaveLength(functionNames.length + 1); + for (let i = 0; i < functionNames.length; i++) { const name = functionNames[i]; expect(calls[i][0]).toEqual(`${name}${path.sep}function.json`) - expect(JSON.parse(calls[i][1])).toEqual(expectedFunctionJson); + expect( + JSON.parse(calls[i][1]) + ).toEqual( + MockFactory.createTestBindingsObject(`..${path.sep}${name}.js`) + ); } + expect(calls[calls.length - 1][0]).toEqual("local.settings.json"); + writeFileSpy.mockRestore(); }); it("invokes offline hook", async () => { @@ -52,6 +55,15 @@ describe("Azure Offline Plugin", () => { }); it("invokes cleanup hook", async () => { + mockFs({ + hello: { + "function.json": "contents" + }, + goodbye: { + "function.json": "contents" + }, + "local.settings.json": "contents", + }); const unlinkSpy = jest.spyOn(fs, "unlinkSync"); const rmdirSpy = jest.spyOn(fs, "rmdirSync") const sls = MockFactory.createTestServerless(); @@ -60,6 +72,7 @@ describe("Azure Offline Plugin", () => { const unlinkCalls = unlinkSpy.mock.calls; expect(unlinkCalls[0][0]).toBe(`hello${path.sep}function.json`); expect(unlinkCalls[1][0]).toBe(`goodbye${path.sep}function.json`); + expect(unlinkCalls[2][0]).toBe("local.settings.json"); const rmdirCalls = rmdirSpy.mock.calls; expect(rmdirCalls[0][0]).toBe("hello"); expect(rmdirCalls[1][0]).toBe("goodbye"); diff --git a/src/services/offlineService.test.ts b/src/services/offlineService.test.ts index 4c4e3c46..c4168007 100644 --- a/src/services/offlineService.test.ts +++ b/src/services/offlineService.test.ts @@ -21,21 +21,27 @@ describe("Offline Service", () => { afterEach(() => { mockFs.restore(); - jest.clearAllMocks(); }) it("builds required files for offline execution", async () => { const sls = MockFactory.createTestServerless(); const service = createService(sls); + const writeFileSpy = jest.spyOn(fs, "writeFileSync"); await service.build(); - const calls = (sls.utils.writeFileSync as any).mock.calls; + const calls = writeFileSpy.mock.calls; const functionNames = sls.service.getAllFunctions(); - const expectedFunctionJson = MockFactory.createTestBindingsObject(); - for (let i = 0; i < calls.length; i++) { + expect(calls).toHaveLength(functionNames.length + 1); + for (let i = 0; i < functionNames.length; i++) { const name = functionNames[i]; expect(calls[i][0]).toEqual(`${name}${path.sep}function.json`) - expect(JSON.parse(calls[i][1])).toEqual(expectedFunctionJson); + expect( + JSON.parse(calls[i][1]) + ).toEqual( + MockFactory.createTestBindingsObject(`..${path.sep}${name}.js`) + ); } + expect(calls[calls.length - 1][0]).toEqual("local.settings.json"); + writeFileSpy.mockRestore(); }); it("cleans up functions files", async () => { @@ -45,7 +51,8 @@ describe("Offline Service", () => { }, goodbye: { "function.json": "contents" - } + }, + "local.settings.json": "contents", }) const sls = MockFactory.createTestServerless(); const service = createService(sls); @@ -55,6 +62,7 @@ describe("Offline Service", () => { const unlinkCalls = unlinkSpy.mock.calls; expect(unlinkCalls[0][0]).toBe(`hello${path.sep}function.json`); expect(unlinkCalls[1][0]).toBe(`goodbye${path.sep}function.json`); + expect(unlinkCalls[2][0]).toBe("local.settings.json"); const rmdirCalls = rmdirSpy.mock.calls; expect(rmdirCalls[0][0]).toBe("hello"); expect(rmdirCalls[1][0]).toBe("goodbye"); diff --git a/src/services/offlineService.ts b/src/services/offlineService.ts index 708d7f0d..5661c83d 100644 --- a/src/services/offlineService.ts +++ b/src/services/offlineService.ts @@ -1,10 +1,21 @@ import Serverless from "serverless"; +import fs from "fs"; import { BaseService } from "./baseService"; import { PackageService } from "./packageService"; export class OfflineService extends BaseService { private packageService: PackageService; + + private localFiles = { + "local.settings.json": JSON.stringify({ + IsEncrypted: false, + Values: { + AzureWebJobsStorage: "", + FUNCTIONS_WORKER_RUNTIME: "node" + } + }), + } public constructor(serverless: Serverless, options: Serverless.Options) { super(serverless, options, false); @@ -14,11 +25,29 @@ export class OfflineService extends BaseService { public async build() { this.log("Building offline service"); await this.packageService.createBindings(); + const filenames = Object.keys(this.localFiles); + for (const filename of filenames) { + if (!fs.existsSync(filename)){ + fs.writeFileSync( + filename, + this.localFiles[filename] + ) + } + } this.log("Finished building offline service"); } public async cleanup() { + this.log("Cleaning up offline files") await this.packageService.cleanUp(); + const filenames = Object.keys(this.localFiles); + for (const filename of filenames) { + if (fs.existsSync(filename)){ + this.log(`Removing file '${filename}'`); + fs.unlinkSync(filename) + } + } + this.log("Finished cleaning up offline files"); } public start() { diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 979bb9a7..4ed98f73 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -20,5 +20,5 @@ export const constants = { queue: "queue", queueName: "queueName", xAzureSettings: "x-azure-settings", - entryPoint: "entryPoint" + entryPoint: "entryPoint", } \ No newline at end of file diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index 25ac9bf8..23eb434e 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -323,9 +323,9 @@ export class MockFactory { } } - public static createTestBindingsObject() { + public static createTestBindingsObject(name: string = "index.js") { return { - scriptFile: "index.js", + scriptFile: name, entryPoint: "handler", disabled: false, bindings: [ From 16680baff0199dad5c902c45e3678251c220b750 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Mon, 17 Jun 2019 10:16:00 -0700 Subject: [PATCH 6/7] Add sls config file getter to base service --- src/services/baseService.ts | 4 ++++ src/services/funcService.ts | 13 +++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/services/baseService.ts b/src/services/baseService.ts index 68aada6b..d95bd169 100644 --- a/src/services/baseService.ts +++ b/src/services/baseService.ts @@ -77,4 +77,8 @@ export abstract class BaseService { protected slsFunctions() { return this.serverless.service["functions"]; } + + protected slsConfigFile(): string { + return ("config" in this.options) ? this.options["config"] : "serverless.yml"; + } } diff --git a/src/services/funcService.ts b/src/services/funcService.ts index 513ad490..13aa4c5b 100644 --- a/src/services/funcService.ts +++ b/src/services/funcService.ts @@ -65,13 +65,16 @@ export class FuncService extends BaseService { } private getServerlessYml() { - return this.serverless.utils.readFileSync("serverless.yml"); + return this.serverless.utils.readFileSync(this.slsConfigFile()); } private updateFunctionsYml(functionYml: any) { const serverlessYml = this.getServerlessYml(); serverlessYml["functions"] = functionYml; - this.serverless.utils.writeFileSync("serverless.yml", yaml.dump(serverlessYml)); + this.serverless.utils.writeFileSync( + this.slsConfigFile(), + yaml.dump(serverlessYml) + ); } private getFunctionHandler(name: string) { @@ -95,12 +98,6 @@ module.exports.handler = async function (context, req) { };` } - private getFunctionJsonString(name: string, options: any) { - // TODO: This is where we would just generate function JSON from SLS object - // using getFunctionSlsObject(name, options). Currently defaulting to http in and out - return JSON.stringify(httpBinding, null, 2); - } - private getFunctionSlsObject(name: string) { return this.defaultFunctionSlsObject(name); } From 17047875724d4ec42e51b5e34a10c6327bf602ac Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 19 Jun 2019 13:03:10 -0700 Subject: [PATCH 7/7] Get rid of extraneous files --- .travis.yml | 65 +++++++---- package-lock.json | 16 ++- package.json | 9 +- scripts/npmPublishBeta.sh | 25 +++++ src/models/apiManagement.ts | 29 +++++ src/plugins/func/bindingTemplates/http.json | 19 ---- src/plugins/func/funcUtils.ts | 77 ------------- src/services/apimService.test.ts | 36 +++++- src/services/apimService.ts | 104 ++++++++++++++---- src/services/funcService.ts | 1 - src/test/mockFactory.ts | 15 ++- .../responses/apim-put-api-policy-200.json | 9 ++ 12 files changed, 262 insertions(+), 143 deletions(-) create mode 100644 scripts/npmPublishBeta.sh delete mode 100644 src/plugins/func/bindingTemplates/http.json delete mode 100644 src/plugins/func/funcUtils.ts create mode 100644 src/test/responses/apim-put-api-policy-200.json diff --git a/.travis.yml b/.travis.yml index ba9b137a..5d82365c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,53 @@ +# since we don't need sudo, travis recommend xenial +dist: xenial + language: node_js -os: linux +# we don't need previous commits, so don't need default depth of 50 +git: + depth: 1 -matrix: - include: - - node_js: '8' - - node_js: '10' +# have to enable branch build to trigger on git tag but +# we don't want the same job to run twice on pull request +branches: + only: + - master + - dev + - /^v.*$/ # Ensure to build release tags of format v1.0.0-1 -sudo: false +stages: + - test + - name: publish + if: tag IS present # https://docs.travis-ci.com/user/conditions-v1 install: -- npm install -script: - - npm run test:ci + - npm install -after_success: -- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage +jobs: + include: + - stage: test + name: "Unit Tests on Node 8" + node_js: "8" + script: + - npm run test:ci + after_success: + - npm run test:coverage + - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage + - name: "Unit Tests on Node 10" + node_js: "10" -deploy: - provider: npm - email: $NPM_EMAIL_ACCOUNT - api_key: - secure: Iu5Q2uuiF+jRWkNRtX77YElMwyEopzKD+iHQQkT7HheH12AwDAW/PZiwLRhBTif8kvHmIIwPhjey6i7QwmJFUmpb433299WF+tinRhy5mBFntdaNmgS2Wvv/hOFJza2XdOeIsKA+s+zPAGArfLonmvQX5QKVmJI7/W+hyqJT0FjtTMqxPyJ00ENuDeZdUEcsaoDAwu1XTA6bj5ILnguR+smeGoZscpp8nwTv27fA+GWLFGf08pPAyuHSwc1/lRxSbLlw/f0ZqnCclGU+SF2EV4/9+xxcPWqtJdJfURR/LaOkg3Q5o4aILRjNJNCbpRkrc5lEcPEvBPLrI6e7F9ssg08aHniaMvdD5B9TsgGqV6sxZX/KvMIHWfzBRLuCzY2bPE8Wbw+ZI/yi9+89NZdRikAMyWocvg4tx1OsQRcrRltsXqQrZyw45IR5oSv0kLx54xrslXu8jjP08jQEpnBncJp883DX6DmO42TRP4QYHsQ8BB5hHQ2qmVoKMydkLy0Kdvq7e+9v/pZHEMsR/HOwz1S5xYsSeqwqoq4u/H+hPFWu3GVJXbsVLHZuaDU3fBRMtJOcjujPszrvN/cNt2JryTT1EYPJotmpURqu/n9vPLMzoi1wIKPrZiAcEzh8MLlM93MyG/yuPeHZOvMuwCVtOXd3KKH8sEJvbTVUtKIvQXw= - on: - tags: true - repo: serverless/serverless-azure-functions - branch: master + - stage: publish + name: "NPM release for dev" # rename before merge to master + node_js: "8" + before_deploy: + - npm run build + deploy: + - provider: npm + email: "sls-az@microsoft.com" + api_key: + secure: hFgX3Ru3/CnT90d5WYXZ6Yr91zsHw+D+ZxaDdimtjMSjhZjKgA6HJ82A9SlpVRIm0WcwVYyBX8nd2j4d+Gkcefd8B4tt/UHOxuzFmt9Q5GvyjdKgA0jzY4XqjUogh142IUJUABRodmh8Wr7mHWCXhNNJu17UUyEf+KRliz72Orp36JEmozdgf+CpzVyzGUL2OLBZSmkjpFTUS4clOVhzwj0G2IHNB7qcupAVQ+r3DOjiU1N4+KkT+yrWH1esqOZYWVcqyVTUgkrfLU1eyaP8IemS+nT3Y9l0VxXY6nnZ4fkENbrivaN5cgp/GPSFb6QSiMbwImaPgxhyj2sIb1ydnoza5lR83b5Tsaa5E90gUbU23kms9J0Qi+6WLaI63kdfvQidl2A5UVpDr+lVusg0gfibkMUo4Jbof67o9coAu+WO80qS0jdn9cAg3gX6Xx9c7fUdHrFfxw4TBxjfI0SO7gJwbZvLZsZ9WsTT/SflIUQL72MhiYTQNIk7ewIVQXjb6eDuZS0tgSu3Gb0W7CIsVW7fwMmMvlEfzkzH119YYVUwwBCACfCgyXaFaamJ1tLHRlp1yjDjkBMZLlbOh18cSrTBu86+RBSX+I1jRvNoUxSTtzZjCI9i7sv9lEP2zq5EEMbA8qjCguu48knqQIrzsPNVCysKrNI964aVb8k4MyY= + skip_cleanup: true + tag: beta # tag all npm publish from dev branch with "beta" TODO: remove before merge to master + on: # https://docs.travis-ci.com/user/deployment/#conditional-releases-with-on + tags: true + repo: serverless/serverless-azure-functions diff --git a/package-lock.json b/package-lock.json index 6839d73f..b5ab78e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless-azure-functions", - "version": "0.7.0", + "version": "1.0.0-3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1558,6 +1558,15 @@ "@types/node": "*" } }, + "@types/xml": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/xml/-/xml-1.0.3.tgz", + "integrity": "sha512-qeqQIjDfSLjmWR0noFQmcPKCtqn0L68MchoEi1Zj33unPfC83Op3j2mBH2g4hAgOaWUobv/O86w7LObo6p4sDQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "12.0.12", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz", @@ -11130,6 +11139,11 @@ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/package.json b/package.json index ef09617e..720da473 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-azure-functions", - "version": "0.7.0", + "version": "1.0.0-3", "description": "Provider plugin for the Serverless Framework v1.x which adds support for Azure Functions.", "license": "MIT", "main": "./lib/index.js", @@ -31,6 +31,9 @@ "internet of things", "serverless.com" ], + "files": [ + "lib/" + ], "dependencies": { "@azure/arm-apimanagement": "^5.1.0", "@azure/arm-appservice": "^5.7.0", @@ -42,7 +45,8 @@ "lodash": "^4.16.6", "open": "^6.3.0", "request": "^2.81.0", - "rimraf": "^2.6.3" + "rimraf": "^2.6.3", + "xml": "^1.0.1" }, "devDependencies": { "@babel/runtime": "^7.4.5", @@ -52,6 +56,7 @@ "@types/open": "^6.1.0", "@types/request": "^2.48.1", "@types/serverless": "^1.18.2", + "@types/xml": "^1.0.3", "@typescript-eslint/eslint-plugin": "^1.9.0", "@typescript-eslint/parser": "^1.9.0", "axios-mock-adapter": "^1.16.0", diff --git a/scripts/npmPublishBeta.sh b/scripts/npmPublishBeta.sh new file mode 100644 index 00000000..a3c30ec3 --- /dev/null +++ b/scripts/npmPublishBeta.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +set -e + +echo "This script will bump npm version and push changes to new branch" + +# echo "checkout new branch for version bump" +git checkout -b npmRelease + +## configure npm to sign commit +npm config set sign-git-tag true + +version=$(npm version prerelease -m "Bumped to version %s") + +echo "Bumped to version ${version}" + +# remove git tag, we don't want to tag pr +git tag -d ${version} + +# push to remote +git push origin npmRelease + +## only for testing CD on test branch +## after development, we push tag on dev/master manually +# git push origin ${version} diff --git a/src/models/apiManagement.ts b/src/models/apiManagement.ts index 0ba19750..8c8abeac 100644 --- a/src/models/apiManagement.ts +++ b/src/models/apiManagement.ts @@ -1,12 +1,41 @@ import { OperationContract, ApiContract, BackendContract } from "@azure/arm-apimanagement/esm/models"; +/** + * Defines the serverless APIM configuration + */ export interface ApiManagementConfig { + /** The name of the APIM azure resource */ name: string; + /** The API contract configuration */ api: ApiContract; + /** The API's backend contract configuration */ backend?: BackendContract; + /** The API's CORS policy */ + cors?: ApiCorsPolicy; } +/** + * Defines the APIM API Operation configuration + */ export interface ApiOperationOptions { + /** The name of the serverless function */ function: string; + /** The APIM operation contract configuration */ operation: OperationContract; +} + +/** + * Defines an APIM API CORS (cross origin resource sharing) policy + */ +export interface ApiCorsPolicy { + /** Whether or not to allow credentials */ + allowCredentials: boolean; + /** A list of allowed domains - also supports wildcard "*" */ + allowedOrigins: string[]; + /** A list of allowed HTTP methods */ + allowedMethods: string[]; + /** A list of allowed headers */ + allowedHeaders: string[]; + /** A list of headers exposed during OPTION preflight requests */ + exposeHeaders: string[]; } \ No newline at end of file diff --git a/src/plugins/func/bindingTemplates/http.json b/src/plugins/func/bindingTemplates/http.json deleted file mode 100644 index 9f2d4d85..00000000 --- a/src/plugins/func/bindingTemplates/http.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get", - "post" - ] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] -} \ No newline at end of file diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts deleted file mode 100644 index afc1449f..00000000 --- a/src/plugins/func/funcUtils.ts +++ /dev/null @@ -1,77 +0,0 @@ -import yaml from "js-yaml"; -import Serverless from "serverless"; -import httpBinding from "./bindingTemplates/http.json" - -export class FuncPluginUtils { - - public static getServerlessYml(sls: Serverless) { - return sls.utils.readFileSync("serverless.yml"); - } - - public static getFunctionsYml(sls: Serverless, serverlessYml?: any) { - serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(sls); - return serverlessYml["functions"]; - } - - public static updateFunctionsYml(sls: Serverless, functionYml: any, serverlessYml?: any) { - serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(sls); - serverlessYml["functions"] = functionYml; - sls.utils.writeFileSync("serverless.yml", yaml.dump(serverlessYml)); - } - - public static getFunctionHandler(name: string) { - return `"use strict"; - -module.exports.handler = async function (context, req) { - context.log("JavaScript HTTP trigger function processed a request."); - - if (req.query.name || (req.body && req.body.name)) { - context.res = { - // status: 200, /* Defaults to 200 */ - body: "${name} " + (req.query.name || req.body.name) - }; - } - else { - context.res = { - status: 400, - body: "Please pass a name on the query string or in the request body" - }; - } -};` - } - - public static getFunctionJsonString(name: string, options: any) { - // TODO: This is where we would just generate function JSON from SLS object - // using getFunctionSlsObject(name, options). Currently defaulting to http in and out - return JSON.stringify(httpBinding, null, 2); - } - - public static getFunctionSlsObject(name: string, options: any) { - return FuncPluginUtils.defaultFunctionSlsObject(name); - } - - private static defaultFunctionSlsObject(name: string) { - return { - handler: `src/handlers/${name}.handler`, - events: FuncPluginUtils.httpEvents() - } - } - - private static httpEvents() { - return [ - { - http: true, - "x-azure-settings": { - authLevel: "anonymous" - } - }, - { - http: true, - "x-azure-settings": { - direction: "out", - name: "res" - } - }, - ] - } -} \ No newline at end of file diff --git a/src/services/apimService.test.ts b/src/services/apimService.test.ts index 51671acb..896c32a6 100644 --- a/src/services/apimService.test.ts +++ b/src/services/apimService.test.ts @@ -5,7 +5,7 @@ import { ApiManagementConfig } from "../models/apiManagement"; import { ApimService } from "./apimService"; import { interpolateJson } from "../test/utils"; import axios from "axios"; -import { Api, Backend, Property, ApiOperation, ApiOperationPolicy, ApiManagementService } from "@azure/arm-apimanagement"; +import { Api, Backend, Property, ApiOperation, ApiOperationPolicy, ApiManagementService, ApiPolicy } from "@azure/arm-apimanagement"; import apimGetService404 from "../test/responses/apim-get-service-404.json"; import apimGetService200 from "../test/responses/apim-get-service-200.json"; import apimGetApi200 from "../test/responses/apim-get-api-200.json"; @@ -18,6 +18,7 @@ import { ApiOperationCreateOrUpdateResponse, ApiManagementServiceResource, ApiGetResponse, ApiManagementServiceGetResponse, OperationContract, + ApiPolicyCreateOrUpdateResponse, } from "@azure/arm-apimanagement/esm/models"; describe("APIM Service", () => { @@ -235,13 +236,13 @@ describe("APIM Service", () => { }); it("ensures API, backend and keys have all been set", async () => { - Api.prototype.createOrUpdate = jest.fn(() => MockFactory.createTestArmSdkResponse(expectedApiResult, 201)); Backend.prototype.createOrUpdate = jest.fn(() => MockFactory.createTestArmSdkResponse(expectedBackend, 201)); Property.prototype.createOrUpdate = jest.fn(() => MockFactory.createTestArmSdkResponse(expectedProperty, 201)); + ApiPolicy.prototype.createOrUpdate = jest.fn(() => Promise.resolve(null)); const apimService = new ApimService(serverless); const result = await apimService.deployApi(); @@ -254,6 +255,9 @@ describe("APIM Service", () => { expectedApi, ); + // No CORS policy by default + expect(ApiPolicy.prototype.createOrUpdate).not.toBeCalled(); + expect(Backend.prototype.createOrUpdate).toBeCalledWith( resourceGroupName, serviceName, @@ -269,6 +273,34 @@ describe("APIM Service", () => { ); }); + it("deploys API CORS policy when defined within configuration", async () => { + Api.prototype.createOrUpdate = + jest.fn(() => MockFactory.createTestArmSdkResponse(expectedApiResult, 201)); + Backend.prototype.createOrUpdate = + jest.fn(() => MockFactory.createTestArmSdkResponse(expectedBackend, 201)); + Property.prototype.createOrUpdate = + jest.fn(() => MockFactory.createTestArmSdkResponse(expectedProperty, 201)); + ApiPolicy.prototype.createOrUpdate = + jest.fn(() => MockFactory.createTestArmSdkResponse(expectedProperty, 201)); + + const corsPolicy = MockFactory.createTestMockApiCorsPolicy(); + serverless.service.provider["apim"]["cors"] = corsPolicy; + + const apimService = new ApimService(serverless); + const result = await apimService.deployApi(); + + expect(result).not.toBeNull(); + expect(ApiPolicy.prototype.createOrUpdate).toBeCalledWith( + resourceGroupName, + serviceName, + apiName, + { + format: "rawxml", + value: expect.stringContaining("cors"), + } + ); + }); + it("returns null when APIM is not configured", async () => { serverless.service.provider["apim"] = null; diff --git a/src/services/apimService.ts b/src/services/apimService.ts index 55e54e85..a6452f95 100644 --- a/src/services/apimService.ts +++ b/src/services/apimService.ts @@ -1,8 +1,9 @@ import Serverless from "serverless"; +import xml from "xml"; import { ApiManagementClient } from "@azure/arm-apimanagement"; import { FunctionAppService } from "./functionAppService"; import { BaseService } from "./baseService"; -import { ApiManagementConfig, ApiOperationOptions } from "../models/apiManagement"; +import { ApiManagementConfig, ApiOperationOptions, ApiCorsPolicy } from "../models/apiManagement"; import { ApiContract, BackendContract, OperationContract, PropertyContract, ApiManagementServiceResource, @@ -130,7 +131,7 @@ export class ApimService extends BaseService { this.log("-> Deploying API"); try { - return await this.apimClient.api.createOrUpdate(this.resourceGroup, this.config.name, this.config.api.name, { + const api = await this.apimClient.api.createOrUpdate(this.resourceGroup, this.config.name, this.config.api.name, { isCurrent: true, subscriptionRequired: this.config.api.subscriptionRequired, displayName: this.config.api.displayName, @@ -138,8 +139,20 @@ export class ApimService extends BaseService { path: this.config.api.path, protocols: this.config.api.protocols, }); + + if (this.config.cors) { + this.log("-> Deploying CORS policy"); + + await this.apimClient.apiPolicy.createOrUpdate(this.resourceGroup, this.config.name, this.config.api.name, { + format: "rawxml", + value: this.createCorsXmlPolicy(this.config.cors) + }); + } + + return api; } catch (e) { this.log("Error creating APIM API"); + this.log(JSON.stringify(e.body, null, 4)); throw e; } } @@ -171,6 +184,7 @@ export class ApimService extends BaseService { }); } catch (e) { this.log("Error creating APIM Backend"); + this.log(JSON.stringify(e.body, null, 4)); throw e; } } @@ -210,28 +224,14 @@ export class ApimService extends BaseService { await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.config.name, this.config.api.name, options.function, { format: "rawxml", - value: ` - - - - - - - - - - - - - - - `, + value: this.createApiOperationXmlPolicy(), }); return operation; } catch (e) { this.log(`Error deploying API operation ${options.function}`); this.log(JSON.stringify(e.body, null, 4)); + throw e; } } @@ -239,7 +239,7 @@ export class ApimService extends BaseService { * Gets the master key for the function app and stores a reference in the APIM instance * @param functionAppUrl The host name for the Azure function app */ - private async ensureFunctionAppKeys(functionApp): Promise { + private async ensureFunctionAppKeys(functionApp: Site): Promise { this.log("-> Deploying API keys"); try { const masterKey = await this.functionAppService.getMasterKey(functionApp); @@ -252,7 +252,73 @@ export class ApimService extends BaseService { }); } catch (e) { this.log("Error creating APIM Property"); + this.log(JSON.stringify(e.body, null, 4)); throw e; } } + + /** + * Creates the XML payload that defines the API operation policy to link to the configured backend + */ + private createApiOperationXmlPolicy(): string { + const operationPolicy = [{ + policies: [ + { + inbound: [ + { base: null }, + { + "set-backend-service": [ + { + "_attr": { + "id": "apim-generated-policy", + "backend-id": this.serviceName, + } + }, + ], + }, + ], + }, + { backend: [{ base: null }] }, + { outbound: [{ base: null }] }, + { "on-error": [{ base: null }] }, + ] + }]; + + return xml(operationPolicy); + } + + /** + * Creates the XML payload that defines the specified CORS policy + * @param corsPolicy The CORS policy + */ + private createCorsXmlPolicy(corsPolicy: ApiCorsPolicy): string { + const origins = corsPolicy.allowedOrigins ? corsPolicy.allowedOrigins.map((origin) => ({ origin })) : null; + const methods = corsPolicy.allowedMethods ? corsPolicy.allowedMethods.map((method) => ({ method })) : null; + const allowedHeaders = corsPolicy.allowedHeaders ? corsPolicy.allowedHeaders.map((header) => ({ header })) : null; + const exposeHeaders = corsPolicy.exposeHeaders ? corsPolicy.exposeHeaders.map((header) => ({ header })) : null; + + const policy = [{ + policies: [ + { + inbound: [ + { base: null }, + { + cors: [ + { "_attr": { "allow-credentials": corsPolicy.allowCredentials } }, + { "allowed-origins": origins }, + { "allowed-methods": methods }, + { "allowed-headers": allowedHeaders }, + { "expose-headers": exposeHeaders }, + ] + } + ], + }, + { backend: [{ base: null }] }, + { outbound: [{ base: null }] }, + { "on-error": [{ base: null }] }, + ] + }]; + + return xml(policy, { indent: "\t" }); + } } \ No newline at end of file diff --git a/src/services/funcService.ts b/src/services/funcService.ts index 13aa4c5b..b84cda7a 100644 --- a/src/services/funcService.ts +++ b/src/services/funcService.ts @@ -1,7 +1,6 @@ import yaml from "js-yaml"; import rimraf from "rimraf"; import Serverless from "serverless"; -import httpBinding from "../plugins/func/bindingTemplates/http.json"; import { BaseService } from "./baseService"; import fs from "fs"; diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index 23eb434e..7efaaa32 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -12,6 +12,7 @@ import PluginManager from "serverless/lib/classes/PluginManager"; import { ServerlessAzureConfig } from "../models/serverless"; import { AzureServiceProvider, ServicePrincipalEnvVariables } from "../models/azureProvider" import { Logger } from "../models/generic"; +import { ApiCorsPolicy } from "../models/apiManagement"; function getAttribute(object: any, prop: string, defaultValue: any): any { if (object && object[prop]) { @@ -236,7 +237,7 @@ export class MockFactory { const result = [] functions = functions || MockFactory.createTestSlsFunctionConfig(); for (const name of Object.keys(functions)) { - result.push({ properties: MockFactory.createTestFunctionEnvelope(name)}); + result.push({ properties: MockFactory.createTestFunctionEnvelope(name) }); } return result; } @@ -271,7 +272,7 @@ export class MockFactory { subscriptionId: "azureSubId", } } - + public static createTestSite(name: string = "Test"): Site { return { id: "appId", @@ -410,6 +411,16 @@ export class MockFactory { }); } + public static createTestMockApiCorsPolicy(): ApiCorsPolicy { + return { + allowCredentials: false, + allowedOrigins: ["*"], + allowedHeaders: ["*"], + exposeHeaders: ["*"], + allowedMethods: ["GET","POST"], + }; + } + private static createTestCli(): Logger { return { log: jest.fn(), diff --git a/src/test/responses/apim-put-api-policy-200.json b/src/test/responses/apim-put-api-policy-200.json new file mode 100644 index 00000000..ca813695 --- /dev/null +++ b/src/test/responses/apim-put-api-policy-200.json @@ -0,0 +1,9 @@ +{ + "id": "/subscriptions/d36d0808-a967-4f73-9fdc-32ea232fc81d/resourceGroups/${resourceGroup.name}/providers/Microsoft.ApiManagement/service/${service.name}/apis/${api.name}/policies/policy", + "type": "Microsoft.ApiManagement/service/apis/policies", + "name": "policy", + "properties": { + "format": "xml", + "value": "\t\t\t\t\t\t\t\t\t\t\t\t*\t\t\t\t\t\t\t\t\t\tGET\t\t\t\tPOST\t\t\t\tPUT\t\t\t\tDELETE\t\t\t\tPATCH\t\t\t\t\t\t\t\t\t\t
*
\t\t\t
\t\t\t\t\t\t\t
*
\t\t\t
\t\t
\t
\t\t\t\t\t\t\t\t\t\t\t\t
" + } +} \ No newline at end of file