From c06a67851efb05c515c0431dbc08a3f0107f2c09 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 1 May 2019 15:13:01 -0400 Subject: [PATCH 001/504] built in integration of serverless enterprise --- lib/classes/PluginManager.js | 6 + lib/classes/PluginManager.test.js | 8 + package-lock.json | 729 +++++++++++++++++++++++++++++- package.json | 1 + 4 files changed, 737 insertions(+), 7 deletions(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 433e3dac7..72717e518 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -92,6 +92,7 @@ class PluginManager { loadAllPlugins(servicePlugins) { this.loadCorePlugins(); this.loadServicePlugins(servicePlugins); + this.loadEnterprisePlugin(); } loadPlugins(plugins) { @@ -151,6 +152,11 @@ class PluginManager { this.loadPlugins(pluginsObject.modules); } + loadEnterprisePlugin() { + const depNodeModules = path.join(__dirname, '../../node_modules'); + this.loadPlugins([`${depNodeModules}/@serverless/enterprise-plugin`]); + } + parsePluginsObject(servicePlugs) { let localPath = (this.serverless && this.serverless.config && this.serverless.config.servicePath) && diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index b1a9a14c5..0d8f91705 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -33,6 +33,8 @@ describe('PluginManager', () => { class ServicePluginMock2 {} + class EnterprisePluginMock {} + class BrokenPluginMock { constructor() { throw new Error('Broken plugin'); @@ -721,6 +723,7 @@ describe('PluginManager', () => { beforeEach(function () { // eslint-disable-line prefer-arrow-callback mockRequire('ServicePluginMock1', ServicePluginMock1); mockRequire('ServicePluginMock2', ServicePluginMock2); + mockRequire('@serverless/enterprise-plugin', EnterprisePluginMock); }); it('should load only core plugins when no service plugins are given', () => { @@ -738,9 +741,11 @@ describe('PluginManager', () => { const servicePluginMock1 = new ServicePluginMock1(); const servicePluginMock2 = new ServicePluginMock2(); + const enterprisePluginMock = new EnterprisePluginMock(); expect(pluginManager.plugins).to.contain(servicePluginMock1); expect(pluginManager.plugins).to.contain(servicePluginMock2); + expect(pluginManager.plugins).to.contain(enterprisePluginMock); // note: this test will be refactored as the Create plugin will be moved // to another directory expect(pluginManager.plugins.length).to.be.above(2); @@ -759,15 +764,18 @@ describe('PluginManager', () => { // This is the exact same functionality like loadCorePlugins() loadCorePluginsMock(); pluginManager.loadServicePlugins(servicePlugins); + pluginManager.loadEnterprisePlugin(); expect(pluginManager.plugins[0]).to.be.instanceof(Create); expect(pluginManager.plugins[1]).to.be.instanceof(ServicePluginMock1); expect(pluginManager.plugins[2]).to.be.instanceof(ServicePluginMock2); + expect(pluginManager.plugins[3]).to.be.instanceof(EnterprisePluginMock); }); afterEach(function () { // eslint-disable-line prefer-arrow-callback mockRequire.stop('ServicePluginMock1'); mockRequire.stop('ServicePluginMock2'); + mockRequire.stop('@serverless/enterprise-plugin'); }); }); diff --git a/package-lock.json b/package-lock.json index bdb92532a..d0c21e371 100644 --- a/package-lock.json +++ b/package-lock.json @@ -139,6 +139,15 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/polyfill": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.4.4.tgz", + "integrity": "sha512-WlthFLfhQQhh+A2Gn5NSFl0Huxz36x86Jn+E9OW7ibK8edKPq+KLy4apM1yDpQ8kJOVi1OVjpP4vSDLdrI04dg==", + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.2" + } + }, "@babel/template": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", @@ -444,6 +453,92 @@ "@types/yargs": "^12.0.9" } }, + "@serverless/enterprise-plugin": { + "version": "file:serverless-enterprise-plugin-0.4.0.tgz", + "integrity": "sha512-4yOXyqaQmTf+u1xM50dFJbiU4pQcAQxiyAXtCzZdiNW3BCxjWTz3ex+9UPO29mGcpy/laQHiyg2x2QTnaUb+GA==", + "requires": { + "@babel/polyfill": "^7.2.5", + "@serverless/event-mocks": "^1.1.1", + "@serverless/platform-sdk": "^0.7.2-13-gb9afd32", + "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", + "semver": "^5.6.0", + "serialize-error": "^4.1.0", + "yamljs": "^0.3.0", + "zlib": "^1.0.5" + }, + "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==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "node-fetch": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.5.0.tgz", + "integrity": "sha512-YuZKluhWGJwCcUu4RlZstdAxr8bFfOVHakc1mplwHkk8J+tqM1Y5yraYvIUpeX8aY7+crCwiELJq7Vl0o0LWXw==" + } + } + }, + "@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==", + "requires": { + "@types/lodash": "^4.14.123", + "lodash": "^4.17.11" + } + }, + "@serverless/platform-sdk": { + "version": "0.7.2-13-gb9afd32", + "resolved": "https://registry.npmjs.org/@serverless/platform-sdk/-/platform-sdk-0.7.2-13-gb9afd32.tgz", + "integrity": "sha512-18AMXJDjiVsZIzUZO4vmqBsc7S2qE5I6pARnGIRkOt0S3J5F0+eawSml7s+iEay8a9n0NRqNBEPxo4fFGbWKKQ==", + "requires": { + "babel-polyfill": "^6.26.0", + "body-parser": "^1.18.3", + "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.3.0", + "querystring": "^0.2.0", + "ramda": "^0.25.0", + "rc": "^1.2.8", + "source-map-support": "^0.5.5", + "uuid": "^3.3.2", + "write-file-atomic": "^2.3.0" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "@types/babel__core": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.0.tgz", @@ -491,6 +586,11 @@ "integrity": "sha512-ohkhb9LehJy+PA40rDtGAji61NCgdtKLAlFoYp4cnuuQEswwdK3vz9SOIkkyc3wrk8dzjphQApNs56yyXLStaQ==", "dev": true }, + "@types/lodash": { + "version": "4.14.123", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.123.tgz", + "integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==" + }, "@types/node": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.12.0.tgz", @@ -521,6 +621,30 @@ "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", "dev": true }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } + } + }, "acorn": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", @@ -790,6 +914,11 @@ "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=" + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -1071,6 +1200,23 @@ "@types/babel__traverse": "^7.0.6" } }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" + } + } + }, "babel-preset-jest": { "version": "24.3.0", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.3.0.tgz", @@ -1081,6 +1227,22 @@ "babel-plugin-jest-hoist": "^24.3.0" } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1138,6 +1300,43 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "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==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } + } + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -1319,6 +1518,11 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -1702,6 +1906,16 @@ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "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==" + }, "convert-source-map": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", @@ -1716,6 +1930,11 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, "cookiejar": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", @@ -1726,11 +1945,25 @@ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" + }, "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==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "coveralls": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.3.tgz", @@ -2019,6 +2252,16 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -2098,6 +2341,16 @@ "safer-buffer": "^2.1.0" } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, "encoding": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", @@ -2232,6 +2485,11 @@ "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=" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2576,6 +2834,11 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -2758,6 +3021,125 @@ "jest-regex-util": "^24.3.0" } }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "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.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "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" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2970,6 +3352,40 @@ } } }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.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==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -2980,6 +3396,21 @@ "pinkie-promise": "^2.0.0" } }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "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==" + } + } + }, "flat-cache": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", @@ -3027,6 +3458,11 @@ "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -3035,6 +3471,11 @@ "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=" + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -3399,6 +3840,18 @@ } } }, + "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==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, "http-response-object": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-1.1.0.tgz", @@ -3614,6 +4067,11 @@ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": 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==" + }, "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", @@ -3850,8 +4308,7 @@ "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" }, "isarray": { "version": "1.0.0", @@ -3863,11 +4320,25 @@ "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==" + }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "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=", + "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", @@ -4763,6 +5234,11 @@ "minimist": "^1.2.0" } }, + "jsonata": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-1.6.4.tgz", + "integrity": "sha512-3MWTH77OHLf3muMknZJS4GnDhGPMITyF9D84hpRQrjt1Hk3pBtTiyZcqodHUDSaDq8VDy9YyIbanRI+3RoW3FA==" + }, "jsonfile": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", @@ -5219,6 +5695,11 @@ "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", "dev": true }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, "mem": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.2.0.tgz", @@ -5233,8 +5714,7 @@ "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 + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, "merge-stream": { "version": "1.0.1", @@ -5462,6 +5942,11 @@ "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==" + }, "neo-async": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", @@ -5480,6 +5965,14 @@ "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=", + "requires": { + "minimatch": "^3.0.2" + } + }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", @@ -5709,6 +6202,14 @@ "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=", + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5722,6 +6223,14 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "requires": { + "is-wsl": "^1.1.0" + } + }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", @@ -5920,6 +6429,11 @@ "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==" + }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -5964,6 +6478,11 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "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=" + }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -6126,6 +6645,15 @@ "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" }, + "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==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, "proxyquire": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-1.8.0.tgz", @@ -6181,6 +6709,11 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "ramda": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", + "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" + }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -6200,6 +6733,11 @@ } } }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, "raven": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", @@ -6219,6 +6757,17 @@ } } }, + "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==", + "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", @@ -6359,6 +6908,11 @@ "resolve": "^1.1.6" } }, + "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==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -6708,6 +7262,86 @@ "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz", "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=" }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "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.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.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" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "serialize-error": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-4.1.0.tgz", + "integrity": "sha512-5j9GgyGsP9vV9Uj1S0lDCvlsd+gc2LEPVK7HHHte7IyPwOD4lVQFeaX143gx3U5AnoCi+wbcb3mvaxVysjpxEw==", + "requires": { + "type-fest": "^0.3.0" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -6754,6 +7388,11 @@ } } }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -6995,7 +7634,6 @@ "version": "0.5.11", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.11.tgz", "integrity": "sha512-//sajEx/fGL3iw6fltKMdPvy8kL3kJ2O3iuYlRoT3k9Kb4BjOoZ+BZzaNHeuaruSt+Kf3Zk9tnfAQg9/AJqUVQ==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -7004,8 +7642,7 @@ "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 + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -7166,6 +7803,11 @@ } } }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", @@ -7561,6 +8203,11 @@ "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==" + }, "toml": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz", @@ -7645,6 +8292,35 @@ "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", "dev": true }, + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -7760,6 +8436,16 @@ "crypto-random-string": "^1.0.0" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "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=" + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -7903,6 +8589,11 @@ "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=" + }, "uuid": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", @@ -7918,6 +8609,11 @@ "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=" + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -7967,6 +8663,11 @@ "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==" + }, "whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", @@ -8130,6 +8831,15 @@ "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.34.tgz", "integrity": "sha1-0A88+ddztyQUCa6SpnQNHbGfSeY=" }, + "yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "requires": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + } + }, "yargs": { "version": "12.0.5", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", @@ -8269,6 +8979,11 @@ "lodash": "^4.8.0", "readable-stream": "^2.0.0" } + }, + "zlib": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", + "integrity": "sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=" } } } diff --git a/package.json b/package.json index 572481c24..7751ab1f2 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "sinon-chai": "^2.9.0" }, "dependencies": { + "@serverless/enterprise-plugin": "file:serverless-enterprise-plugin-0.4.0.tgz", "archiver": "^1.1.0", "async": "^1.5.2", "aws-sdk": "^2.430.0", From dccec5501a83239c5c5b4775ecfd970870226c52 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 1 May 2019 16:23:25 -0400 Subject: [PATCH 002/504] reference sfe plugin from npm, not local tarball --- package-lock.json | 5 +++-- package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d0c21e371..69be6b395 100644 --- a/package-lock.json +++ b/package-lock.json @@ -454,8 +454,9 @@ } }, "@serverless/enterprise-plugin": { - "version": "file:serverless-enterprise-plugin-0.4.0.tgz", - "integrity": "sha512-4yOXyqaQmTf+u1xM50dFJbiU4pQcAQxiyAXtCzZdiNW3BCxjWTz3ex+9UPO29mGcpy/laQHiyg2x2QTnaUb+GA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-0.4.1.tgz", + "integrity": "sha512-kgGa1IWctQ8tUMyKEFztQGMp5YnpSboK6uWQhvn8OXwlQqhuT5izIf6+/WDJogd3o9ULdijVA9Ipq0kPuQ/t5Q==", "requires": { "@babel/polyfill": "^7.2.5", "@serverless/event-mocks": "^1.1.1", diff --git a/package.json b/package.json index 7751ab1f2..d8ecf62b3 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "sinon-chai": "^2.9.0" }, "dependencies": { - "@serverless/enterprise-plugin": "file:serverless-enterprise-plugin-0.4.0.tgz", + "@serverless/enterprise-plugin": "latest", "archiver": "^1.1.0", "async": "^1.5.2", "aws-sdk": "^2.430.0", From 691406310172a010e34c027f5957dcb56a747823 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 3 May 2019 09:16:15 -0400 Subject: [PATCH 003/504] avoid duplicate plugin issue w/SFE --- lib/classes/PluginManager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 72717e518..dd6c4ae21 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -149,7 +149,8 @@ class PluginManager { if (pluginsObject.localPath) { module.paths.unshift(pluginsObject.localPath); } - this.loadPlugins(pluginsObject.modules); + this.loadPlugins(pluginsObject.modules + .filter(name => name !== '@serverless/enterprise-plugin')); } loadEnterprisePlugin() { From 2a3d0397fa019c9ff195c58d2b085e60ad20103a Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 3 May 2019 09:20:56 -0400 Subject: [PATCH 004/504] add option to disable SFE --- lib/classes/PluginManager.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index dd6c4ae21..aa35c49d1 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -154,6 +154,10 @@ class PluginManager { } loadEnterprisePlugin() { + if (process.env.SERVERLESS_DISABLE_ENTERPRISE && + process.env.SERVERLESS_DISABLE_ENTERPRISE.toLowerCase() === 'true') { + return; + } const depNodeModules = path.join(__dirname, '../../node_modules'); this.loadPlugins([`${depNodeModules}/@serverless/enterprise-plugin`]); } From b7d0c53d7c5c94635913cb82a42e238a7caee1dc Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 3 May 2019 11:05:41 -0400 Subject: [PATCH 005/504] check that node version is compatible with SFE plugin --- lib/classes/PluginManager.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index aa35c49d1..75c3f628d 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -4,10 +4,11 @@ const path = require('path'); const Module = require('module'); const BbPromise = require('bluebird'); const _ = require('lodash'); +const crypto = require('crypto'); +const semver = require('semver'); const writeFile = require('../utils/fs/writeFile'); const getCacheFilePath = require('../utils/getCacheFilePath'); const serverlessConfigFileUtils = require('../utils/getServerlessConfigFile'); -const crypto = require('crypto'); const getCommandSuggestion = require('../utils/getCommandSuggestion'); /** @@ -154,12 +155,16 @@ class PluginManager { } loadEnterprisePlugin() { + const qualifiedEnterpriseModulePath = path.join( + __dirname, '../../node_modules/@serverless/enterprise-plugin'); + const sfeNodeReq = require(`${qualifiedEnterpriseModulePath}/package.json`).engines.node; if (process.env.SERVERLESS_DISABLE_ENTERPRISE && - process.env.SERVERLESS_DISABLE_ENTERPRISE.toLowerCase() === 'true') { + process.env.SERVERLESS_DISABLE_ENTERPRISE.toLowerCase() === 'true' || + !semver.satisfies(process.version, sfeNodeReq) + ) { return; } - const depNodeModules = path.join(__dirname, '../../node_modules'); - this.loadPlugins([`${depNodeModules}/@serverless/enterprise-plugin`]); + this.loadPlugins([qualifiedEnterpriseModulePath]); } parsePluginsObject(servicePlugs) { From ab88c1aa361fd7f9728fa0f57f87bd0e3de787f9 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 3 May 2019 11:10:49 -0400 Subject: [PATCH 006/504] check for version of node that the SFEplugin requires too --- lib/classes/PluginManager.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 75c3f628d..b6ab298f9 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -157,11 +157,11 @@ class PluginManager { loadEnterprisePlugin() { const qualifiedEnterpriseModulePath = path.join( __dirname, '../../node_modules/@serverless/enterprise-plugin'); + // eslint-disable-next-line global-require const sfeNodeReq = require(`${qualifiedEnterpriseModulePath}/package.json`).engines.node; - if (process.env.SERVERLESS_DISABLE_ENTERPRISE && - process.env.SERVERLESS_DISABLE_ENTERPRISE.toLowerCase() === 'true' || - !semver.satisfies(process.version, sfeNodeReq) - ) { + if ((process.env.SERVERLESS_DISABLE_ENTERPRISE && + process.env.SERVERLESS_DISABLE_ENTERPRISE.toLowerCase() === 'true') || + !semver.satisfies(process.version, sfeNodeReq)) { return; } this.loadPlugins([qualifiedEnterpriseModulePath]); From da1587bdbdf1fea660b6b27a90ac509c8be59662 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 3 May 2019 13:05:24 -0400 Subject: [PATCH 007/504] serverlessrc based config instead of env var --- lib/classes/PluginManager.js | 4 ++-- lib/utils/config/index.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index b6ab298f9..47be53efc 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -9,6 +9,7 @@ const semver = require('semver'); const writeFile = require('../utils/fs/writeFile'); const getCacheFilePath = require('../utils/getCacheFilePath'); const serverlessConfigFileUtils = require('../utils/getServerlessConfigFile'); +const config = require('../utils/config'); const getCommandSuggestion = require('../utils/getCommandSuggestion'); /** @@ -159,8 +160,7 @@ class PluginManager { __dirname, '../../node_modules/@serverless/enterprise-plugin'); // eslint-disable-next-line global-require const sfeNodeReq = require(`${qualifiedEnterpriseModulePath}/package.json`).engines.node; - if ((process.env.SERVERLESS_DISABLE_ENTERPRISE && - process.env.SERVERLESS_DISABLE_ENTERPRISE.toLowerCase() === 'true') || + if (config.getGlobalConfig().enterpriseDisabled || !semver.satisfies(process.version, sfeNodeReq)) { return; } diff --git a/lib/utils/config/index.js b/lib/utils/config/index.js index 33753dcf9..71012d8c3 100644 --- a/lib/utils/config/index.js +++ b/lib/utils/config/index.js @@ -23,6 +23,7 @@ function createConfig() { userId: null, // currentUserId frameworkId: initialSetup.generateFrameworkId(), trackingDisabled: initialSetup.configureTrack(), // default false + enterpriseDisabled: false, meta: { created_at: Math.round(+new Date() / 1000), // config file creation date updated_at: null, // config file updated date From 6e09241674d4037a8702bdbeb8c1ca47698cc90e Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 3 May 2019 11:16:51 -0400 Subject: [PATCH 008/504] TEMPORARY - switching SFE plugin dep to `next` tag to be able to test --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 69be6b395..49e00f0f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -454,9 +454,9 @@ } }, "@serverless/enterprise-plugin": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-0.4.1.tgz", - "integrity": "sha512-kgGa1IWctQ8tUMyKEFztQGMp5YnpSboK6uWQhvn8OXwlQqhuT5izIf6+/WDJogd3o9ULdijVA9Ipq0kPuQ/t5Q==", + "version": "0.5.0-6-gf93502b", + "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-0.5.0-6-gf93502b.tgz", + "integrity": "sha512-QO245gLN3cL0k6yeBYf0DTbE3xuY4uRRTR6JfPiFI33NB7gsicue5N38LvFbeDig6K+teQtH2dLjId9eT8D+Rg==", "requires": { "@babel/polyfill": "^7.2.5", "@serverless/event-mocks": "^1.1.1", diff --git a/package.json b/package.json index d8ecf62b3..63dbcda9b 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "sinon-chai": "^2.9.0" }, "dependencies": { - "@serverless/enterprise-plugin": "latest", + "@serverless/enterprise-plugin": "next", "archiver": "^1.1.0", "async": "^1.5.2", "aws-sdk": "^2.430.0", From aaf0dc697945a4f0a48c5f0331db5e8eb22877da Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 3 May 2019 17:39:58 -0400 Subject: [PATCH 009/504] upgrade npm --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3bf161084..f2fa2e063 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,6 +42,7 @@ matrix: - LINTING=true sudo: false install: +- travis_retry npm install -g npm - travis_retry npm install script: - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi From 4634632ec71bc4a21ce603c5b75f91e3154583bf Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 6 May 2019 09:03:25 -0400 Subject: [PATCH 010/504] remove obsolete node versions from travis --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f2fa2e063..dc77133f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,6 @@ matrix: - secure: ruPmxtjLY9jX/TQQe1d8vxwL0KsjsTzJK/EL6rZM954JJc+3c5gshxB1Ca9+WRwIGdMjCZgKo4dQcn2DrAap7KoDz+GRCi40kc5AOr2ZLmjxJeCJls+sQ713QJ01GgIA1X2zmz/bDSw5lan8ODR3eYI0fIn37ZV4yxNJ5PObBLCMQW6nnOdMXxcLLi2iJRqu3k0gb85wsUNOB0AF2cBrbxoYmGsrGjGuPjIXPeEdnVJ0jzBY+jDrBraIHEBN9tecIyG77ZgEhlPopqTrj39dEPMwAow+derEVWugPRF3Wps6z+Yx0qtxETptWuOgn0V9GpfZF9vJlHifQUuuJEGtttVCYjZCUOIoIRwWnX+/SygjlgLJ9PpVa6XyZfnqygJXnv7wouGGkiyZEDbayu65qP3Ls/Dj+N2nEtamOPf3dGBeaX8KKF61h80bH7o9JL/t6yxyvm+yDRTkYd9SNk3U7dFXoBcCcjMNNUkKBKtY9G3EZlE0ZV0D7jD33Na4yX8mKBHkYLKXCAn+rh6DIwLNkV8IOtAYD77qan6v4qH9fpPMX4UkLu9SRM1r5RfsZU0gX7sCP9OKFKkAx5BWUqjg+D6Xa0EACtO8bPDHA548hWU2vYx7ghuVJfVNOUlAl97OXclx2SZwXJ46rHaHgrH2I3gi3qyHMib3iUV7SLdRQnY= - secure: p9ka7mRzo/ecjnDh/dz19g0iVfQdvsGRAtg/4ONeiq75I2+oqHzu+VxUBA1Z2IQbpCEAMo21CarR3fg2I6MFUeazL0nEpqr1PoOAI8nPFeQlg/h+jLXsrPAkDcu2/b8ij7J5MXeLdZXUVqiPcGkr68x/tCMk/rwxftljQhvXPQfc7Lxm/m61ELnC7rLJulhxWZLNIq1hwQ9nh0GMKb4hm0KmPn8ksccVL+wyDikkgXCuvIujhTBjhNivAe4mG8mqnNsW1Ugh++SUe1ld27TtbH7wQj02SSG4Bxfwc3Gz0GFdAL1GyOkWI2WvrqP4a0KYTRUo+pUr9E+HZ1SNlxU5t6QWtmDiy5MKkxzgeTXmkKiJ98vMlF0ja5bpp46NjYarzDafqE8FozHzLtr+uAtqr6gRAgU1rWaG9BE3gKeW/f4B/2MfPI26b7SxuU1MwGVy0I76hb0Ujbgb3X8G4TYTGb6Nhoewc+RZExPwVhfrN8cJjo45masndv5tQAZMSRX/JUFjs4h/QMXNsn0A53GXgf6eIzUu15m+W8TJYFiKQeq9nMejzEE4sWMO3BFnkxueBGVCEurOc1GgdEnKxeqlp+psxHcJRlNCxC1HkUVOzfpkCr/Jy42vM8jQomAMv41Z9zWjOagVphWT25xNeSILfRt4yPku5wfW4CAxp+fl4KQ= include: - - node_js: '4.4' - env: SLS_IGNORE_WARNING=* - - node_js: '5.11' - env: SLS_IGNORE_WARNING=* - node_js: '6.2' env: SLS_IGNORE_WARNING=* - node_js: '6.2' From 049d14706ecb1ea192731b20ae437a1ad3ec1595 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 8 May 2019 09:21:50 -0400 Subject: [PATCH 011/504] update check --- lib/classes/PluginManager.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 47be53efc..f230af0a5 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -6,6 +6,7 @@ const BbPromise = require('bluebird'); const _ = require('lodash'); const crypto = require('crypto'); const semver = require('semver'); +const updateNotifier = require('update-notifier'); const writeFile = require('../utils/fs/writeFile'); const getCacheFilePath = require('../utils/getCacheFilePath'); const serverlessConfigFileUtils = require('../utils/getServerlessConfigFile'); @@ -159,12 +160,19 @@ class PluginManager { const qualifiedEnterpriseModulePath = path.join( __dirname, '../../node_modules/@serverless/enterprise-plugin'); // eslint-disable-next-line global-require - const sfeNodeReq = require(`${qualifiedEnterpriseModulePath}/package.json`).engines.node; + const sfePkgJson = require(`${qualifiedEnterpriseModulePath}/package.json`); if (config.getGlobalConfig().enterpriseDisabled || - !semver.satisfies(process.version, sfeNodeReq)) { + !semver.satisfies(process.version, sfePkgJson.engines.node)) { return; } this.loadPlugins([qualifiedEnterpriseModulePath]); + if (this.serverless.enterpriseEnabled) { + const updates = updateNotifier({ pkg: sfePkgJson, interval: 1 }); + if (updates.update) { + this.serverless.cli.log('An updated version of Serverless Enterprise is available. ' + + 'Please upgrade by running `npm i -g serverless`'); + } + } } parsePluginsObject(servicePlugs) { From 9b4ec28a1e377cf1163ab153e99b5afd18727038 Mon Sep 17 00:00:00 2001 From: exoego Date: Sat, 11 May 2019 01:58:03 +0900 Subject: [PATCH 012/504] Throw error only if authorizer has non-empty claims --- .../compile/events/apiGateway/lib/validate.js | 2 +- .../events/apiGateway/lib/validate.test.js | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js index 9fde97c46..08e13da2f 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -283,7 +283,7 @@ module.exports = { if (integration === 'AWS_PROXY' && typeof arn === 'string' && awsArnRegExs.cognitoIdpArnExpr.test(arn) - && authorizer.claims) { + && claims.length > 0) { const errorMessage = [ 'Cognito claims can only be filtered when using the lambda integration type', ]; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js index e8fb97929..a24bc7848 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js @@ -280,6 +280,51 @@ describe('#validate()', () => { expect(() => awsCompileApigEvents.validate()).to.throw(Error); }); + it('should not throw if an cognito claims are undefined with a lambda proxy', () => { + awsCompileApigEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + path: '/{proxy+}', + method: 'ANY', + integration: 'lambda-proxy', + authorizer: { + arn: 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ', + name: 'CognitoAuthorier', + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileApigEvents.validate()).not.to.throw(Error); + }); + + it('should not throw if an cognito claims are empty arrays with a lambda proxy', () => { + awsCompileApigEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + path: '/{proxy+}', + method: 'ANY', + integration: 'lambda-proxy', + authorizer: { + arn: 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ', + name: 'CognitoAuthorier', + claims: [], + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileApigEvents.validate()).not.to.throw(Error); + }); + it('should accept AWS_IAM as authorizer', () => { awsCompileApigEvents.serverless.service.functions = { foo: {}, From 0e0f4eb664d93943c38805c5dd540af8823c1e71 Mon Sep 17 00:00:00 2001 From: beary Date: Mon, 13 May 2019 12:58:20 +0800 Subject: [PATCH 013/504] Solve the problem of principal format in China region --- .../events/apiGateway/lib/permissions.js | 4 +- .../compile/events/cloudWatchEvent/index.js | 2 +- .../compile/events/cloudWatchLog/index.js | 3 +- .../compile/events/cognitoUserPool/index.js | 2 +- .../aws/package/compile/events/iot/index.js | 2 +- .../aws/package/compile/events/s3/index.js | 2 +- .../package/compile/events/schedule/index.js | 2 +- .../aws/package/compile/events/sns/index.js | 2 +- .../events/websockets/lib/permissions.js | 4 +- .../events/websockets/lib/permissions.test.js | 48 ++----------------- 10 files changed, 15 insertions(+), 56 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js index 61641b535..eed70c66c 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js @@ -19,7 +19,7 @@ module.exports = { 'Fn::GetAtt': [singlePermissionMapping.lambdaLogicalId, 'Arn'], }, Action: 'lambda:InvokeFunction', - Principal: { 'Fn::Join': ['', ['apigateway.', { Ref: 'AWS::URLSuffix' }]] }, + Principal: 'apigateway.amazonaws.com', SourceArn: { 'Fn::Join': ['', [ @@ -56,7 +56,7 @@ module.exports = { Properties: { FunctionName: authorizer.arn, Action: 'lambda:InvokeFunction', - Principal: { 'Fn::Join': ['', ['apigateway.', { Ref: 'AWS::URLSuffix' }]] }, + Principal: 'apigateway.amazonaws.com', }, }, }); diff --git a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js index 9f8fff08d..7762aba4b 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js @@ -114,7 +114,7 @@ class AwsCompileCloudWatchEventEvents { "FunctionName": { "Fn::GetAtt": ["${ lambdaLogicalId}", "Arn"] }, "Action": "lambda:InvokeFunction", - "Principal": { "Fn::Join": ["", ["events.", { "Ref": "AWS::URLSuffix" }]] }, + "Principal": "events.amazonaws.com", "SourceArn": { "Fn::GetAtt": ["${cloudWatchLogicalId}", "Arn"] } } } diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js index b5808ef47..1bf50ad62 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js @@ -112,8 +112,7 @@ class AwsCompileCloudWatchLogEvents { "Fn::Join": [ "", [ "logs.", { "Ref": "AWS::Region" }, - ".", - { "Ref": "AWS::URLSuffix" } + ".amazonaws.com" ] ] }, "SourceArn": { diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js index a85c67a4c..80812d46c 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js @@ -151,7 +151,7 @@ class AwsCompileCognitoUserPoolEvents { ], }, Action: 'lambda:InvokeFunction', - Principal: { 'Fn::Join': ['', ['cognito-idp.', { Ref: 'AWS::URLSuffix' }]] }, + Principal: 'cognito-idp.amazonaws.com', SourceArn: { 'Fn::GetAtt': [ userPoolLogicalId, diff --git a/lib/plugins/aws/package/compile/events/iot/index.js b/lib/plugins/aws/package/compile/events/iot/index.js index d8d5ef892..1ae11ec5c 100644 --- a/lib/plugins/aws/package/compile/events/iot/index.js +++ b/lib/plugins/aws/package/compile/events/iot/index.js @@ -80,7 +80,7 @@ class AwsCompileIoTEvents { "Properties": { "FunctionName": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] }, "Action": "lambda:InvokeFunction", - "Principal": { "Fn::Join": ["", [ "iot.", { "Ref": "AWS::URLSuffix" } ]] }, + "Principal": "iot.amazonaws.com", "SourceArn": { "Fn::Join": ["", [ "arn:", diff --git a/lib/plugins/aws/package/compile/events/s3/index.js b/lib/plugins/aws/package/compile/events/s3/index.js index 4c2c9dc4a..30a1e67e8 100644 --- a/lib/plugins/aws/package/compile/events/s3/index.js +++ b/lib/plugins/aws/package/compile/events/s3/index.js @@ -177,7 +177,7 @@ class AwsCompileS3Events { ], }, Action: 'lambda:InvokeFunction', - Principal: { 'Fn::Join': ['', ['s3.', { Ref: 'AWS::URLSuffix' }]] }, + Principal: 's3.amazonaws.com', SourceArn: { 'Fn::Join': ['', [ diff --git a/lib/plugins/aws/package/compile/events/schedule/index.js b/lib/plugins/aws/package/compile/events/schedule/index.js index e44e53506..65394aacc 100644 --- a/lib/plugins/aws/package/compile/events/schedule/index.js +++ b/lib/plugins/aws/package/compile/events/schedule/index.js @@ -139,7 +139,7 @@ class AwsCompileScheduledEvents { "FunctionName": { "Fn::GetAtt": ["${ lambdaLogicalId}", "Arn"] }, "Action": "lambda:InvokeFunction", - "Principal": { "Fn::Join": ["", [ "events.", { "Ref": "AWS::URLSuffix" } ]] }, + "Principal": "events.amazonaws.com", "SourceArn": { "Fn::GetAtt": ["${scheduleLogicalId}", "Arn"] } } } diff --git a/lib/plugins/aws/package/compile/events/sns/index.js b/lib/plugins/aws/package/compile/events/sns/index.js index 09cb21a6b..e3e1e9f90 100644 --- a/lib/plugins/aws/package/compile/events/sns/index.js +++ b/lib/plugins/aws/package/compile/events/sns/index.js @@ -195,7 +195,7 @@ class AwsCompileSNSEvents { Properties: { FunctionName: endpoint, Action: 'lambda:InvokeFunction', - Principal: { 'Fn::Join': ['', ['sns.', { Ref: 'AWS::URLSuffix' }]] }, + Principal: 'sns.amazonaws.com', SourceArn: topicArn, }, }, diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js index fe28e2368..4b42f42f8 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js @@ -20,7 +20,7 @@ module.exports = { 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], }, Action: 'lambda:InvokeFunction', - Principal: { 'Fn::Join': ['', ['apigateway.', { Ref: 'AWS::URLSuffix' }]] }, + Principal: 'apigateway.amazonaws.com', }, }, }); @@ -35,7 +35,7 @@ module.exports = { DependsOn: [this.websocketsApiLogicalId], Properties: { Action: 'lambda:InvokeFunction', - Principal: { 'Fn::Join': ['', ['apigateway.', { Ref: 'AWS::URLSuffix' }]] }, + Principal: 'apigateway.amazonaws.com', }, }, }; diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js index 25e6a8663..2f96f2c56 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js @@ -51,17 +51,7 @@ describe('#compilePermissions()', () => { ], }, Action: 'lambda:InvokeFunction', - Principal: { - 'Fn::Join': [ - '', - [ - 'apigateway.', - { - Ref: 'AWS::URLSuffix', - }, - ], - ], - }, + Principal: 'apigateway.amazonaws.com', }, }, SecondLambdaPermissionWebsockets: { @@ -78,17 +68,7 @@ describe('#compilePermissions()', () => { ], }, Action: 'lambda:InvokeFunction', - Principal: { - 'Fn::Join': [ - '', - [ - 'apigateway.', - { - Ref: 'AWS::URLSuffix', - }, - ], - ], - }, + Principal: 'apigateway.amazonaws.com', }, }, }); @@ -127,17 +107,7 @@ describe('#compilePermissions()', () => { ], }, Action: 'lambda:InvokeFunction', - Principal: { - 'Fn::Join': [ - '', - [ - 'apigateway.', - { - Ref: 'AWS::URLSuffix', - }, - ], - ], - }, + Principal: 'apigateway.amazonaws.com', }, }, AuthLambdaPermissionWebsockets: { @@ -154,17 +124,7 @@ describe('#compilePermissions()', () => { ], }, Action: 'lambda:InvokeFunction', - Principal: { - 'Fn::Join': [ - '', - [ - 'apigateway.', - { - Ref: 'AWS::URLSuffix', - }, - ], - ], - }, + Principal: 'apigateway.amazonaws.com', }, }, }); From f222f76cb4122b5591a2f3f7bf0bea3f8c25793a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 13 May 2019 20:40:21 +0200 Subject: [PATCH 014/504] Upgrade sinon to v2 --- bin/test | 9 +--- package-lock.json | 112 +++++++++++++++++++++------------------------- package.json | 3 +- 3 files changed, 54 insertions(+), 70 deletions(-) diff --git a/bin/test b/bin/test index 0a6536f35..03ca6d6d5 100755 --- a/bin/test +++ b/bin/test @@ -7,14 +7,7 @@ process.on('unhandledRejection', err => { }); if (process.argv.length <= 2) { - process.argv.push( - '!(node_modules)/**/*.test.js', - '--require=sinon-bluebird', - '-R', - 'spec', - '--recursive', - '--no-exit' - ); + process.argv.push('!(node_modules)/**/*.test.js', '-R', 'spec', '--recursive', '--no-exit'); } require('mocha/bin/_mocha'); diff --git a/package-lock.json b/package-lock.json index 9a1ead6ec..ae3f221e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3008,12 +3008,12 @@ } }, "formatio": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", - "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", "dev": true, "requires": { - "samsam": "~1.1" + "samsam": "1.x" } }, "formidable": { @@ -4132,12 +4132,6 @@ "kind-of": "^6.0.0" } }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4214,12 +4208,6 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true }, - "is-generator-function": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", - "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", - "dev": true - }, "is-installed-globally": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", @@ -5580,9 +5568,9 @@ "dev": true }, "lolex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", - "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", "dev": true }, "loose-envify": { @@ -6260,18 +6248,6 @@ "object-keys": "^1.0.11" } }, - "object.entries": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", - "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -6545,6 +6521,23 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -7157,9 +7150,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "samsam": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", - "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", "dev": true }, "sane": { @@ -7331,23 +7324,29 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "sinon": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", - "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", + "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", "dev": true, "requires": { - "formatio": "1.1.1", - "lolex": "1.3.2", - "samsam": "1.1.2", - "util": ">=0.10.3 <1" + "diff": "^3.1.0", + "formatio": "1.2.0", + "lolex": "^1.6.0", + "native-promise-only": "^0.8.1", + "path-to-regexp": "^1.7.0", + "samsam": "^1.1.3", + "text-encoding": "0.6.4", + "type-detect": "^4.0.0" + }, + "dependencies": { + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + } } }, - "sinon-bluebird": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/sinon-bluebird/-/sinon-bluebird-3.1.0.tgz", - "integrity": "sha1-+SaA+lRtVTpPX2LekEJetdxHhB0=", - "dev": true - }, "sinon-chai": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.14.0.tgz", @@ -8030,6 +8029,12 @@ "require-main-filename": "^2.0.0" } }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8439,19 +8444,6 @@ "os-homedir": "^1.0.0" } }, - "util": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.0.tgz", - "integrity": "sha512-pPSOFl7VLhZ7LO/SFABPraZEEurkJUWSMn3MuA/r3WQZc+Z1fqou2JqLSOZbCLl73EUIxuUVX8X4jkX2vfJeAA==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "object.entries": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index f179fb8d9..6c43e26eb 100644 --- a/package.json +++ b/package.json @@ -87,8 +87,7 @@ "mock-require": "^1.3.0", "parse-github-url": "^1.0.1", "proxyquire": "^1.7.10", - "sinon": "^1.17.5", - "sinon-bluebird": "^3.1.0", + "sinon": "^2.4.1", "sinon-chai": "^2.9.0", "strip-ansi": "^5.2.0" }, From cfa523d91f489bd7da680040df21efb7eb9582c6 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 13 May 2019 20:43:22 +0200 Subject: [PATCH 015/504] Rely on stub(...).callsFake (Upgrade to sinon v2) --- lib/classes/Variables.test.js | 75 +++++++++++-------- .../aws/deploy/lib/checkForChanges.test.js | 3 +- lib/plugins/aws/provider/awsProvider.test.js | 2 +- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 7da83663b..975b33898 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -60,7 +60,8 @@ describe('Variables', () => { () => { const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService') .returns(BbPromise.resolve()); - const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl', (val) => { + const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl') + .callsFake((val) => { expect(val).to.equal(serverless.service); expect(val.provider.variableSyntax).to.be.undefined; expect(val.serverless).to.be.undefined; @@ -74,14 +75,14 @@ describe('Variables', () => { }); it('should clear caches and remaining state *before* [pre]populating service', () => { - const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService', - (val) => { + const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService') + .callsFake((val) => { expect(serverless.variables.deep).to.eql([]); expect(serverless.variables.tracker.getAll()).to.eql([]); return BbPromise.resolve(val); }); - const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl', - (val) => { + const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl') + .callsFake((val) => { expect(serverless.variables.deep).to.eql([]); expect(serverless.variables.tracker.getAll()).to.eql([]); return BbPromise.resolve(val); @@ -98,16 +99,16 @@ describe('Variables', () => { }); it('should clear caches and remaining *after* [pre]populating service', () => { - const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService', - (val) => { + const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService') + .callsFake((val) => { serverless.variables.deep.push('${foo:}'); const promise = BbPromise.resolve(val); serverless.variables.tracker.add('foo:', promise, '${foo:}'); promise.state = 'resolved'; return BbPromise.resolve(); }); - const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl', - (val) => { + const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl') + .callsFake((val) => { serverless.variables.deep.push('${bar:}'); const promise = BbPromise.resolve(val); serverless.variables.tracker.add('bar:', promise, '${bar:}'); @@ -208,7 +209,7 @@ describe('Variables', () => { let requestStub; beforeEach(() => { awsProvider = new AwsProvider(serverless, {}); - requestStub = sinon.stub(awsProvider, 'request', () => + requestStub = sinon.stub(awsProvider, 'request').callsFake(() => BbPromise.reject(new serverless.classes.Error('Not found.', 400))); }); afterEach(() => { @@ -269,7 +270,7 @@ describe('Variables', () => { awsProvider = new AwsProvider(serverless, {}); populateObjectImplStub = sinon.stub(serverless.variables, 'populateObjectImpl'); populateObjectImplStub.withArgs(serverless.variables.service).returns(BbPromise.resolve()); - requestStub = sinon.stub(awsProvider, 'request', () => + requestStub = sinon.stub(awsProvider, 'request').callsFake(() => BbPromise.reject(new Error('unexpected'))); }); afterEach(() => { @@ -429,8 +430,8 @@ describe('Variables', () => { stage: 'prod', }; expectedPopulatedObject['some.nested.key'] = 'hello'; - const populateValueStub = sinon.stub(serverless.variables, 'populateValue', - // eslint-disable-next-line no-template-curly-in-string + const populateValueStub = sinon.stub(serverless.variables, 'populateValue') + .callsFake(// eslint-disable-next-line no-template-curly-in-string val => (val === '${opt:stage}' ? BbPromise.resolve('prod') : BbPromise.resolve(val))); return serverless.variables.populateObject(object) .should.become(expectedPopulatedObject) @@ -1149,7 +1150,8 @@ module.exports = { const param = '/some/path/to/invalidparam'; const property = `\${ssm:${param}}`; const error = new serverless.classes.Error(`Parameter ${param} not found.`, 400); - const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); + const requestStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.reject(error)); const warnIfNotFoundSpy = sinon.spy(serverless.variables, 'warnIfNotFound'); return serverless.variables.populateProperty(property) @@ -1174,7 +1176,8 @@ module.exports = { const param = '/some/path/to/invalidparam'; const property = `\${ssm:${param}}`; const error = new serverless.classes.Error('Some random failure.', 123); - const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); + const requestStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.reject(error)); return serverless.variables.populateProperty(property) .should.be.rejectedWith(serverless.classes.Error) .then(() => expect(requestStub.callCount).to.equal(1)) @@ -1796,7 +1799,7 @@ module.exports = { }], }], }; - const cfStub = sinon.stub(serverless.getProvider('aws'), 'request', + const cfStub = sinon.stub(serverless.getProvider('aws'), 'request').callsFake( () => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromCf('cf:some-stack.MockExport') .should.become('MockValue') @@ -1827,7 +1830,7 @@ module.exports = { }], }], }; - const cfStub = sinon.stub(serverless.getProvider('aws'), 'request', + const cfStub = sinon.stub(serverless.getProvider('aws'), 'request').callsFake( () => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromCf('cf.us-east-1:some-stack.MockExport') .should.become('MockValue') @@ -1858,7 +1861,7 @@ module.exports = { }], }], }; - const cfStub = sinon.stub(serverless.getProvider('aws'), 'request', + const cfStub = sinon.stub(serverless.getProvider('aws'), 'request').callsFake( () => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromCf('cf:some-stack.DoestNotExist') .should.be.rejectedWith(serverless.classes.Error, @@ -1890,7 +1893,8 @@ module.exports = { const awsResponseMock = { Body: 'MockValue', }; - const s3Stub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + const s3Stub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromS3('s3:some.bucket/path/to/key') .should.become('MockValue') .then(() => { @@ -1909,7 +1913,8 @@ module.exports = { it('should throw error if error getting value from S3', () => { const error = new Error('The specified bucket is not valid'); - const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); + const requestStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.reject(error)); return expect(serverless.variables.getValueFromS3('s3:some.bucket/path/to/key')) .to.be.rejectedWith( serverless.classes.Error, @@ -1937,7 +1942,8 @@ module.exports = { serverless.variables.options = options; }); it('should get variable from Ssm using regular-style param', () => { - const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + const ssmStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromSsm(`ssm:${param}`) .should.become(value) .then(() => { @@ -1954,7 +1960,8 @@ module.exports = { .finally(() => ssmStub.restore()); }); it('should get variable from Ssm using path-style param', () => { - const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + const ssmStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromSsm(`ssm:${param}`) .should.become(value) .then(() => { @@ -1971,7 +1978,8 @@ module.exports = { .finally(() => ssmStub.restore()); }); it('should get encrypted variable from Ssm using extended syntax', () => { - const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + const ssmStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromSsm(`ssm:${param}~true`) .should.become(value) .then(() => { @@ -1988,7 +1996,8 @@ module.exports = { .finally(() => ssmStub.restore()); }); it('should get unencrypted variable from Ssm using extended syntax', () => { - const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + const ssmStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromSsm(`ssm:${param}~false`) .should.become(value) .then(() => { @@ -2005,7 +2014,8 @@ module.exports = { .finally(() => ssmStub.restore()); }); it('should ignore bad values for extended syntax', () => { - const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + const ssmStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromSsm(`ssm:${param}~badvalue`) .should.become(value) .then(() => { @@ -2030,7 +2040,8 @@ module.exports = { Value: jsonLikeText, }, }; - const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponse)); + const ssmStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.resolve(awsResponse)); return serverless.variables.getValueFromSsm(`ssm:${secretParam}~true`) .should.become(jsonLikeText) .then().finally(() => ssmStub.restore()); @@ -2047,7 +2058,8 @@ module.exports = { Value: jsonLikeText, }, }; - const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponse)); + const ssmStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.resolve(awsResponse)); return serverless.variables.getValueFromSsm(`ssm:${secretParam}~true`) .should.become(json) .then().finally(() => ssmStub.restore()); @@ -2060,7 +2072,8 @@ module.exports = { Value: plainText, }, }; - const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponse)); + const ssmStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.resolve(awsResponse)); return serverless.variables.getValueFromSsm(`ssm:${secretParam}~true`) .should.become(plainText) .then().finally(() => ssmStub.restore()); @@ -2068,7 +2081,8 @@ module.exports = { }); it('should return undefined if SSM parameter does not exist', () => { const error = new serverless.classes.Error(`Parameter ${param} not found.`, 400); - const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); + const requestStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.reject(error)); return serverless.variables.getValueFromSsm(`ssm:${param}`) .should.become(undefined) .then().finally(() => requestStub.restore()); @@ -2077,7 +2091,8 @@ module.exports = { it('should reject if SSM request returns unexpected error', () => { const error = new Error( 'User: is not authorized to perform: ssm:GetParameter on resource: '); - const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); + const requestStub = sinon.stub(awsProvider, 'request') + .callsFake(() => BbPromise.reject(error)); return serverless.variables.getValueFromSsm(`ssm:${param}`) .should.be.rejected .then().finally(() => requestStub.restore()); diff --git a/lib/plugins/aws/deploy/lib/checkForChanges.test.js b/lib/plugins/aws/deploy/lib/checkForChanges.test.js index 1e4b013d6..a7b71aa61 100644 --- a/lib/plugins/aws/deploy/lib/checkForChanges.test.js +++ b/lib/plugins/aws/deploy/lib/checkForChanges.test.js @@ -545,7 +545,8 @@ describe('checkForChanges', () => { it('should not call checkLogGroup if deployment is not required', () => { awsDeploy.checkIfDeploymentIsNecessary.restore(); - sandbox.stub(awsDeploy, 'checkIfDeploymentIsNecessary', () => new Promise((resolve) => { + sandbox.stub(awsDeploy, 'checkIfDeploymentIsNecessary') + .callsFake(() => new Promise((resolve) => { awsDeploy.serverless.service.provider.shouldNotDeploy = true; resolve(); })); diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 1cf09dbf2..8f4db9e98 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -255,7 +255,7 @@ describe('AwsProvider', () => { describe('#request()', () => { beforeEach(() => { - sinon.stub(global, 'setTimeout', (cb) => { cb(); }); + sinon.stub(global, 'setTimeout').callsFake((cb) => { cb(); }); }); afterEach(() => { From ba27f9a0e37bf3bf94619d0362b07545b21b15c4 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 13 May 2019 15:30:18 -0400 Subject: [PATCH 016/504] add integration tests using `sls package` Just the beginning. a few simple file inclusion tests and a cfn tempalte test closes #5938 --- .travis.yml | 2 +- package.json | 1 + tests/local-suite/packaging.tests.js | 119 +++++++++++++++++++++++++++ tests/utils/index.js | 5 ++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 tests/local-suite/packaging.tests.js diff --git a/.travis.yml b/.travis.yml index 3bf161084..a7fd54d22 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ sudo: false install: - travis_retry npm install script: -- if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi +- if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test && npm local-integration-test; fi - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then npm run lint; fi - if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$INTEGRATION_TEST_SUITE" diff --git a/package.json b/package.json index f179fb8d9..147caab8e 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "lint": "eslint . --cache", "docs": "node scripts/generate-readme.js", "integration-test-cleanup": "node scripts/integration-test-cleanup.js", + "local-integration-test": "jest --maxWorkers=5 local-suite", "simple-integration-test": "jest --maxWorkers=5 simple-suite", "complex-integration-test": "jest --maxWorkers=5 integration", "postinstall": "node ./scripts/postinstall.js" diff --git a/tests/local-suite/packaging.tests.js b/tests/local-suite/packaging.tests.js new file mode 100644 index 000000000..17f75af1e --- /dev/null +++ b/tests/local-suite/packaging.tests.js @@ -0,0 +1,119 @@ +const fs = require('fs'); +const path = require('path'); +const execSync = require('child_process').execSync; +const BbPromise = require('bluebird'); +const fse = require('fs-extra'); +const testUtils = require('../utils/index'); + +const serverlessExec = path.join(__dirname, '..', '..', 'bin', 'serverless'); + +describe('simple packaging', () => { + let cwd + beforeEach(() => { + cwd = testUtils.getTmpDirPath(); + fse.mkdirsSync(cwd); + }) + + it('packages the default aws template correctly', () => { + const templateName = 'aws-nodejs'; + execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + .then(zipfiles => { + expect(zipfiles).toEqual(['handler.js']); + }); + }) + + it('packages the default aws template with an npm dep correctly', () => { + const templateName = 'aws-nodejs'; + execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); + execSync(`npm init --yes`, { cwd }); + execSync(`npm i lodash`, { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + .then(zipfiles => { + const nodeModules = new Set(zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set(['lodash'])); + expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); + }); + }) + + it('doesn\'t package a dev dependency', () => { + const templateName = 'aws-nodejs'; + execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); + execSync(`npm init --yes`, { cwd }); + execSync(`npm i --save-dev lodash`, { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + .then(zipfiles => { + const nodeModules = new Set(zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set([])); + expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); + }); + }) + + it('ignores package json files per ignore directive', () => { + const templateName = 'aws-nodejs'; + execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); + execSync(`npm init --yes`, { cwd }); + execSync(`echo 'package: {exclude: ["package*.json"]}' >> serverless.yml`, { cwd }); + execSync(`npm i lodash`, { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + .then(zipfiles => { + const nodeModules = new Set(zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set(['lodash'])); + expect(nonNodeModulesFiles).toEqual(['handler.js']); + }); + }) +}) + +describe('cloudformation packaging', () => { + let cwd + beforeEach(() => { + cwd = testUtils.getTmpDirPath(); + fse.mkdirsSync(cwd); + }) + + it('creates the correct default function resource', () => { + const templateName = 'aws-nodejs'; + execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + const cfnTemplate = JSON.parse(fs.readFileSync(path.join( + cwd, '.serverless/cloudformation-template-update-stack.json'))) + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key).toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/aws-nodejs.zip/) + delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; + expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "ServerlessDeploymentBucket" + } + }, + "FunctionName": "aws-nodejs-dev-hello", + "Handler": "handler.hello", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "IamRoleLambdaExecution", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Timeout": 6 + }, + "DependsOn": [ + "HelloLogGroup", + "IamRoleLambdaExecution" + ] + }) + return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + .then(zipfiles => { + expect(zipfiles).toEqual(['handler.js']); + }); + }) +}) diff --git a/tests/utils/index.js b/tests/utils/index.js index 687760759..081ea7a56 100644 --- a/tests/utils/index.js +++ b/tests/utils/index.js @@ -4,6 +4,7 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); const crypto = require('crypto'); +const JSZip = require('jszip'); const BbPromise = require('bluebird'); const fse = require('fs-extra'); const execSync = require('child_process').execSync; @@ -34,12 +35,16 @@ const replaceTextInFile = (filePath, subString, newSubString) => { fs.writeFileSync(filePath, fileContent.replace(subString, newSubString)); }; +const listZipFiles = filename => new JSZip().loadAsync(fs.readFileSync(filename)) + .then(zip => Object.keys(zip.files)); + module.exports = { serverlessExec, getTmpDirPath, getTmpFilePath, replaceTextInFile, ServerlessPlugin, + listZipFiles, createTestService: (templateName, testServiceDir) => { const hrtime = process.hrtime(); From a981d264f2636673ff2b949fdf7e4578e5f18e91 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 13 May 2019 15:40:33 -0400 Subject: [PATCH 017/504] delint --- tests/local-suite/packaging.tests.js | 98 ++++++++++++++-------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/tests/local-suite/packaging.tests.js b/tests/local-suite/packaging.tests.js index 17f75af1e..f2fd0fd39 100644 --- a/tests/local-suite/packaging.tests.js +++ b/tests/local-suite/packaging.tests.js @@ -1,18 +1,17 @@ const fs = require('fs'); const path = require('path'); const execSync = require('child_process').execSync; -const BbPromise = require('bluebird'); const fse = require('fs-extra'); const testUtils = require('../utils/index'); const serverlessExec = path.join(__dirname, '..', '..', 'bin', 'serverless'); describe('simple packaging', () => { - let cwd + let cwd; beforeEach(() => { cwd = testUtils.getTmpDirPath(); fse.mkdirsSync(cwd); - }) + }); it('packages the default aws template correctly', () => { const templateName = 'aws-nodejs'; @@ -22,98 +21,101 @@ describe('simple packaging', () => { .then(zipfiles => { expect(zipfiles).toEqual(['handler.js']); }); - }) + }); it('packages the default aws template with an npm dep correctly', () => { const templateName = 'aws-nodejs'; execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); - execSync(`npm init --yes`, { cwd }); - execSync(`npm i lodash`, { cwd }); + execSync('npm init --yes', { cwd }); + execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { - const nodeModules = new Set(zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); expect(nodeModules).toEqual(new Set(['lodash'])); expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); }); - }) + }); it('doesn\'t package a dev dependency', () => { const templateName = 'aws-nodejs'; execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); - execSync(`npm init --yes`, { cwd }); - execSync(`npm i --save-dev lodash`, { cwd }); + execSync('npm init --yes', { cwd }); + execSync('npm i --save-dev lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { - const nodeModules = new Set(zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); expect(nodeModules).toEqual(new Set([])); expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); }); - }) + }); it('ignores package json files per ignore directive', () => { const templateName = 'aws-nodejs'; execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); - execSync(`npm init --yes`, { cwd }); - execSync(`echo 'package: {exclude: ["package*.json"]}' >> serverless.yml`, { cwd }); - execSync(`npm i lodash`, { cwd }); + execSync('npm init --yes', { cwd }); + execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); + execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { - const nodeModules = new Set(zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); + const nodeModules = new Set(i + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); expect(nodeModules).toEqual(new Set(['lodash'])); expect(nonNodeModulesFiles).toEqual(['handler.js']); }); - }) -}) + }); +}); describe('cloudformation packaging', () => { - let cwd + let cwd; beforeEach(() => { cwd = testUtils.getTmpDirPath(); fse.mkdirsSync(cwd); - }) + }); it('creates the correct default function resource', () => { const templateName = 'aws-nodejs'; execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( - cwd, '.serverless/cloudformation-template-update-stack.json'))) - expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key).toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/aws-nodejs.zip/) - delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; - expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "ServerlessDeploymentBucket" - } + cwd, '.serverless/cloudformation-template-update-stack.json'))); + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key).toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/aws-nodejs.zip/); + delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; + expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { + Ref: 'ServerlessDeploymentBucket', }, - "FunctionName": "aws-nodejs-dev-hello", - "Handler": "handler.hello", - "MemorySize": 1024, - "Role": { - "Fn::GetAtt": [ - "IamRoleLambdaExecution", - "Arn" - ] - }, - "Runtime": "nodejs8.10", - "Timeout": 6 }, - "DependsOn": [ - "HelloLogGroup", - "IamRoleLambdaExecution" - ] - }) + FunctionName: 'aws-nodejs-dev-hello', + Handler: 'handler.hello', + MemorySize: 1024, + Role: { + 'Fn::GetAtt': [ + 'IamRoleLambdaExecution', + 'Arn', + ], + }, + Runtime: 'nodejs8.10', + Timeout: 6, + }, + DependsOn: [ + 'HelloLogGroup', + 'IamRoleLambdaExecution', + ], + }); return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { expect(zipfiles).toEqual(['handler.js']); }); - }) -}) + }); +}); From fadade6014276a0f43c91ad44e3000f3f50a5f46 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 13 May 2019 16:08:23 -0400 Subject: [PATCH 018/504] doh --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a7fd54d22..7cc93cba4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ sudo: false install: - travis_retry npm install script: -- if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test && npm local-integration-test; fi +- if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test && npm run local-integration-test; fi - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then npm run lint; fi - if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$INTEGRATION_TEST_SUITE" From 317f1eabe29f106e1401f37658a5abf9ad3a514a Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 13 May 2019 16:19:41 -0400 Subject: [PATCH 019/504] super doh --- tests/local-suite/packaging.tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/local-suite/packaging.tests.js b/tests/local-suite/packaging.tests.js index f2fd0fd39..d6b2b9867 100644 --- a/tests/local-suite/packaging.tests.js +++ b/tests/local-suite/packaging.tests.js @@ -64,7 +64,7 @@ describe('simple packaging', () => { execSync(`${serverlessExec} package`, { cwd }); return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { - const nodeModules = new Set(i + const nodeModules = new Set( zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); expect(nodeModules).toEqual(new Set(['lodash'])); From 9bfe2929b21d854a79087fcc7108243779a4ce4a Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Fri, 10 May 2019 12:48:50 +0200 Subject: [PATCH 020/504] Update release process docs and toolings --- RELEASE_CHECKLIST.md | 38 ++++++++++++++++---------------------- scripts/prs-since-last-tag | 6 +++--- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index 672996109..606b0c43c 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -8,30 +8,27 @@ More info about our release process can be found in the [`RELEASE_PROCESS.md`](. - [ ] Look through all open issues and PRs (if any) of that milestone and close them / move them to another milestone if still open -- [ ] Look through all closed issues and PRs of that milestone to see what has changed. Run `./scripts/prs-since-last-tag` or if you want to run against a specific tag `./scripts/prs-since-last-tag v1.20.0` to get a list of all merged PR's since a specific tag -- [ ] Close milestone on GitHub -- [ ] Create a new draft release in GitHub - -# Testing - -- [ ] Create a Serverless service (with some events), deploy and test it intensively -- [ ] Look through the milestone and test all of the new major changes -- [ ] Run `npm test` -- [ ] Run `npm run simple-integration-test` -- [ ] Run `npm run complex-integration-test` +- [ ] Create a new branch for the release +- [ ] Bump the version number in `package.json` +- [ ] Run `./scripts/prs-since-last-tag ` +- [ ] Save the terminal output to your clipboard +- [ ] Close the milestone on GitHub +- [ ] Create a new [**draft** release](https://github.com/serverless/serverless/releases/new) in GitHub + - [ ] Use the content in your clipboard as a description (without the heading) + - [ ] Ensure that the "Tag version" follows our naming convention ## Prepare Package -- [ ] Create a new branch to bump version in `package.json` -- [ ] Install the latest `npm` version or Docker container with latest `node` and `npm` -- [ ] Bump version in `package.json`, remove `node_modules` folder and run `npm install` and `npm prune --production && npm shrinkwrap` -- [ ] Look through closed PRs and update `CHANGELOG.md` +- [ ] Install the latest `npm` version or Docker container with latest `node` and `npm` (Ensure to work with an `npm` version which is distributed with latest `node` version) +- [ ] Remove the `node_modules` folder and the `package-lock.json` file and run `npm install` (Removing both ensures that `package-lock.json` is updated with the latest versions of dependencies) +- [ ] Update `CHANGELOG.md` with the content from your clipboard - [ ] Make sure all files that need to be pushed are included in `package.json -> files` -- [ ] Send PR and merge PR with new version to be released -- [ ] Add the changes you made to `CHANGELOG.md` to the description of the GitHub release draft -- [ ] Go back to branch you want to release from (e.g. `master`) and pull bumped version changes from GitHub +- [ ] Commit your changes (make sure that `package.json`, `package-lock.json` and `CHANGELOG.md` are updated) +- [ ] Push your branch and open up a new PR +- [ ] Await approval and merge the PR into `master` +- [ ] Go back to the branch you want to release from (e.g. `master`) and pull the changes from GitHub - [ ] Make sure there are no local changes to your repository (or reset with `git reset --hard HEAD`) -- [ ] Check `package.json`, `package-lock.json` and `npm-shrinkwrap.json` version config to make sure it fits what we want to release +- [ ] Check `package.json` and `package-lock.json` version config to make sure it fits what we want to release ## Releasing @@ -42,6 +39,3 @@ milestone if still open - [ ] Validate that `npm install` works (`npm install -g serverless@` or `npm install -g serverless` if latest is released) -## Post-Release - -- [ ] Run `./scripts/generate-release-contributors-list ` and hand the generated list over to the release blog post author diff --git a/scripts/prs-since-last-tag b/scripts/prs-since-last-tag index 17b9a8d3d..90c9575dd 100755 --- a/scripts/prs-since-last-tag +++ b/scripts/prs-since-last-tag @@ -12,12 +12,12 @@ version=$(jq -r .version package.json) token=$(git config --global github.token) echo "# $version ($(date +%F))" +echo for pr in $(git log --reverse --grep "Merge pull request" "$last_tag"..HEAD | sed -nEe 's/.*#([0-9]+).*/\1/p'); do title=$(curl -s https://$token@api.github.com/repos/serverless/serverless/pulls/$pr | jq -r .title) - echo " - [$title](https://github.com/serverless/serverless/pull/$pr)" + echo "- [$title](https://github.com/serverless/serverless/pull/$pr)" done echo echo "## Meta" -echo " - [Comparison since last release](https://github.com/serverless/serverless/compare/$last_tag...v$version)" -echo +echo "- [Comparison since last release](https://github.com/serverless/serverless/compare/$last_tag...v$version)" echo From c642ce1e5c2029d108a5ae8baddbaee8b5c5fc03 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 11:40:34 +0200 Subject: [PATCH 021/504] Remove no longer supported mocha option --- bin/test | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/test b/bin/test index 0a6536f35..3c7e9f60d 100755 --- a/bin/test +++ b/bin/test @@ -13,7 +13,6 @@ if (process.argv.length <= 2) { '-R', 'spec', '--recursive', - '--no-exit' ); } From 502e85a38dba622cd1f1e02e11e19f12f0c8faef Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 11:40:53 +0200 Subject: [PATCH 022/504] Remove not effective mocha options --- bin/test | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/test b/bin/test index 3c7e9f60d..fa58d9410 100755 --- a/bin/test +++ b/bin/test @@ -10,9 +10,6 @@ if (process.argv.length <= 2) { process.argv.push( '!(node_modules)/**/*.test.js', '--require=sinon-bluebird', - '-R', - 'spec', - '--recursive', ); } From c4e5509ba51047eb810733d1ba5d0cdea98f9eb4 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 11:41:42 +0200 Subject: [PATCH 023/504] Ensure mocha options are passed prior test paths This ensures that options are validated properly --- bin/test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/test b/bin/test index fa58d9410..b5df522b0 100755 --- a/bin/test +++ b/bin/test @@ -8,8 +8,8 @@ process.on('unhandledRejection', err => { if (process.argv.length <= 2) { process.argv.push( - '!(node_modules)/**/*.test.js', '--require=sinon-bluebird', + '!(node_modules)/**/*.test.js', ); } From 73ea7fd9e07eb09874c2a2a4ceca22bcc6ddce03 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 11:45:54 +0200 Subject: [PATCH 024/504] Improve setTimeout stub Making it sync breaks bluebird internals (e.g. detection of unhandled rejections). With this patch we make sure it's quick enough but still async --- lib/plugins/aws/provider/awsProvider.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 1cf09dbf2..67296ffcc 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -255,7 +255,10 @@ describe('AwsProvider', () => { describe('#request()', () => { beforeEach(() => { - sinon.stub(global, 'setTimeout', (cb) => { cb(); }); + const originalSetTimeout = setTimeout; + sinon.stub(global, 'setTimeout', (cb, timeout) => { + return originalSetTimeout(cb, Math.min(timeout || 0, 10)); + }); }); afterEach(() => { From d193dc8b63be1c4adda04cacdcf8b928b58c0a57 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 12:40:56 +0200 Subject: [PATCH 025/504] Ensure to delete env vars Due to string coercion assigining undefined sets them with "undefined" string --- lib/classes/PluginManager.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index f97787555..fc60dec29 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -1438,7 +1438,7 @@ describe('PluginManager', () => { return pluginManager.run(commandsArray).then(() => { expect(consoleLogStub.called).is.equal(true); pluginManager.serverless.cli.log.restore(); - process.env.SLS_DEBUG = undefined; + delete process.env.SLS_DEBUG; }); }); @@ -1727,7 +1727,7 @@ describe('PluginManager', () => { return pluginManager.run(commandsArray).then(() => { expect(consoleLogStub.called).is.equal(true); pluginManager.serverless.cli.log.restore(); - process.env.SLS_DEBUG = undefined; + delete process.env.SLS_DEBUG; }); }); From 1ce8c21d85dcb48b0b8f1fa4d274650bf103275e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 12:54:26 +0200 Subject: [PATCH 026/504] Improve mock setup --- lib/plugins/aws/provider/awsProvider.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 67296ffcc..5bbf7f9af 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -30,8 +30,8 @@ describe('AwsProvider', () => { beforeEach(() => { serverless = new Serverless(options); + serverless.cli = new serverless.classes.CLI(); awsProvider = new AwsProvider(serverless, options); - awsProvider.serverless.cli = new serverless.classes.CLI(); }); describe('#getProviderName()', () => { From 5e85ce9b27d914febf141485751632d9b02871cf Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 12:56:25 +0200 Subject: [PATCH 027/504] Fix restoration of serverless env --- lib/plugins/aws/provider/awsProvider.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 5bbf7f9af..8f932935d 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -70,7 +70,8 @@ describe('AwsProvider', () => { expect(typeof newAwsProvider.sdk.config.logger).to.not.equal('undefined'); // reset env - process.env.SLS_DEBUG = BK_SLS_DEBUG; + if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; + else delete process.env.SLS_DEBUG; }); it('should set AWS proxy', () => { From 8562bdc2b6e307cb9117780886aae320a7f0b4dc Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 12:57:01 +0200 Subject: [PATCH 028/504] Fix assertion --- lib/plugins/aws/provider/awsProvider.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 8f932935d..668724d59 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -59,7 +59,7 @@ describe('AwsProvider', () => { }); it('should have no AWS logger', () => { - expect(awsProvider.sdk.config.logger).to.be.undefined; + expect(awsProvider.sdk.config.logger).to.be.null; }); it('should set AWS logger', () => { From d63eb58b4dc85bd15736d1f595faf22519a031f6 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 13:24:10 +0200 Subject: [PATCH 029/504] Ensure to not pollute process.env --- lib/classes/Error.test.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/classes/Error.test.js b/lib/classes/Error.test.js index c1832f123..38d6803ff 100644 --- a/lib/classes/Error.test.js +++ b/lib/classes/Error.test.js @@ -107,15 +107,21 @@ describe('Error', () => { }); it('should print stack trace with SLS_DEBUG', () => { - process.env.SLS_DEBUG = 1; - const error = new ServerlessError('a message'); - logError(error); + const BK_SLS_DEBUG = process.env.SLS_DEBUG; + process.env.SLS_DEBUG = '1'; + try { + const error = new ServerlessError('a message'); + logError(error); - const message = consoleLogSpy.args.join('\n'); + const message = consoleLogSpy.args.join('\n'); - expect(consoleLogSpy.called).to.equal(true); - expect(message).to.have.string('Stack Trace'); - expect(message).to.have.string(error.stack); + expect(consoleLogSpy.called).to.equal(true); + expect(message).to.have.string('Stack Trace'); + expect(message).to.have.string(error.stack); + } finally { + if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; + else delete process.env.SLS_DEBUG; + } }); it('should not print stack trace without SLS_DEBUG', () => { From 648368ab6049a9ede19d052036c8673487a48c7a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 13:27:35 +0200 Subject: [PATCH 030/504] Ensure no side effects --- lib/classes/PluginManager.test.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index fc60dec29..9906b51ba 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -1080,10 +1080,16 @@ describe('PluginManager', () => { }); it('should log a debug message about deprecated when using SLS_DEBUG', () => { - process.env.SLS_DEBUG = true; - pluginManager.loadHooks(deprecatedPluginInstance); + const BK_SLS_DEBUG = process.env.SLS_DEBUG; + process.env.SLS_DEBUG = '1'; + try { + pluginManager.loadHooks(deprecatedPluginInstance); - expect(consoleLogStub.calledOnce).to.equal(true); + expect(consoleLogStub.calledOnce).to.equal(true); + } finally { + if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; + else delete process.env.SLS_DEBUG; + } }); }); @@ -1422,6 +1428,7 @@ describe('PluginManager', () => { it('should show warning if in debug mode and the given command has no hooks', () => { const consoleLogStub = sinon.stub(pluginManager.serverless.cli, 'log').returns(); + const BK_SLS_DEBUG = process.env.SLS_DEBUG; process.env.SLS_DEBUG = '*'; class HooklessPlugin { constructor() { @@ -1438,7 +1445,9 @@ describe('PluginManager', () => { return pluginManager.run(commandsArray).then(() => { expect(consoleLogStub.called).is.equal(true); pluginManager.serverless.cli.log.restore(); - delete process.env.SLS_DEBUG; + }).finally(() => { + if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; + else delete process.env.SLS_DEBUG; }); }); @@ -1711,6 +1720,7 @@ describe('PluginManager', () => { it('should show warning in debug mode and when the given command has no hooks', () => { const consoleLogStub = sinon.stub(pluginManager.serverless.cli, 'log').returns(); + const BK_SLS_DEBUG = process.env.SLS_DEBUG; process.env.SLS_DEBUG = '*'; class HooklessPlugin { constructor() { @@ -1727,7 +1737,9 @@ describe('PluginManager', () => { return pluginManager.run(commandsArray).then(() => { expect(consoleLogStub.called).is.equal(true); pluginManager.serverless.cli.log.restore(); - delete process.env.SLS_DEBUG; + }).finally(() => { + if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; + else delete process.env.SLS_DEBUG; }); }); From 8c9e11d9965642aee823c0f9a78fbf05bbc0bb3b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 13:37:25 +0200 Subject: [PATCH 031/504] Ensure no side effects --- lib/plugins/aws/provider/awsProvider.test.js | 124 ++++++++++++++----- 1 file changed, 91 insertions(+), 33 deletions(-) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 668724d59..b200bdd48 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -65,13 +65,14 @@ describe('AwsProvider', () => { it('should set AWS logger', () => { const BK_SLS_DEBUG = process.env.SLS_DEBUG; process.env.SLS_DEBUG = 'true'; - const newAwsProvider = new AwsProvider(serverless, options); + try { + const newAwsProvider = new AwsProvider(serverless, options); - expect(typeof newAwsProvider.sdk.config.logger).to.not.equal('undefined'); - - // reset env - if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; - else delete process.env.SLS_DEBUG; + expect(typeof newAwsProvider.sdk.config.logger).to.not.equal('undefined'); + } finally { + if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; + else delete process.env.SLS_DEBUG; + } }); it('should set AWS proxy', () => { @@ -82,13 +83,16 @@ describe('AwsProvider', () => { }); it('should set AWS timeout', () => { + const BK_AWS_CLIENT_TIMEOUT = process.env.AWS_CLIENT_TIMEOUT; process.env.AWS_CLIENT_TIMEOUT = '120000'; - const newAwsProvider = new AwsProvider(serverless, options); + try { + const newAwsProvider = new AwsProvider(serverless, options); - expect(typeof newAwsProvider.sdk.config.httpOptions.timeout).to.not.equal('undefined'); - - // clear env - delete process.env.AWS_CLIENT_TIMEOUT; + expect(typeof newAwsProvider.sdk.config.httpOptions.timeout).to.not.equal('undefined'); + } finally { + if (BK_AWS_CLIENT_TIMEOUT) process.env.AWS_CLIENT_TIMEOUT = BK_AWS_CLIENT_TIMEOUT; + else delete process.env.AWS_CLIENT_TIMEOUT; + } }); describe('stage name validation', () => { @@ -1015,13 +1019,25 @@ describe('AwsProvider', () => { secretAccessKey: 'secretAccessKey', sessionToken: 'sessionToken', }; + const backup = { + AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY, + AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN, + }; process.env.AWS_ACCESS_KEY_ID = testVal.accessKeyId; process.env.AWS_SECRET_ACCESS_KEY = testVal.secretAccessKey; process.env.AWS_SESSION_TOKEN = testVal.sessionToken; - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); - expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); - expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); + try { + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); + expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); + expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); + } finally { + for (const key of Object.keys(backup)) { + if (backup[key]) process.env[key] = backup[key]; + else delete process.env[key]; + } + } }); it('should get credentials from environment declared stage specific credentials', () => { @@ -1030,52 +1046,94 @@ describe('AwsProvider', () => { secretAccessKey: 'secretAccessKey', sessionToken: 'sessionToken', }; + const backup = { + AWS_TESTSTAGE_ACCESS_KEY_ID: process.env.AWS_TESTSTAGE_ACCESS_KEY_ID, + AWS_TESTSTAGE_SECRET_ACCESS_KEY: process.env.AWS_TESTSTAGE_SECRET_ACCESS_KEY, + AWS_TESTSTAGE_SESSION_TOKEN: process.env.AWS_TESTSTAGE_SESSION_TOKEN, + }; process.env.AWS_TESTSTAGE_ACCESS_KEY_ID = testVal.accessKeyId; process.env.AWS_TESTSTAGE_SECRET_ACCESS_KEY = testVal.secretAccessKey; process.env.AWS_TESTSTAGE_SESSION_TOKEN = testVal.sessionToken; - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); - expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); - expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); + try { + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); + expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); + expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); + } finally { + for (const key of Object.keys(backup)) { + if (backup[key]) process.env[key] = backup[key]; + else delete process.env[key]; + } + } }); it('should get credentials from environment declared for-all-stages profile', () => { + const BK_AWS_PROFILE = process.env.AWS_PROFILE; process.env.AWS_PROFILE = 'notDefault'; - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.profile).to.equal('notDefault'); + try { + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.profile).to.equal('notDefault'); + } finally { + if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; + else delete process.env.AWS_PROFILE; + } }); it('should get credentials from environment declared stage-specific profile', () => { + const BK_AWS_TESTSTAGE_PROFILE = process.env.AWS_TESTSTAGE_PROFILE; process.env.AWS_TESTSTAGE_PROFILE = 'notDefault'; - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.profile).to.equal('notDefault'); + try { + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.profile).to.equal('notDefault'); + } finally { + if (BK_AWS_TESTSTAGE_PROFILE) process.env.AWS_TESTSTAGE_PROFILE = BK_AWS_TESTSTAGE_PROFILE; + else delete process.env.AWS_TESTSTAGE_PROFILE; + } }); it('should get credentials when profile is provied via --aws-profile option', () => { + const BK_AWS_PROFILE = process.env.AWS_PROFILE; process.env.AWS_PROFILE = 'notDefaultTemporary'; - newAwsProvider.options['aws-profile'] = 'notDefault'; + try { + newAwsProvider.options['aws-profile'] = 'notDefault'; - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.profile).to.equal('notDefault'); + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.profile).to.equal('notDefault'); + } finally { + if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; + else delete process.env.AWS_PROFILE; + } }); it('should get credentials when profile is provied via --aws-profile option even if profile is defined in serverless.yml', () => { // eslint-disable-line max-len + const BK_AWS_PROFILE = process.env.AWS_PROFILE; process.env.AWS_PROFILE = 'notDefaultTemporary'; - newAwsProvider.options['aws-profile'] = 'notDefault'; + try { + newAwsProvider.options['aws-profile'] = 'notDefault'; - serverless.service.provider.profile = 'notDefaultTemporary2'; + serverless.service.provider.profile = 'notDefaultTemporary2'; - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.profile).to.equal('notDefault'); + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.profile).to.equal('notDefault'); + } finally { + if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; + else delete process.env.AWS_PROFILE; + } }); it('should get credentials when profile is provied via process.env.AWS_PROFILE even if profile is defined in serverless.yml', () => { // eslint-disable-line max-len + const BK_AWS_PROFILE = process.env.AWS_PROFILE; process.env.AWS_PROFILE = 'notDefault'; - serverless.service.provider.profile = 'notDefaultTemporary'; + try { + serverless.service.provider.profile = 'notDefaultTemporary'; - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.profile).to.equal('notDefault'); + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.profile).to.equal('notDefault'); + } finally { + if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; + else delete process.env.AWS_PROFILE; + } }); it('should set the signatureVersion to v4 if the serverSideEncryption is aws:kms', () => { From 7437950223fb6647c59d621184e5963e92b1ec10 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 13:42:58 +0200 Subject: [PATCH 032/504] Improve process.env vars handling --- lib/classes/Error.test.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/classes/Error.test.js b/lib/classes/Error.test.js index 38d6803ff..90c6c7969 100644 --- a/lib/classes/Error.test.js +++ b/lib/classes/Error.test.js @@ -67,9 +67,15 @@ describe('Error', () => { }); describe('#logError()', () => { + let BK_SLS_DEBUG; beforeEach(() => { + BK_SLS_DEBUG = process.env.SLS_DEBUG; delete process.env.SLS_DEBUG; }); + afterEach(() => { + if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; + else delete process.env.SLS_DEBUG; + }); it('should log error and exit', () => { const error = new ServerlessError('a message', 'a status code'); @@ -107,21 +113,15 @@ describe('Error', () => { }); it('should print stack trace with SLS_DEBUG', () => { - const BK_SLS_DEBUG = process.env.SLS_DEBUG; process.env.SLS_DEBUG = '1'; - try { - const error = new ServerlessError('a message'); - logError(error); + const error = new ServerlessError('a message'); + logError(error); - const message = consoleLogSpy.args.join('\n'); + const message = consoleLogSpy.args.join('\n'); - expect(consoleLogSpy.called).to.equal(true); - expect(message).to.have.string('Stack Trace'); - expect(message).to.have.string(error.stack); - } finally { - if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; - else delete process.env.SLS_DEBUG; - } + expect(consoleLogSpy.called).to.equal(true); + expect(message).to.have.string('Stack Trace'); + expect(message).to.have.string(error.stack); }); it('should not print stack trace without SLS_DEBUG', () => { From c35cfb19fb7159ed55f945943e259a496d1e8a09 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 13:44:49 +0200 Subject: [PATCH 033/504] Improve toggling of process.env vars --- lib/classes/PluginManager.test.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 9906b51ba..9e56fa0bc 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -1051,8 +1051,10 @@ describe('PluginManager', () => { describe('#loadHooks()', () => { let deprecatedPluginInstance; let consoleLogStub; + let BK_SLS_DEBUG; beforeEach(() => { + BK_SLS_DEBUG = process.env.SLS_DEBUG; deprecatedPluginInstance = new DeprecatedLifecycleEventsPluginMock(); pluginManager.deprecatedEvents = { 'deprecated:deprecated': 'new:new', @@ -1061,9 +1063,10 @@ describe('PluginManager', () => { }); afterEach(() => { + if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; + else delete process.env.SLS_DEBUG; pluginManager.deprecatedEvents = {}; pluginManager.serverless.cli.log.restore(); - delete process.env.SLS_DEBUG; }); it('should replace deprecated events with the new ones', () => { @@ -1080,16 +1083,10 @@ describe('PluginManager', () => { }); it('should log a debug message about deprecated when using SLS_DEBUG', () => { - const BK_SLS_DEBUG = process.env.SLS_DEBUG; process.env.SLS_DEBUG = '1'; - try { - pluginManager.loadHooks(deprecatedPluginInstance); + pluginManager.loadHooks(deprecatedPluginInstance); - expect(consoleLogStub.calledOnce).to.equal(true); - } finally { - if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; - else delete process.env.SLS_DEBUG; - } + expect(consoleLogStub.calledOnce).to.equal(true); }); }); From 0326d1972207e0e320b80a4ec75234fdf73acd48 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 13:47:32 +0200 Subject: [PATCH 034/504] Ensure no side effects --- lib/plugins/aws/invokeLocal/index.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index f2919a400..43a93939b 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -250,7 +250,9 @@ describe('AwsInvokeLocal', () => { }); describe('#loadEnvVars()', () => { + let BK_AWS_PROFILE; beforeEach(() => { + BK_AWS_PROFILE = process.env.AWS_PROFILE; serverless.config.servicePath = true; serverless.service.provider = { environment: { @@ -270,7 +272,8 @@ describe('AwsInvokeLocal', () => { }); afterEach(() => { - delete process.env.AWS_PROFILE; + if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; + else delete process.env.AWS_PROFILE; }); it('it should load provider env vars', () => awsInvokeLocal From 4268dba3beceb7c892be8ceadbd11ea1fd024ff2 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 14:20:32 +0200 Subject: [PATCH 035/504] Ensure no side effects --- lib/plugins/aws/provider/awsProvider.test.js | 37 +++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index b200bdd48..5bb35b0a9 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -41,10 +41,16 @@ describe('AwsProvider', () => { }); describe('#constructor()', () => { - afterEach('Environment Variable Cleanup', () => { - // clear env + let backupProxyEnv; + + beforeEach('Environment Variable Cleanup', () => { + backupProxyEnv = process.env.proxy; delete process.env.proxy; }); + afterEach('Environment Variable Cleanup', () => { + if (backupProxyEnv) process.env.proxy = backupProxyEnv; + else delete process.env.proxy; + }); it('should set Serverless instance', () => { expect(typeof awsProvider.serverless).to.not.equal('undefined'); @@ -136,9 +142,21 @@ describe('AwsProvider', () => { }); describe('certificate authority - environment variable', () => { - afterEach('Environment Variable Cleanup', () => { - // clear env + let backupEnv; + + beforeEach('Environment Variable Cleanup', () => { + backupEnv = { + ca: process.env.ca, + proxy: process.env.proxy, + }; delete process.env.ca; + delete process.env.proxy; + }); + afterEach('Environment Variable Cleanup', () => { + for (const key of Object.keys(backupEnv)) { + if (backupEnv[key]) process.env[key] = backupEnv[key]; + else delete process.env[key]; + } }); it('should set AWS ca single', () => { process.env.ca = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; @@ -170,7 +188,14 @@ describe('AwsProvider', () => { const tmpdir = os.tmpdir(); let file1 = null; let file2 = null; + let backupEnv; beforeEach('Create CA Files and env vars', () => { + backupEnv = { + ca: process.env.ca, + cafile: process.env.cafile, + }; + delete process.env.ca; + delete process.env.cafile; file1 = path.join(tmpdir, 'ca1.txt'); file2 = path.join(tmpdir, 'ca2.txt'); fs.writeFileSync(file1, certContents); @@ -178,6 +203,10 @@ describe('AwsProvider', () => { }); afterEach('CA File Cleanup', () => { + for (const key of Object.keys(backupEnv)) { + if (backupEnv[key]) process.env[key] = backupEnv[key]; + else delete process.env[key]; + } // delete files fs.unlinkSync(file1); fs.unlinkSync(file2); From 7a76563904a4e0dbe990684c61f26336469b190f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 14:24:49 +0200 Subject: [PATCH 036/504] Cleanup process.env handling --- lib/plugins/aws/provider/awsProvider.test.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 5bb35b0a9..b18c56b81 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -142,21 +142,15 @@ describe('AwsProvider', () => { }); describe('certificate authority - environment variable', () => { - let backupEnv; + let backupCaEnv; beforeEach('Environment Variable Cleanup', () => { - backupEnv = { - ca: process.env.ca, - proxy: process.env.proxy, - }; + backupCaEnv = process.env.ca; delete process.env.ca; - delete process.env.proxy; }); afterEach('Environment Variable Cleanup', () => { - for (const key of Object.keys(backupEnv)) { - if (backupEnv[key]) process.env[key] = backupEnv[key]; - else delete process.env[key]; - } + if (backupCaEnv) process.env.ca = backupCaEnv; + else delete process.env.ca; }); it('should set AWS ca single', () => { process.env.ca = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; From b13239d9b91c2604c7ef35ac65155a1c640cee37 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 14:39:19 +0200 Subject: [PATCH 037/504] Ensure no side effects --- lib/plugins/invoke/invoke.test.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/plugins/invoke/invoke.test.js b/lib/plugins/invoke/invoke.test.js index c4830bee6..4c04aa478 100644 --- a/lib/plugins/invoke/invoke.test.js +++ b/lib/plugins/invoke/invoke.test.js @@ -11,12 +11,20 @@ const expect = chai.expect; describe('Invoke', () => { let invoke; let serverless; + let BK_IS_LOCAL; beforeEach(() => { + BK_IS_LOCAL = process.env.IS_LOCAL; + delete process.env.IS_LOCAL; serverless = new Serverless(); invoke = new Invoke(serverless); }); + afterEach(() => { + if (BK_IS_LOCAL) process.env.IS_LOCAL = BK_IS_LOCAL; + else delete process.env.IS_LOCAL; + }); + describe('#constructor()', () => { it('should have commands', () => expect(invoke.commands).to.be.not.empty); it('should have hooks', () => expect(invoke.hooks).to.be.not.empty); @@ -24,7 +32,6 @@ describe('Invoke', () => { describe('#loadEnvVarsForLocal()', () => { it('should set IS_LOCAL', () => { - delete process.env.IS_LOCAL; return expect(invoke.loadEnvVarsForLocal()).to.be.fulfilled .then(() => expect(process.env.IS_LOCAL).to.equal('true')); }); @@ -37,7 +44,6 @@ describe('Invoke', () => { }); it('should set IS_LOCAL', () => { - delete process.env.IS_LOCAL; return expect(invoke.hooks['invoke:local:loadEnvVars']()).to.be.fulfilled .then(() => expect(process.env.IS_LOCAL).to.equal('true')); }); From e666c7f267bc8e5e68af50921ba4b2330fdbfeec Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 14:45:15 +0200 Subject: [PATCH 038/504] Ensure test doesn't depend on env state --- lib/plugins/aws/provider/awsProvider.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index b18c56b81..a849ce293 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -816,8 +816,11 @@ describe('AwsProvider', () => { const relevantEnvironment = { AWS_SHARED_CREDENTIALS_FILE: testUtils.getTmpFilePath('credentials'), }; + let BK_AWS_PROFILE; beforeEach(() => { + BK_AWS_PROFILE = process.env.AWS_PROFILE; + delete process.env.AWS_PROFILE; originalProviderProfile = serverless.service.provider.profile; originalEnvironmentVariables = testUtils.replaceEnv(relevantEnvironment); serverless.utils.writeFileSync( @@ -834,6 +837,8 @@ describe('AwsProvider', () => { }); afterEach(() => { + if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; + else delete process.env.AWS_PROFILE; testUtils.replaceEnv(originalEnvironmentVariables); serverless.service.provider.profile = originalProviderProfile; }); From 155eb6c6f20d229139146e0b1a70b7370ebd03f8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 14:52:03 +0200 Subject: [PATCH 039/504] Fix lint issue --- lib/plugins/aws/provider/awsProvider.test.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index a849ce293..89d2c7ee0 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -284,9 +284,8 @@ describe('AwsProvider', () => { describe('#request()', () => { beforeEach(() => { const originalSetTimeout = setTimeout; - sinon.stub(global, 'setTimeout', (cb, timeout) => { - return originalSetTimeout(cb, Math.min(timeout || 0, 10)); - }); + sinon.stub(global, 'setTimeout', (cb, timeout) => + originalSetTimeout(cb, Math.min(timeout || 0, 10))); }); afterEach(() => { From 40cd3c0fc9758ad221d6c33ada74b115417bcc71 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 15:32:15 +0200 Subject: [PATCH 040/504] Fix lint issues --- lib/plugins/invoke/invoke.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/plugins/invoke/invoke.test.js b/lib/plugins/invoke/invoke.test.js index 4c04aa478..3db7fb64b 100644 --- a/lib/plugins/invoke/invoke.test.js +++ b/lib/plugins/invoke/invoke.test.js @@ -31,10 +31,10 @@ describe('Invoke', () => { }); describe('#loadEnvVarsForLocal()', () => { - it('should set IS_LOCAL', () => { - return expect(invoke.loadEnvVarsForLocal()).to.be.fulfilled - .then(() => expect(process.env.IS_LOCAL).to.equal('true')); - }); + it('should set IS_LOCAL', () => + expect(invoke.loadEnvVarsForLocal()).to.be.fulfilled.then(() => + expect(process.env.IS_LOCAL).to.equal('true') + )); }); describe('hooks', () => { @@ -43,10 +43,10 @@ describe('Invoke', () => { expect(invoke.commands.invoke.commands.local.lifecycleEvents).to.contain('loadEnvVars'); }); - it('should set IS_LOCAL', () => { - return expect(invoke.hooks['invoke:local:loadEnvVars']()).to.be.fulfilled - .then(() => expect(process.env.IS_LOCAL).to.equal('true')); - }); + it('should set IS_LOCAL', () => + expect(invoke.hooks['invoke:local:loadEnvVars']()).to.be.fulfilled.then(() => + expect(process.env.IS_LOCAL).to.equal('true') + )); it('should accept a single env option', () => { invoke.options = { env: 'NAME=value' }; From 08b462ecb4334560af5f08e0c418b11755d0182d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 15:42:10 +0200 Subject: [PATCH 041/504] Ensure no trailing comma (node v4 support) --- bin/test | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bin/test b/bin/test index b5df522b0..4d5da5468 100755 --- a/bin/test +++ b/bin/test @@ -7,10 +7,7 @@ process.on('unhandledRejection', err => { }); if (process.argv.length <= 2) { - process.argv.push( - '--require=sinon-bluebird', - '!(node_modules)/**/*.test.js', - ); + process.argv.push('--require=sinon-bluebird', '!(node_modules)/**/*.test.js'); } require('mocha/bin/_mocha'); From 856e0ec69e628d2929ac895a4979e960f7962f77 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 14 May 2019 10:10:18 -0400 Subject: [PATCH 042/504] switch to latest --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index efc9f6b25..2ec2d91f1 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "strip-ansi": "^5.2.0" }, "dependencies": { - "@serverless/enterprise-plugin": "next", + "@serverless/enterprise-plugin": "latest", "archiver": "^1.1.0", "async": "^1.5.2", "aws-sdk": "^2.430.0", From aa6b3934a7d5481f4f40cda970f1c98709864c8a Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 14 May 2019 10:26:09 -0400 Subject: [PATCH 043/504] update npm before starting travis stuff --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 7cc93cba4..421840c05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,6 +44,7 @@ sudo: false install: - travis_retry npm install script: +- npm i -g npm - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test && npm run local-integration-test; fi - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then npm run lint; fi From 2344bbc70e138c3449f719f870f2055809fd9d21 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 14 May 2019 10:58:30 -0400 Subject: [PATCH 044/504] droping ancient nodes that cant run new npm --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 421840c05..81e59e9de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,6 @@ matrix: - secure: ruPmxtjLY9jX/TQQe1d8vxwL0KsjsTzJK/EL6rZM954JJc+3c5gshxB1Ca9+WRwIGdMjCZgKo4dQcn2DrAap7KoDz+GRCi40kc5AOr2ZLmjxJeCJls+sQ713QJ01GgIA1X2zmz/bDSw5lan8ODR3eYI0fIn37ZV4yxNJ5PObBLCMQW6nnOdMXxcLLi2iJRqu3k0gb85wsUNOB0AF2cBrbxoYmGsrGjGuPjIXPeEdnVJ0jzBY+jDrBraIHEBN9tecIyG77ZgEhlPopqTrj39dEPMwAow+derEVWugPRF3Wps6z+Yx0qtxETptWuOgn0V9GpfZF9vJlHifQUuuJEGtttVCYjZCUOIoIRwWnX+/SygjlgLJ9PpVa6XyZfnqygJXnv7wouGGkiyZEDbayu65qP3Ls/Dj+N2nEtamOPf3dGBeaX8KKF61h80bH7o9JL/t6yxyvm+yDRTkYd9SNk3U7dFXoBcCcjMNNUkKBKtY9G3EZlE0ZV0D7jD33Na4yX8mKBHkYLKXCAn+rh6DIwLNkV8IOtAYD77qan6v4qH9fpPMX4UkLu9SRM1r5RfsZU0gX7sCP9OKFKkAx5BWUqjg+D6Xa0EACtO8bPDHA548hWU2vYx7ghuVJfVNOUlAl97OXclx2SZwXJ46rHaHgrH2I3gi3qyHMib3iUV7SLdRQnY= - secure: p9ka7mRzo/ecjnDh/dz19g0iVfQdvsGRAtg/4ONeiq75I2+oqHzu+VxUBA1Z2IQbpCEAMo21CarR3fg2I6MFUeazL0nEpqr1PoOAI8nPFeQlg/h+jLXsrPAkDcu2/b8ij7J5MXeLdZXUVqiPcGkr68x/tCMk/rwxftljQhvXPQfc7Lxm/m61ELnC7rLJulhxWZLNIq1hwQ9nh0GMKb4hm0KmPn8ksccVL+wyDikkgXCuvIujhTBjhNivAe4mG8mqnNsW1Ugh++SUe1ld27TtbH7wQj02SSG4Bxfwc3Gz0GFdAL1GyOkWI2WvrqP4a0KYTRUo+pUr9E+HZ1SNlxU5t6QWtmDiy5MKkxzgeTXmkKiJ98vMlF0ja5bpp46NjYarzDafqE8FozHzLtr+uAtqr6gRAgU1rWaG9BE3gKeW/f4B/2MfPI26b7SxuU1MwGVy0I76hb0Ujbgb3X8G4TYTGb6Nhoewc+RZExPwVhfrN8cJjo45masndv5tQAZMSRX/JUFjs4h/QMXNsn0A53GXgf6eIzUu15m+W8TJYFiKQeq9nMejzEE4sWMO3BFnkxueBGVCEurOc1GgdEnKxeqlp+psxHcJRlNCxC1HkUVOzfpkCr/Jy42vM8jQomAMv41Z9zWjOagVphWT25xNeSILfRt4yPku5wfW4CAxp+fl4KQ= include: - - node_js: '4.4' - env: SLS_IGNORE_WARNING=* - - node_js: '5.11' - env: SLS_IGNORE_WARNING=* - node_js: '6.2' env: SLS_IGNORE_WARNING=* - node_js: '6.2' From 38d971bad6c318537f30bd39d3496336d1df881e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 17:22:34 +0200 Subject: [PATCH 045/504] Fix ignore rules Do not ignore /bin folder --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7ecc11ddb..0d5cd2ea9 100755 --- a/.gitignore +++ b/.gitignore @@ -57,5 +57,4 @@ jest # DotNet obj/ -[Bb]in/ [Oo]bj/ From 657510c81aea8fb69264589bc5bbfde0e79d8e1e Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 14 May 2019 11:23:38 -0400 Subject: [PATCH 046/504] lint --- tests/local-suite/packaging.tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/local-suite/packaging.tests.js b/tests/local-suite/packaging.tests.js index d6b2b9867..7c947a313 100644 --- a/tests/local-suite/packaging.tests.js +++ b/tests/local-suite/packaging.tests.js @@ -86,7 +86,8 @@ describe('cloudformation packaging', () => { execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( cwd, '.serverless/cloudformation-template-update-stack.json'))); - expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key).toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/aws-nodejs.zip/); + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key) + .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/aws-nodejs.zip/); delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ Type: 'AWS::Lambda::Function', From 58fafa95f6e56987cd8dabc49f3ec56b7320f186 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 14 May 2019 17:23:53 +0200 Subject: [PATCH 047/504] Test runner that runs tests in isolation --- bin/test-isolated | 48 +++++++++++++++ package-lock.json | 147 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 ++ 3 files changed, 199 insertions(+) create mode 100755 bin/test-isolated diff --git a/bin/test-isolated b/bin/test-isolated new file mode 100755 index 000000000..6c86c71f1 --- /dev/null +++ b/bin/test-isolated @@ -0,0 +1,48 @@ +#!/usr/bin/env node + +// Basic isolated tests runner +// Ensures each test file is run in distinct process and does not interfere with other test runs. +// To be used to confirm test files do not introduce and work by chance of side effects +// Temporary solution until we migrate to runner which provides that (reliably) on its own + +'use strict'; + +process.on('unhandledRejection', err => { + throw err; +}); + +const cpus = require('os').cpus(); +const globby = require('globby'); +const spawn = require('child-process-ext/spawn'); +const cliFooter = require('cli-progress-footer')(); +const pLimit = require('p-limit'); + +cliFooter.shouldAddProgressAnimationPrefix = true; +const processesCount = Math.max(cpus.length - 1, 1); + +const patterns = process.argv.length <= 2 ? ['**/*.test.js'] : process.argv.slice(2); +patterns.push('!node_modules/**'); + +const ongoing = new Set(); +const run = path => { + ongoing.add(path); + cliFooter.updateProgress(Array.from(ongoing)); + const onFinally = ({ stdoutBuffer, stderrBuffer }) => { + ongoing.delete(path); + cliFooter.updateProgress(Array.from(ongoing)); + process.stdout.write(String(stdoutBuffer)); + process.stderr.write(String(stderrBuffer)); + }; + return spawn('./bin/test', ['--require=sinon-bluebird', path], { + env: Object.assign({ FORCE_COLOR: '1' }, process.env), + }).then(onFinally, error => { + onFinally(error); + throw error; + }); +}; + +globby(patterns).then(paths => { + const limit = pLimit(processesCount); + + return Promise.all(paths.map(path => limit(() => run(path)))); +}); diff --git a/package-lock.json b/package-lock.json index 9a1ead6ec..c6fa3bb1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,16 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "2-thenable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/2-thenable/-/2-thenable-1.0.0.tgz", + "integrity": "sha512-HqiDzaLDFCXkcCO/SwoyhRwqYtINFHF7t9BDRq4x90TOKNAJpiqUt9X5lQ08bwxYzc067HUywDjGySpebHcUpw==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.47" + } + }, "@babel/code-frame": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", @@ -1404,6 +1414,33 @@ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, + "child-process-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/child-process-ext/-/child-process-ext-2.0.0.tgz", + "integrity": "sha512-yTIjjYfLcZ8/gje3+O//GW57REjRaYx+I+7SEHnkzMJSrSESFGPStDG01hWpYWgdhcC5oTTP8Vstp/rXM3ddCg==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.5", + "es5-ext": "^0.10.46", + "split2": "^3.1", + "stream-promise": "^3.1" + }, + "dependencies": { + "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" + } + } + } + }, "ci-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", @@ -1492,6 +1529,20 @@ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, + "cli-color": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", + "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", + "dev": true, + "requires": { + "ansi-regex": "^2.1.1", + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.5" + } + }, "cli-cursor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", @@ -1500,6 +1551,19 @@ "restore-cursor": "^1.0.1" } }, + "cli-progress-footer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cli-progress-footer/-/cli-progress-footer-1.1.1.tgz", + "integrity": "sha512-J0uW2u06pWI0tMKCbcCiMOZd8TbWj4EpuYgPo4Jiwih/FfGbd4dbLcJieO0Ior1pY1HBrnmCuHFk6GB9azE4pg==", + "dev": true, + "requires": { + "cli-color": "^1.4", + "d": "1", + "es5-ext": "^0.10.47", + "process-utils": "^2.0.1", + "timers-ext": "^0.1.7" + } + }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -5608,6 +5672,15 @@ "yallist": "^2.1.2" } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "requires": { + "es5-ext": "~0.10.2" + } + }, "lsmod": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", @@ -5792,6 +5865,22 @@ "p-is-promise": "^2.0.0" } }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -6672,6 +6761,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, + "process-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/process-utils/-/process-utils-2.1.0.tgz", + "integrity": "sha512-FjkSXOw9XJ8qadsgOQHuwyhvqUq2D9JkEfqExrYe+ik2OHJw0q/hTOgrxybg0lgw3K/HTrBQ3s9nM7yI3Ukk2g==", + "dev": true, + "requires": { + "type": "^1.0.1" + } + }, "progress": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", @@ -7604,6 +7702,28 @@ "extend-shallow": "^3.0.0" } }, + "split2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.1.1.tgz", + "integrity": "sha512-emNzr1s7ruq4N+1993yht631/JH+jaj0NYBosuKmLcq+JkGQ9MmTw1RB1fGaTCzUuseRIClrlSLHRNYGwWQ58Q==", + "dev": true, + "requires": { + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -7713,6 +7833,17 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, + "stream-promise": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-promise/-/stream-promise-3.2.0.tgz", + "integrity": "sha512-P+7muTGs2C8yRcgJw/PPt61q7O517tDHiwYEzMWo1GSBCcZedUMT/clz7vUNsSxFphIlJ6QUL4GexQKlfJoVtA==", + "dev": true, + "requires": { + "2-thenable": "^1.0.0", + "es5-ext": "^0.10.49", + "is-stream": "^1.1.0" + } + }, "string-length": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", @@ -8074,6 +8205,16 @@ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "tmp": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", @@ -8207,6 +8348,12 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, + "type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", + "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==", + "dev": true + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/package.json b/package.json index f179fb8d9..d67ce2611 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ }, "scripts": { "test-bare": "node bin/test", + "test-isolated": "node bin/test-isolated", "test": "istanbul cover -x \"**/*.test.js\" bin/test", "lint": "eslint . --cache", "docs": "node scripts/generate-readme.js", @@ -70,6 +71,8 @@ "devDependencies": { "chai": "^3.5.0", "chai-as-promised": "^6.0.0", + "child-process-ext": "^2.0.0", + "cli-progress-footer": "^1.1.1", "coveralls": "^3.0.3", "eslint": "^3.3.1", "eslint-config-airbnb": "^10.0.1", @@ -85,6 +88,7 @@ "mocha": "^5.2.0", "mocha-lcov-reporter": "^1.2.0", "mock-require": "^1.3.0", + "p-limit": "^2.2.0", "parse-github-url": "^1.0.1", "proxyquire": "^1.7.10", "sinon": "^1.17.5", From c3a32cf45b7d215e12abd6439a901589c9ff3891 Mon Sep 17 00:00:00 2001 From: My Ho Date: Tue, 14 May 2019 10:56:09 -0700 Subject: [PATCH 048/504] azure: exclude development dependency files when packaging functions .funcignore is not being used so have to add the ignore files to the `exclude` section in serverless.yml --- lib/plugins/create/templates/azure-nodejs/serverless.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/plugins/create/templates/azure-nodejs/serverless.yml b/lib/plugins/create/templates/azure-nodejs/serverless.yml index 74e94f9fd..3e49bd916 100644 --- a/lib/plugins/create/templates/azure-nodejs/serverless.yml +++ b/lib/plugins/create/templates/azure-nodejs/serverless.yml @@ -25,13 +25,15 @@ plugins: - serverless-azure-functions # you can add packaging information here -#package: +package: # include: # - include-me.js # - include-me-dir/** -# exclude: + exclude: # - exclude-me.js # - exclude-me-dir/** + - local.settings.json + - .vscode/** functions: hello: From 1629b16acae133f49f8ea8b00860e4a1cc12750a Mon Sep 17 00:00:00 2001 From: Peter Squicciarini Date: Tue, 14 May 2019 14:05:21 -0700 Subject: [PATCH 049/504] Update services.md Removed the paragraph about multiple services behind a shared API gateway --- docs/providers/aws/guide/services.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/providers/aws/guide/services.md b/docs/providers/aws/guide/services.md index e69c24278..b271406e7 100644 --- a/docs/providers/aws/guide/services.md +++ b/docs/providers/aws/guide/services.md @@ -37,8 +37,6 @@ comments/ ``` This makes sense since related functions usually use common infrastructure resources, and you want to keep those functions and resources together as a single unit of deployment, for better organization and separation of concerns. -**Note:** Currently, every service will create a separate REST API on AWS API Gateway. Due to a limitation with AWS API Gateway, you can only have a custom domain per one REST API. If you plan on making a large REST API, please make note of this limitation. Also, [a fix is in the works](https://github.com/serverless/serverless/issues/3078) and is a top priority. - ## Creation To create a service, use the `create` command. You must also pass in a runtime (e.g., node.js, python etc.) you would like to write the service in. You can also pass in a path to create a directory and auto-name your service: From 5b5425d9395fcd9f1d562317cf191290530d2eb1 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Wed, 15 May 2019 14:27:06 +0200 Subject: [PATCH 050/504] Update AWS Node.js runtime to version 10 --- docs/providers/aws/cli-reference/print.md | 4 +- docs/providers/aws/events/apigateway.md | 4 +- docs/providers/aws/events/websocket.md | 8 +- .../examples/hello-world/node/serverless.yml | 2 +- docs/providers/aws/guide/credentials.md | 4 +- docs/providers/aws/guide/functions.md | 14 ++-- docs/providers/aws/guide/serverless.yml.md | 4 +- docs/providers/aws/guide/services.md | 6 +- docs/providers/aws/guide/variables.md | 4 +- lib/classes/Service.test.js | 4 +- lib/classes/Utils.test.js | 2 +- lib/plugins/aws/invokeLocal/index.js | 2 +- lib/plugins/aws/invokeLocal/index.test.js | 12 +-- .../aws/package/compile/functions/index.js | 2 +- .../package/compile/functions/index.test.js | 76 +++++++++---------- .../aws/package/compile/layers/index.test.js | 4 +- .../aws-alexa-typescript/serverless.yml | 2 +- .../aws-clojurescript-gradle/serverless.yml | 2 +- .../aws-kotlin-nodejs-gradle/serverless.yml | 2 +- .../aws-nodejs-ecma-script/serverless.yml | 2 +- .../aws-nodejs-typescript/serverless.yml | 2 +- .../templates/aws-nodejs/serverless.yml | 2 +- .../templates/hello-world/serverless.yml | 2 +- .../api-keys/service/serverless.yml | 2 +- .../cors/service/serverless.yml | 2 +- .../custom-authorizers/service/serverless.yml | 2 +- .../simple-api/service/serverless.yml | 2 +- .../api-keys/service/serverless.yml | 2 +- .../cors/service/serverless.yml | 2 +- .../custom-authorizers/service/serverless.yml | 2 +- .../simple-api/service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../custom-resources/service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../nested-handlers/service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../general/package/service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../sns/existing-topic/service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../service/serverless.yml | 2 +- .../custom-plugins/service/serverless.yml | 2 +- .../local-plugins/service/serverless.yml | 2 +- 60 files changed, 120 insertions(+), 120 deletions(-) diff --git a/docs/providers/aws/cli-reference/print.md b/docs/providers/aws/cli-reference/print.md index 4009dac29..39584d5b9 100644 --- a/docs/providers/aws/cli-reference/print.md +++ b/docs/providers/aws/cli-reference/print.md @@ -42,7 +42,7 @@ custom: provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x stage: ${opt:stage, "dev"} functions: @@ -66,7 +66,7 @@ custom: bucketName: test provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x stage: dev # <-- Resolved functions: hello: diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index ad8ff969d..d8bfb68af 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -1220,7 +1220,7 @@ service: my-api provider: name: aws - runtime: nodejs8.10 + runtime: nodejs10.x stage: dev region: eu-west-2 @@ -1390,7 +1390,7 @@ Resource policies are policy documents that are used to control the invocation o ```yml provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x resourcePolicy: - Effect: Allow diff --git a/docs/providers/aws/events/websocket.md b/docs/providers/aws/events/websocket.md index 320df6bc6..8d88f4681 100644 --- a/docs/providers/aws/events/websocket.md +++ b/docs/providers/aws/events/websocket.md @@ -60,7 +60,7 @@ service: serverless-ws-test provider: name: aws - runtime: nodejs8.10 + runtime: nodejs10.x websocketsApiName: custom-websockets-api-name websocketsApiRouteSelectionExpression: $request.body.action # custom routes are selected by the value of the action property in the body @@ -127,7 +127,7 @@ functions: identitySource: - 'route.request.header.Auth' - 'route.request.querystring.Auth' - + auth: handler: handler.auth ``` @@ -147,7 +147,7 @@ functions: identitySource: - 'route.request.header.Auth' - 'route.request.querystring.Auth' - + auth: handler: handler.auth ``` @@ -177,7 +177,7 @@ const sendMessageToClient = (url, connectionId, payload) => new Promise((resolve module.exports.defaultHandler = async (event, context) => { const domain = event.requestContext.domainName; const stage = event.requestContext.stage; - const connectionId = event.requestContext.connectionId; + const connectionId = event.requestContext.connectionId; const callbackUrlForAWS = util.format(util.format('https://%s/%s', domain, stage)); //construct the needed url await sendMessageToClient(callbackUrlForAWS, connectionId, event); diff --git a/docs/providers/aws/examples/hello-world/node/serverless.yml b/docs/providers/aws/examples/hello-world/node/serverless.yml index 97b220d9f..4d44fe7c3 100644 --- a/docs/providers/aws/examples/hello-world/node/serverless.yml +++ b/docs/providers/aws/examples/hello-world/node/serverless.yml @@ -3,7 +3,7 @@ service: hello-world # Service Name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: helloWorld: diff --git a/docs/providers/aws/guide/credentials.md b/docs/providers/aws/guide/credentials.md index 208c58e55..2fb055262 100644 --- a/docs/providers/aws/guide/credentials.md +++ b/docs/providers/aws/guide/credentials.md @@ -131,7 +131,7 @@ You can even set up different profiles for different accounts, which can be used service: new-service provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x stage: dev profile: devProfile ``` @@ -176,7 +176,7 @@ This example `serverless.yml` snippet will load the profile depending upon the s service: new-service provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x stage: ${opt:stage, self:custom.defaultStage} profile: ${self:custom.profiles.${self:provider.stage}} custom: diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index ecdfe09ed..af8154cc6 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -24,7 +24,7 @@ service: myService provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x memorySize: 512 # optional, in MB, default is 1024 timeout: 10 # optional, in seconds, default is 6 versionFunctions: false # optional, default is true @@ -59,7 +59,7 @@ service: myService provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: functionOne: @@ -79,7 +79,7 @@ service: myService provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x memorySize: 512 # will be inherited by all functions functions: @@ -95,7 +95,7 @@ service: myService provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: functionOne: @@ -133,7 +133,7 @@ service: myService provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x iamRoleStatements: # permissions for all of your functions can be set here - Effect: Allow Action: # Gives permission to DynamoDB tables in a specific region @@ -390,7 +390,7 @@ service: service provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: @@ -443,7 +443,7 @@ service: myService provider: name: aws - runtime: nodejs8.10 + runtime: nodejs10.x tracing: lambda: true ``` diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 1ccfe878b..413602b47 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -25,7 +25,7 @@ frameworkVersion: ">=1.0.0 <2.0.0" provider: name: aws - runtime: nodejs8.10 + runtime: nodejs10.x stage: ${opt:stage, 'dev'} # Set the default stage used. Default is dev region: ${opt:region, 'us-east-1'} # Overwrite the default region used. Default is us-east-1 stackName: custom-stack-name # Use a custom name for the CloudFormation stack @@ -148,7 +148,7 @@ functions: description: My function # The description of your function. memorySize: 512 # memorySize for this specific function. reservedConcurrency: 5 # optional, reserved concurrency limit for this function. By default, AWS uses account concurrency limit - runtime: nodejs6.10 # Runtime for this specific function. Overrides the default which is set on the provider level + runtime: nodejs10.x # Runtime for this specific function. Overrides the default which is set on the provider level timeout: 10 # Timeout for this specific function. Overrides the default set above. role: arn:aws:iam::XXXXXX:role/role # IAM role which will be used for this function onError: arn:aws:sns:us-east-1:XXXXXX:sns-topic # Optional SNS topic / SQS arn (Ref, Fn::GetAtt and Fn::ImportValue are supported as well) which will be used for the DeadLetterConfig diff --git a/docs/providers/aws/guide/services.md b/docs/providers/aws/guide/services.md index b271406e7..b2d73b8ba 100644 --- a/docs/providers/aws/guide/services.md +++ b/docs/providers/aws/guide/services.md @@ -99,7 +99,7 @@ service: users provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x stage: dev # Set the default stage used. Default is dev region: us-east-1 # Overwrite the default region used. Default is us-east-1 stackName: my-custom-stack-name-${self:provider.stage} # Overwrite default CloudFormation stack name. Default is ${self:service}-${self:provider.stage} @@ -229,7 +229,7 @@ service: users provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x memorySize: 512 … @@ -246,7 +246,7 @@ service: users provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x memorySize: 512 … diff --git a/docs/providers/aws/guide/variables.md b/docs/providers/aws/guide/variables.md index 804820b22..895269374 100644 --- a/docs/providers/aws/guide/variables.md +++ b/docs/providers/aws/guide/variables.md @@ -183,7 +183,7 @@ You can add such custom output to CloudFormation stack. For example: service: another-service provider: name: aws - runtime: nodejs8.10 + runtime: nodejs10.x region: ap-northeast-1 memorySize: 512 functions: @@ -562,7 +562,7 @@ service: new-service provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x variableSyntax: "\\${{([ ~:a-zA-Z0-9._@\\'\",\\-\\/\\(\\)]+?)}}" # notice the double quotes for yaml to ignore the escape characters! # variableSyntax: "\\${((?!AWS)[ ~:a-zA-Z0-9._@'\",\\-\\/\\(\\)]+?)}" # Use this for allowing CloudFormation Pseudo-Parameters in your serverless.yml -- e.g. ${AWS::Region}. All other Serverless variables work as usual. diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index 3c57f91d6..0ececfd87 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -97,14 +97,14 @@ describe('Service', () => { const data = { provider: { name: 'testProvider', - runtime: 'nodejs6.10', + runtime: 'nodejs10.x', }, }; const serviceInstance = new Service(serverless, data); expect(serviceInstance.provider.name).to.be.equal('testProvider'); - expect(serviceInstance.provider.runtime).to.be.equal('nodejs6.10'); + expect(serviceInstance.provider.runtime).to.be.equal('nodejs10.x'); }); }); diff --git a/lib/classes/Utils.test.js b/lib/classes/Utils.test.js index 1c3fd1a2c..aa0f2d49f 100644 --- a/lib/classes/Utils.test.js +++ b/lib/classes/Utils.test.js @@ -728,7 +728,7 @@ describe('Utils', () => { service: 'new-service', provider: { name: 'aws', - runtime: 'nodejs6.10', + runtime: 'nodejs10.x', stage: 'dev', region: 'us-east-1', variableSyntax: '\\${foo}', diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index cc75d1dad..692be94a1 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -38,7 +38,7 @@ class AwsInvokeLocal { getRuntime() { return this.options.functionObj.runtime || this.serverless.service.provider.runtime - || 'nodejs4.3'; + || 'nodejs10.x'; } validateFile(filePath, key) { diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index f2919a400..698c1577d 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -382,7 +382,7 @@ describe('AwsInvokeLocal', () => { ); it('should call invokeLocalNodeJs for any node.js runtime version', () => { - awsInvokeLocal.options.functionObj.runtime = 'nodejs6.10'; + awsInvokeLocal.options.functionObj.runtime = 'nodejs10.x'; return awsInvokeLocal.invokeLocal().then(() => { expect(invokeLocalNodeJsStub.calledOnce).to.be.equal(true); expect(invokeLocalNodeJsStub.calledWithExactly( @@ -482,8 +482,8 @@ describe('AwsInvokeLocal', () => { }); }); - it('should call invokeLocalDocker if using --docker option with nodejs8.10', () => { - awsInvokeLocal.options.functionObj.runtime = 'nodejs8.10'; + it('should call invokeLocalDocker if using --docker option with nodejs10.x', () => { + awsInvokeLocal.options.functionObj.runtime = 'nodejs10.x'; awsInvokeLocal.options.functionObj.handler = 'handler.foobar'; awsInvokeLocal.options.docker = true; return awsInvokeLocal.invokeLocal().then(() => { @@ -1163,7 +1163,7 @@ describe('AwsInvokeLocal', () => { handler: 'handler.hello', name: 'hello', timeout: 4, - runtime: 'nodejs8.10', + runtime: 'nodejs10.x', environment: { functionVar: 'functionValue', }, @@ -1190,9 +1190,9 @@ describe('AwsInvokeLocal', () => { expect(pluginMangerSpawnPackageStub.calledOnce).to.equal(true); expect(spawnStub.getCall(0).args).to.deep.equal(['docker', ['version']]); expect(spawnStub.getCall(1).args).to.deep.equal(['docker', - ['images', '-q', 'lambci/lambda:nodejs8.10']]); + ['images', '-q', 'lambci/lambda:nodejs10.x']]); expect(spawnStub.getCall(2).args).to.deep.equal(['docker', - ['pull', 'lambci/lambda:nodejs8.10']]); + ['pull', 'lambci/lambda:nodejs10.x']]); expect(spawnStub.getCall(3).args).to.deep.equal(['docker', [ 'build', '-t', diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 410480c74..ad3a6f18c 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -115,7 +115,7 @@ class AwsCompileFunctions { || 6; const Runtime = functionObject.runtime || this.serverless.service.provider.runtime - || 'nodejs4.3'; + || 'nodejs10.x'; newFunction.Properties.Handler = Handler; newFunction.Properties.FunctionName = FunctionName; diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index 722285ba2..430924b85 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -416,7 +416,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, }, }; @@ -460,7 +460,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, VpcConfig: { SecurityGroupIds: ['xxx'], @@ -507,7 +507,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, VpcConfig: { SecurityGroupIds: ['xxx'], @@ -556,7 +556,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, Tags: [ { Key: 'foo', Value: 'bar' }, @@ -604,7 +604,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, Tags: [ { Key: 'foo', Value: 'bar' }, @@ -657,7 +657,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, Tags: [ { Key: 'foo', Value: 'bar' }, @@ -765,7 +765,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, DeadLetterConfig: { TargetArn: 'arn:aws:sns:region:accountid:foo', @@ -834,7 +834,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, DeadLetterConfig: { TargetArn: { @@ -880,7 +880,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, DeadLetterConfig: { TargetArn: { @@ -926,7 +926,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, DeadLetterConfig: { TargetArn: { @@ -972,7 +972,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, DeadLetterConfig: { TargetArn: 'arn:aws:sns:region:accountid:foo', @@ -1085,7 +1085,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, KmsKeyArn: 'arn:aws:kms:region:accountid:foo/bar', }, @@ -1133,7 +1133,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func1.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, KmsKeyArn: 'arn:aws:kms:region:accountid:foo/function', }, @@ -1154,7 +1154,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func2.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, KmsKeyArn: 'arn:aws:kms:region:accountid:foo/service', }, @@ -1213,7 +1213,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, KmsKeyArn: 'arn:aws:kms:region:accountid:foo/bar', }, @@ -1267,7 +1267,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, KmsKeyArn: 'arn:aws:kms:region:accountid:foo/bar', }, @@ -1338,7 +1338,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, TracingConfig: { Mode: 'Active', @@ -1388,7 +1388,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func1.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, TracingConfig: { Mode: 'Active', @@ -1411,7 +1411,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func2.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, TracingConfig: { Mode: 'PassThrough', @@ -1471,7 +1471,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, TracingConfig: { Mode: 'Active', @@ -1527,7 +1527,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, TracingConfig: { Mode: 'PassThrough', @@ -1581,7 +1581,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, Environment: { Variables: { @@ -1631,7 +1631,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, Environment: { Variables: { @@ -1680,7 +1680,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, Environment: { Variables: { @@ -1732,7 +1732,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, Environment: { Variables: { @@ -1846,7 +1846,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 128, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 10, }, }; @@ -1902,7 +1902,7 @@ describe('AwsCompileFunctions', () => { }); }); - it('should default to the nodejs4.3 runtime when no provider runtime is given', () => { + it('should default to the nodejs10.x runtime when no provider runtime is given', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact .split(path.sep).pop(); @@ -1928,7 +1928,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, }, }; @@ -2069,12 +2069,12 @@ describe('AwsCompileFunctions', () => { const expectedOutputs = { FuncLambdaFunctionQualifiedArn: { Description: 'Current Lambda function version', - Value: { Ref: 'FuncLambdaVersionl6Rjpaz0gycgsEDI51sLed039fH2uR4W8Q2IW8cNo' }, + Value: { Ref: 'FuncLambdaVersionpcyXz9PqN5xesfBZOOhY7t6jhi8kOCyGDknpfuhJ4' }, }, AnotherFuncLambdaFunctionQualifiedArn: { Description: 'Current Lambda function version', Value: { - Ref: 'AnotherFuncLambdaVersion6JZQneYqP4bC0Z3ywMc3XJPyECHK4RMGhpv8iis4E', + Ref: 'AnotherFuncLambdaVersionIo6IWr3BPLeAaVjlRwEGEz4vvDzC43h07eOBY0fXyI', }, }, }; @@ -2103,12 +2103,12 @@ describe('AwsCompileFunctions', () => { const expectedOutputs = { FuncLambdaFunctionQualifiedArn: { Description: 'Current Lambda function version', - Value: { Ref: 'FuncLambdaVersionl6Rjpaz0gycgsEDI51sLed039fH2uR4W8Q2IW8cNo' }, + Value: { Ref: 'FuncLambdaVersionpcyXz9PqN5xesfBZOOhY7t6jhi8kOCyGDknpfuhJ4' }, }, AnotherFuncLambdaFunctionQualifiedArn: { Description: 'Current Lambda function version', Value: { - Ref: 'AnotherFuncLambdaVersion6JZQneYqP4bC0Z3ywMc3XJPyECHK4RMGhpv8iis4E', + Ref: 'AnotherFuncLambdaVersionIo6IWr3BPLeAaVjlRwEGEz4vvDzC43h07eOBY0fXyI', }, }, }; @@ -2143,7 +2143,7 @@ describe('AwsCompileFunctions', () => { 'FuncLambdaFunctionQualifiedArn', { Description: 'Current Lambda function version', - Value: { Ref: 'FuncLambdaVersiona6VymfU25aF6eS2qysm7sHqPyy8RqYUzoTvDeBrrBA' }, + Value: { Ref: 'FuncLambdaVersionI1xWetHMVQO8bvzGqgmokPl25rtJA0A8g6lZNYdkdg' }, } ); @@ -2168,7 +2168,7 @@ describe('AwsCompileFunctions', () => { .then(() => { expect( awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaVersionOKy3yjVllZnozzdvQqHlRN8lBwkZyA6l76TCAEyork + .Resources.FuncLambdaVersionBzAYHivcbYLoEZcl7hN9cBrakBNygN0PiUC9UjQVMA .Properties.Description ).to.equal('Lambda function description'); }); @@ -2225,7 +2225,7 @@ describe('AwsCompileFunctions', () => { MemorySize: 1024, ReservedConcurrentExecutions: 5, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, }, }; @@ -2266,7 +2266,7 @@ describe('AwsCompileFunctions', () => { MemorySize: 1024, ReservedConcurrentExecutions: 0, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, }, }; @@ -2434,7 +2434,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, }, }; @@ -2475,7 +2475,7 @@ describe('AwsCompileFunctions', () => { Handler: 'func.function.handler', MemorySize: 1024, Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'nodejs4.3', + Runtime: 'nodejs10.x', Timeout: 6, Layers: ['arn:aws:xxx:*:*'], }, diff --git a/lib/plugins/aws/package/compile/layers/index.test.js b/lib/plugins/aws/package/compile/layers/index.test.js index c9ec66124..22245035b 100644 --- a/lib/plugins/aws/package/compile/layers/index.test.js +++ b/lib/plugins/aws/package/compile/layers/index.test.js @@ -292,7 +292,7 @@ describe('AwsCompileLayers', () => { test: { path: 'layer', description: 'desc', - compatibleRuntimes: ['nodejs8.10'], + compatibleRuntimes: ['nodejs10.x'], licenseInfo: 'GPL', }, }; @@ -305,7 +305,7 @@ describe('AwsCompileLayers', () => { }, LayerName: 'test', Description: 'desc', - CompatibleRuntimes: ['nodejs8.10'], + CompatibleRuntimes: ['nodejs10.x'], LicenseInfo: 'GPL', }, }; diff --git a/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml b/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml index 3dde4cb45..1d9dbfc08 100644 --- a/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml +++ b/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml @@ -7,7 +7,7 @@ plugins: provider: name: aws - runtime: nodejs8.10 + runtime: nodejs10.x custom: alexa: diff --git a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml index 2b1f024fd..61aa5695b 100644 --- a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml @@ -19,7 +19,7 @@ service: aws-clojurescript-gradle provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x # you can overwrite defaults here # stage: dev diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml index be7630fd7..50dded09f 100644 --- a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml @@ -19,7 +19,7 @@ service: aws-kotlin-nodejs-gradle # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x # you can overwrite defaults here # stage: dev diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml b/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml index 5f25d7ec4..7f5208848 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml @@ -7,7 +7,7 @@ plugins: provider: name: aws - runtime: nodejs8.10 + runtime: nodejs10.x functions: first: diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml b/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml index ef04a44b7..9b9b808f7 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml @@ -7,7 +7,7 @@ plugins: provider: name: aws - runtime: nodejs8.10 + runtime: nodejs10.x functions: hello: diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index 2d59fb360..74b8d7ec2 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -19,7 +19,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs8.10 + runtime: nodejs10.x # you can overwrite defaults here # stage: dev diff --git a/lib/plugins/create/templates/hello-world/serverless.yml b/lib/plugins/create/templates/hello-world/serverless.yml index 22f21292b..a2e0a0fd2 100644 --- a/lib/plugins/create/templates/hello-world/serverless.yml +++ b/lib/plugins/create/templates/hello-world/serverless.yml @@ -10,7 +10,7 @@ service: serverless-hello-world # The `provider` block defines where your service will be deployed provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x # The `functions` block defines what code to deploy functions: diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml index c790bf1e9..19f08adba 100644 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x apiKeys: - WillBeReplacedBeforeDeployment diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml index 630d7f28b..b0f885660 100644 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml index f02629f9f..c5d573a40 100644 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/serverless.yml index 78e77a2a0..b72f5a4c1 100644 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/serverless.yml +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/api-gateway/integration-lambda/api-keys/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda/api-keys/service/serverless.yml index f183f62f6..dca0516ba 100644 --- a/tests/integration/aws/api-gateway/integration-lambda/api-keys/service/serverless.yml +++ b/tests/integration/aws/api-gateway/integration-lambda/api-keys/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x apiKeys: - WillBeReplacedBeforeDeployment diff --git a/tests/integration/aws/api-gateway/integration-lambda/cors/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda/cors/service/serverless.yml index 2fefd441e..89d6fc2d6 100644 --- a/tests/integration/aws/api-gateway/integration-lambda/cors/service/serverless.yml +++ b/tests/integration/aws/api-gateway/integration-lambda/cors/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/serverless.yml index bbe8486fd..c0b5a7250 100644 --- a/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/serverless.yml +++ b/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/api-gateway/integration-lambda/simple-api/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda/simple-api/service/serverless.yml index 5906e242a..b4ef6a1a2 100644 --- a/tests/integration/aws/api-gateway/integration-lambda/simple-api/service/serverless.yml +++ b/tests/integration/aws/api-gateway/integration-lambda/simple-api/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/serverless.yml b/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/serverless.yml index a885180bd..ab1075c76 100644 --- a/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/serverless.yml +++ b/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: cwe1: diff --git a/tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/serverless.yml b/tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/serverless.yml index 8612c47e2..b17378649 100644 --- a/tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/serverless.yml +++ b/tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: cwe1: diff --git a/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/serverless.yml b/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/serverless.yml index 98985d743..ac28afdca 100644 --- a/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/serverless.yml +++ b/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: cwe1: diff --git a/tests/integration/aws/cloud-watch-event/single-event-single-function/service/serverless.yml b/tests/integration/aws/cloud-watch-event/single-event-single-function/service/serverless.yml index 4e528e7c3..09df4393c 100644 --- a/tests/integration/aws/cloud-watch-event/single-event-single-function/service/serverless.yml +++ b/tests/integration/aws/cloud-watch-event/single-event-single-function/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: cwe1: diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml index 7dc6f310a..9335d606a 100644 --- a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml +++ b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: preSignUp1: diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml index 121de6025..e2d62ae50 100644 --- a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml +++ b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: preSignUp: diff --git a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml index 9ecaf3f5f..b3d78c5b9 100644 --- a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml +++ b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: preSignUp: diff --git a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml index 97167e310..4e5dcd826 100644 --- a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml +++ b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: preSignUp: diff --git a/tests/integration/aws/general/custom-resources/service/serverless.yml b/tests/integration/aws/general/custom-resources/service/serverless.yml index 58e8c1047..30d1b454f 100644 --- a/tests/integration/aws/general/custom-resources/service/serverless.yml +++ b/tests/integration/aws/general/custom-resources/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/general/environment-variables/service/serverless.yml b/tests/integration/aws/general/environment-variables/service/serverless.yml index e660815a2..00cc3e865 100644 --- a/tests/integration/aws/general/environment-variables/service/serverless.yml +++ b/tests/integration/aws/general/environment-variables/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x environment: provider_level_variable_1: provider_level_1 provider_level_variable_2: provider_level_2 diff --git a/tests/integration/aws/general/nested-handlers/service/serverless.yml b/tests/integration/aws/general/nested-handlers/service/serverless.yml index 9e874ba9a..870644b34 100644 --- a/tests/integration/aws/general/nested-handlers/service/serverless.yml +++ b/tests/integration/aws/general/nested-handlers/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/general/overwrite-resources/service/serverless.yml b/tests/integration/aws/general/overwrite-resources/service/serverless.yml index 6f255c362..19d78e077 100644 --- a/tests/integration/aws/general/overwrite-resources/service/serverless.yml +++ b/tests/integration/aws/general/overwrite-resources/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/general/package/service/serverless.yml b/tests/integration/aws/general/package/service/serverless.yml index cb030f421..b01bfe299 100644 --- a/tests/integration/aws/general/package/service/serverless.yml +++ b/tests/integration/aws/general/package/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/iot/multiple-rules-multiple-functions/service/serverless.yml b/tests/integration/aws/iot/multiple-rules-multiple-functions/service/serverless.yml index 0b1188863..c018fbd45 100644 --- a/tests/integration/aws/iot/multiple-rules-multiple-functions/service/serverless.yml +++ b/tests/integration/aws/iot/multiple-rules-multiple-functions/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: iot1: diff --git a/tests/integration/aws/iot/multiple-rules-single-function/service/serverless.yml b/tests/integration/aws/iot/multiple-rules-single-function/service/serverless.yml index ac4825ba3..5e13e0c96 100644 --- a/tests/integration/aws/iot/multiple-rules-single-function/service/serverless.yml +++ b/tests/integration/aws/iot/multiple-rules-single-function/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: iot1: diff --git a/tests/integration/aws/iot/single-rule-multiple-functions/service/serverless.yml b/tests/integration/aws/iot/single-rule-multiple-functions/service/serverless.yml index b41a289c6..0d8e4d393 100644 --- a/tests/integration/aws/iot/single-rule-multiple-functions/service/serverless.yml +++ b/tests/integration/aws/iot/single-rule-multiple-functions/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: iot1: diff --git a/tests/integration/aws/iot/single-rule-single-function/service/serverless.yml b/tests/integration/aws/iot/single-rule-single-function/service/serverless.yml index 4235c46b0..cb7ecf35f 100644 --- a/tests/integration/aws/iot/single-rule-single-function/service/serverless.yml +++ b/tests/integration/aws/iot/single-rule-single-function/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: iot1: diff --git a/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/serverless.yml b/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/serverless.yml index 49dce85ce..05cee4c24 100644 --- a/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/serverless.yml +++ b/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/serverless.yml b/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/serverless.yml index 3c046a71e..b7d95c417 100644 --- a/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/serverless.yml +++ b/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: create: diff --git a/tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/serverless.yml b/tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/serverless.yml index 3d14e700a..212cb8ab2 100644 --- a/tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/serverless.yml +++ b/tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/s3/single-event-single-function-single-bucket/service/serverless.yml b/tests/integration/aws/s3/single-event-single-function-single-bucket/service/serverless.yml index 90a22cd81..17446857c 100644 --- a/tests/integration/aws/s3/single-event-single-function-single-bucket/service/serverless.yml +++ b/tests/integration/aws/s3/single-event-single-function-single-bucket/service/serverless.yml @@ -3,7 +3,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/serverless.yml b/tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/serverless.yml index 4ed8d4953..6bcb6fe38 100644 --- a/tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/serverless.yml +++ b/tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/sns/existing-topic/service/serverless.yml b/tests/integration/aws/sns/existing-topic/service/serverless.yml index 472535142..8be85bc03 100644 --- a/tests/integration/aws/sns/existing-topic/service/serverless.yml +++ b/tests/integration/aws/sns/existing-topic/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/sns/multiple-topics-multiple-functions/service/serverless.yml b/tests/integration/aws/sns/multiple-topics-multiple-functions/service/serverless.yml index 6e35cd187..209f3cba8 100644 --- a/tests/integration/aws/sns/multiple-topics-multiple-functions/service/serverless.yml +++ b/tests/integration/aws/sns/multiple-topics-multiple-functions/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/sns/multiple-topics-single-function/service/serverless.yml b/tests/integration/aws/sns/multiple-topics-single-function/service/serverless.yml index 3a29ef219..c0f1f29f9 100644 --- a/tests/integration/aws/sns/multiple-topics-single-function/service/serverless.yml +++ b/tests/integration/aws/sns/multiple-topics-single-function/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/sns/single-topic-multiple-functions/service/serverless.yml b/tests/integration/aws/sns/single-topic-multiple-functions/service/serverless.yml index 28ec23690..e81c8c2c6 100644 --- a/tests/integration/aws/sns/single-topic-multiple-functions/service/serverless.yml +++ b/tests/integration/aws/sns/single-topic-multiple-functions/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/aws/sns/single-topic-single-function/service/serverless.yml b/tests/integration/aws/sns/single-topic-single-function/service/serverless.yml index 2168713c2..3430ea40c 100644 --- a/tests/integration/aws/sns/single-topic-single-function/service/serverless.yml +++ b/tests/integration/aws/sns/single-topic-single-function/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/general/custom-plugins/service/serverless.yml b/tests/integration/general/custom-plugins/service/serverless.yml index 9357321be..b572235a4 100644 --- a/tests/integration/general/custom-plugins/service/serverless.yml +++ b/tests/integration/general/custom-plugins/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: diff --git a/tests/integration/general/local-plugins/service/serverless.yml b/tests/integration/general/local-plugins/service/serverless.yml index 0a155c126..c89417975 100644 --- a/tests/integration/general/local-plugins/service/serverless.yml +++ b/tests/integration/general/local-plugins/service/serverless.yml @@ -2,7 +2,7 @@ service: aws-nodejs # NOTE: update this with your service name provider: name: aws - runtime: nodejs6.10 + runtime: nodejs10.x functions: hello: From 7ede73b0cd81e10002e78d98252c42d2aeca21f5 Mon Sep 17 00:00:00 2001 From: exoego Date: Wed, 15 May 2019 07:25:54 +0900 Subject: [PATCH 051/504] Use tagResource/untagResource API to overcome updateStage's limitation that tag key cannot contain space. --- .../events/apiGateway/lib/hack/updateStage.js | 55 ++++++++++++------- .../apiGateway/lib/hack/updateStage.test.js | 42 +++++++++----- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js index 320b9392c..525a08198 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js @@ -17,6 +17,8 @@ module.exports = { // this array is used to gather all the patch operations we need to // perform on the stage this.apiGatewayStagePatchOperations = []; + this.apiGatewayTagResourceParams = []; + this.apiGatewayUntagResourceParams = []; this.apiGatewayStageState = {}; this.apiGatewayDeploymentId = null; this.apiGatewayRestApiId = null; @@ -57,6 +59,8 @@ module.exports = { .then(handleLogs.bind(this)) .then(handleTags.bind(this)) .then(applyUpdates.bind(this)) + .then(addTags.bind(this)) + .then(removeTags.bind(this)) .then(removeLogGroup.bind(this)); }); }, @@ -198,32 +202,43 @@ function handleLogs() { function handleTags() { const provider = this.state.service.provider; - const tagsMerged = Object.assign({}, provider.stackTags, provider.tags); + const tagsMerged = _.mapValues(Object.assign({}, provider.stackTags, provider.tags), + v => String(v)); + const currentTags = this.apiGatewayStageState.tags || {}; + const tagKeysToBeRemoved = Object.keys(currentTags) + .filter(currentKey => !_.isString(tagsMerged[currentKey])); - const tagsToCreate = _.entriesIn(tagsMerged).map(pair => ({ - key: String(pair[0]), - value: String(pair[1]), - })); - if (tagsToCreate) { - tagsToCreate.forEach((tag) => { - const operation = { op: 'replace', path: `/variables/${tag.key}`, value: tag.value }; - this.apiGatewayStagePatchOperations.push(operation); + const restApiId = this.apiGatewayRestApiId; + const stageName = this.options.stage; + const region = this.options.region; + const resourceArn = `arn:aws:apigateway:${region}::/restapis/${restApiId}/stages/${stageName}`; + + if (tagKeysToBeRemoved.length > 0) { + this.apiGatewayUntagResourceParams.push({ + resourceArn, + tagKeys: tagKeysToBeRemoved, }); } - - if (this.apiGatewayStageState.variables) { - const stateTagKeys = Object.keys(this.apiGatewayStageState.variables); - const newTagKeys = Object.keys(tagsMerged); - const tagsToRemove = _.difference(stateTagKeys, newTagKeys); - if (tagsToRemove) { - tagsToRemove.forEach((tagKey) => { - const operation = { op: 'remove', path: `/variables/${tagKey}` }; - this.apiGatewayStagePatchOperations.push(operation); - }); - } + if (!_.isEqual(currentTags, tagsMerged) && _.size(tagsMerged) > 0) { + this.apiGatewayTagResourceParams.push({ + resourceArn, + tags: tagsMerged, + }); } } +function addTags() { + const requests = this.apiGatewayTagResourceParams.map(tagResourceParam => + this.provider.request('APIGateway', 'tagResource', tagResourceParam)); + return BbPromise.all(requests); +} + +function removeTags() { + const requests = this.apiGatewayUntagResourceParams.map(untagResourceParam => + this.provider.request('APIGateway', 'untagResource', untagResourceParam)); + return BbPromise.all(requests); +} + function applyUpdates() { const restApiId = this.apiGatewayRestApiId; const patchOperations = this.apiGatewayStagePatchOperations; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js index 3d9256256..c253a3267 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js @@ -49,7 +49,7 @@ describe('#updateStage()', () => { stageName: 'dev', }) .resolves({ - variables: { + tags: { old: 'tag', }, }); @@ -66,11 +66,11 @@ describe('#updateStage()', () => { it('should update the stage based on the serverless file configuration', () => { context.state.service.provider.tags = { - foo: 'bar', - bar: 'baz', + 'Containing Space': 'bar', + bar: 'high-priority', }; context.state.service.provider.stackTags = { - baz: 'qux', + bar: 'low-priority', num: 123, }; context.state.service.provider.tracing = { @@ -87,15 +87,10 @@ describe('#updateStage()', () => { { op: 'replace', path: '/accessLogSettings/format', value: 'requestId: $context.requestId, ip: $context.identity.sourceIp, caller: $context.identity.caller, user: $context.identity.user, requestTime: $context.requestTime, httpMethod: $context.httpMethod, resourcePath: $context.resourcePath, status: $context.status, protocol: $context.protocol, responseLength: $context.responseLength' }, { op: 'replace', path: '/*/*/logging/dataTrace', value: 'true' }, { op: 'replace', path: '/*/*/logging/loglevel', value: 'INFO' }, - { op: 'replace', path: '/variables/baz', value: 'qux' }, - { op: 'replace', path: '/variables/num', value: '123' }, - { op: 'replace', path: '/variables/foo', value: 'bar' }, - { op: 'replace', path: '/variables/bar', value: 'baz' }, - { op: 'remove', path: '/variables/old' }, ]; expect(providerGetAccountIdStub).to.be.calledOnce; - expect(providerRequestStub.args).to.have.length(3); + expect(providerRequestStub.args).to.have.length(5); expect(providerRequestStub.args[0][0]).to.equal('APIGateway'); expect(providerRequestStub.args[0][1]).to.equal('getRestApis'); expect(providerRequestStub.args[0][2]).to.deep.equal({ @@ -112,16 +107,34 @@ describe('#updateStage()', () => { stageName: 'dev', patchOperations, }); + expect(providerRequestStub.args[3][0]).to.equal('APIGateway'); + expect(providerRequestStub.args[3][1]).to.equal('tagResource'); + expect(providerRequestStub.args[3][2]).to.deep.equal({ + resourceArn: 'arn:aws:apigateway:us-east-1::/restapis/someRestApiId/stages/dev', + tags: { + 'Containing Space': 'bar', + bar: 'high-priority', + num: '123', + }, + }); + expect(providerRequestStub.args[4][0]).to.equal('APIGateway'); + expect(providerRequestStub.args[4][1]).to.equal('untagResource'); + expect(providerRequestStub.args[4][2]).to.deep.equal({ + resourceArn: 'arn:aws:apigateway:us-east-1::/restapis/someRestApiId/stages/dev', + tagKeys: ['old'], + }); }); }); - it('should perform default actions if settings are not configure', () => - updateStage.call(context).then(() => { + it('should perform default actions if settings are not configure', () => { + context.state.service.provider.tags = { + old: 'tag', + }; + return updateStage.call(context).then(() => { const patchOperations = [ { op: 'replace', path: '/tracingEnabled', value: 'false' }, { op: 'replace', path: '/*/*/logging/dataTrace', value: 'false' }, { op: 'replace', path: '/*/*/logging/loglevel', value: 'OFF' }, - { op: 'remove', path: '/variables/old' }, ]; expect(providerGetAccountIdStub).to.be.calledOnce; @@ -147,7 +160,8 @@ describe('#updateStage()', () => { expect(providerRequestStub.args[3][2]).to.deep.equal({ logGroupName: '/aws/api-gateway/my-service-dev', }); - })); + }); + }); it('should create a new stage and proceed as usual if none can be found', () => { providerRequestStub.withArgs('APIGateway', 'getStage', { From 127e36c5d0403c30b662bb5efd9afb9ef99a8c58 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 15 May 2019 11:34:34 -0400 Subject: [PATCH 052/504] support for SFE outputs in config --- lib/classes/Service.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 7bd1a2ed9..0ec8d0606 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -162,6 +162,8 @@ class Service { that.layers = serverlessFile.layers || {}; } + that.outputs = serverlessFile.outputs; + return this; } From 5a48c84c2e4e618b540a20345a6a43c69ffe5f27 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 15 May 2019 17:36:29 +0200 Subject: [PATCH 053/504] Ensure needed test methods --- lib/classes/PluginManager.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 9e56fa0bc..2c2202299 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -22,6 +22,7 @@ const BbPromise = require('bluebird'); const getCacheFilePath = require('../utils/getCacheFilePath'); chai.use(require('chai-as-promised')); +chai.use(require('sinon-chai')); const expect = chai.expect; From 93d1ab254e823734f51c42da7ebb27b54671f290 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 15 May 2019 17:42:08 +0200 Subject: [PATCH 054/504] Improve failed test report --- bin/test-isolated | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/test-isolated b/bin/test-isolated index 6c86c71f1..95ce08fac 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -15,6 +15,7 @@ const cpus = require('os').cpus(); const globby = require('globby'); const spawn = require('child-process-ext/spawn'); const cliFooter = require('cli-progress-footer')(); +const chalk = require('chalk') const pLimit = require('p-limit'); cliFooter.shouldAddProgressAnimationPrefix = true; @@ -36,7 +37,10 @@ const run = path => { return spawn('./bin/test', ['--require=sinon-bluebird', path], { env: Object.assign({ FORCE_COLOR: '1' }, process.env), }).then(onFinally, error => { + ongoing.clear(); onFinally(error); + process.stderr.write(chalk.red.bold(`${path} failed\n\n`)); + if (error.code === 2) process.exit(2); throw error; }); }; From d64435e56343105bb7707a16ec8d3b4388e08d33 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 15 May 2019 17:44:33 +0200 Subject: [PATCH 055/504] Ensure needed tooling in test --- lib/plugins/aws/lib/getServiceState.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/lib/getServiceState.test.js b/lib/plugins/aws/lib/getServiceState.test.js index 72dc72462..02f4bf187 100644 --- a/lib/plugins/aws/lib/getServiceState.test.js +++ b/lib/plugins/aws/lib/getServiceState.test.js @@ -1,11 +1,14 @@ 'use strict'; -const expect = require('chai').expect; +const chai = require('chai'); const sinon = require('sinon'); const Serverless = require('../../../Serverless'); const AwsProvider = require('../provider/awsProvider'); const getServiceState = require('./getServiceState'); +const expect = chai.expect; +chai.use(require('sinon-chai')); + describe('#getServiceState()', () => { let serverless; let readFileSyncStub; From 7264a1f09fa3612929313ca83d0e1132027a382f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 15 May 2019 17:48:09 +0200 Subject: [PATCH 056/504] Fix lint issue --- bin/test-isolated | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/test-isolated b/bin/test-isolated index 95ce08fac..762955e90 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -15,7 +15,7 @@ const cpus = require('os').cpus(); const globby = require('globby'); const spawn = require('child-process-ext/spawn'); const cliFooter = require('cli-progress-footer')(); -const chalk = require('chalk') +const chalk = require('chalk'); const pLimit = require('p-limit'); cliFooter.shouldAddProgressAnimationPrefix = true; From 5dbad2aa9f306a146c2b8b33171e616394073dbb Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 15 May 2019 17:52:11 +0200 Subject: [PATCH 057/504] Ensure needed tooling in test --- .../package/compile/events/apiGateway/lib/restApi.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js index 559d825d5..127e61e03 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js @@ -1,10 +1,13 @@ 'use strict'; -const expect = require('chai').expect; +const chai = require('chai'); const AwsCompileApigEvents = require('../index'); const Serverless = require('../../../../../../../Serverless'); const AwsProvider = require('../../../../../provider/awsProvider'); +const expect = chai.expect; +chai.use(require('chai-as-promised')); + describe('#compileRestApi()', () => { let serverless; let awsCompileApigEvents; From d2377249038d1e7832609ffc52560304b88db64c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 15 May 2019 17:53:35 +0200 Subject: [PATCH 058/504] Ensure needed tooling for test --- lib/plugins/info/info.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/plugins/info/info.test.js b/lib/plugins/info/info.test.js index be89c6234..24aba7c65 100644 --- a/lib/plugins/info/info.test.js +++ b/lib/plugins/info/info.test.js @@ -1,10 +1,13 @@ 'use strict'; -const expect = require('chai').expect; +const chai = require('chai'); const Info = require('./info'); const Serverless = require('../../Serverless'); const sinon = require('sinon'); +const expect = chai.expect; +chai.use(require('chai-as-promised')); + describe('Info', () => { let info; let serverless; From 2f6be36f36af4e6dcad6921a3d2270e1a78ae1c0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 15 May 2019 17:56:35 +0200 Subject: [PATCH 059/504] Ensure needed tooling for test --- lib/plugins/print/print.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/plugins/print/print.test.js b/lib/plugins/print/print.test.js index 25e01f5f9..5787492db 100644 --- a/lib/plugins/print/print.test.js +++ b/lib/plugins/print/print.test.js @@ -1,13 +1,16 @@ 'use strict'; const os = require('os'); -const expect = require('chai').expect; +const chai = require('chai'); const sinon = require('sinon'); const proxyquire = require('proxyquire'); const Serverless = require('../../Serverless'); const CLI = require('../../classes/CLI'); const YAML = require('js-yaml'); +chai.use(require('chai-as-promised')); + +const expect = chai.expect; describe('Print', () => { let print; From f519ab98a4d20d280cc720f815ca68d9f5876e18 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 15 May 2019 17:57:50 +0200 Subject: [PATCH 060/504] Ensure needed tooling for tests --- lib/plugins/remove/remove.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/plugins/remove/remove.test.js b/lib/plugins/remove/remove.test.js index 89871dbe5..5d114d7a6 100644 --- a/lib/plugins/remove/remove.test.js +++ b/lib/plugins/remove/remove.test.js @@ -1,10 +1,14 @@ 'use strict'; -const expect = require('chai').expect; +const chai = require('chai'); const Remove = require('./remove'); const Serverless = require('../../Serverless'); const sinon = require('sinon'); +chai.use(require('chai-as-promised')); + +const expect = chai.expect; + describe('Remove', () => { let remove; let serverless; From 2d99d7f0825253028358943a8d4223e3993f1dee Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 15 May 2019 17:59:56 +0200 Subject: [PATCH 061/504] Ensure needed tooling for test --- lib/utils/getServerlessConfigFile.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/utils/getServerlessConfigFile.test.js b/lib/utils/getServerlessConfigFile.test.js index 17318bb56..33b9661fb 100644 --- a/lib/utils/getServerlessConfigFile.test.js +++ b/lib/utils/getServerlessConfigFile.test.js @@ -1,13 +1,17 @@ 'use strict'; const path = require('path'); -const expect = require('chai').expect; +const chai = require('chai'); const testUtils = require('../../tests/utils'); const writeFileSync = require('./fs/writeFileSync'); const serverlessConfigFileUtils = require('./getServerlessConfigFile'); const getServerlessConfigFile = serverlessConfigFileUtils.getServerlessConfigFile; +chai.use(require('chai-as-promised')); + +const expect = chai.expect; + describe('#getServerlessConfigFile()', () => { let tmpDirPath; From 8fcfee6168465405cfddff6895db4356cde97da7 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 10:07:20 +0200 Subject: [PATCH 062/504] Ensure to not wait for stdin when testing --- lib/plugins/aws/invoke/index.test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/plugins/aws/invoke/index.test.js b/lib/plugins/aws/invoke/index.test.js index 915911695..1d31aa963 100644 --- a/lib/plugins/aws/invoke/index.test.js +++ b/lib/plugins/aws/invoke/index.test.js @@ -51,6 +51,7 @@ describe('AwsInvoke', () => { }); describe('#extendedValidate()', () => { + let backupIsTTY; beforeEach(() => { serverless.config.servicePath = true; serverless.service.environment = { @@ -73,6 +74,15 @@ describe('AwsInvoke', () => { }; awsInvoke.options.data = null; awsInvoke.options.path = false; + + // Ensure there's no attempt to read path from stdin + backupIsTTY = process.stdin.isTTY; + process.stdin.isTTY = true; + }); + + afterEach(() => { + if (backupIsTTY) process.stdin.isTTY = backupIsTTY; + delete process.stdin.isTTY; }); it('it should throw error if function is not provided', () => { From 7cb80fb018ddbcc72e15f9052f14574cba2bb2a2 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 10:07:35 +0200 Subject: [PATCH 063/504] Error when no test files where matched --- bin/test-isolated | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/test-isolated b/bin/test-isolated index 762955e90..c0057a9d5 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -46,6 +46,11 @@ const run = path => { }; globby(patterns).then(paths => { + if (!paths.length) { + process.stderr.write(chalk.red.bold('No test files matched\n\n')); + process.exit(1); + } + const limit = pLimit(processesCount); return Promise.all(paths.map(path => limit(() => run(path)))); From db44d68e11e8c93d2409f175c32b93dc11c19b54 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 10:15:18 +0200 Subject: [PATCH 064/504] Improve tests isolation --- bin/test-isolated | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/test-isolated b/bin/test-isolated index c0057a9d5..1df1925a6 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -35,7 +35,7 @@ const run = path => { process.stderr.write(String(stderrBuffer)); }; return spawn('./bin/test', ['--require=sinon-bluebird', path], { - env: Object.assign({ FORCE_COLOR: '1' }, process.env), + env: { FORCE_COLOR: '1', PATH: process.env.PATH }, }).then(onFinally, error => { ongoing.clear(); onFinally(error); From d4b4c7e90cfbb5069bba2e1c1e2a1aa30de399aa Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 10:18:43 +0200 Subject: [PATCH 065/504] Ensure immediate output if just one test is run --- bin/test-isolated | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bin/test-isolated b/bin/test-isolated index 1df1925a6..1d020aca7 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -50,6 +50,14 @@ globby(patterns).then(paths => { process.stderr.write(chalk.red.bold('No test files matched\n\n')); process.exit(1); } + if (paths.length === 1) { + const spawnDeferred = spawn('./bin/test', ['--require=sinon-bluebird', paths[0]], { + env: { FORCE_COLOR: '1', PATH: process.env.PATH }, + }); + spawnDeferred.stdout.pipe(process.stdout); + spawnDeferred.stderr.pipe(process.stderr); + return spawnDeferred; + } const limit = pLimit(processesCount); From 6fe596f735ff7395036034d4c7198ca4f3c8075e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 10:21:27 +0200 Subject: [PATCH 066/504] Ensure we do not wait of stdin input in test run --- lib/plugins/aws/invokeLocal/index.test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 43a93939b..ab2aac8cc 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -83,6 +83,7 @@ describe('AwsInvokeLocal', () => { }); describe('#extendedValidate()', () => { + let backupIsTTY; beforeEach(() => { serverless.config.servicePath = true; serverless.service.environment = { @@ -105,6 +106,15 @@ describe('AwsInvokeLocal', () => { }; awsInvokeLocal.options.data = null; awsInvokeLocal.options.path = false; + + // Ensure there's no attempt to read path from stdin + backupIsTTY = process.stdin.isTTY; + process.stdin.isTTY = true; + }); + + afterEach(() => { + if (backupIsTTY) process.stdin.isTTY = backupIsTTY; + delete process.stdin.isTTY; }); it('should not throw error when there are no input data', () => { From 7ed67fa6dc7da74b17e7a64f1621b1ed5886e2aa Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 10:23:58 +0200 Subject: [PATCH 067/504] Remove obsolete explicit coercion --- bin/test-isolated | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/test-isolated b/bin/test-isolated index 1d020aca7..44fdf2736 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -31,8 +31,8 @@ const run = path => { const onFinally = ({ stdoutBuffer, stderrBuffer }) => { ongoing.delete(path); cliFooter.updateProgress(Array.from(ongoing)); - process.stdout.write(String(stdoutBuffer)); - process.stderr.write(String(stderrBuffer)); + process.stdout.write(stdoutBuffer); + process.stderr.write(stderrBuffer); }; return spawn('./bin/test', ['--require=sinon-bluebird', path], { env: { FORCE_COLOR: '1', PATH: process.env.PATH }, From 7208e45796e56abc9aa9b9e7db1f644ba23005e9 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 12:26:58 +0200 Subject: [PATCH 068/504] Ensure temporary files are cleaned up --- bin/test-isolated | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/bin/test-isolated b/bin/test-isolated index 44fdf2736..395bc7c28 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -19,12 +19,23 @@ const chalk = require('chalk'); const pLimit = require('p-limit'); cliFooter.shouldAddProgressAnimationPrefix = true; -const processesCount = Math.max(cpus.length - 1, 1); const patterns = process.argv.length <= 2 ? ['**/*.test.js'] : process.argv.slice(2); patterns.push('!node_modules/**'); - const ongoing = new Set(); + +const resolveGitStatus = () => + spawn('git', ['status', '--porcelain']).then( + ({ stdoutBuffer }) => String(stdoutBuffer), + error => { + process.stdout.write(error.stdoutBuffer); + process.stderr.write(error.stderrBuffer); + throw error; + } + ); + +const initGitStatusDeferred = resolveGitStatus(); + const run = path => { ongoing.add(path); cliFooter.updateProgress(Array.from(ongoing)); @@ -33,15 +44,24 @@ const run = path => { cliFooter.updateProgress(Array.from(ongoing)); process.stdout.write(stdoutBuffer); process.stderr.write(stderrBuffer); + return Promise.all([initGitStatusDeferred, resolveGitStatus()]).then( + ([initStatus, currentStatus]) => { + if (initStatus !== currentStatus) { + process.stderr.write(chalk.red.bold(`${path} didn't clean creted temporary files\n\n`)); + process.exit(1); + } + } + ); }; return spawn('./bin/test', ['--require=sinon-bluebird', path], { env: { FORCE_COLOR: '1', PATH: process.env.PATH }, }).then(onFinally, error => { ongoing.clear(); - onFinally(error); - process.stderr.write(chalk.red.bold(`${path} failed\n\n`)); - if (error.code === 2) process.exit(2); - throw error; + return onFinally(error).then(() => { + process.stderr.write(chalk.red.bold(`${path} failed\n\n`)); + if (error.code === 2) process.exit(2); + throw error; + }); }); }; @@ -59,7 +79,7 @@ globby(patterns).then(paths => { return spawnDeferred; } - const limit = pLimit(processesCount); + const limit = pLimit(1); - return Promise.all(paths.map(path => limit(() => run(path)))); + return initGitStatusDeferred.then(() => Promise.all(paths.map(path => limit(() => run(path))))); }); From 7e0b8b23036f9f1de38590d81994a2e5d3d6762f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 12:39:38 +0200 Subject: [PATCH 069/504] Cleanup test-isolated scripts As we no longer introduce parallelizm, its fine to remove any logic that handled that --- bin/test-isolated | 55 +++++++++++---------------------- package-lock.json | 77 ----------------------------------------------- package.json | 2 -- 3 files changed, 17 insertions(+), 117 deletions(-) diff --git a/bin/test-isolated b/bin/test-isolated index 395bc7c28..ad4f4c4b7 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -11,18 +11,12 @@ process.on('unhandledRejection', err => { throw err; }); -const cpus = require('os').cpus(); const globby = require('globby'); const spawn = require('child-process-ext/spawn'); -const cliFooter = require('cli-progress-footer')(); const chalk = require('chalk'); -const pLimit = require('p-limit'); - -cliFooter.shouldAddProgressAnimationPrefix = true; const patterns = process.argv.length <= 2 ? ['**/*.test.js'] : process.argv.slice(2); patterns.push('!node_modules/**'); -const ongoing = new Set(); const resolveGitStatus = () => spawn('git', ['status', '--porcelain']).then( @@ -37,32 +31,24 @@ const resolveGitStatus = () => const initGitStatusDeferred = resolveGitStatus(); const run = path => { - ongoing.add(path); - cliFooter.updateProgress(Array.from(ongoing)); - const onFinally = ({ stdoutBuffer, stderrBuffer }) => { - ongoing.delete(path); - cliFooter.updateProgress(Array.from(ongoing)); - process.stdout.write(stdoutBuffer); - process.stderr.write(stderrBuffer); - return Promise.all([initGitStatusDeferred, resolveGitStatus()]).then( - ([initStatus, currentStatus]) => { - if (initStatus !== currentStatus) { - process.stderr.write(chalk.red.bold(`${path} didn't clean creted temporary files\n\n`)); - process.exit(1); - } + const onFinally = () => + Promise.all([initGitStatusDeferred, resolveGitStatus()]).then(([initStatus, currentStatus]) => { + if (initStatus !== currentStatus) { + process.stderr.write(chalk.red.bold(`${path} didn't clean creted temporary files\n\n`)); + process.exit(1); } - ); - }; + }); + return spawn('./bin/test', ['--require=sinon-bluebird', path], { + stdio: 'inherit', env: { FORCE_COLOR: '1', PATH: process.env.PATH }, - }).then(onFinally, error => { - ongoing.clear(); - return onFinally(error).then(() => { + }).then(onFinally, error => + onFinally(error).then(() => { process.stderr.write(chalk.red.bold(`${path} failed\n\n`)); if (error.code === 2) process.exit(2); throw error; - }); - }); + }) + ); }; globby(patterns).then(paths => { @@ -70,16 +56,9 @@ globby(patterns).then(paths => { process.stderr.write(chalk.red.bold('No test files matched\n\n')); process.exit(1); } - if (paths.length === 1) { - const spawnDeferred = spawn('./bin/test', ['--require=sinon-bluebird', paths[0]], { - env: { FORCE_COLOR: '1', PATH: process.env.PATH }, - }); - spawnDeferred.stdout.pipe(process.stdout); - spawnDeferred.stderr.pipe(process.stderr); - return spawnDeferred; - } - - const limit = pLimit(1); - - return initGitStatusDeferred.then(() => Promise.all(paths.map(path => limit(() => run(path))))); + return initGitStatusDeferred.then(function self() { + const path = paths.shift(); + if (path) return run(path).then(self); + return null; + }); }); diff --git a/package-lock.json b/package-lock.json index c6fa3bb1f..fa3d17e59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1529,20 +1529,6 @@ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, - "cli-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", - "dev": true, - "requires": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.5" - } - }, "cli-cursor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", @@ -1551,19 +1537,6 @@ "restore-cursor": "^1.0.1" } }, - "cli-progress-footer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cli-progress-footer/-/cli-progress-footer-1.1.1.tgz", - "integrity": "sha512-J0uW2u06pWI0tMKCbcCiMOZd8TbWj4EpuYgPo4Jiwih/FfGbd4dbLcJieO0Ior1pY1HBrnmCuHFk6GB9azE4pg==", - "dev": true, - "requires": { - "cli-color": "^1.4", - "d": "1", - "es5-ext": "^0.10.47", - "process-utils": "^2.0.1", - "timers-ext": "^0.1.7" - } - }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -5672,15 +5645,6 @@ "yallist": "^2.1.2" } }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "dev": true, - "requires": { - "es5-ext": "~0.10.2" - } - }, "lsmod": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", @@ -5865,22 +5829,6 @@ "p-is-promise": "^2.0.0" } }, - "memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" - } - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -6761,15 +6709,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, - "process-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/process-utils/-/process-utils-2.1.0.tgz", - "integrity": "sha512-FjkSXOw9XJ8qadsgOQHuwyhvqUq2D9JkEfqExrYe+ik2OHJw0q/hTOgrxybg0lgw3K/HTrBQ3s9nM7yI3Ukk2g==", - "dev": true, - "requires": { - "type": "^1.0.1" - } - }, "progress": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", @@ -8205,16 +8144,6 @@ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, - "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dev": true, - "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, "tmp": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", @@ -8348,12 +8277,6 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, - "type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", - "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==", - "dev": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/package.json b/package.json index d67ce2611..5d7a4a079 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "chai": "^3.5.0", "chai-as-promised": "^6.0.0", "child-process-ext": "^2.0.0", - "cli-progress-footer": "^1.1.1", "coveralls": "^3.0.3", "eslint": "^3.3.1", "eslint-config-airbnb": "^10.0.1", @@ -88,7 +87,6 @@ "mocha": "^5.2.0", "mocha-lcov-reporter": "^1.2.0", "mock-require": "^1.3.0", - "p-limit": "^2.2.0", "parse-github-url": "^1.0.1", "proxyquire": "^1.7.10", "sinon": "^1.17.5", From c3dd6502abe9c5e0cfccfd5f45c3fa2f03f05ed4 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 12:48:10 +0200 Subject: [PATCH 070/504] Ensure to cleanup temporary files --- lib/plugins/aws/invokeLocal/index.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index ab2aac8cc..6d9afce90 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -1196,6 +1196,7 @@ describe('AwsInvokeLocal', () => { delete require.cache[require.resolve('./index')]; delete require.cache[require.resolve('child_process')]; serverless.pluginManager.spawn.restore(); + return fse.remove('.serverless'); }); it('calls docker with packaged artifact', () => From 1f8378fb8b36bb00f7ad8e0b9bdc282610080534 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 13:41:56 +0200 Subject: [PATCH 071/504] Improve var naming --- bin/test-isolated | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/bin/test-isolated b/bin/test-isolated index ad4f4c4b7..befa6c0d1 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -28,16 +28,18 @@ const resolveGitStatus = () => } ); -const initGitStatusDeferred = resolveGitStatus(); +const initialGitStatusDeferred = resolveGitStatus(); const run = path => { const onFinally = () => - Promise.all([initGitStatusDeferred, resolveGitStatus()]).then(([initStatus, currentStatus]) => { - if (initStatus !== currentStatus) { - process.stderr.write(chalk.red.bold(`${path} didn't clean creted temporary files\n\n`)); - process.exit(1); + Promise.all([initialGitStatusDeferred, resolveGitStatus()]).then( + ([initialStatus, currentStatus]) => { + if (initialStatus !== currentStatus) { + process.stderr.write(chalk.red.bold(`${path} didn't clean creted temporary files\n\n`)); + process.exit(1); + } } - }); + ); return spawn('./bin/test', ['--require=sinon-bluebird', path], { stdio: 'inherit', @@ -56,7 +58,7 @@ globby(patterns).then(paths => { process.stderr.write(chalk.red.bold('No test files matched\n\n')); process.exit(1); } - return initGitStatusDeferred.then(function self() { + return initialGitStatusDeferred.then(function self() { const path = paths.shift(); if (path) return run(path).then(self); return null; From fb4c950a591792c1fb6c9b629060645e58519b03 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 16 May 2019 21:35:22 +0200 Subject: [PATCH 072/504] Ensure patch for known Mocha bug --- bin/test | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bin/test b/bin/test index 4d5da5468..4f3d4345d 100755 --- a/bin/test +++ b/bin/test @@ -2,6 +2,16 @@ 'use strict'; +// Workaround Mocha v5 issue: https://github.com/mochajs/mocha/issues/3226 +// Fixed in v6, but not really: https://github.com/mochajs/mocha/issues/3917 +process.on('uncaughtException', err => { + if (!process.listenerCount('exit')) return; + // Mocha done it's report, and registered process.exit listener which silences any further + // eventual crashes. Recover by unregistering the listener + process.removeAllListeners('exit'); + throw err; +}); + process.on('unhandledRejection', err => { throw err; }); From 1db17bcb514cfebd8840a07174985a3259e0f2bc Mon Sep 17 00:00:00 2001 From: Simon Elder Date: Fri, 17 May 2019 13:42:04 +1000 Subject: [PATCH 073/504] Add authorization scopes with a cloudformation referenced authorizerid --- .../apiGateway/lib/method/authorization.js | 10 ++- .../apiGateway/lib/method/index.test.js | 73 ++++++++++++++++++- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js index 03957b8e8..21ae109d0 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js @@ -15,12 +15,18 @@ module.exports = { if (http.authorizer) { if (http.authorizer.type && http.authorizer.authorizerId) { - return { + const authReturn = { Properties: { AuthorizationType: http.authorizer.type, AuthorizerId: http.authorizer.authorizerId, }, }; + if (http.authorizer.type === 'COGNITO_USER_POOLS' + && http.authorizer.scopes + && http.authorizer.scopes.length) { + authReturn.Properties.AuthorizationScopes = http.authorizer.scopes; + } + return authReturn; } const authorizerLogicalId = this.provider.naming @@ -39,7 +45,7 @@ module.exports = { }, DependsOn: authorizerLogicalId, }; - if (http.authorizer.scopes) { + if (http.authorizer.scopes && http.authorizer.scopes.length) { cognitoReturn.Properties.AuthorizationScopes = http.authorizer.scopes; } return cognitoReturn; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js index 2ac2b2957..e6871ab9d 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js @@ -489,7 +489,7 @@ describe('#compileMethods()', () => { }); }); - it('should set custom authorizer config with authorizeId', () => { + it('should set custom authorizer config with authorizerId', () => { awsCompileApigEvents.validated.events = [ { functionName: 'First', @@ -542,7 +542,7 @@ describe('#compileMethods()', () => { }); }); - it('should set authorizer config for a cognito user pool', () => { + it('should set authorizer config for a cognito user pool when given authorizer arn', () => { awsCompileApigEvents.validated.events = [ { functionName: 'First', @@ -583,6 +583,75 @@ describe('#compileMethods()', () => { }); }); + it('should set authorizer config for a cognito user pool when given authorizerId Ref', () => { + awsCompileApigEvents.validated.events = [ + { + functionName: 'First', + http: { + authorizer: { + name: 'authorizer', + type: 'COGNITO_USER_POOLS', + authorizerId: { Ref: 'CognitoAuthorizer' }, + scopes: ['myapp/read', 'myapp/write'], + }, + integration: 'AWS', + path: 'users/create', + method: 'post', + }, + }, + ]; + + return awsCompileApigEvents.compileMethods().then(() => { + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType + ).to.equal('COGNITO_USER_POOLS'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizationScopes + ).to.contain('myapp/read'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizerId.Ref + ).to.equal('CognitoAuthorizer'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersCreatePost.Properties + .Integration.RequestTemplates['application/json'] + ).to.not.match(/undefined/); + }); + }); + + it('should not scopes for a cognito user pool when given empty scopes array', () => { + awsCompileApigEvents.validated.events = [ + { + functionName: 'First', + http: { + authorizer: { + name: 'authorizer', + type: 'COGNITO_USER_POOLS', + authorizerId: { Ref: 'CognitoAuthorizer' }, + scopes: [], + }, + integration: 'AWS', + path: 'users/create', + method: 'post', + }, + }, + ]; + + return awsCompileApigEvents.compileMethods().then(() => { + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersCreatePost.Properties + ).to.not.have.property('AuthorizationScopes'); + }); + }); + + it('should set claims for a cognito user pool', () => { awsCompileApigEvents.validated.events = [ { From 834dc5a49a85ca468ddaff71c894971e32c9b08d Mon Sep 17 00:00:00 2001 From: Simon Elder Date: Fri, 17 May 2019 16:22:51 +1000 Subject: [PATCH 074/504] Update documentation example .... --- docs/providers/aws/events/apigateway.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index ad8ff969d..101c2c0ea 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -1355,6 +1355,8 @@ functions: type: COGNITO_USER_POOLS # TOKEN or REQUEST or COGNITO_USER_POOLS, same as AWS Cloudformation documentation authorizerId: Ref: ApiGatewayAuthorizer # or hard-code Authorizer ID + scopes: # Optional - List of Oauth2 scopes when type is COGNITO_USER_POOLS + - myapp/myscope deleteUser: ... From a04362017820829ac3c130e7526456b9393b095e Mon Sep 17 00:00:00 2001 From: Simon Elder Date: Fri, 17 May 2019 16:27:32 +1000 Subject: [PATCH 075/504] Fix typo .... --- docs/providers/aws/events/apigateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 101c2c0ea..e37d30520 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -499,7 +499,7 @@ functions: - nickname ``` -### Using asyncronous integration +### Using asynchronous integration Use `async: true` when integrating a lambda function using [event invocation](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#SSS-Invoke-request-InvocationType). This lets API Gateway to return immediately with a 200 status code while the lambda continues running. If not othewise speficied integration type will be `AWS`. From 9bb787e1170a6caaf4fb197caa002a72e0dd2778 Mon Sep 17 00:00:00 2001 From: Simon Elder Date: Fri, 17 May 2019 16:30:55 +1000 Subject: [PATCH 076/504] Some more typos .... --- docs/providers/aws/events/apigateway.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index e37d30520..3e60adfe8 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -501,7 +501,7 @@ functions: ### Using asynchronous integration -Use `async: true` when integrating a lambda function using [event invocation](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#SSS-Invoke-request-InvocationType). This lets API Gateway to return immediately with a 200 status code while the lambda continues running. If not othewise speficied integration type will be `AWS`. +Use `async: true` when integrating a lambda function using [event invocation](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#SSS-Invoke-request-InvocationType). This lets API Gateway to return immediately with a 200 status code while the lambda continues running. If not otherwise specified integration type will be `AWS`. ```yml functions: @@ -868,7 +868,7 @@ If you want to spread a string into multiple lines, you can use the `>` or `|` s #### Pass Through Behavior API Gateway provides multiple ways to handle requests where the Content-Type header does not match any of the specified mapping templates. When this happens, the request payload will either be passed through the integration request *without transformation* or rejected with a `415 - Unsupported Media Type`, depending on the configuration. -You can define this behavior as follows (if not specified, a value of **NEVER** will be used): +You can define this behaviour as follows (if not specified, a value of **NEVER** will be used): ```yml functions: From 72713bd03b2956aa581559d38046da0d807ac23c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 17 May 2019 14:19:58 +0200 Subject: [PATCH 077/504] Workaround mocha teardown delay issue --- bin/test | 7 ++++++- tests/mocha-reporter.js | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 tests/mocha-reporter.js diff --git a/bin/test b/bin/test index 4f3d4345d..a0b18bb48 100755 --- a/bin/test +++ b/bin/test @@ -17,7 +17,12 @@ process.on('unhandledRejection', err => { }); if (process.argv.length <= 2) { - process.argv.push('--require=sinon-bluebird', '!(node_modules)/**/*.test.js'); + process.argv.push( + '--require=sinon-bluebird', + '-R', + 'tests/mocha-reporter', + '!(node_modules)/**/*.test.js' + ); } require('mocha/bin/_mocha'); diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js new file mode 100644 index 000000000..5869ad450 --- /dev/null +++ b/tests/mocha-reporter.js @@ -0,0 +1,12 @@ +'use strict'; + +const Spec = require('mocha/lib/reporters/spec'); +const Runner = require('mocha/lib/runner'); + +// Ensure faster tests propagation +// It's to expose errors otherwise hidden by race conditions +// Reported to Mocha with: https://github.com/mochajs/mocha/issues/3920 +Runner.immediately = process.nextTick; + +module.exports = Spec; + From fb896887f5b8668335103467c2017679dd63e3a8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 17 May 2019 14:56:45 +0200 Subject: [PATCH 078/504] Move all mocha patches into mocha-reporter --- bin/test | 14 -------------- tests/mocha-reporter.js | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/bin/test b/bin/test index a0b18bb48..ffdd7734e 100755 --- a/bin/test +++ b/bin/test @@ -2,20 +2,6 @@ 'use strict'; -// Workaround Mocha v5 issue: https://github.com/mochajs/mocha/issues/3226 -// Fixed in v6, but not really: https://github.com/mochajs/mocha/issues/3917 -process.on('uncaughtException', err => { - if (!process.listenerCount('exit')) return; - // Mocha done it's report, and registered process.exit listener which silences any further - // eventual crashes. Recover by unregistering the listener - process.removeAllListeners('exit'); - throw err; -}); - -process.on('unhandledRejection', err => { - throw err; -}); - if (process.argv.length <= 2) { process.argv.push( '--require=sinon-bluebird', diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 5869ad450..0ae295ee2 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -1,5 +1,21 @@ 'use strict'; +// Unahdled rejections are not exposed in mocha, we need to force it +// https://github.com/mochajs/mocha/issues/2640 +process.on('unhandledRejection', err => { + throw err; +}); + +// Workaround Mocha v5 issue: https://github.com/mochajs/mocha/issues/3226 +// Fixed in v6, but not really: https://github.com/mochajs/mocha/issues/3917 +process.on('uncaughtException', err => { + if (!process.listenerCount('exit')) return; + // Mocha done it's report, and registered process.exit listener which silences any further + // eventual crashes. Recover by unregistering the listener + process.removeAllListeners('exit'); + throw err; +}); + const Spec = require('mocha/lib/reporters/spec'); const Runner = require('mocha/lib/runner'); @@ -9,4 +25,3 @@ const Runner = require('mocha/lib/runner'); Runner.immediately = process.nextTick; module.exports = Spec; - From 36beb60947ce1d405b92ade5f672d60a21f587cd Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 17 May 2019 14:58:15 +0200 Subject: [PATCH 079/504] Fix typo --- bin/test-isolated | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/test-isolated b/bin/test-isolated index befa6c0d1..7d057ce39 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -35,7 +35,7 @@ const run = path => { Promise.all([initialGitStatusDeferred, resolveGitStatus()]).then( ([initialStatus, currentStatus]) => { if (initialStatus !== currentStatus) { - process.stderr.write(chalk.red.bold(`${path} didn't clean creted temporary files\n\n`)); + process.stderr.write(chalk.red.bold(`${path} didn't clean created temporary files\n\n`)); process.exit(1); } } From f38006305780189220a44d01b2f7216d9c85e73b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 17 May 2019 15:04:59 +0200 Subject: [PATCH 080/504] Whitespace --- lib/classes/Error.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/classes/Error.test.js b/lib/classes/Error.test.js index 90c6c7969..c24b50e95 100644 --- a/lib/classes/Error.test.js +++ b/lib/classes/Error.test.js @@ -68,10 +68,12 @@ describe('Error', () => { describe('#logError()', () => { let BK_SLS_DEBUG; + beforeEach(() => { BK_SLS_DEBUG = process.env.SLS_DEBUG; delete process.env.SLS_DEBUG; }); + afterEach(() => { if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; else delete process.env.SLS_DEBUG; From f4f29998264f6e518021a4dc2db389a1ef828c50 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 17 May 2019 16:10:38 +0200 Subject: [PATCH 081/504] Improve comment --- tests/mocha-reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 0ae295ee2..e1a3c68ad 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -1,6 +1,6 @@ 'use strict'; -// Unahdled rejections are not exposed in mocha, we need to force it +// Unahdled rejections are not exposed in Mocha, we need to force it // https://github.com/mochajs/mocha/issues/2640 process.on('unhandledRejection', err => { throw err; From 644d1570fd09a3f8d294b5c7e9b15c42db1a8fad Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 17 May 2019 16:15:43 +0200 Subject: [PATCH 082/504] Ensure to rely on Mocha patches --- bin/test-isolated | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/test-isolated b/bin/test-isolated index 7d057ce39..db3be6755 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -41,7 +41,7 @@ const run = path => { } ); - return spawn('./bin/test', ['--require=sinon-bluebird', path], { + return spawn('./bin/test', ['--require=sinon-bluebird', '-R', 'tests/mocha-reporter', path], { stdio: 'inherit', env: { FORCE_COLOR: '1', PATH: process.env.PATH }, }).then(onFinally, error => From 19ab01b6798b41ec2ab08770aff61796bd4770d1 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 17 May 2019 16:42:50 +0200 Subject: [PATCH 083/504] Ensure to return promises --- .../aws/deploy/lib/checkForChanges.test.js | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/plugins/aws/deploy/lib/checkForChanges.test.js b/lib/plugins/aws/deploy/lib/checkForChanges.test.js index 1e4b013d6..7dab38f09 100644 --- a/lib/plugins/aws/deploy/lib/checkForChanges.test.js +++ b/lib/plugins/aws/deploy/lib/checkForChanges.test.js @@ -552,9 +552,7 @@ describe('checkForChanges', () => { const spy = sinon.spy(awsDeploy, 'checkLogGroupSubscriptionFilterResourceLimitExceeded'); - return awsDeploy.checkForChanges().then(() => { - expect(spy).to.not.have.been.called; - }); + return awsDeploy.checkForChanges().then(() => expect(spy).to.not.have.been.called); }); it('should work normally when there are functions without events', () => { @@ -562,7 +560,7 @@ describe('checkForChanges', () => { first: {}, }; - expect(awsDeploy.checkForChanges()).to.be.fulfilled; + return expect(awsDeploy.checkForChanges()).to.be.fulfilled; }); it('should work normally when there are functions events that are not cloudWwatchLog', () => { @@ -574,7 +572,7 @@ describe('checkForChanges', () => { }, }; - expect(awsDeploy.checkForChanges()).to.be.fulfilled; + return expect(awsDeploy.checkForChanges()).to.be.fulfilled; }); describe('option to force update is set', () => { @@ -599,9 +597,8 @@ describe('checkForChanges', () => { subscriptionFilters: [], }; - return awsDeploy.checkForChanges().then(() => { - expect(deleteSubscriptionFilterStub).to.not.have.been.called; - }); + return awsDeploy.checkForChanges().then(() => + expect(deleteSubscriptionFilterStub).to.not.have.been.called); }); it('should not call delete if there is a subFilter and the ARNs are the same', () => { @@ -623,9 +620,8 @@ describe('checkForChanges', () => { ], }; - return awsDeploy.checkForChanges().then(() => { - expect(deleteSubscriptionFilterStub).to.not.have.been.called; - }); + return awsDeploy.checkForChanges().then(() => + expect(deleteSubscriptionFilterStub).to.not.have.been.called); }); it('should call delete if there is a subFilter but the ARNs are not the same', () => { @@ -647,9 +643,8 @@ describe('checkForChanges', () => { ], }; - return awsDeploy.checkForChanges().then(() => { - expect(deleteSubscriptionFilterStub).to.have.been.called; - }); + return awsDeploy.checkForChanges().then(() => + expect(deleteSubscriptionFilterStub).to.have.been.called); }); }); }); From d7e5e2a6cfc607869a8e0f44196076d1ee819472 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 17 May 2019 16:53:11 +0200 Subject: [PATCH 084/504] Improve test mock --- lib/plugins/aws/deployFunction/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/deployFunction/index.test.js b/lib/plugins/aws/deployFunction/index.test.js index 0321e2422..5cfbfc78a 100644 --- a/lib/plugins/aws/deployFunction/index.test.js +++ b/lib/plugins/aws/deployFunction/index.test.js @@ -89,7 +89,7 @@ describe('AwsDeployFunction', () => { }); it('it should throw error if function is not provided', () => { - serverless.service.functions = null; + serverless.service.functions = {}; expect(() => awsDeployFunction.checkIfFunctionExists()).to.throw(Error); }); From 748f119d927b8b98f34ed356fe2837861cd183a6 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 17 May 2019 17:15:23 +0200 Subject: [PATCH 085/504] Ensure to wait for resolution --- lib/plugins/aws/deployFunction/index.test.js | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/plugins/aws/deployFunction/index.test.js b/lib/plugins/aws/deployFunction/index.test.js index 5cfbfc78a..a423e32c0 100644 --- a/lib/plugins/aws/deployFunction/index.test.js +++ b/lib/plugins/aws/deployFunction/index.test.js @@ -49,17 +49,18 @@ describe('AwsDeployFunction', () => { name: 'first', }, }; - serverless.init(); - serverless.setProvider('aws', new AwsProvider(serverless, options)); - cryptoStub = { - createHash: function () { return this; }, // eslint-disable-line - update: function () { return this; }, // eslint-disable-line - digest: sinon.stub(), - }; - AwsDeployFunction = proxyquire('./index.js', { - crypto: cryptoStub, + return serverless.init().then(() => { + serverless.setProvider('aws', new AwsProvider(serverless, options)); + cryptoStub = { + createHash: function () { return this; }, // eslint-disable-line + update: function () { return this; }, // eslint-disable-line + digest: sinon.stub(), + }; + AwsDeployFunction = proxyquire('./index.js', { + crypto: cryptoStub, + }); + awsDeployFunction = new AwsDeployFunction(serverless, options); }); - awsDeployFunction = new AwsDeployFunction(serverless, options); }); describe('#constructor()', () => { From 411762bc8b6748417f14069579642db408aad288 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 09:53:55 +0200 Subject: [PATCH 086/504] Ensure to return promise --- lib/plugins/aws/invokeLocal/index.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 80ec3379c..6ab117fa5 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -802,7 +802,8 @@ describe('AwsInvokeLocal', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; awsInvokeLocal.serverless.service.provider.timeout = 0.00001; - awsInvokeLocal.invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withRemainingTime') + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withRemainingTime') .then(() => { const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); expect(remainingTimes.stop).to.eql(0); From 0ea81b1e63afbd81010a443c0decdd85a66adf64 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 09:57:47 +0200 Subject: [PATCH 087/504] Ensure to return promise --- lib/plugins/aws/package/lib/mergeIamTemplates.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js index b282f82cf..43df16c77 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js @@ -297,8 +297,8 @@ describe('#mergeIamTemplates()', () => { }); it('should add RetentionInDays to a CloudWatch LogGroup resource if logRetentionInDays is given' - , () => { - [5, '5'].forEach((logRetentionInDays) => { + , () => + Promise.all([5, '5'].map((logRetentionInDays) => { awsPackage.serverless.service.provider.logRetentionInDays = logRetentionInDays; const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName); return awsPackage.mergeIamTemplates().then(() => { @@ -314,8 +314,8 @@ describe('#mergeIamTemplates()', () => { } ); }); - }); - }); + })) + ); it('should throw error if RetentionInDays is 0 or not an integer' , () => { From 196f09ac71eb7a7f2f632d926cddeb0e5c7cc023 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 10:00:21 +0200 Subject: [PATCH 088/504] Ensure to return promise --- lib/utils/getCommandSuggestion.test.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/utils/getCommandSuggestion.test.js b/lib/utils/getCommandSuggestion.test.js index 56ea46337..499570f6b 100644 --- a/lib/utils/getCommandSuggestion.test.js +++ b/lib/utils/getCommandSuggestion.test.js @@ -4,10 +4,14 @@ const expect = require('chai').expect; const getCommandSuggestion = require('./getCommandSuggestion'); const Serverless = require('../../lib/Serverless'); -const serverless = new Serverless(); -serverless.init(); - describe('#getCommandSuggestion', () => { + let serverless; + + before(() => { + serverless = new Serverless(); + return serverless.init(); + }); + it('should return "package" as a suggested command if you input "pekage"', () => { const suggestedCommand = getCommandSuggestion('pekage', serverless.cli.loadedCommands); expect(suggestedCommand).to.be.equal('package'); From 40bf5c566ba800d43c388ca8c40105b9b39a1a09 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 10:27:09 +0200 Subject: [PATCH 089/504] Improve mocha args injection --- bin/test | 11 ++++------- bin/test-isolated | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/bin/test b/bin/test index ffdd7734e..0f9173b14 100755 --- a/bin/test +++ b/bin/test @@ -2,13 +2,10 @@ 'use strict'; -if (process.argv.length <= 2) { - process.argv.push( - '--require=sinon-bluebird', - '-R', - 'tests/mocha-reporter', - '!(node_modules)/**/*.test.js' - ); +if (process.argv.length <= 2) process.argv.push('!(node_modules)/**/*.test.js'); +if (!process.argv.includes('-R')) process.argv.splice(2, 0, '-R', 'tests/mocha-reporter'); +if (!process.argv.includes('--require=sinon-bluebird')) { + process.argv.splice(2, 0, '--require=sinon-bluebird'); } require('mocha/bin/_mocha'); diff --git a/bin/test-isolated b/bin/test-isolated index db3be6755..59616d2fe 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -41,7 +41,7 @@ const run = path => { } ); - return spawn('./bin/test', ['--require=sinon-bluebird', '-R', 'tests/mocha-reporter', path], { + return spawn('./bin/test', [path], { stdio: 'inherit', env: { FORCE_COLOR: '1', PATH: process.env.PATH }, }).then(onFinally, error => From 318b59134c171159510bf800169daaaa2469a127 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 10:35:34 +0200 Subject: [PATCH 090/504] Improve doc --- tests/mocha-reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index e1a3c68ad..bd9636056 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -1,6 +1,6 @@ 'use strict'; -// Unahdled rejections are not exposed in Mocha, we need to force it +// Unahdled rejections are not exposed in Mocha, enforce it // https://github.com/mochajs/mocha/issues/2640 process.on('unhandledRejection', err => { throw err; From 1d4e59436c87c267f4891a09c0df94317123bdec Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 10:57:58 +0200 Subject: [PATCH 091/504] Configure detection of orphaned async calls --- tests/mocha-reporter.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index bd9636056..6aad16b29 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -24,4 +24,16 @@ const Runner = require('mocha/lib/runner'); // Reported to Mocha with: https://github.com/mochajs/mocha/issues/3920 Runner.immediately = process.nextTick; -module.exports = Spec; +module.exports = class ServerlessSpec extends Spec { + constructor(runner) { + super(runner); + runner.on('end', () => + setTimeout(() => { + // If tests end with any orphaned async call then this callback will be invoked + // It's a signal there's some misconfiguration in promises + // or in general in async flow handling + throw new Error('Test ended with unfinished async jobs'); + }).unref() + ); + } +}; From 87afded3d7e2178578cf300cef1c95e65ecfbb01 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 10:58:05 +0200 Subject: [PATCH 092/504] Ensure to return promise --- lib/classes/PluginManager.test.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 2c2202299..cba248616 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -1932,18 +1932,18 @@ describe('PluginManager', () => { beforeEach(function () { // eslint-disable-line prefer-arrow-callback serverlessInstance = new Serverless(); - serverlessInstance.init(); + return serverlessInstance.init().then(() => { + // Cannot rely on shebang in severless.js to invoke script using NodeJS on Windows. + const execPrefix = os.platform() === 'win32' ? 'node ' : ''; + serverlessExec = execPrefix + path.join(serverlessInstance.config.serverlessPath, + '..', 'bin', 'serverless'); + const tmpDir = testUtils.getTmpDirPath(); + serviceDir = path.join(tmpDir, 'service'); + fse.mkdirsSync(serviceDir); + process.chdir(serviceDir); - // Cannot rely on shebang in severless.js to invoke script using NodeJS on Windows. - const execPrefix = os.platform() === 'win32' ? 'node ' : ''; - serverlessExec = execPrefix + path.join(serverlessInstance.config.serverlessPath, - '..', 'bin', 'serverless'); - const tmpDir = testUtils.getTmpDirPath(); - serviceDir = path.join(tmpDir, 'service'); - fse.mkdirsSync(serviceDir); - process.chdir(serviceDir); - - execSync(`${serverlessExec} create --template aws-nodejs`); + execSync(`${serverlessExec} create --template aws-nodejs`); + }); }); it('should expose a working integration between the CLI and the plugin system', () => { From f191775cc9f2e5c543b25c0f9e5999f918b34407 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 12:09:45 +0200 Subject: [PATCH 093/504] Ensure to return after promises resolve --- lib/classes/PromiseTracker.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/classes/PromiseTracker.test.js b/lib/classes/PromiseTracker.test.js index 0202770d8..08073d3dc 100644 --- a/lib/classes/PromiseTracker.test.js +++ b/lib/classes/PromiseTracker.test.js @@ -26,6 +26,7 @@ describe('PromiseTracker', () => { promiseTracker.add('foo', BbPromise.resolve(), '${foo:}'); promiseTracker.add('foo', BbPromise.delay(10), '${foo:}'); promiseTracker.report(); // shouldn't throw + return Promise.all(promiseTracker.getAll()); }); it('reports no pending promises when none have been added', () => { const promises = promiseTracker.getPending(); @@ -56,6 +57,7 @@ describe('PromiseTracker', () => { expect(promises).to.be.an.instanceof(Array); expect(promises.length).to.equal(1); expect(promises[0]).to.equal(promise); + return Promise.all(promiseTracker.getAll()); }); it('reports no promises when none have been added', () => { const promises = promiseTracker.getAll(); From f4b1112ebf95582cb81f880c605de1193aebf86d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 12:11:23 +0200 Subject: [PATCH 094/504] Ensure to return promise --- .../awsConfigCredentials.test.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index 05e9a6f2b..025295f1b 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -32,13 +32,14 @@ describe('AwsConfigCredentials', () => { sandbox.stub(os, 'homedir').returns(tmpDirPath); serverless = new Serverless(); - serverless.init(); - const options = { - provider: 'aws', - key: 'some-key', - secret: 'some-secret', - }; - awsConfigCredentials = new AwsConfigCredentials(serverless, options); + return serverless.init().then(() => { + const options = { + provider: 'aws', + key: 'some-key', + secret: 'some-secret', + }; + awsConfigCredentials = new AwsConfigCredentials(serverless, options); + }); }); afterEach(() => { From 28c1c1894a76a590e747b6a754518efb2bef95ff Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 12:35:27 +0200 Subject: [PATCH 095/504] Ensure to not leave orphaned async call --- lib/plugins/aws/invokeLocal/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 80ec3379c..8bb7ddf4b 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -1196,7 +1196,7 @@ describe('AwsInvokeLocal', () => { delete require.cache[require.resolve('./index')]; delete require.cache[require.resolve('child_process')]; serverless.pluginManager.spawn.restore(); - return fse.remove('.serverless'); + fse.removeSync('.serverless'); }); it('calls docker with packaged artifact', () => From 3ad898b45079131c4b8bf56810dc003a19e21763 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 13:37:53 +0200 Subject: [PATCH 096/504] Improve whitespace handling --- lib/classes/PluginManager.test.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 2c2202299..8e93e5486 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -1732,13 +1732,15 @@ describe('PluginManager', () => { const commandsArray = ['foo']; - return pluginManager.run(commandsArray).then(() => { - expect(consoleLogStub.called).is.equal(true); - pluginManager.serverless.cli.log.restore(); - }).finally(() => { - if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; - else delete process.env.SLS_DEBUG; - }); + return pluginManager.run(commandsArray) + .then(() => { + expect(consoleLogStub.called).is.equal(true); + pluginManager.serverless.cli.log.restore(); + }) + .finally(() => { + if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; + else delete process.env.SLS_DEBUG; + }); }); describe('when invoking a command', () => { From 56b8c8f42e516b9855816c3c904e84bdda34e940 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 20 May 2019 13:39:29 +0200 Subject: [PATCH 097/504] Whitespace --- lib/classes/PluginManager.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 8e93e5486..c30a5c3e2 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -1718,8 +1718,10 @@ describe('PluginManager', () => { it('should show warning in debug mode and when the given command has no hooks', () => { const consoleLogStub = sinon.stub(pluginManager.serverless.cli, 'log').returns(); + const BK_SLS_DEBUG = process.env.SLS_DEBUG; process.env.SLS_DEBUG = '*'; + class HooklessPlugin { constructor() { this.commands = { From 581d647ce17cf3a977a76660763f2fcd776f7d19 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 20 May 2019 09:13:16 -0400 Subject: [PATCH 098/504] try not upgrading npm --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dc77133f0..b5d61ae11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,6 @@ matrix: - LINTING=true sudo: false install: -- travis_retry npm install -g npm - travis_retry npm install script: - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi From fcfd7f3bf156242d410d0b66b744c010eb9f141d Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 20 May 2019 11:58:52 -0400 Subject: [PATCH 099/504] version bump for version 1.43.0 --- CHANGELOG.md | 11 +++++++++++ package-lock.json | 43 ++++++++++++------------------------------- package.json | 2 +- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f0c3cbf6..93db86918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 1.43.0 (2019-05-20) + +- [Update services.md](https://github.com/serverless/serverless/pull/6138) +- [Azure: exclude development dependency files when packaging functions](https://github.com/serverless/serverless/pull/6137) +- [Update release process docs and toolings](https://github.com/serverless/serverless/pull/6113) +- [Update AWS Node.js runtime to version 10](https://github.com/serverless/serverless/pull/6142) +- [Fix tests setup issues](https://github.com/serverless/serverless/pull/6147) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.42.3...v1.43.0) + # 1.42.3 (2019-05-14) - [Update deploy.md](https://github.com/serverless/serverless/pull/6110) diff --git a/package-lock.json b/package-lock.json index d7d9b568c..25875f368 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.42.3", + "version": "1.43.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3078,8 +3078,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3100,14 +3099,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3122,20 +3119,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3252,8 +3246,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3265,7 +3258,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3280,7 +3272,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3288,14 +3279,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3314,7 +3303,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3395,8 +3383,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3408,7 +3395,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3494,8 +3480,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3531,7 +3516,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3551,7 +3535,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3595,14 +3578,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/package.json b/package.json index 43714b314..9bd10a40f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.42.3", + "version": "1.43.0", "engines": { "node": ">=4.0" }, From 25ca3bb84d0abef27d939e0f6a28920a113605a1 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 14 May 2019 09:46:55 +0200 Subject: [PATCH 100/504] Setup Travis to work with Windows / Remove AppVeyor --- .appveyor.yml | 24 -- .travis.yml | 93 ++++---- lib/plugins/aws/invoke/index.js | 59 +++-- lib/plugins/aws/invoke/index.test.js | 93 +++++--- .../aws/invokeLocal/fixture/handler.py | 3 +- lib/plugins/aws/invokeLocal/index.js | 68 +++--- lib/plugins/aws/invokeLocal/index.test.js | 214 ++++++++++-------- lib/plugins/aws/lib/getServiceState.test.js | 10 +- lib/plugins/aws/lib/validate.js | 17 +- lib/plugins/aws/lib/validate.test.js | 43 ++-- lib/plugins/create/create.test.js | 44 ---- 11 files changed, 323 insertions(+), 345 deletions(-) delete mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index f6818ae30..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,24 +0,0 @@ -environment: - global: - SLS_IGNORE_WARNING: "*" - matrix: - - NODEJS_VERSION: "4" - - NODEJS_VERSION: "6" - - NODEJS_VERSION: "8" - -install: - # For Ruby invoke local testing - - set PATH=C:\Ruby25\bin;%PATH% - # Get the version of Node.js - - ps: Install-Product node $Env:NODEJS_VERSION - # install modules - - rm -rf node_modules - - npm install - - node --version - - npm --version - -test_script: - - npm test - -# Don't actually build. -build: off diff --git a/.travis.yml b/.travis.yml index 3bf161084..9982bc353 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,56 +1,59 @@ language: node_js matrix: exclude: - - node_js: '6.2' - env: - - secure: ruPmxtjLY9jX/TQQe1d8vxwL0KsjsTzJK/EL6rZM954JJc+3c5gshxB1Ca9+WRwIGdMjCZgKo4dQcn2DrAap7KoDz+GRCi40kc5AOr2ZLmjxJeCJls+sQ713QJ01GgIA1X2zmz/bDSw5lan8ODR3eYI0fIn37ZV4yxNJ5PObBLCMQW6nnOdMXxcLLi2iJRqu3k0gb85wsUNOB0AF2cBrbxoYmGsrGjGuPjIXPeEdnVJ0jzBY+jDrBraIHEBN9tecIyG77ZgEhlPopqTrj39dEPMwAow+derEVWugPRF3Wps6z+Yx0qtxETptWuOgn0V9GpfZF9vJlHifQUuuJEGtttVCYjZCUOIoIRwWnX+/SygjlgLJ9PpVa6XyZfnqygJXnv7wouGGkiyZEDbayu65qP3Ls/Dj+N2nEtamOPf3dGBeaX8KKF61h80bH7o9JL/t6yxyvm+yDRTkYd9SNk3U7dFXoBcCcjMNNUkKBKtY9G3EZlE0ZV0D7jD33Na4yX8mKBHkYLKXCAn+rh6DIwLNkV8IOtAYD77qan6v4qH9fpPMX4UkLu9SRM1r5RfsZU0gX7sCP9OKFKkAx5BWUqjg+D6Xa0EACtO8bPDHA548hWU2vYx7ghuVJfVNOUlAl97OXclx2SZwXJ46rHaHgrH2I3gi3qyHMib3iUV7SLdRQnY= - - secure: p9ka7mRzo/ecjnDh/dz19g0iVfQdvsGRAtg/4ONeiq75I2+oqHzu+VxUBA1Z2IQbpCEAMo21CarR3fg2I6MFUeazL0nEpqr1PoOAI8nPFeQlg/h+jLXsrPAkDcu2/b8ij7J5MXeLdZXUVqiPcGkr68x/tCMk/rwxftljQhvXPQfc7Lxm/m61ELnC7rLJulhxWZLNIq1hwQ9nh0GMKb4hm0KmPn8ksccVL+wyDikkgXCuvIujhTBjhNivAe4mG8mqnNsW1Ugh++SUe1ld27TtbH7wQj02SSG4Bxfwc3Gz0GFdAL1GyOkWI2WvrqP4a0KYTRUo+pUr9E+HZ1SNlxU5t6QWtmDiy5MKkxzgeTXmkKiJ98vMlF0ja5bpp46NjYarzDafqE8FozHzLtr+uAtqr6gRAgU1rWaG9BE3gKeW/f4B/2MfPI26b7SxuU1MwGVy0I76hb0Ujbgb3X8G4TYTGb6Nhoewc+RZExPwVhfrN8cJjo45masndv5tQAZMSRX/JUFjs4h/QMXNsn0A53GXgf6eIzUu15m+W8TJYFiKQeq9nMejzEE4sWMO3BFnkxueBGVCEurOc1GgdEnKxeqlp+psxHcJRlNCxC1HkUVOzfpkCr/Jy42vM8jQomAMv41Z9zWjOagVphWT25xNeSILfRt4yPku5wfW4CAxp+fl4KQ= + - os: linux + node_js: "6.2" + env: + - secure: ruPmxtjLY9jX/TQQe1d8vxwL0KsjsTzJK/EL6rZM954JJc+3c5gshxB1Ca9+WRwIGdMjCZgKo4dQcn2DrAap7KoDz+GRCi40kc5AOr2ZLmjxJeCJls+sQ713QJ01GgIA1X2zmz/bDSw5lan8ODR3eYI0fIn37ZV4yxNJ5PObBLCMQW6nnOdMXxcLLi2iJRqu3k0gb85wsUNOB0AF2cBrbxoYmGsrGjGuPjIXPeEdnVJ0jzBY+jDrBraIHEBN9tecIyG77ZgEhlPopqTrj39dEPMwAow+derEVWugPRF3Wps6z+Yx0qtxETptWuOgn0V9GpfZF9vJlHifQUuuJEGtttVCYjZCUOIoIRwWnX+/SygjlgLJ9PpVa6XyZfnqygJXnv7wouGGkiyZEDbayu65qP3Ls/Dj+N2nEtamOPf3dGBeaX8KKF61h80bH7o9JL/t6yxyvm+yDRTkYd9SNk3U7dFXoBcCcjMNNUkKBKtY9G3EZlE0ZV0D7jD33Na4yX8mKBHkYLKXCAn+rh6DIwLNkV8IOtAYD77qan6v4qH9fpPMX4UkLu9SRM1r5RfsZU0gX7sCP9OKFKkAx5BWUqjg+D6Xa0EACtO8bPDHA548hWU2vYx7ghuVJfVNOUlAl97OXclx2SZwXJ46rHaHgrH2I3gi3qyHMib3iUV7SLdRQnY= + - secure: p9ka7mRzo/ecjnDh/dz19g0iVfQdvsGRAtg/4ONeiq75I2+oqHzu+VxUBA1Z2IQbpCEAMo21CarR3fg2I6MFUeazL0nEpqr1PoOAI8nPFeQlg/h+jLXsrPAkDcu2/b8ij7J5MXeLdZXUVqiPcGkr68x/tCMk/rwxftljQhvXPQfc7Lxm/m61ELnC7rLJulhxWZLNIq1hwQ9nh0GMKb4hm0KmPn8ksccVL+wyDikkgXCuvIujhTBjhNivAe4mG8mqnNsW1Ugh++SUe1ld27TtbH7wQj02SSG4Bxfwc3Gz0GFdAL1GyOkWI2WvrqP4a0KYTRUo+pUr9E+HZ1SNlxU5t6QWtmDiy5MKkxzgeTXmkKiJ98vMlF0ja5bpp46NjYarzDafqE8FozHzLtr+uAtqr6gRAgU1rWaG9BE3gKeW/f4B/2MfPI26b7SxuU1MwGVy0I76hb0Ujbgb3X8G4TYTGb6Nhoewc+RZExPwVhfrN8cJjo45masndv5tQAZMSRX/JUFjs4h/QMXNsn0A53GXgf6eIzUu15m+W8TJYFiKQeq9nMejzEE4sWMO3BFnkxueBGVCEurOc1GgdEnKxeqlp+psxHcJRlNCxC1HkUVOzfpkCr/Jy42vM8jQomAMv41Z9zWjOagVphWT25xNeSILfRt4yPku5wfW4CAxp+fl4KQ= include: - - node_js: '4.4' - env: SLS_IGNORE_WARNING=* - - node_js: '5.11' - env: SLS_IGNORE_WARNING=* - - node_js: '6.2' - env: SLS_IGNORE_WARNING=* - - node_js: '6.2' - env: - - INTEGRATION_TEST=true - - INTEGRATION_TEST_SUITE=simple - - SLS_IGNORE_WARNING=* - - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= - - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= - - node_js: '8.9' - env: SLS_IGNORE_WARNING=* - - node_js: '8.9' - env: - - INTEGRATION_TEST=true - - INTEGRATION_TEST_SUITE=simple - - SLS_IGNORE_WARNING=* - - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= - - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= - - node_js: '8.9' - env: - - DISABLE_TESTS=true - - LINTING=true - - node_js: '10.6' - env: - - DISABLE_TESTS=true - - LINTING=true - - node_js: '10.7' - env: - - DISABLE_TESTS=true - - LINTING=true + # Linux tests + - os: linux + node_js: "4.4" + env: SLS_IGNORE_WARNING=* + - os: linux + node_js: "5.11" + env: SLS_IGNORE_WARNING=* + - os: linux + node_js: "6.2" + env: SLS_IGNORE_WARNING=* + - os: linux + node_js: "8.9" + env: SLS_IGNORE_WARNING=* + - os: linux + node_js: "10.7" + env: SLS_IGNORE_WARNING=* + - os: linux + node_js: "10.7" + env: + - INTEGRATION_TEST=true + - INTEGRATION_TEST_SUITE=simple + - SLS_IGNORE_WARNING=* + - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= + - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= + - os: linux + node_js: "10.7" + env: + - DISABLE_TESTS=true + - LINTING=true + # Windows tests + - os: windows + node_js: "10.7" + env: SLS_IGNORE_WARNING=* sudo: false install: -- travis_retry npm install + - travis_retry npm install + # ensure that Python 2 and Ruby are installed + - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python ruby; else choco install python2 ruby; fi + - if [ $TRAVIS_OS_NAME = windows ]; then export PATH="/c/Python27:/c/Python27/Scripts:$PATH"; fi script: -- if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi -- if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then - npm run lint; fi -- if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$INTEGRATION_TEST_SUITE" - == "simple" ]]; then npm run simple-integration-test; fi + - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi + - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then + npm run lint; fi + - if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$INTEGRATION_TEST_SUITE" + == "simple" ]]; then npm run simple-integration-test; fi 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: services@serverless.com diff --git a/lib/plugins/aws/invoke/index.js b/lib/plugins/aws/invoke/index.js index 2354a1d28..6d3382ddb 100644 --- a/lib/plugins/aws/invoke/index.js +++ b/lib/plugins/aws/invoke/index.js @@ -24,43 +24,38 @@ class AwsInvoke { } extendedValidate() { - this.validate(); + return this.validate().then(() => { + // validate function exists in service + this.options.functionObj = this.serverless.service.getFunction(this.options.function); + this.options.data = this.options.data || ''; - // validate function exists in service - this.options.functionObj = this.serverless.service.getFunction(this.options.function); - this.options.data = this.options.data || ''; - - return new BbPromise(resolve => { - if (this.options.data) { - resolve(); - } else if (this.options.path) { - const absolutePath = path.isAbsolute(this.options.path) ? - this.options.path : - path.join(this.serverless.config.servicePath, this.options.path); - if (!this.serverless.utils.fileExistsSync(absolutePath)) { - throw new this.serverless.classes.Error('The file you provided does not exist.'); + return new BbPromise(resolve => { + if (this.options.data) { + return resolve(); + } else if (this.options.path) { + const absolutePath = path.isAbsolute(this.options.path) ? + this.options.path : + path.join(this.serverless.config.servicePath, this.options.path); + if (!this.serverless.utils.fileExistsSync(absolutePath)) { + throw new this.serverless.classes.Error('The file you provided does not exist.'); + } + this.options.data = this.serverless.utils.readFileSync(absolutePath); + return resolve(); } - this.options.data = this.serverless.utils.readFileSync(absolutePath); - resolve(); - } else { + + return stdin().then(input => { + this.options.data = input; + return resolve(); + }).catch(() => resolve()); + }).then(() => { try { - stdin().then(input => { - this.options.data = input; - resolve(); - }); + if (!this.options.raw) { + this.options.data = JSON.parse(this.options.data); + } } catch (exception) { - // resolve if no stdin was provided - resolve(); + // do nothing if it's a simple string or object already } - } - }).then(() => { - try { - if (!this.options.raw) { - this.options.data = JSON.parse(this.options.data); - } - } catch (exception) { - // do nothing if it's a simple string or object already - } + }); }); } diff --git a/lib/plugins/aws/invoke/index.test.js b/lib/plugins/aws/invoke/index.test.js index 1d31aa963..3e1211ef4 100644 --- a/lib/plugins/aws/invoke/index.test.js +++ b/lib/plugins/aws/invoke/index.test.js @@ -1,22 +1,37 @@ 'use strict'; -const expect = require('chai').expect; +const chai = require('chai'); const sinon = require('sinon'); const path = require('path'); -const AwsInvoke = require('./index'); +const proxyquire = require('proxyquire'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); const testUtils = require('../../../../tests/utils'); +chai.use(require('chai-as-promised')); + +const expect = chai.expect; + describe('AwsInvoke', () => { + let AwsInvoke; + let awsInvoke; + let serverless; + let stdinStub; const options = { stage: 'dev', region: 'us-east-1', function: 'first', }; - const serverless = new Serverless(); - serverless.setProvider('aws', new AwsProvider(serverless, options)); - const awsInvoke = new AwsInvoke(serverless, options); + + beforeEach(() => { + stdinStub = sinon.stub().resolves(''); + AwsInvoke = proxyquire('./index', { + 'get-stdin': stdinStub, + }); + serverless = new Serverless(); + serverless.setProvider('aws', new AwsProvider(serverless, options)); + awsInvoke = new AwsInvoke(serverless, options); + }); describe('#constructor()', () => { it('should have hooks', () => expect(awsInvoke.hooks).to.be.not.empty); @@ -87,40 +102,44 @@ describe('AwsInvoke', () => { it('it should throw error if function is not provided', () => { serverless.service.functions = null; - expect(() => awsInvoke.extendedValidate()).to.throw(Error); + return expect(awsInvoke.extendedValidate()).to.be.rejected; }); - it('should not throw error when there are no input data', () => { + it('should not throw error when there is no input data', () => { awsInvoke.options.data = undefined; - return awsInvoke.extendedValidate().then(() => { - expect(awsInvoke.options.data).to.equal(''); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvoke.options.data).to.equal(''); + }); }); it('should keep data if it is a simple string', () => { awsInvoke.options.data = 'simple-string'; - return awsInvoke.extendedValidate().then(() => { - expect(awsInvoke.options.data).to.equal('simple-string'); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvoke.options.data).to.equal('simple-string'); + }); }); it('should parse data if it is a json string', () => { awsInvoke.options.data = '{"key": "value"}'; - return awsInvoke.extendedValidate().then(() => { - expect(awsInvoke.options.data).to.deep.equal({ key: 'value' }); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvoke.options.data).to.deep.equal({ key: 'value' }); + }); }); it('should skip parsing data if "raw" requested', () => { awsInvoke.options.data = '{"key": "value"}'; awsInvoke.options.raw = true; - return awsInvoke.extendedValidate().then(() => { - expect(awsInvoke.options.data).to.deep.equal('{"key": "value"}'); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvoke.options.data).to.deep.equal('{"key": "value"}'); + }); }); it('it should parse file if relative file path is provided', () => { @@ -132,9 +151,10 @@ describe('AwsInvoke', () => { .join(serverless.config.servicePath, 'data.json'), JSON.stringify(data)); awsInvoke.options.path = 'data.json'; - return awsInvoke.extendedValidate().then(() => { - expect(awsInvoke.options.data).to.deep.equal(data); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvoke.options.data).to.deep.equal(data); + }); }); it('it should parse file if absolute file path is provided', () => { @@ -146,9 +166,10 @@ describe('AwsInvoke', () => { serverless.utils.writeFileSync(dataFile, JSON.stringify(data)); awsInvoke.options.path = dataFile; - return awsInvoke.extendedValidate().then(() => { - expect(awsInvoke.options.data).to.deep.equal(data); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvoke.options.data).to.deep.equal(data); + }); }); it('it should parse a yaml file if file path is provided', () => { @@ -159,32 +180,30 @@ describe('AwsInvoke', () => { .join(serverless.config.servicePath, 'data.yml'), yamlContent); awsInvoke.options.path = 'data.yml'; - return awsInvoke.extendedValidate().then(() => { - expect(awsInvoke.options.data).to.deep.equal({ - testProp: 'testValue', + return expect(awsInvoke.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvoke.options.data).to.deep.equal({ + testProp: 'testValue', + }); }); - }); }); it('it should throw error if service path is not set', () => { serverless.config.servicePath = false; - expect(() => awsInvoke.extendedValidate()).to.throw(Error); + return expect(awsInvoke.extendedValidate()).to.be.rejected; }); it('it should throw error if file path does not exist', () => { serverless.config.servicePath = testUtils.getTmpDirPath(); awsInvoke.options.path = 'some/path'; - return awsInvoke.extendedValidate().catch((err) => { - expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.equal('The file you provided does not exist.'); - }); + return expect(awsInvoke.extendedValidate()) + .to.be.rejectedWith('The file you provided does not exist.'); }); - it('should resolve if path is not given', (done) => { + it('should resolve if path is not given', () => { awsInvoke.options.path = false; - - awsInvoke.extendedValidate().then(() => done()); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled; }); }); diff --git a/lib/plugins/aws/invokeLocal/fixture/handler.py b/lib/plugins/aws/invokeLocal/fixture/handler.py index 3a36db642..13ffcf9f9 100644 --- a/lib/plugins/aws/invokeLocal/fixture/handler.py +++ b/lib/plugins/aws/invokeLocal/fixture/handler.py @@ -1,5 +1,6 @@ from time import sleep + def withRemainingTime(event, context): start = context.get_remaining_time_in_millis() sleep(0.001) @@ -8,4 +9,4 @@ def withRemainingTime(event, context): return { "start": start, "stop": stop - } \ No newline at end of file + } diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 692be94a1..75aef59b9 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -58,48 +58,42 @@ class AwsInvokeLocal { } extendedValidate() { - this.validate(); + return this.validate().then(() => { + // validate function exists in service + this.options.functionObj = this.serverless.service.getFunction(this.options.function); + this.options.data = this.options.data || ''; - // validate function exists in service - this.options.functionObj = this.serverless.service.getFunction(this.options.function); - this.options.data = this.options.data || ''; + return new BbPromise(resolve => { + if (this.options.contextPath) { + this.validateFile(this.options.contextPath, 'context'); + } - return new BbPromise(resolve => { - if (this.options.contextPath) { - this.validateFile(this.options.contextPath, 'context'); - } + if (this.options.data) { + return resolve(); + } else if (this.options.path) { + this.validateFile(this.options.path, 'data'); + return resolve(); + } - if (this.options.data) { - resolve(); - } else if (this.options.path) { - this.validateFile(this.options.path, 'data'); - - resolve(); - } else { + return stdin().then(input => { + this.options.data = input; + return resolve(); + }).catch(() => resolve()); + }).then(() => { try { - stdin().then(input => { - this.options.data = input; - resolve(); - }); + // unless asked to preserve raw input, attempt to parse any provided objects + if (!this.options.raw) { + if (this.options.data) { + this.options.data = JSON.parse(this.options.data); + } + if (this.options.context) { + this.options.context = JSON.parse(this.options.context); + } + } } catch (exception) { - // resolve if no stdin was provided - resolve(); + // do nothing if it's a simple string or object already } - } - }).then(() => { - try { - // unless asked to preserve raw input, attempt to parse any provided objects - if (!this.options.raw) { - if (this.options.data) { - this.options.data = JSON.parse(this.options.data); - } - if (this.options.context) { - this.options.context = JSON.parse(this.options.context); - } - } - } catch (exception) { - // do nothing if it's a simple string or object already - } + }); }); } @@ -275,8 +269,6 @@ class AwsInvokeLocal { buildDockerImage(layerPaths) { const runtime = this.getRuntime(); - - const imageName = 'sls-docker'; return new BbPromise((resolve, reject) => { diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 8bb7ddf4b..68999966b 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -7,8 +7,8 @@ const path = require('path'); const mockRequire = require('mock-require'); const EventEmitter = require('events'); const fse = require('fs-extra'); +const proxyquire = require('proxyquire'); const stripAnsi = require('strip-ansi'); -const AwsInvokeLocal = require('./index'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); @@ -21,10 +21,12 @@ chai.should(); const expect = chai.expect; describe('AwsInvokeLocal', () => { + let AwsInvokeLocal; + let awsInvokeLocal; let options; let serverless; let provider; - let awsInvokeLocal; + let stdinStub; beforeEach(() => { options = { @@ -32,6 +34,10 @@ describe('AwsInvokeLocal', () => { region: 'us-east-1', function: 'first', }; + stdinStub = sinon.stub().resolves(''); + AwsInvokeLocal = proxyquire('./index', { + 'get-stdin': stdinStub, + }); serverless = new Serverless(); serverless.config.servicePath = 'servicePath'; serverless.cli = new CLI(serverless); @@ -120,56 +126,62 @@ describe('AwsInvokeLocal', () => { it('should not throw error when there are no input data', () => { awsInvokeLocal.options.data = undefined; - return awsInvokeLocal.extendedValidate().then(() => { - expect(awsInvokeLocal.options.data).to.equal(''); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvokeLocal.options.data).to.equal(''); + }); }); it('it should throw error if function is not provided', () => { serverless.service.functions = null; - expect(() => awsInvokeLocal.extendedValidate()).to.throw(Error); + return expect(awsInvokeLocal.extendedValidate()).to.be.rejected; }); it('should keep data if it is a simple string', () => { awsInvokeLocal.options.data = 'simple-string'; - return awsInvokeLocal.extendedValidate().then(() => { - expect(awsInvokeLocal.options.data).to.equal('simple-string'); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvokeLocal.options.data).to.equal('simple-string'); + }); }); it('should parse data if it is a json string', () => { awsInvokeLocal.options.data = '{"key": "value"}'; - return awsInvokeLocal.extendedValidate().then(() => { - expect(awsInvokeLocal.options.data).to.deep.equal({ key: 'value' }); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal({ key: 'value' }); + }); }); it('should skip parsing data if "raw" requested', () => { awsInvokeLocal.options.data = '{"key": "value"}'; awsInvokeLocal.options.raw = true; - return awsInvokeLocal.extendedValidate().then(() => { - expect(awsInvokeLocal.options.data).to.deep.equal('{"key": "value"}'); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal('{"key": "value"}'); + }); }); it('should parse context if it is a json string', () => { awsInvokeLocal.options.context = '{"key": "value"}'; - return awsInvokeLocal.extendedValidate().then(() => { - expect(awsInvokeLocal.options.context).to.deep.equal({ key: 'value' }); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvokeLocal.options.context).to.deep.equal({ key: 'value' }); + }); }); it('should skip parsing context if "raw" requested', () => { awsInvokeLocal.options.context = '{"key": "value"}'; awsInvokeLocal.options.raw = true; - return awsInvokeLocal.extendedValidate().then(() => { - expect(awsInvokeLocal.options.context).to.deep.equal('{"key": "value"}'); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvokeLocal.options.context).to.deep.equal('{"key": "value"}'); + }); }); it('it should parse file if relative file path is provided', () => { @@ -181,9 +193,10 @@ describe('AwsInvokeLocal', () => { .join(serverless.config.servicePath, 'data.json'), JSON.stringify(data)); awsInvokeLocal.options.contextPath = 'data.json'; - return awsInvokeLocal.extendedValidate().then(() => { - expect(awsInvokeLocal.options.context).to.deep.equal(data); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvokeLocal.options.context).to.deep.equal(data); + }); }); it('it should parse file if absolute file path is provided', () => { @@ -198,9 +211,10 @@ describe('AwsInvokeLocal', () => { awsInvokeLocal.options.path = dataFile; awsInvokeLocal.options.contextPath = false; - return awsInvokeLocal.extendedValidate().then(() => { - expect(awsInvokeLocal.options.data).to.deep.equal(data); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal(data); + }); }); it('it should parse a yaml file if file path is provided', () => { @@ -211,9 +225,10 @@ describe('AwsInvokeLocal', () => { .join(serverless.config.servicePath, 'data.yml'), yamlContent); awsInvokeLocal.options.path = 'data.yml'; - return awsInvokeLocal.extendedValidate().then(() => { - expect(awsInvokeLocal.options.data).to.deep.equal({ event: 'data' }); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal({ event: 'data' }); + }); }); it('it should require a js file if file path is provided', () => { @@ -229,33 +244,31 @@ describe('AwsInvokeLocal', () => { .join(serverless.config.servicePath, 'data.js'), jsContent); awsInvokeLocal.options.path = 'data.js'; - return awsInvokeLocal.extendedValidate().then(() => { - expect(awsInvokeLocal.options.data).to.deep.equal({ - headers: { 'Content-Type': 'application/json' }, - body: '[100,200]', + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled + .then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal({ + headers: { 'Content-Type': 'application/json' }, + body: '[100,200]', + }); }); - }); }); it('it should throw error if service path is not set', () => { serverless.config.servicePath = false; - expect(() => awsInvokeLocal.extendedValidate()).to.throw(Error); + return expect(awsInvokeLocal.extendedValidate()).to.be.rejected; }); it('it should reject error if file path does not exist', () => { serverless.config.servicePath = testUtils.getTmpDirPath(); awsInvokeLocal.options.path = 'some/path'; - return awsInvokeLocal.extendedValidate().catch((err) => { - expect(err).to.be.instanceOf(Error); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.rejected; }); - it('should resolve if path is not given', (done) => { + it('should resolve if path is not given', () => { awsInvokeLocal.options.path = false; - - awsInvokeLocal.extendedValidate().then(() => done()); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled; }); }); @@ -863,9 +876,7 @@ describe('AwsInvokeLocal', () => { }); }); - // Ignored because it fails in CI - // See https://github.com/serverless/serverless/pull/4047#issuecomment-320460285 - describe.skip('#invokeLocalPython', () => { + describe('#invokeLocalPython', () => { beforeEach(() => { awsInvokeLocal.options = { functionObj: { @@ -881,7 +892,13 @@ describe('AwsInvokeLocal', () => { }); describe('context.remainingTimeInMillis', () => { - it('should become lower over time', () => { + it('should become lower over time', function () { + // skipping in CI for now due to handler loading issues + // in the Windows machine on Travis CI + if (process.env.CI) { + this.skip(); + } + awsInvokeLocal.serverless.config.servicePath = __dirname; return awsInvokeLocal.invokeLocalPython( @@ -895,6 +912,59 @@ describe('AwsInvokeLocal', () => { }); }); + describe('#invokeLocalRuby', () => { + let curdir; + + beforeEach(() => { + curdir = process.cwd(); + process.chdir(__dirname); + awsInvokeLocal.options = { + functionObj: { + name: '', + }, + }; + + sinon.stub(serverless.cli, 'consoleLog'); + }); + + afterEach(() => { + serverless.cli.consoleLog.restore(); + process.chdir(curdir); + }); + + describe('context.remainingTimeInMillis', () => { + it('should become lower over time', () => { + awsInvokeLocal.serverless.config.servicePath = __dirname; + + return awsInvokeLocal.invokeLocalRuby( + 'ruby', + 'fixture/handler', + 'withRemainingTime').then(() => { + const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); + expect(remainingTimes.start).to.be.above(remainingTimes.stop); + }); + }); + }); + + describe('calling a class method', () => { + it('should execute', () => { + awsInvokeLocal.serverless.config.servicePath = __dirname; + + return awsInvokeLocal.invokeLocalRuby( + 'ruby', + 'fixture/handler', + 'MyModule::MyClass.my_class_method').then(() => { + const result = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); + expect(result.foo).to.eq('bar'); + }); + }); + }); + }); + + // ------------- + // TODO: the following tests need to be refactored since they use mockRequire in a breaking way + // ------------- + describe('#callJavaBridge()', () => { let awsInvokeLocalMocked; let writeChildStub; @@ -1080,54 +1150,6 @@ describe('AwsInvokeLocal', () => { }); }); - describe('#invokeLocalRuby', () => { - let curdir; - beforeEach(() => { - curdir = process.cwd(); - process.chdir(__dirname); - awsInvokeLocal.options = { - functionObj: { - name: '', - }, - }; - - sinon.stub(serverless.cli, 'consoleLog'); - }); - - afterEach(() => { - serverless.cli.consoleLog.restore(); - process.chdir(curdir); - }); - - describe('context.remainingTimeInMillis', () => { - it('should become lower over time', () => { - awsInvokeLocal.serverless.config.servicePath = __dirname; - - return awsInvokeLocal.invokeLocalRuby( - 'ruby', - 'fixture/handler', - 'withRemainingTime').then(() => { - const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); - expect(remainingTimes.start).to.be.above(remainingTimes.stop); - }); - }); - }); - - describe('calling a class method', () => { - it('should execute', () => { - awsInvokeLocal.serverless.config.servicePath = __dirname; - - return awsInvokeLocal.invokeLocalRuby( - 'ruby', - 'fixture/handler', - 'MyModule::MyClass.my_class_method').then(() => { - const result = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); - expect(result.foo).to.eq('bar'); - }); - }); - }); - }); - describe('#invokeLocalDocker()', () => { let awsInvokeLocalMocked; let spawnStub; @@ -1201,6 +1223,8 @@ describe('AwsInvokeLocal', () => { it('calls docker with packaged artifact', () => awsInvokeLocalMocked.invokeLocalDocker().then(() => { + const dockerfilePath = path.join('.serverless', 'invokeLocal', 'Dockerfile'); + expect(pluginMangerSpawnPackageStub.calledOnce).to.equal(true); expect(spawnStub.getCall(0).args).to.deep.equal(['docker', ['version']]); expect(spawnStub.getCall(1).args).to.deep.equal(['docker', @@ -1213,7 +1237,7 @@ describe('AwsInvokeLocal', () => { 'sls-docker', 'servicePath', '-f', - '.serverless/invokeLocal/Dockerfile', + dockerfilePath, ]]); expect(spawnStub.getCall(4).args).to.deep.equal(['docker', [ 'run', diff --git a/lib/plugins/aws/lib/getServiceState.test.js b/lib/plugins/aws/lib/getServiceState.test.js index 02f4bf187..88ef7f4e8 100644 --- a/lib/plugins/aws/lib/getServiceState.test.js +++ b/lib/plugins/aws/lib/getServiceState.test.js @@ -1,5 +1,6 @@ 'use strict'; +const path = require('path'); const chai = require('chai'); const sinon = require('sinon'); const Serverless = require('../../../Serverless'); @@ -32,16 +33,17 @@ describe('#getServiceState()', () => { }); it('should use the default state file path if the "package" option is not used', () => { + const stateFilePath = path.join('my-service', '.serverless', 'serverless-state.json'); awsPlugin.getServiceState(); - expect(readFileSyncStub).to.be - .calledWithExactly('my-service/.serverless/serverless-state.json'); + + expect(readFileSyncStub).to.be.calledWithExactly(stateFilePath); }); it('should use the argument-based state file path if the "package" option is used ', () => { + const stateFilePath = path.join('my-service', 'some-package-path', 'serverless-state.json'); options.package = 'some-package-path'; awsPlugin.getServiceState(); - expect(readFileSyncStub).to.be - .calledWithExactly('my-service/some-package-path/serverless-state.json'); + expect(readFileSyncStub).to.be.calledWithExactly(stateFilePath); }); }); diff --git a/lib/plugins/aws/lib/validate.js b/lib/plugins/aws/lib/validate.js index 3673348ee..7e9b0b9b9 100644 --- a/lib/plugins/aws/lib/validate.js +++ b/lib/plugins/aws/lib/validate.js @@ -4,14 +4,17 @@ const BbPromise = require('bluebird'); module.exports = { validate() { - if (!this.serverless.config.servicePath) { - throw new this.serverless.classes - .Error('This command can only be run inside a service directory'); - } + return new BbPromise((resolve, reject) => { + if (!this.serverless.config.servicePath) { + const error = new this.serverless.classes + .Error('This command can only be run inside a service directory'); + reject(error); + } - this.options.stage = this.provider.getStage(); - this.options.region = this.provider.getRegion(); + this.options.stage = this.provider.getStage(); + this.options.region = this.provider.getRegion(); - return BbPromise.resolve(); + return resolve(); + }); }, }; diff --git a/lib/plugins/aws/lib/validate.test.js b/lib/plugins/aws/lib/validate.test.js index 87ec6abeb..ce5fe6ae3 100644 --- a/lib/plugins/aws/lib/validate.test.js +++ b/lib/plugins/aws/lib/validate.test.js @@ -1,11 +1,14 @@ 'use strict'; -const expect = require('chai').expect; - +const chai = require('chai'); const AwsProvider = require('../provider/awsProvider'); const validate = require('../lib/validate'); const Serverless = require('../../../Serverless'); +chai.use(require('chai-as-promised')); + +const expect = chai.expect; + describe('#validate', () => { const serverless = new Serverless(); let provider; @@ -27,22 +30,23 @@ describe('#validate', () => { }); describe('#validate()', () => { - it('should succeed if inside service (servicePath defined)', () => { - expect(() => awsPlugin.validate()).to.not.throw(Error); - }); + it('should succeed if inside service (servicePath defined)', () => + expect(awsPlugin.validate()).to.be.fulfilled + ); it('should throw error if not inside service (servicePath not defined)', () => { awsPlugin.serverless.config.servicePath = false; - expect(() => awsPlugin.validate()).to.throw(Error); + return expect(awsPlugin.validate()).to.be.rejected; }); // NOTE: starting here, test order is important it('should default to "dev" if stage is not provided', () => { awsPlugin.options.stage = false; - return awsPlugin.validate().then(() => { - expect(awsPlugin.provider.getStage()).to.equal('dev'); - }); + return expect(awsPlugin.validate()).to.be.fulfilled + .then(() => { + expect(awsPlugin.provider.getStage()).to.equal('dev'); + }); }); it('should use the service.provider stage if present', () => { @@ -51,16 +55,18 @@ describe('#validate', () => { stage: 'some-stage', }; - return awsPlugin.validate().then(() => { - expect(awsPlugin.provider.getStage()).to.equal('some-stage'); - }); + return expect(awsPlugin.validate()).to.be.fulfilled + .then(() => { + expect(awsPlugin.provider.getStage()).to.equal('some-stage'); + }); }); it('should default to "us-east-1" region if region is not provided', () => { awsPlugin.options.region = false; - return awsPlugin.validate().then(() => { - expect(awsPlugin.options.region).to.equal('us-east-1'); - }); + return expect(awsPlugin.validate()).to.be.fulfilled + .then(() => { + expect(awsPlugin.options.region).to.equal('us-east-1'); + }); }); it('should use the service.provider region if present', () => { @@ -69,9 +75,10 @@ describe('#validate', () => { region: 'some-region', }; - return awsPlugin.validate().then(() => { - expect(awsPlugin.options.region).to.equal('some-region'); - }); + return expect(awsPlugin.validate()).to.be.fulfilled + .then(() => { + expect(awsPlugin.options.region).to.equal('some-region'); + }); }); }); }); diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index dcd44fc5b..a77efcf5c 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -214,28 +214,6 @@ describe('Create', () => { expect(dirContent).to.include('.gitignore'); }); }); - it('should generate scaffolding for "aws-nodejs-typescript" ' + - 'template and override service name if user passed', () => { - process.chdir(tmpDir); - create.options.template = 'aws-nodejs-typescript'; - create.options.name = 'my-awesome-service'; - - return create.create().then(() => { - const dirContent = fs.readdirSync(tmpDir); - expect(dirContent).to.include('serverless.yml'); - expect(dirContent).to.include('handler.ts'); - expect(dirContent).to.include('tsconfig.json'); - expect(dirContent).to.include('package.json'); - expect(dirContent).to.include('webpack.config.js'); - expect(dirContent).to.include('.gitignore'); - - // check if the service was renamed - const serverlessYmlfileContent = fse - .readFileSync(path.join(tmpDir, 'serverless.yml')).toString(); - expect((/service:\n {2}name: my-awesome-service/) - .test(serverlessYmlfileContent)).to.equal(true); - }); - }); it('should generate scaffolding for "aws-alexa-typescript" template', () => { process.chdir(tmpDir); @@ -251,28 +229,6 @@ describe('Create', () => { expect(dirContent).to.include('.gitignore'); }); }); - it('should generate scaffolding for "aws-alexa-typescript" ' + - 'template and override service name if user passed', () => { - process.chdir(tmpDir); - create.options.template = 'aws-alexa-typescript'; - create.options.name = 'my-awesome-service'; - - return create.create().then(() => { - const dirContent = fs.readdirSync(tmpDir); - expect(dirContent).to.include('serverless.yml'); - expect(dirContent).to.include('handler.ts'); - expect(dirContent).to.include('tsconfig.json'); - expect(dirContent).to.include('package.json'); - expect(dirContent).to.include('webpack.config.js'); - expect(dirContent).to.include('.gitignore'); - - // check if the service was renamed - const serverlessYmlfileContent = fse - .readFileSync(path.join(tmpDir, 'serverless.yml')).toString(); - expect((/service:\n {2}name: my-awesome-service/) - .test(serverlessYmlfileContent)).to.equal(true); - }); - }); it('should generate scaffolding for "aws-nodejs-ecma-script" template', () => { process.chdir(tmpDir); From f2588b69c451b3f9f556ad22531dd392cf2e90dc Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 21 May 2019 16:40:25 +0200 Subject: [PATCH 101/504] Improve comment --- tests/mocha-reporter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 6aad16b29..91d7a0b2e 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -30,8 +30,7 @@ module.exports = class ServerlessSpec extends Spec { runner.on('end', () => setTimeout(() => { // If tests end with any orphaned async call then this callback will be invoked - // It's a signal there's some misconfiguration in promises - // or in general in async flow handling + // It's a signal there's some promise chain (or in general async flow) miconfiguration throw new Error('Test ended with unfinished async jobs'); }).unref() ); From 762df0751207201fb0efd4c3da313cbf0868a2d6 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 21 May 2019 16:53:30 +0200 Subject: [PATCH 102/504] Ensure to not send track requests when testing --- lib/plugins/create/create.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index dcd44fc5b..d57d1543e 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -10,6 +10,7 @@ const sinon = require('sinon'); const testUtils = require('../../../tests/utils'); const walkDirSync = require('../../utils/fs/walkDirSync'); const download = require('../../utils/downloadTemplateFromRepo'); +const userStats = require('../../utils/userStats'); describe('Create', () => { let create; @@ -21,6 +22,11 @@ describe('Create', () => { create = new Create(serverless, options); create.serverless.cli = new serverless.classes.CLI(); logSpy = sinon.spy(create.serverless.cli, 'log'); + sinon.stub(userStats, 'track').resolves(); + }); + + after(() => { + userStats.track.restore(); }); describe('#constructor()', () => { From ef728aa8bcb5c266f9f4ba0b878a86ddd3bdfbf6 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 14 May 2019 12:45:08 -0400 Subject: [PATCH 103/504] update AWS templates with sfe app/tenant placeholders --- .../create/templates/aws-alexa-typescript/serverless.yml | 2 ++ lib/plugins/create/templates/aws-clojure-gradle/serverless.yml | 2 ++ .../create/templates/aws-clojurescript-gradle/serverless.yml | 2 ++ lib/plugins/create/templates/aws-csharp/serverless.yml | 2 ++ lib/plugins/create/templates/aws-fsharp/serverless.yml | 2 ++ lib/plugins/create/templates/aws-go-dep/serverless.yml | 2 ++ lib/plugins/create/templates/aws-go-mod/serverless.yml | 2 ++ lib/plugins/create/templates/aws-go/serverless.yml | 2 ++ lib/plugins/create/templates/aws-groovy-gradle/serverless.yml | 2 ++ lib/plugins/create/templates/aws-java-gradle/serverless.yml | 2 ++ lib/plugins/create/templates/aws-java-maven/serverless.yml | 2 ++ .../create/templates/aws-kotlin-jvm-gradle/serverless.yml | 2 ++ .../create/templates/aws-kotlin-jvm-maven/serverless.yml | 2 ++ .../create/templates/aws-kotlin-nodejs-gradle/serverless.yml | 2 ++ .../create/templates/aws-nodejs-ecma-script/serverless.yml | 2 ++ .../create/templates/aws-nodejs-typescript/serverless.yml | 2 ++ lib/plugins/create/templates/aws-nodejs/serverless.yml | 2 ++ lib/plugins/create/templates/aws-provided/serverless.yml | 2 ++ lib/plugins/create/templates/aws-python/serverless.yml | 2 ++ lib/plugins/create/templates/aws-python3/serverless.yml | 2 ++ lib/plugins/create/templates/aws-ruby/serverless.yml | 2 ++ lib/plugins/create/templates/aws-scala-sbt/serverless.yml | 2 ++ 22 files changed, 44 insertions(+) diff --git a/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml b/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml index 3dde4cb45..d4ea38e3f 100644 --- a/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml +++ b/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml @@ -1,5 +1,7 @@ service: name: aws-alexa-typescript +#app: your-app-name +#tenant: your-tenant-name plugins: - serverless-webpack diff --git a/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml b/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml index 0a68753e2..b167892d0 100644 --- a/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml @@ -13,6 +13,8 @@ service: aws-clojure # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml index 2b1f024fd..fac71f26d 100644 --- a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-clojurescript-gradle +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-csharp/serverless.yml b/lib/plugins/create/templates/aws-csharp/serverless.yml index 7350eac9b..81c9930c5 100644 --- a/lib/plugins/create/templates/aws-csharp/serverless.yml +++ b/lib/plugins/create/templates/aws-csharp/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-csharp # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-fsharp/serverless.yml b/lib/plugins/create/templates/aws-fsharp/serverless.yml index a81378e29..2c304a440 100644 --- a/lib/plugins/create/templates/aws-fsharp/serverless.yml +++ b/lib/plugins/create/templates/aws-fsharp/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-fsharp # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-go-dep/serverless.yml b/lib/plugins/create/templates/aws-go-dep/serverless.yml index 2ae460403..b36eca0f8 100644 --- a/lib/plugins/create/templates/aws-go-dep/serverless.yml +++ b/lib/plugins/create/templates/aws-go-dep/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-go-dep # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-go-mod/serverless.yml b/lib/plugins/create/templates/aws-go-mod/serverless.yml index 896364433..7cc8abe00 100644 --- a/lib/plugins/create/templates/aws-go-mod/serverless.yml +++ b/lib/plugins/create/templates/aws-go-mod/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-go-mod # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-go/serverless.yml b/lib/plugins/create/templates/aws-go/serverless.yml index 0feaf948f..fe0c0492a 100644 --- a/lib/plugins/create/templates/aws-go/serverless.yml +++ b/lib/plugins/create/templates/aws-go/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-go # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml index fa290306e..27bbd89ba 100644 --- a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-groovy-gradle # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-java-gradle/serverless.yml b/lib/plugins/create/templates/aws-java-gradle/serverless.yml index 863e9b8a8..faff3364a 100644 --- a/lib/plugins/create/templates/aws-java-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-java-gradle/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-java-gradle # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.yml b/lib/plugins/create/templates/aws-java-maven/serverless.yml index c42d11853..807128ade 100644 --- a/lib/plugins/create/templates/aws-java-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-java-maven/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-java-maven # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml index 9d8deccdf..92bb8538f 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-kotlin-jvm-gradle # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml index e251df392..3702074f7 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-kotlin-jvm-maven # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml index be7630fd7..41e3ce7f0 100644 --- a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-kotlin-nodejs-gradle # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml b/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml index 5f25d7ec4..cabaaa77c 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml @@ -1,5 +1,7 @@ service: name: aws-nodejs-ecma-script +#app: your-app-name +#tenant: your-tenant-name # Add the serverless-webpack plugin plugins: diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml b/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml index ef04a44b7..5effc539d 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml @@ -1,5 +1,7 @@ service: name: aws-nodejs-typescript +#app: your-app-name +#tenant: your-tenant-name # Add the serverless-webpack plugin plugins: diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index 2d59fb360..e1c9b3fc0 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-nodejs # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-provided/serverless.yml b/lib/plugins/create/templates/aws-provided/serverless.yml index 65559fa50..90922dea2 100644 --- a/lib/plugins/create/templates/aws-provided/serverless.yml +++ b/lib/plugins/create/templates/aws-provided/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-provided # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-python/serverless.yml b/lib/plugins/create/templates/aws-python/serverless.yml index a6bd8a865..d77ff1625 100644 --- a/lib/plugins/create/templates/aws-python/serverless.yml +++ b/lib/plugins/create/templates/aws-python/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-python # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-python3/serverless.yml b/lib/plugins/create/templates/aws-python3/serverless.yml index 5c2fba0fd..da05941bb 100644 --- a/lib/plugins/create/templates/aws-python3/serverless.yml +++ b/lib/plugins/create/templates/aws-python3/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-python3 # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-ruby/serverless.yml b/lib/plugins/create/templates/aws-ruby/serverless.yml index 0b42c6568..de18084b9 100644 --- a/lib/plugins/create/templates/aws-ruby/serverless.yml +++ b/lib/plugins/create/templates/aws-ruby/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-ruby # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml index 366fd5302..1b4684faa 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml +++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml @@ -12,6 +12,8 @@ # Happy Coding! service: aws-scala-sbt # NOTE: update this with your service name +#app: your-app-name +#tenant: your-tenant-name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details From bad4b3ccb6fac8d9949d49a40312cfc744ecb56e Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 21 May 2019 11:16:06 -0400 Subject: [PATCH 104/504] rename test suite --- .travis.yml | 2 +- package.json | 2 +- tests/{local-suite => packaging-suite}/packaging.tests.js | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename tests/{local-suite => packaging-suite}/packaging.tests.js (100%) diff --git a/.travis.yml b/.travis.yml index 81e59e9de..876b97c72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,7 +41,7 @@ install: - travis_retry npm install script: - npm i -g npm -- if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test && npm run local-integration-test; fi +- if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test && npm run packaging-integration-test; fi - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then npm run lint; fi - if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$INTEGRATION_TEST_SUITE" diff --git a/package.json b/package.json index 147caab8e..d8f5c6765 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "lint": "eslint . --cache", "docs": "node scripts/generate-readme.js", "integration-test-cleanup": "node scripts/integration-test-cleanup.js", - "local-integration-test": "jest --maxWorkers=5 local-suite", + "packaging-integration-test": "jest --maxWorkers=5 packaging-suite", "simple-integration-test": "jest --maxWorkers=5 simple-suite", "complex-integration-test": "jest --maxWorkers=5 integration", "postinstall": "node ./scripts/postinstall.js" diff --git a/tests/local-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js similarity index 100% rename from tests/local-suite/packaging.tests.js rename to tests/packaging-suite/packaging.tests.js From c6a974d1de8fcd03d49afda47673ffb40619016e Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 21 May 2019 11:18:15 -0400 Subject: [PATCH 105/504] update tests with strict and new copy --- tests/packaging-suite/packaging.tests.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/packaging-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js index 7c947a313..b8ab81fb2 100644 --- a/tests/packaging-suite/packaging.tests.js +++ b/tests/packaging-suite/packaging.tests.js @@ -1,3 +1,5 @@ +'use strict'; + const fs = require('fs'); const path = require('path'); const execSync = require('child_process').execSync; @@ -6,14 +8,14 @@ const testUtils = require('../utils/index'); const serverlessExec = path.join(__dirname, '..', '..', 'bin', 'serverless'); -describe('simple packaging', () => { +describe('Integration test - Packaging', () => { let cwd; beforeEach(() => { cwd = testUtils.getTmpDirPath(); fse.mkdirsSync(cwd); }); - it('packages the default aws template correctly', () => { + it('packages the default aws template correctly in the zip', () => { const templateName = 'aws-nodejs'; execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -23,7 +25,7 @@ describe('simple packaging', () => { }); }); - it('packages the default aws template with an npm dep correctly', () => { + it('packages the default aws template with an npm dep correctly in the zip', () => { const templateName = 'aws-nodejs'; execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); execSync('npm init --yes', { cwd }); @@ -39,7 +41,7 @@ describe('simple packaging', () => { }); }); - it('doesn\'t package a dev dependency', () => { + it('doesn\'t package a dev dependency in the zip', () => { const templateName = 'aws-nodejs'; execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); execSync('npm init --yes', { cwd }); @@ -55,7 +57,7 @@ describe('simple packaging', () => { }); }); - it('ignores package json files per ignore directive', () => { + it('ignores package json files per ignore directive in the zip', () => { const templateName = 'aws-nodejs'; execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); execSync('npm init --yes', { cwd }); @@ -73,14 +75,14 @@ describe('simple packaging', () => { }); }); -describe('cloudformation packaging', () => { +describe('Integration test - Packaging', () => { let cwd; beforeEach(() => { cwd = testUtils.getTmpDirPath(); fse.mkdirsSync(cwd); }); - it('creates the correct default function resource', () => { + it('creates the correct default function resource in cfn template', () => { const templateName = 'aws-nodejs'; execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); execSync(`${serverlessExec} package`, { cwd }); From dc4e4ea569dc18c3a56a49f280a11d96a7db4b51 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 21 May 2019 11:22:59 -0400 Subject: [PATCH 106/504] why did i upgrade npm? --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 876b97c72..c1059938f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,6 @@ sudo: false install: - travis_retry npm install script: -- npm i -g npm - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test && npm run packaging-integration-test; fi - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then npm run lint; fi From 34fc5718c16ef5b034c98a7bd73096ae991b701c Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 21 May 2019 11:36:04 -0400 Subject: [PATCH 107/504] expect node 10! --- tests/packaging-suite/packaging.tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/packaging-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js index b8ab81fb2..d6fdf1631 100644 --- a/tests/packaging-suite/packaging.tests.js +++ b/tests/packaging-suite/packaging.tests.js @@ -108,7 +108,7 @@ describe('Integration test - Packaging', () => { 'Arn', ], }, - Runtime: 'nodejs8.10', + Runtime: 'nodejs10.x', Timeout: 6, }, DependsOn: [ From a8db8f4685ba57db69fd06e7dc9d8754c0e07060 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 21 May 2019 11:37:07 -0400 Subject: [PATCH 108/504] re-add npm upgrade and reason why --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index c1059938f..06d77fc41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,6 +39,8 @@ matrix: sudo: false install: - travis_retry npm install + # needed for consistency in output during packaging tests. old versions don't create a lock file +- travis_retry npm install -g npm script: - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test && npm run packaging-integration-test; fi - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then From c17196aef76fd6392361bac86c017e6245827b46 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 21 May 2019 14:27:53 -0400 Subject: [PATCH 109/504] remove sfe node version check --- lib/classes/PluginManager.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 688491a76..45406cdfd 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -5,7 +5,6 @@ const Module = require('module'); const BbPromise = require('bluebird'); const _ = require('lodash'); const crypto = require('crypto'); -const semver = require('semver'); const updateNotifier = require('update-notifier'); const writeFile = require('../utils/fs/writeFile'); const getCacheFilePath = require('../utils/getCacheFilePath'); @@ -161,10 +160,6 @@ class PluginManager { __dirname, '../../node_modules/@serverless/enterprise-plugin'); // eslint-disable-next-line global-require const sfePkgJson = require(`${qualifiedEnterpriseModulePath}/package.json`); - if (config.getGlobalConfig().enterpriseDisabled || - !semver.satisfies(process.version, sfePkgJson.engines.node)) { - return; - } this.loadPlugins([qualifiedEnterpriseModulePath]); if (this.serverless.enterpriseEnabled) { const updates = updateNotifier({ pkg: sfePkgJson, interval: 1 }); From ad235a6a7a87c0fe0cf4fe104bf20c7a797d64fa Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 21 May 2019 14:31:58 -0400 Subject: [PATCH 110/504] remove sfe node version check & change what require hacks we use --- lib/classes/PluginManager.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 45406cdfd..782eb95fd 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -156,11 +156,10 @@ class PluginManager { } loadEnterprisePlugin() { - const qualifiedEnterpriseModulePath = path.join( - __dirname, '../../node_modules/@serverless/enterprise-plugin'); + module.paths.unshift(path.join( __dirname, '../../node_modules') + this.loadPlugins(['@serverless/enterprise-plugin']); // eslint-disable-next-line global-require - const sfePkgJson = require(`${qualifiedEnterpriseModulePath}/package.json`); - this.loadPlugins([qualifiedEnterpriseModulePath]); + const sfePkgJson = require(`@serverless/enterprise-plugin/package.json`); if (this.serverless.enterpriseEnabled) { const updates = updateNotifier({ pkg: sfePkgJson, interval: 1 }); if (updates.update) { From 631a939621e409cdde2d94bbfd3ea4b583153fda Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 21 May 2019 14:34:10 -0400 Subject: [PATCH 111/504] fat fingers --- lib/classes/PluginManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 782eb95fd..1a7d55280 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -156,7 +156,7 @@ class PluginManager { } loadEnterprisePlugin() { - module.paths.unshift(path.join( __dirname, '../../node_modules') + module.paths.unshift(path.join(__dirname, '../../node_modules')); this.loadPlugins(['@serverless/enterprise-plugin']); // eslint-disable-next-line global-require const sfePkgJson = require(`@serverless/enterprise-plugin/package.json`); From 598fd4728ee2e2a5f750d2f6d034c80ed3bfab91 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 10:14:08 +0200 Subject: [PATCH 112/504] Ensure to return promise --- lib/plugins/aws/info/getResourceCount.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/info/getResourceCount.test.js b/lib/plugins/aws/info/getResourceCount.test.js index ba150a4ff..405742a00 100644 --- a/lib/plugins/aws/info/getResourceCount.test.js +++ b/lib/plugins/aws/info/getResourceCount.test.js @@ -104,7 +104,7 @@ describe('#getResourceCount()', () => { outputs: [], }; - expect(awsInfo.getResourceCount()).to.be.fulfilled.then(() => { + return expect(awsInfo.getResourceCount()).to.be.fulfilled.then(() => { expect(listStackResourcesStub.calledOnce).to.equal(true); expect(listStackResourcesStub.calledWithExactly( 'CloudFormation', From 08ad0b5775ae970c1f82fc839465f0054f635e07 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 10:20:40 +0200 Subject: [PATCH 113/504] Ensure to return promise --- .../events/apiGateway/lib/validate.test.js | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js index e8fb97929..d2694f76c 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js @@ -1451,14 +1451,14 @@ describe('#validate()', () => { }, }; // initialize so we get the log method from the CLI in place - serverless.init(); + return serverless.init().then(() => { + const logStub = sinon.stub(serverless.cli, 'log'); - const logStub = sinon.stub(serverless.cli, 'log'); + awsCompileApigEvents.validate(); - awsCompileApigEvents.validate(); - - expect(logStub.calledTwice).to.be.equal(true); - expect(logStub.args[0][0].length).to.be.at.least(1); + expect(logStub.calledTwice).to.be.equal(true); + expect(logStub.args[0][0].length).to.be.at.least(1); + }); }); it('should not show a warning message when using request.parameter with HTTP-PROXY', () => { @@ -1493,13 +1493,13 @@ describe('#validate()', () => { }, }; // initialize so we get the log method from the CLI in place - serverless.init(); + return serverless.init().then(() => { + const logStub = sinon.stub(serverless.cli, 'log'); - const logStub = sinon.stub(serverless.cli, 'log'); + awsCompileApigEvents.validate(); - awsCompileApigEvents.validate(); - - expect(logStub.called).to.be.equal(false); + expect(logStub.called).to.be.equal(false); + }); }); it('should remove non-parameter or uri request/response config with HTTP-PROXY', () => { @@ -1529,17 +1529,17 @@ describe('#validate()', () => { }, }; // initialize so we get the log method from the CLI in place - serverless.init(); + return serverless.init().then(() => { + // don't want to print the logs in this test + sinon.stub(serverless.cli, 'log'); - // don't want to print the logs in this test - sinon.stub(serverless.cli, 'log'); - - const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); - expect(validated.events[0].http.response).to.equal(undefined); - expect(validated.events[0].http.request.uri).to.equal('http://www.example.com'); - expect(validated.events[0].http.request.parameters).to.deep.equal({ - 'method.request.path.foo': true, + const validated = awsCompileApigEvents.validate(); + expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events[0].http.response).to.equal(undefined); + expect(validated.events[0].http.request.uri).to.equal('http://www.example.com'); + expect(validated.events[0].http.request.parameters).to.deep.equal({ + 'method.request.path.foo': true, + }); }); }); @@ -1608,14 +1608,14 @@ describe('#validate()', () => { }, }; // initialize so we get the log method from the CLI in place - serverless.init(); + return serverless.init().then(() => { + const logStub = sinon.stub(serverless.cli, 'log'); - const logStub = sinon.stub(serverless.cli, 'log'); + awsCompileApigEvents.validate(); - awsCompileApigEvents.validate(); - - expect(logStub.calledTwice).to.be.equal(true); - expect(logStub.args[0][0].length).to.be.at.least(1); + expect(logStub.calledTwice).to.be.equal(true); + expect(logStub.args[0][0].length).to.be.at.least(1); + }); }); it('should not show a warning message when using request.parameter with LAMBDA-PROXY', () => { @@ -1649,13 +1649,13 @@ describe('#validate()', () => { }, }; // initialize so we get the log method from the CLI in place - serverless.init(); + return serverless.init().then(() => { + const logStub = sinon.stub(serverless.cli, 'log'); - const logStub = sinon.stub(serverless.cli, 'log'); + awsCompileApigEvents.validate(); - awsCompileApigEvents.validate(); - - expect(logStub.called).to.be.equal(false); + expect(logStub.called).to.be.equal(false); + }); }); it('should remove non-parameter request/response config with LAMBDA-PROXY', () => { @@ -1684,16 +1684,16 @@ describe('#validate()', () => { }, }; // initialize so we get the log method from the CLI in place - serverless.init(); + return serverless.init().then(() => { + // don't want to print the logs in this test + sinon.stub(serverless.cli, 'log'); - // don't want to print the logs in this test - sinon.stub(serverless.cli, 'log'); - - const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); - expect(validated.events[0].http.response).to.equal(undefined); - expect(validated.events[0].http.request.parameters).to.deep.equal({ - 'method.request.path.foo': true, + const validated = awsCompileApigEvents.validate(); + expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events[0].http.response).to.equal(undefined); + expect(validated.events[0].http.request.parameters).to.deep.equal({ + 'method.request.path.foo': true, + }); }); }); From 004a8ea072d280aaaa0cf431115103750ea0854e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 10:43:54 +0200 Subject: [PATCH 114/504] Speedup unhandled rejection notifications It's to ensure it doesn't interfere with async leaks detection --- tests/mocha-reporter.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 91d7a0b2e..f2da18c0c 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -24,6 +24,17 @@ const Runner = require('mocha/lib/runner'); // Reported to Mocha with: https://github.com/mochajs/mocha/issues/3920 Runner.immediately = process.nextTick; +// Speedup Bluebird's unhandled rejection notifications +// So they do not interfere with an async leaks detector (configured below) +const BbPromise = require('bluebird'); +/* eslint-disable no-underscore-dangle */ +BbPromise.prototype._ensurePossibleRejectionHandled = function () { + if ((this._bitField & 524288) !== 0) return; + this._setRejectionIsUnhandled(); + process.nextTick(() => this._notifyUnhandledRejection()); +}; +/* eslint-enable */ + module.exports = class ServerlessSpec extends Spec { constructor(runner) { super(runner); From 44c359d05005290a70ccd9b7824fd852cc8f60e9 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 10:55:17 +0200 Subject: [PATCH 115/504] Ensure to return promise --- .../package/compile/events/apiGateway/lib/restApi.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js index 127e61e03..aee58434e 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js @@ -173,7 +173,7 @@ describe('#compileRestApi()', () => { it('throw error if apiKeySourceType is not HEADER or AUTHORIZER', () => { awsCompileApigEvents.serverless.service.provider.apiGateway = { apiKeySourceType: 'Testing' }; - expect(awsCompileApigEvents.compileRestApi()).to.be.rejectedWith(Error); + return expect(awsCompileApigEvents.compileRestApi()).to.be.rejectedWith(Error); }); it('should compile correctly if minimumCompressionSize is an integer', () => { @@ -187,20 +187,20 @@ describe('#compileRestApi()', () => { awsCompileApigEvents.serverless.service.provider.apiGateway = { minimumCompressionSize: 'Testing', }; - expect(awsCompileApigEvents.compileRestApi()).to.be.rejectedWith(Error); + return expect(awsCompileApigEvents.compileRestApi()).to.be.rejectedWith(Error); }); it('should throw error if minimumCompressionSize is less than 0', () => { awsCompileApigEvents.serverless.service.provider.apiGateway = { minimumCompressionSize: -1, }; - expect(awsCompileApigEvents.compileRestApi()).to.be.rejectedWith(Error); + return expect(awsCompileApigEvents.compileRestApi()).to.be.rejectedWith(Error); }); it('should throw error if minimumCompressionSize is greater than 10485760', () => { awsCompileApigEvents.serverless.service.provider.apiGateway = { minimumCompressionSize: 10485761, }; - expect(awsCompileApigEvents.compileRestApi()).to.be.rejectedWith(Error); + return expect(awsCompileApigEvents.compileRestApi()).to.be.rejectedWith(Error); }); }); From faf39b4b776fa96fc1e63d13cdda062811925811 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 11:24:39 +0200 Subject: [PATCH 116/504] Ensure to return promise --- lib/plugins/install/install.test.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/plugins/install/install.test.js b/lib/plugins/install/install.test.js index 5f2b7a0f5..c2e0acfd1 100644 --- a/lib/plugins/install/install.test.js +++ b/lib/plugins/install/install.test.js @@ -28,9 +28,10 @@ describe('Install', () => { serverless = new Serverless(); install = new Install(serverless); - serverless.init(); - install.serverless.cli = new serverless.classes.CLI(); - logSpy = sinon.spy(install.serverless.cli, 'log'); + return serverless.init().then(() => { + install.serverless.cli = new serverless.classes.CLI(); + logSpy = sinon.spy(install.serverless.cli, 'log'); + }); }); afterEach(() => { From 3950fd2046ed9561f251692bbfaccdb4abf30257 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 11:24:51 +0200 Subject: [PATCH 117/504] Ensure to not send track requests when testing --- lib/plugins/install/install.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/plugins/install/install.test.js b/lib/plugins/install/install.test.js index c2e0acfd1..6ae38ba95 100644 --- a/lib/plugins/install/install.test.js +++ b/lib/plugins/install/install.test.js @@ -6,6 +6,7 @@ const Install = require('./install.js'); const sinon = require('sinon'); const testUtils = require('../../../tests/utils'); const download = require('../../utils/downloadTemplateFromRepo'); +const userStats = require('../../utils/userStats'); const fse = require('fs-extra'); const path = require('path'); @@ -26,6 +27,8 @@ describe('Install', () => { servicePath = tmpDir; + sinon.stub(userStats, 'track').resolves(); + serverless = new Serverless(); install = new Install(serverless); return serverless.init().then(() => { @@ -35,6 +38,7 @@ describe('Install', () => { }); afterEach(() => { + userStats.track.restore(); // change back to the old cwd process.chdir(cwd); }); From bbd4317f947ef8d2fd912316c2dc40a220df84d9 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 11:26:36 +0200 Subject: [PATCH 118/504] Ensure to return promise --- lib/plugins/package/package.test.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/plugins/package/package.test.js b/lib/plugins/package/package.test.js index 8b2b2a0b2..38608e0cf 100644 --- a/lib/plugins/package/package.test.js +++ b/lib/plugins/package/package.test.js @@ -17,12 +17,13 @@ describe('Package', () => { beforeEach(() => { serverless = new Serverless(); - serverless.init(); - options = { - stage: 'dev', - region: 'us-east-1', - }; - pkg = new Package(serverless, options); + return serverless.init().then(() => { + options = { + stage: 'dev', + region: 'us-east-1', + }; + pkg = new Package(serverless, options); + }); }); describe('#constructor()', () => { From a9fc2ab7daad3801ca67768af9ebe4d726750386 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 11:28:09 +0200 Subject: [PATCH 119/504] Ensure to return promise --- lib/plugins/slstats/slstats.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/plugins/slstats/slstats.test.js b/lib/plugins/slstats/slstats.test.js index 6bb76ce49..3affbb65c 100644 --- a/lib/plugins/slstats/slstats.test.js +++ b/lib/plugins/slstats/slstats.test.js @@ -13,8 +13,9 @@ describe('SlStats', () => { beforeEach(() => { serverless = new Serverless(); - serverless.init(); - slStats = new SlStats(serverless); + return serverless.init().then(() => { + slStats = new SlStats(serverless); + }); }); describe('#constructor()', () => { From bee3f13484204854cd3fe3a3e18988dc63e81efa Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 12:30:02 +0200 Subject: [PATCH 120/504] Ensure to send user tracking requests --- lib/plugins/plugin/install/install.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/plugins/plugin/install/install.test.js b/lib/plugins/plugin/install/install.test.js index d6a543dd1..7e49c19ad 100644 --- a/lib/plugins/plugin/install/install.test.js +++ b/lib/plugins/plugin/install/install.test.js @@ -12,6 +12,7 @@ const PluginInstall = require('./install'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); const testUtils = require('../../../../tests/utils'); +const userStats = require('../../../utils/userStats'); chai.use(require('chai-as-promised')); const expect = require('chai').expect; const _ = require('lodash'); @@ -64,10 +65,12 @@ describe('PluginInstall', () => { beforeEach(() => { installStub = sinon .stub(pluginInstall, 'install').returns(BbPromise.resolve()); + sinon.stub(userStats, 'track').resolves(); }); afterEach(() => { pluginInstall.install.restore(); + userStats.track.restore(); }); it('should have the sub-command "install"', () => { From dc30b4338fc891b527baa2d0e3b8424ef9aa10da Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 12:31:47 +0200 Subject: [PATCH 121/504] Ensure to return promise --- lib/utils/renameService.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/renameService.test.js b/lib/utils/renameService.test.js index 7fb600f9b..d580a8cfc 100644 --- a/lib/utils/renameService.test.js +++ b/lib/utils/renameService.test.js @@ -24,7 +24,7 @@ describe('renameService', () => { servicePath = tmpDir; serverless = new Serverless(); - serverless.init(); + return serverless.init(); }); afterEach(() => { From 4edf46eaefe2d3689e5fbe79d4df1ee114ae375f Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Fri, 3 May 2019 13:32:06 +0200 Subject: [PATCH 122/504] Update required Node.js version / Add version check --- .travis.yml | 6 ------ VERSIONING.md | 4 ++++ bin/serverless | 15 +++++++++++++++ package.json | 2 +- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9982bc353..0e7d73b41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,6 @@ matrix: - secure: p9ka7mRzo/ecjnDh/dz19g0iVfQdvsGRAtg/4ONeiq75I2+oqHzu+VxUBA1Z2IQbpCEAMo21CarR3fg2I6MFUeazL0nEpqr1PoOAI8nPFeQlg/h+jLXsrPAkDcu2/b8ij7J5MXeLdZXUVqiPcGkr68x/tCMk/rwxftljQhvXPQfc7Lxm/m61ELnC7rLJulhxWZLNIq1hwQ9nh0GMKb4hm0KmPn8ksccVL+wyDikkgXCuvIujhTBjhNivAe4mG8mqnNsW1Ugh++SUe1ld27TtbH7wQj02SSG4Bxfwc3Gz0GFdAL1GyOkWI2WvrqP4a0KYTRUo+pUr9E+HZ1SNlxU5t6QWtmDiy5MKkxzgeTXmkKiJ98vMlF0ja5bpp46NjYarzDafqE8FozHzLtr+uAtqr6gRAgU1rWaG9BE3gKeW/f4B/2MfPI26b7SxuU1MwGVy0I76hb0Ujbgb3X8G4TYTGb6Nhoewc+RZExPwVhfrN8cJjo45masndv5tQAZMSRX/JUFjs4h/QMXNsn0A53GXgf6eIzUu15m+W8TJYFiKQeq9nMejzEE4sWMO3BFnkxueBGVCEurOc1GgdEnKxeqlp+psxHcJRlNCxC1HkUVOzfpkCr/Jy42vM8jQomAMv41Z9zWjOagVphWT25xNeSILfRt4yPku5wfW4CAxp+fl4KQ= include: # Linux tests - - os: linux - node_js: "4.4" - env: SLS_IGNORE_WARNING=* - - os: linux - node_js: "5.11" - env: SLS_IGNORE_WARNING=* - os: linux node_js: "6.2" env: SLS_IGNORE_WARNING=* diff --git a/VERSIONING.md b/VERSIONING.md index b0162bf8d..1b57039b2 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -49,6 +49,10 @@ Any non-backward compatible changes leads to a major version bump. This includes If we remove a helper function from the serverless object passed down to a plugin then this is a breaking change since some people might rely on it in custom made plugins. +### Node.js versions + +The Serverless Framework supports the major cloud providers Node.js runtime versions. Support for old Node.js versions will be removed once Cloud providers announce that such runtimes are not supported anymore. + ### FAQ 1. Is it okay to mark a feature as deprecated in version 1.4.0 and then remove it in 1.8.0 diff --git a/bin/serverless b/bin/serverless index b8cff829c..04e2eb50e 100755 --- a/bin/serverless +++ b/bin/serverless @@ -7,6 +7,20 @@ const BbPromise = require('bluebird'); const logError = require('../lib/classes/Error').logError; const uuid = require('uuid'); const initializeErrorReporter = require('../lib/utils/sentry').initializeErrorReporter; +const semver = require('semver'); +const packageJson = require('../package.json'); + +function checkNodeVersion(serverless) { + const requiredVersion = packageJson.engines.node; + const nodeVersion = process.version; + if (!semver.satisfies(nodeVersion, requiredVersion)) { + const msg = [ + `Serverless Framework requires Node.js version ${requiredVersion}. `, + `You're running version ${nodeVersion}.`, + ].join(''); + serverless.cli.log(msg); + } +} Error.stackTraceLimit = Infinity; @@ -40,6 +54,7 @@ const invocationId = uuid.v4(); serverless.invocationId = invocationId; return serverless.init() + .then(() => checkNodeVersion(serverless)) .then(() => serverless.run()) .then(() => process.exit(0)) .catch((err) => { diff --git a/package.json b/package.json index 9bd10a40f..fcb3a464e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "serverless", "version": "1.43.0", "engines": { - "node": ">=4.0" + "node": ">=6.0" }, "preferGlobal": true, "homepage": "https://github.com/serverless/serverless#readme", From 2ffd4cd2c18d6285b293ba0d663a9bcc8e869636 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 12:45:14 +0200 Subject: [PATCH 123/504] Ensure to return promise --- lib/plugins/aws/package/compile/functions/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index 430924b85..51a8cc132 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -388,7 +388,7 @@ describe('AwsCompileFunctions', () => { }, }; - expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); }); it('should create a simple function resource', () => { From e7f36638163e733afc4f4fe8eefc94fa990ff106 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 12:46:27 +0200 Subject: [PATCH 124/504] Resolve promise on 'close' event As this one confirms closure of all file descriptors and works with async leaks detection --- lib/plugins/aws/package/compile/functions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index ad3a6f18c..0ebedde2c 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -394,7 +394,7 @@ class AwsCompileFunctions { fileHash.write(chunk); versionHash.write(chunk); }) - .on('end', () => { + .on('close', () => { cb(); }) .on('error', error => { From 355f8d247a3e5ca5e0a48234d7b09f442a464673 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 13:36:54 +0200 Subject: [PATCH 125/504] Typo --- tests/mocha-reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index f2da18c0c..677bdad4a 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -1,6 +1,6 @@ 'use strict'; -// Unahdled rejections are not exposed in Mocha, enforce it +// Unhandled rejections are not exposed in Mocha, enforce it // https://github.com/mochajs/mocha/issues/2640 process.on('unhandledRejection', err => { throw err; From ba0f5523a2960d47cb6b74a9ac74d67ebe31f554 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 13:37:51 +0200 Subject: [PATCH 126/504] Typo --- tests/mocha-reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 677bdad4a..5b061e6ac 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -24,7 +24,7 @@ const Runner = require('mocha/lib/runner'); // Reported to Mocha with: https://github.com/mochajs/mocha/issues/3920 Runner.immediately = process.nextTick; -// Speedup Bluebird's unhandled rejection notifications +// Speed up Bluebird's unhandled rejection notifications // So they do not interfere with an async leaks detector (configured below) const BbPromise = require('bluebird'); /* eslint-disable no-underscore-dangle */ From 3cdb51d07acea509b2cde54af2270d3a012c5444 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 13:40:14 +0200 Subject: [PATCH 127/504] Increase timeout as test occasionally times out --- lib/classes/Service.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index 0ececfd87..fd77a8eed 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -123,7 +123,8 @@ describe('Service', () => { return expect(noService.load()).to.eventually.resolve; }); - it('should load serverless.yml from filesystem', () => { + it('should load serverless.yml from filesystem', function () { + this.timeout(3000); const SUtils = new Utils(); const serverlessYml = { service: 'new-service', From 59f915811d82332f686586fd4181e499d14c0c21 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 13:44:08 +0200 Subject: [PATCH 128/504] Ensure to not issue track requests when testing --- lib/plugins/plugin/list/list.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/plugins/plugin/list/list.test.js b/lib/plugins/plugin/list/list.test.js index 0585127ca..3799571b7 100644 --- a/lib/plugins/plugin/list/list.test.js +++ b/lib/plugins/plugin/list/list.test.js @@ -6,6 +6,7 @@ const BbPromise = require('bluebird'); const PluginList = require('./list'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); +const userStats = require('../../../utils/userStats'); chai.use(require('chai-as-promised')); const expect = require('chai').expect; @@ -26,10 +27,12 @@ describe('PluginList', () => { beforeEach(() => { listStub = sinon .stub(pluginList, 'list').returns(BbPromise.resolve()); + sinon.stub(userStats, 'track').resolves(); }); afterEach(() => { pluginList.list.restore(); + userStats.track.restore(); }); it('should have the sub-command "list"', () => { From 0c54d9389d1082c57e2151afe8d062808ee08ca8 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 22 May 2019 07:50:24 -0400 Subject: [PATCH 129/504] version specifier instead of `latests` --- package-lock.json | 232 ++++++++++++++-------------------------------- package.json | 2 +- 2 files changed, 73 insertions(+), 161 deletions(-) diff --git a/package-lock.json b/package-lock.json index 61b6c905c..25efeb438 100644 --- a/package-lock.json +++ b/package-lock.json @@ -465,13 +465,13 @@ } }, "@serverless/enterprise-plugin": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-0.5.1.tgz", - "integrity": "sha512-0za+mPb184Qj84XiHcqgZrxhF+ZTWRfKPlxfejonkpLgodwefG3KTNzKD5RttE4EuSosnyycwDpyykPR/ZScGA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.0.tgz", + "integrity": "sha512-m66Wl7kN0aAjM1QFzNPtRCr9LOG33nXWuLrWRXxT93DJE5UEbfq0f/OQ+sk8sCf0m07IdXHzehrvK1dRb+NRlw==", "requires": { "@babel/polyfill": "^7.2.5", "@serverless/event-mocks": "^1.1.1", - "@serverless/platform-sdk": "^0.7.3", + "@serverless/platform-sdk": "^1.0.0", "chalk": "^2.4.2", "flat": "^4.1.0", "fs-extra": "^7.0.1", @@ -507,9 +507,9 @@ } }, "node-fetch": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.5.0.tgz", - "integrity": "sha512-YuZKluhWGJwCcUu4RlZstdAxr8bFfOVHakc1mplwHkk8J+tqM1Y5yraYvIUpeX8aY7+crCwiELJq7Vl0o0LWXw==" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" } } }, @@ -523,25 +523,25 @@ } }, "@serverless/platform-sdk": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@serverless/platform-sdk/-/platform-sdk-0.7.3.tgz", - "integrity": "sha512-iqVbfuA+2GSkzXinaHL8m/a0fTP70nHTCrZkbsZVIQ2FclBlIs2xVznI0GtI0TZ7jaypBq44yeModYmVd5SkYg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@serverless/platform-sdk/-/platform-sdk-1.0.0.tgz", + "integrity": "sha512-+MYfXA36tXERShJDbfMQcEJKt4eRHQBbUeAzrY7emzJ/RYViPTw49Z8iHvYXhsMIi0v0DOlGHma7xNtGaKNm6Q==", "requires": { "babel-polyfill": "^6.26.0", - "body-parser": "^1.18.3", + "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.3.0", + "opn": "^5.5.0", "querystring": "^0.2.0", "ramda": "^0.25.0", "rc": "^1.2.8", - "source-map-support": "^0.5.5", + "source-map-support": "^0.5.12", "uuid": "^3.3.2", - "write-file-atomic": "^2.3.0" + "write-file-atomic": "^2.4.2" }, "dependencies": { "uuid": { @@ -618,9 +618,9 @@ } }, "@types/lodash": { - "version": "4.14.126", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.126.tgz", - "integrity": "sha512-HxQ+wQnBtnL0LszZrVdMqWIlzZNyKuMLUb6swQ3mo6ysPqpAu7gfnapCQIi0B+Mrf0fNLZh8AWgJs2njejVasg==" + "version": "4.14.130", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.130.tgz", + "integrity": "sha512-H++wk0tbneBsRVfLkgAAd0IIpmpVr2Bj4T0HncoOsQf3/xrJexRYQK2Tqo0Ej3pFslM8GkMgdis9bu6xIb1ycw==" }, "@types/stack-utils": { "version": "1.0.1", @@ -1850,9 +1850,12 @@ "dev": true }, "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } }, "content-type": { "version": "1.0.4", @@ -1889,9 +1892,9 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", - "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.7.tgz", + "integrity": "sha512-ydmsQxDVH7lDpYoirXft8S83ddKKfdsrsmWhtyj7xafXVLbLhKOyfD7kAi2ueFfeP7m9rNavjW59O3hLLzzC5A==" }, "core-util-is": { "version": "1.0.2", @@ -3020,63 +3023,46 @@ } }, "express": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", - "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.0.tgz", + "integrity": "sha512-1Z7/t3Z5ZnBG252gKUPyItc4xdeaA0X934ca2ewckAsVsw9EG71i++ZHZPYnus8g/s5Bty8IMpSVEuRkmwwPRQ==", "requires": { - "accepts": "~1.3.5", + "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.3", - "content-disposition": "0.5.2", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "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.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.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.4", - "qs": "6.5.2", - "range-parser": "~1.2.0", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", "safe-buffer": "5.1.2", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", + "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": { - "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", - "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, "debug": { "version": "2.6.9", @@ -3086,55 +3072,10 @@ "ms": "2.0.0" } }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", - "unpipe": "1.0.0" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" } } }, @@ -3328,16 +3269,16 @@ } }, "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", "unpipe": "~1.0.0" }, "dependencies": { @@ -3353,11 +3294,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" } } }, @@ -7832,9 +7768,9 @@ "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=" }, "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -7843,12 +7779,12 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "range-parser": "~1.2.1", + "statuses": "~1.5.0" }, "dependencies": { "debug": { @@ -7857,38 +7793,14 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "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=" + } } - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" } } }, @@ -7901,14 +7813,14 @@ } }, "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" + "parseurl": "~1.3.3", + "send": "0.17.1" } }, "set-blocking": { diff --git a/package.json b/package.json index 2ec2d91f1..d431db6ca 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "strip-ansi": "^5.2.0" }, "dependencies": { - "@serverless/enterprise-plugin": "latest", + "@serverless/enterprise-plugin": "^1.0.0", "archiver": "^1.1.0", "async": "^1.5.2", "aws-sdk": "^2.430.0", From 48fd0e80cd6212604c0cd5082f829480eabc1280 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 13:50:39 +0200 Subject: [PATCH 130/504] Ensure to return promise --- lib/plugins/invoke/invoke.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/invoke/invoke.test.js b/lib/plugins/invoke/invoke.test.js index 3db7fb64b..9dffcde10 100644 --- a/lib/plugins/invoke/invoke.test.js +++ b/lib/plugins/invoke/invoke.test.js @@ -50,14 +50,14 @@ describe('Invoke', () => { it('should accept a single env option', () => { invoke.options = { env: 'NAME=value' }; - expect(invoke.hooks['invoke:local:loadEnvVars']()).to.be.fulfilled + return expect(invoke.hooks['invoke:local:loadEnvVars']()).to.be.fulfilled .then(() => expect(process.env.NAME).to.equal('value')); }); it('should accept multiple env options', () => { invoke.options = { env: ['NAME1=val1', 'NAME2=val2'] }; - expect(invoke.hooks['invoke:local:loadEnvVars']()).to.be.fulfilled + return expect(invoke.hooks['invoke:local:loadEnvVars']()).to.be.fulfilled .then(() => expect(process.env.NAME1).to.equal('val1')) .then(() => expect(process.env.NAME2).to.equal('val2')); }); From 51c998caaf561f1fc0c7934a92ffe505be562dad Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 13:53:37 +0200 Subject: [PATCH 131/504] Ensure to not issue tracking requests when testing --- lib/plugins/plugin/search/search.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/plugins/plugin/search/search.test.js b/lib/plugins/plugin/search/search.test.js index bdb81dcc6..93e2afa54 100644 --- a/lib/plugins/plugin/search/search.test.js +++ b/lib/plugins/plugin/search/search.test.js @@ -6,6 +6,7 @@ const BbPromise = require('bluebird'); const PluginSearch = require('./search'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); +const userStats = require('../../../utils/userStats'); chai.use(require('chai-as-promised')); const expect = require('chai').expect; @@ -50,10 +51,12 @@ describe('PluginSearch', () => { beforeEach(() => { searchStub = sinon .stub(pluginSearch, 'search').returns(BbPromise.resolve()); + sinon.stub(userStats, 'track').resolves(); }); afterEach(() => { pluginSearch.search.restore(); + userStats.track.restore(); }); it('should have the sub-command "search"', () => { From bd5a4ac52e4db5dd8bbf718967bfad72717e2ce7 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 22 May 2019 08:04:36 -0400 Subject: [PATCH 132/504] remove use of sls create in packaging tests --- tests/packaging-suite/handler.js | 14 ++++++++++++++ tests/packaging-suite/packaging.tests.js | 15 ++++++++++----- tests/packaging-suite/serverless.yml | 10 ++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 tests/packaging-suite/handler.js create mode 100644 tests/packaging-suite/serverless.yml diff --git a/tests/packaging-suite/handler.js b/tests/packaging-suite/handler.js new file mode 100644 index 000000000..8d524e278 --- /dev/null +++ b/tests/packaging-suite/handler.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports.hello = async (event) => { + return { + statusCode: 200, + body: JSON.stringify({ + message: 'Go Serverless v1.0! Your function executed successfully!', + input: event, + }, null, 2), + }; + + // Use this code if you don't use the http event with the LAMBDA-PROXY integration + // return { message: 'Go Serverless v1.0! Your function executed successfully!', event }; +}; diff --git a/tests/packaging-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js index d6fdf1631..4d5e75801 100644 --- a/tests/packaging-suite/packaging.tests.js +++ b/tests/packaging-suite/packaging.tests.js @@ -17,7 +17,8 @@ describe('Integration test - Packaging', () => { it('packages the default aws template correctly in the zip', () => { const templateName = 'aws-nodejs'; - execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); + fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync(`${serverlessExec} package`, { cwd }); return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { @@ -27,7 +28,8 @@ describe('Integration test - Packaging', () => { it('packages the default aws template with an npm dep correctly in the zip', () => { const templateName = 'aws-nodejs'; - execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); + fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync('npm init --yes', { cwd }); execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -43,7 +45,8 @@ describe('Integration test - Packaging', () => { it('doesn\'t package a dev dependency in the zip', () => { const templateName = 'aws-nodejs'; - execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); + fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync('npm init --yes', { cwd }); execSync('npm i --save-dev lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -59,7 +62,8 @@ describe('Integration test - Packaging', () => { it('ignores package json files per ignore directive in the zip', () => { const templateName = 'aws-nodejs'; - execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); + fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync('npm init --yes', { cwd }); execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); execSync('npm i lodash', { cwd }); @@ -84,7 +88,8 @@ describe('Integration test - Packaging', () => { it('creates the correct default function resource in cfn template', () => { const templateName = 'aws-nodejs'; - execSync(`${serverlessExec} create --template ${templateName}`, { cwd }); + fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( cwd, '.serverless/cloudformation-template-update-stack.json'))); diff --git a/tests/packaging-suite/serverless.yml b/tests/packaging-suite/serverless.yml new file mode 100644 index 000000000..700a3a5bd --- /dev/null +++ b/tests/packaging-suite/serverless.yml @@ -0,0 +1,10 @@ +service: aws-nodejs + +provider: + name: aws + runtime: nodejs10.x + + +functions: + hello: + handler: handler.hello From 45698c3d020af3fbeb8ec79ec0d9ab66f833986d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 22 May 2019 14:05:59 +0200 Subject: [PATCH 133/504] Ensure to issue track requests when testing --- lib/plugins/plugin/uninstall/uninstall.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/plugins/plugin/uninstall/uninstall.test.js b/lib/plugins/plugin/uninstall/uninstall.test.js index 8f75cd91c..ab6b418c8 100644 --- a/lib/plugins/plugin/uninstall/uninstall.test.js +++ b/lib/plugins/plugin/uninstall/uninstall.test.js @@ -11,6 +11,7 @@ const PluginUninstall = require('./uninstall'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); const testUtils = require('../../../../tests/utils'); +const userStats = require('../../../utils/userStats'); chai.use(require('chai-as-promised')); const expect = require('chai').expect; @@ -58,10 +59,12 @@ describe('PluginUninstall', () => { beforeEach(() => { uninstallStub = sinon .stub(pluginUninstall, 'uninstall').returns(BbPromise.resolve()); + sinon.stub(userStats, 'track').resolves(); }); afterEach(() => { pluginUninstall.uninstall.restore(); + userStats.track.restore(); }); it('should have the sub-command "uninstall"', () => { From cabb1e3be61321dd9197dce23388639c55416347 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 22 May 2019 08:06:57 -0400 Subject: [PATCH 134/504] remove unused packaging name --- tests/packaging-suite/packaging.tests.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/packaging-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js index 4d5e75801..0343934ef 100644 --- a/tests/packaging-suite/packaging.tests.js +++ b/tests/packaging-suite/packaging.tests.js @@ -16,7 +16,6 @@ describe('Integration test - Packaging', () => { }); it('packages the default aws template correctly in the zip', () => { - const templateName = 'aws-nodejs'; fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync(`${serverlessExec} package`, { cwd }); @@ -27,7 +26,6 @@ describe('Integration test - Packaging', () => { }); it('packages the default aws template with an npm dep correctly in the zip', () => { - const templateName = 'aws-nodejs'; fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync('npm init --yes', { cwd }); @@ -44,7 +42,6 @@ describe('Integration test - Packaging', () => { }); it('doesn\'t package a dev dependency in the zip', () => { - const templateName = 'aws-nodejs'; fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync('npm init --yes', { cwd }); @@ -87,7 +84,6 @@ describe('Integration test - Packaging', () => { }); it('creates the correct default function resource in cfn template', () => { - const templateName = 'aws-nodejs'; fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync(`${serverlessExec} package`, { cwd }); From 50c6fd35d89a3b521dc42174b0a542a8a8986de8 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 22 May 2019 08:19:35 -0400 Subject: [PATCH 135/504] a test covering artifact directive usage --- tests/packaging-suite/packaging.tests.js | 37 +++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/packaging-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js index 0343934ef..13428ff30 100644 --- a/tests/packaging-suite/packaging.tests.js +++ b/tests/packaging-suite/packaging.tests.js @@ -58,7 +58,6 @@ describe('Integration test - Packaging', () => { }); it('ignores package json files per ignore directive in the zip', () => { - const templateName = 'aws-nodejs'; fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync('npm init --yes', { cwd }); @@ -82,6 +81,42 @@ describe('Integration test - Packaging', () => { cwd = testUtils.getTmpDirPath(); fse.mkdirsSync(cwd); }); + it('package artifact directive works', () => { + fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fs.copyFileSync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')) + execSync('echo \'package: {artifact: artifact.zip}\' >> serverless.yml', { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + const cfnTemplate = JSON.parse(fs.readFileSync(path.join( + cwd, '.serverless/cloudformation-template-update-stack.json'))); + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key) + .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/artifact.zip/); + delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; + expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { + Ref: 'ServerlessDeploymentBucket', + }, + }, + FunctionName: 'aws-nodejs-dev-hello', + Handler: 'handler.hello', + MemorySize: 1024, + Role: { + 'Fn::GetAtt': [ + 'IamRoleLambdaExecution', + 'Arn', + ], + }, + Runtime: 'nodejs10.x', + Timeout: 6, + }, + DependsOn: [ + 'HelloLogGroup', + 'IamRoleLambdaExecution', + ], + }); + }); it('creates the correct default function resource in cfn template', () => { fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) From bc9dc0895b2970ab008f05ccecb7ccd3590893e9 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 22 May 2019 08:36:37 -0400 Subject: [PATCH 136/504] package individually test --- tests/packaging-suite/handler2.js | 14 +++++++ tests/packaging-suite/individually.yml | 24 ++++++++++++ tests/packaging-suite/packaging.tests.js | 50 ++++++++++++++++++++---- 3 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 tests/packaging-suite/handler2.js create mode 100644 tests/packaging-suite/individually.yml diff --git a/tests/packaging-suite/handler2.js b/tests/packaging-suite/handler2.js new file mode 100644 index 000000000..8d524e278 --- /dev/null +++ b/tests/packaging-suite/handler2.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports.hello = async (event) => { + return { + statusCode: 200, + body: JSON.stringify({ + message: 'Go Serverless v1.0! Your function executed successfully!', + input: event, + }, null, 2), + }; + + // Use this code if you don't use the http event with the LAMBDA-PROXY integration + // return { message: 'Go Serverless v1.0! Your function executed successfully!', event }; +}; diff --git a/tests/packaging-suite/individually.yml b/tests/packaging-suite/individually.yml new file mode 100644 index 000000000..df2d79d23 --- /dev/null +++ b/tests/packaging-suite/individually.yml @@ -0,0 +1,24 @@ +service: aws-nodejs + +provider: + name: aws + runtime: nodejs10.x + +package: + individually: true + +functions: + hello: + handler: handler.hello + package: + include: + - handler.js + exclude: + - handler2.js + hello2: + handler: handler2.hello + package: + include: + - handler2.js + exclude: + - handler.js diff --git a/tests/packaging-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js index 13428ff30..e321533e5 100644 --- a/tests/packaging-suite/packaging.tests.js +++ b/tests/packaging-suite/packaging.tests.js @@ -73,14 +73,7 @@ describe('Integration test - Packaging', () => { expect(nonNodeModulesFiles).toEqual(['handler.js']); }); }); -}); -describe('Integration test - Packaging', () => { - let cwd; - beforeEach(() => { - cwd = testUtils.getTmpDirPath(); - fse.mkdirsSync(cwd); - }); it('package artifact directive works', () => { fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) fs.copyFileSync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')) @@ -157,4 +150,47 @@ describe('Integration test - Packaging', () => { expect(zipfiles).toEqual(['handler.js']); }); }); + + it('handles package individually with include/excludes correctly', () => { + fs.copyFileSync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')) + fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fs.copyFileSync(path.join(__dirname, 'handler2.js'), path.join(cwd, 'handler2.js')) + execSync(`${serverlessExec} package`, { cwd }); + const cfnTemplate = JSON.parse(fs.readFileSync(path.join( + cwd, '.serverless/cloudformation-template-update-stack.json'))); + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key) + .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/hello.zip/); + expect(cfnTemplate.Resources.Hello2LambdaFunction.Properties.Code.S3Key) + .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/hello2.zip/); + delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; + expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { + Ref: 'ServerlessDeploymentBucket', + }, + }, + FunctionName: 'aws-nodejs-dev-hello', + Handler: 'handler.hello', + MemorySize: 1024, + Role: { + 'Fn::GetAtt': [ + 'IamRoleLambdaExecution', + 'Arn', + ], + }, + Runtime: 'nodejs10.x', + Timeout: 6, + }, + DependsOn: [ + 'HelloLogGroup', + 'IamRoleLambdaExecution', + ], + }); + return testUtils.listZipFiles(path.join(cwd, '.serverless/hello.zip')) + .then(zipfiles => expect(zipfiles).toEqual(['handler.js'])) + .then(() => testUtils.listZipFiles(path.join(cwd, '.serverless/hello2.zip'))) + .then(zipfiles => expect(zipfiles).toEqual(['handler2.js'])); + }); }); From 60c422e2768d6be72293c45d1c45a70187619b48 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 22 May 2019 08:48:12 -0400 Subject: [PATCH 137/504] checkin artifact.zip for packaging tests --- tests/packaging-suite/artifact.zip | Bin 0 -> 1696 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/packaging-suite/artifact.zip diff --git a/tests/packaging-suite/artifact.zip b/tests/packaging-suite/artifact.zip new file mode 100644 index 0000000000000000000000000000000000000000..325409f5c64b05b1928d6d1b1674a40285573dd2 GIT binary patch literal 1696 zcmZ{lYg7_u7{^HgNz=UME$?abN}7hIX01>Xr>MhQ*u2X{O_NLnomk##q?Y%XWWx-D zTr@-kc>*4fVf&w0=Lyyra6`{DoL_eUXRf$9K=v;Y7j z>G?+r(l-1o5f>94NwmQyb5Nm3-U0RW$$Cff8*t+tGTW2W-Khbp%op?I$XdTy&=Td-~~m{aT5i&WD=G4~a3$BWqm3BlP`?%$WSR3ZBg{IgD>%qQF*s~c-vSl$)4s7f=wG3h? zIMrrPLk>NdN>emEuCR>$>6qKl0kqEf)K*9fDX{^_YRr2&tp95dvU)cp8W8~4P%*J; zH)QjZw4YVtXZYGX4db-bMRxIB<*#`3wT6=rHR z;tR8sWpxG(#91VKQpKtpj@3<(qFSx$$&)mU19q|WXQNiZg zE(a6Td8~LSjYBKQHfU=X7dquO8)3xr?|6qXfxXk%b1C@yH4-@tN%!%^d*9}DX(x0I z;?F292(LH~lt+?&u>~BM3C*!DKCD5z*31PY1`C$&Uo(jr8SK3pJDnsrC}Q%#Q@%&~ zKzD)J7G;K)=@e!vG2g-=n>u?OaV&f({@b1lRB>#)&7^TF5X7^@ReXDk&7`a7bTnby(4GgnC}?mN(B3Y<_OU z++^Tu$}uMddKB(D-@dd{c%r(fq2o}#oFja;<}!kL2g#pI&B?$zx))8CX*tOon5+a9#U+&YrUO@qs9Y0YeL_{-iR>iAuomgDa+hR~W+n z-*F|@4En?qnXwmHcXR4IyLzvW1zswEEsFwqX!yG61N=SrH@%eF09bTh@w1O=c(hGx>bW!u&7ByxpBn>c+ z{S%r=VQrvhAE|zLH1{5h7J!uF+8N$vReAl)YU7~#?8Bb(&#|Y}dcC)F!#>Q2?Anex z?*%z_7fZ^Upl;B6eL@Ql9GB8kM9@p943%@!^%84eB%ZV8-m(87+qt)@bac}-3wS+d zqp4ee&a?j3j2e!-`8L;}<*VK3l*jC_KyR~pLBc6rLG-xllmL7tRUD8io=^b0k{^Pj zzG&p+9^e0pGW43~7veiE(y2{eS=T-#vDX`>TjalDRE{i$L=->LSn-xDgI;q-)=L6R zxi*c3n~Fr*U^|ZD3WX{yeY4HBOI7fCIuHPWYHYJ@hm5uCKaub`_qHS9lMDe&>UQ^8 kK>U00^G~<2_(|J~4pM~v7m_F>2)sQbBQ28jR{8t&7YaP-_W%F@ literal 0 HcmV?d00001 From c6380b0f21ae1bc4f26d38b31834cca9adcd5d07 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 22 May 2019 09:09:34 -0400 Subject: [PATCH 138/504] huh. copyFileSync is newisH/ --- tests/packaging-suite/packaging.tests.js | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/packaging-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js index e321533e5..58dbb8312 100644 --- a/tests/packaging-suite/packaging.tests.js +++ b/tests/packaging-suite/packaging.tests.js @@ -16,8 +16,8 @@ describe('Integration test - Packaging', () => { }); it('packages the default aws template correctly in the zip', () => { - fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync(`${serverlessExec} package`, { cwd }); return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { @@ -26,8 +26,8 @@ describe('Integration test - Packaging', () => { }); it('packages the default aws template with an npm dep correctly in the zip', () => { - fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync('npm init --yes', { cwd }); execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -42,8 +42,8 @@ describe('Integration test - Packaging', () => { }); it('doesn\'t package a dev dependency in the zip', () => { - fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync('npm init --yes', { cwd }); execSync('npm i --save-dev lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -58,8 +58,8 @@ describe('Integration test - Packaging', () => { }); it('ignores package json files per ignore directive in the zip', () => { - fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync('npm init --yes', { cwd }); execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); execSync('npm i lodash', { cwd }); @@ -75,8 +75,8 @@ describe('Integration test - Packaging', () => { }); it('package artifact directive works', () => { - fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fs.copyFileSync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fse.copyFileSync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')) execSync('echo \'package: {artifact: artifact.zip}\' >> serverless.yml', { cwd }); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( @@ -112,8 +112,8 @@ describe('Integration test - Packaging', () => { }); it('creates the correct default function resource in cfn template', () => { - fs.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( cwd, '.serverless/cloudformation-template-update-stack.json'))); @@ -152,9 +152,9 @@ describe('Integration test - Packaging', () => { }); it('handles package individually with include/excludes correctly', () => { - fs.copyFileSync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')) - fs.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) - fs.copyFileSync(path.join(__dirname, 'handler2.js'), path.join(cwd, 'handler2.js')) + fse.copyFileSync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')) + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'handler2.js'), path.join(cwd, 'handler2.js')) execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( cwd, '.serverless/cloudformation-template-update-stack.json'))); From d135b225b9bb1beeb05a1eaea7a9e43baf771167 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 22 May 2019 09:38:31 -0400 Subject: [PATCH 139/504] lint and remove identical file --- tests/packaging-suite/handler.js | 2 +- tests/packaging-suite/handler2.js | 14 ----------- tests/packaging-suite/packaging.tests.js | 30 ++++++++++++------------ 3 files changed, 16 insertions(+), 30 deletions(-) delete mode 100644 tests/packaging-suite/handler2.js diff --git a/tests/packaging-suite/handler.js b/tests/packaging-suite/handler.js index 8d524e278..4e3448672 100644 --- a/tests/packaging-suite/handler.js +++ b/tests/packaging-suite/handler.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports.hello = async (event) => { +module.exports.hello = function (event) { return { statusCode: 200, body: JSON.stringify({ diff --git a/tests/packaging-suite/handler2.js b/tests/packaging-suite/handler2.js deleted file mode 100644 index 8d524e278..000000000 --- a/tests/packaging-suite/handler2.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -module.exports.hello = async (event) => { - return { - statusCode: 200, - body: JSON.stringify({ - message: 'Go Serverless v1.0! Your function executed successfully!', - input: event, - }, null, 2), - }; - - // Use this code if you don't use the http event with the LAMBDA-PROXY integration - // return { message: 'Go Serverless v1.0! Your function executed successfully!', event }; -}; diff --git a/tests/packaging-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js index 58dbb8312..e849febdb 100644 --- a/tests/packaging-suite/packaging.tests.js +++ b/tests/packaging-suite/packaging.tests.js @@ -16,8 +16,8 @@ describe('Integration test - Packaging', () => { }); it('packages the default aws template correctly in the zip', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync(`${serverlessExec} package`, { cwd }); return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { @@ -26,8 +26,8 @@ describe('Integration test - Packaging', () => { }); it('packages the default aws template with an npm dep correctly in the zip', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync('npm init --yes', { cwd }); execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -42,8 +42,8 @@ describe('Integration test - Packaging', () => { }); it('doesn\'t package a dev dependency in the zip', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync('npm init --yes', { cwd }); execSync('npm i --save-dev lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -58,8 +58,8 @@ describe('Integration test - Packaging', () => { }); it('ignores package json files per ignore directive in the zip', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync('npm init --yes', { cwd }); execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); execSync('npm i lodash', { cwd }); @@ -75,8 +75,8 @@ describe('Integration test - Packaging', () => { }); it('package artifact directive works', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fse.copyFileSync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copyFileSync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')); execSync('echo \'package: {artifact: artifact.zip}\' >> serverless.yml', { cwd }); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( @@ -112,8 +112,8 @@ describe('Integration test - Packaging', () => { }); it('creates the correct default function resource in cfn template', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')) - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) + fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( cwd, '.serverless/cloudformation-template-update-stack.json'))); @@ -152,9 +152,9 @@ describe('Integration test - Packaging', () => { }); it('handles package individually with include/excludes correctly', () => { - fse.copyFileSync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')) - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')) - fse.copyFileSync(path.join(__dirname, 'handler2.js'), path.join(cwd, 'handler2.js')) + fse.copyFileSync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')); + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler2.js')); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( cwd, '.serverless/cloudformation-template-update-stack.json'))); From b70deefc453a3722185dd9d83a1de180a01e18f7 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 10:54:21 +0200 Subject: [PATCH 140/504] Increase timeout, as timeout crash was observed --- lib/plugins/aws/deployFunction/index.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/deployFunction/index.test.js b/lib/plugins/aws/deployFunction/index.test.js index a423e32c0..015e930b0 100644 --- a/lib/plugins/aws/deployFunction/index.test.js +++ b/lib/plugins/aws/deployFunction/index.test.js @@ -20,7 +20,8 @@ describe('AwsDeployFunction', () => { let awsDeployFunction; let cryptoStub; - beforeEach(() => { + beforeEach(function () { + this.timeout(3000); serverless = new Serverless(); serverless.servicePath = true; serverless.service.environment = { From b62049f6ce39bed6184e75d4472785652a385306 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 11:33:22 +0200 Subject: [PATCH 141/504] Improve doc --- tests/mocha-reporter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 5b061e6ac..bdcee4ad2 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -24,8 +24,8 @@ const Runner = require('mocha/lib/runner'); // Reported to Mocha with: https://github.com/mochajs/mocha/issues/3920 Runner.immediately = process.nextTick; -// Speed up Bluebird's unhandled rejection notifications -// So they do not interfere with an async leaks detector (configured below) +// Speed up Bluebird's unhandled rejection notifications so it's on par with timing +// observable in native promises, and they do not interfere with an async leaks detector const BbPromise = require('bluebird'); /* eslint-disable no-underscore-dangle */ BbPromise.prototype._ensurePossibleRejectionHandled = function () { From f80b240c0e3b8fbfff7ba5ae8685fc2cec782588 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 12:00:52 +0200 Subject: [PATCH 142/504] Bring back p-limit based tests run It's to provide an option to run isolated tests in parallel --- bin/test-isolated | 10 +++++----- package-lock.json | 45 +++++++++++++++++++++++++++++++-------------- package.json | 1 + 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/bin/test-isolated b/bin/test-isolated index 59616d2fe..9e66e9266 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -14,6 +14,7 @@ process.on('unhandledRejection', err => { const globby = require('globby'); const spawn = require('child-process-ext/spawn'); const chalk = require('chalk'); +const pLimit = require('p-limit'); const patterns = process.argv.length <= 2 ? ['**/*.test.js'] : process.argv.slice(2); patterns.push('!node_modules/**'); @@ -58,9 +59,8 @@ globby(patterns).then(paths => { process.stderr.write(chalk.red.bold('No test files matched\n\n')); process.exit(1); } - return initialGitStatusDeferred.then(function self() { - const path = paths.shift(); - if (path) return run(path).then(self); - return null; - }); + + const limit = pLimit(1); + return initialGitStatusDeferred.then(() => + Promise.all(paths.map(path => limit(() => run(path))))); }); diff --git a/package-lock.json b/package-lock.json index 25875f368..82ddc1e7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3078,7 +3078,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -3099,12 +3100,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3119,17 +3122,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3246,7 +3252,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3258,6 +3265,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3272,6 +3280,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3279,12 +3288,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3303,6 +3314,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3383,7 +3395,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3395,6 +3408,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3480,7 +3494,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3516,6 +3531,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3535,6 +3551,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3578,12 +3595,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -6425,7 +6444,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -6448,8 +6466,7 @@ "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==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "package-json": { "version": "4.0.1", diff --git a/package.json b/package.json index 9bd10a40f..84d7bacf6 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "mocha": "^5.2.0", "mocha-lcov-reporter": "^1.2.0", "mock-require": "^1.3.0", + "p-limit": "^2.2.0", "parse-github-url": "^1.0.1", "proxyquire": "^1.7.10", "sinon": "^1.17.5", From 9ead688fff4398e2be77f602323d837cf437b64f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 12:15:16 +0200 Subject: [PATCH 143/504] Prettier --- bin/test-isolated | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/test-isolated b/bin/test-isolated index 9e66e9266..c4ce06ffb 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -62,5 +62,6 @@ globby(patterns).then(paths => { const limit = pLimit(1); return initialGitStatusDeferred.then(() => - Promise.all(paths.map(path => limit(() => run(path))))); + Promise.all(paths.map(path => limit(() => run(path)))) + ); }); From 98e69f068f64f110b17996b0a1d8adac6aa512a5 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 23 May 2019 12:16:50 +0200 Subject: [PATCH 144/504] Add engineStrict / remove version check in bin --- bin/serverless | 15 --------------- package.json | 1 + 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/bin/serverless b/bin/serverless index 04e2eb50e..b8cff829c 100755 --- a/bin/serverless +++ b/bin/serverless @@ -7,20 +7,6 @@ const BbPromise = require('bluebird'); const logError = require('../lib/classes/Error').logError; const uuid = require('uuid'); const initializeErrorReporter = require('../lib/utils/sentry').initializeErrorReporter; -const semver = require('semver'); -const packageJson = require('../package.json'); - -function checkNodeVersion(serverless) { - const requiredVersion = packageJson.engines.node; - const nodeVersion = process.version; - if (!semver.satisfies(nodeVersion, requiredVersion)) { - const msg = [ - `Serverless Framework requires Node.js version ${requiredVersion}. `, - `You're running version ${nodeVersion}.`, - ].join(''); - serverless.cli.log(msg); - } -} Error.stackTraceLimit = Infinity; @@ -54,7 +40,6 @@ const invocationId = uuid.v4(); serverless.invocationId = invocationId; return serverless.init() - .then(() => checkNodeVersion(serverless)) .then(() => serverless.run()) .then(() => process.exit(0)) .catch((err) => { diff --git a/package.json b/package.json index fcb3a464e..ad4f466d9 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "engines": { "node": ">=6.0" }, + "engineStrict": true, "preferGlobal": true, "homepage": "https://github.com/serverless/serverless#readme", "description": "Serverless Framework - Build web, mobile and IoT applications with serverless architectures using AWS Lambda, Azure Functions, Google CloudFunctions & more", From b467c0c41743f160c6233a21855a2588fb9f42c0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 12:17:04 +0200 Subject: [PATCH 145/504] Move function into context in which it's used --- bin/test-isolated | 48 ++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/bin/test-isolated b/bin/test-isolated index c4ce06ffb..2e1f3fecb 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -31,35 +31,37 @@ const resolveGitStatus = () => const initialGitStatusDeferred = resolveGitStatus(); -const run = path => { - const onFinally = () => - Promise.all([initialGitStatusDeferred, resolveGitStatus()]).then( - ([initialStatus, currentStatus]) => { - if (initialStatus !== currentStatus) { - process.stderr.write(chalk.red.bold(`${path} didn't clean created temporary files\n\n`)); - process.exit(1); - } - } - ); - - return spawn('./bin/test', [path], { - stdio: 'inherit', - env: { FORCE_COLOR: '1', PATH: process.env.PATH }, - }).then(onFinally, error => - onFinally(error).then(() => { - process.stderr.write(chalk.red.bold(`${path} failed\n\n`)); - if (error.code === 2) process.exit(2); - throw error; - }) - ); -}; - globby(patterns).then(paths => { if (!paths.length) { process.stderr.write(chalk.red.bold('No test files matched\n\n')); process.exit(1); } + const run = path => { + const onFinally = () => + Promise.all([initialGitStatusDeferred, resolveGitStatus()]).then( + ([initialStatus, currentStatus]) => { + if (initialStatus !== currentStatus) { + process.stderr.write( + chalk.red.bold(`${path} didn't clean created temporary files\n\n`) + ); + process.exit(1); + } + } + ); + + return spawn('./bin/test', [path], { + stdio: 'inherit', + env: { FORCE_COLOR: '1', PATH: process.env.PATH }, + }).then(onFinally, error => + onFinally(error).then(() => { + process.stderr.write(chalk.red.bold(`${path} failed\n\n`)); + if (error.code === 2) process.exit(2); + throw error; + }) + ); + }; + const limit = pLimit(1); return initialGitStatusDeferred.then(() => Promise.all(paths.map(path => limit(() => run(path)))) From 1f70100be27dd3579c49072cf9dd32e904570b3f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 12:19:02 +0200 Subject: [PATCH 146/504] Generalize initial setup --- bin/test-isolated | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/test-isolated b/bin/test-isolated index 2e1f3fecb..1dfc635ff 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -31,6 +31,8 @@ const resolveGitStatus = () => const initialGitStatusDeferred = resolveGitStatus(); +const initialSetupDeferred = initialGitStatusDeferred; + globby(patterns).then(paths => { if (!paths.length) { process.stderr.write(chalk.red.bold('No test files matched\n\n')); @@ -63,7 +65,5 @@ globby(patterns).then(paths => { }; const limit = pLimit(1); - return initialGitStatusDeferred.then(() => - Promise.all(paths.map(path => limit(() => run(path)))) - ); + return initialSetupDeferred.then(() => Promise.all(paths.map(path => limit(() => run(path))))); }); From 5e14363345f3d57f2d5a9459397933d8b926aac7 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 12:48:13 +0200 Subject: [PATCH 147/504] Bring back support for multi process run --- .eslintrc.js | 1 + bin/test-isolated | 56 ++++++++++++++++++++++---------- package-lock.json | 81 ++++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 4 files changed, 122 insertions(+), 17 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 778635bfb..c6bf7968e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,6 +4,7 @@ module.exports = { "plugins": [], "rules": { "func-names": "off", + "global-require": "off", // Interferes with optional and eventual circular references // doesn't work in node v4 :( "strict": "off", diff --git a/bin/test-isolated b/bin/test-isolated index 1dfc635ff..d6b36239b 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -39,31 +39,55 @@ globby(patterns).then(paths => { process.exit(1); } + const processesCount = 1; + const isMultiProcessRun = processesCount > 1; + + const { ongoing, cliFooter } = (() => { + if (!isMultiProcessRun) return {}; + return { ongoing: new Set(), cliFooter: require('cli-progress-footer')() }; + })(); + const run = path => { - const onFinally = () => - Promise.all([initialGitStatusDeferred, resolveGitStatus()]).then( - ([initialStatus, currentStatus]) => { - if (initialStatus !== currentStatus) { - process.stderr.write( - chalk.red.bold(`${path} didn't clean created temporary files\n\n`) - ); - process.exit(1); + if (isMultiProcessRun) { + ongoing.add(path); + cliFooter.updateProgress(Array.from(ongoing)); + } + + const onFinally = (() => { + if (isMultiProcessRun) { + return ({ stdoutBuffer, stderrBuffer }) => { + ongoing.delete(path); + cliFooter.updateProgress(Array.from(ongoing)); + process.stdout.write(stdoutBuffer); + process.stderr.write(stderrBuffer); + }; + } + return () => + Promise.all([initialGitStatusDeferred, resolveGitStatus()]).then( + ([initialStatus, currentStatus]) => { + if (initialStatus !== currentStatus) { + process.stderr.write( + chalk.red.bold(`${path} didn't clean created temporary files\n\n`) + ); + process.exit(1); + } } - } - ); + ); + })(); return spawn('./bin/test', [path], { - stdio: 'inherit', + stdio: isMultiProcessRun ? null : 'inherit', env: { FORCE_COLOR: '1', PATH: process.env.PATH }, - }).then(onFinally, error => - onFinally(error).then(() => { + }).then(onFinally, error => { + if (isMultiProcessRun) ongoing.clear(); + return onFinally(error).then(() => { process.stderr.write(chalk.red.bold(`${path} failed\n\n`)); if (error.code === 2) process.exit(2); throw error; - }) - ); + }); + }); }; - const limit = pLimit(1); + const limit = pLimit(processesCount); return initialSetupDeferred.then(() => Promise.all(paths.map(path => limit(() => run(path))))); }); diff --git a/package-lock.json b/package-lock.json index 82ddc1e7a..21c4439a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1512,6 +1512,20 @@ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, + "cli-color": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", + "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", + "dev": true, + "requires": { + "ansi-regex": "^2.1.1", + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.5" + } + }, "cli-cursor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", @@ -1520,6 +1534,19 @@ "restore-cursor": "^1.0.1" } }, + "cli-progress-footer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cli-progress-footer/-/cli-progress-footer-1.1.1.tgz", + "integrity": "sha512-J0uW2u06pWI0tMKCbcCiMOZd8TbWj4EpuYgPo4Jiwih/FfGbd4dbLcJieO0Ior1pY1HBrnmCuHFk6GB9azE4pg==", + "dev": true, + "requires": { + "cli-color": "^1.4", + "d": "1", + "es5-ext": "^0.10.47", + "process-utils": "^2.0.1", + "timers-ext": "^0.1.7" + } + }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -5615,6 +5642,15 @@ "yallist": "^2.1.2" } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "requires": { + "es5-ext": "~0.10.2" + } + }, "lsmod": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", @@ -5799,6 +5835,22 @@ "p-is-promise": "^2.0.0" } }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -6444,6 +6496,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -6466,7 +6519,8 @@ "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==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "package-json": { "version": "4.0.1", @@ -6677,6 +6731,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, + "process-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/process-utils/-/process-utils-2.1.0.tgz", + "integrity": "sha512-FjkSXOw9XJ8qadsgOQHuwyhvqUq2D9JkEfqExrYe+ik2OHJw0q/hTOgrxybg0lgw3K/HTrBQ3s9nM7yI3Ukk2g==", + "dev": true, + "requires": { + "type": "^1.0.1" + } + }, "progress": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", @@ -8104,6 +8167,16 @@ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "tmp": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", @@ -8237,6 +8310,12 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, + "type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", + "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==", + "dev": true + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/package.json b/package.json index 84d7bacf6..0cbddfaea 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "chai": "^3.5.0", "chai-as-promised": "^6.0.0", "child-process-ext": "^2.0.0", + "cli-progress-footer": "^1.1.1", "coveralls": "^3.0.3", "eslint": "^3.3.1", "eslint-config-airbnb": "^10.0.1", From 6fb7cb3595c0fb821dd22d8458b2075836e08d6a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 12:54:30 +0200 Subject: [PATCH 148/504] Allow parallel runs without fs cleanup check It allows to pursue faster testing of async leaks --- bin/test-isolated | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/bin/test-isolated b/bin/test-isolated index d6b36239b..afa95f560 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -16,6 +16,13 @@ const spawn = require('child-process-ext/spawn'); const chalk = require('chalk'); const pLimit = require('p-limit'); +const shouldApplyFsCleanupCheck = (() => { + const argIndex = process.argv.findIndex(arg => arg === '--skip-fs-cleanup-check'); + if (argIndex === -1) return true; + process.argv.splice(argIndex, 1); + return false; +})(); + const patterns = process.argv.length <= 2 ? ['**/*.test.js'] : process.argv.slice(2); patterns.push('!node_modules/**'); @@ -29,9 +36,11 @@ const resolveGitStatus = () => } ); -const initialGitStatusDeferred = resolveGitStatus(); +const initialGitStatusDeferred = shouldApplyFsCleanupCheck ? resolveGitStatus() : null; -const initialSetupDeferred = initialGitStatusDeferred; +const initialSetupDeferred = shouldApplyFsCleanupCheck + ? initialGitStatusDeferred + : Promise.resolve(); globby(patterns).then(paths => { if (!paths.length) { @@ -39,7 +48,10 @@ globby(patterns).then(paths => { process.exit(1); } - const processesCount = 1; + const processesCount = shouldApplyFsCleanupCheck + ? 1 + : Math.max(require('os').cpus().length - 1, 1); + const isMultiProcessRun = processesCount > 1; const { ongoing, cliFooter } = (() => { @@ -62,6 +74,7 @@ globby(patterns).then(paths => { process.stderr.write(stderrBuffer); }; } + if (!shouldApplyFsCleanupCheck) return () => {}; return () => Promise.all([initialGitStatusDeferred, resolveGitStatus()]).then( ([initialStatus, currentStatus]) => { From 6e8ea2f8e382722e9324ecd4a3b63d3baea0607d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 13:06:54 +0200 Subject: [PATCH 149/504] Ensure no false positives on async leaks check --- tests/mocha-reporter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index bdcee4ad2..31ab2df01 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -43,7 +43,9 @@ module.exports = class ServerlessSpec extends Spec { // If tests end with any orphaned async call then this callback will be invoked // It's a signal there's some promise chain (or in general async flow) miconfiguration throw new Error('Test ended with unfinished async jobs'); - }).unref() + // Timeout '2' to ensure no false positives, with '1' there are observable rare scenarios + // of possibly a garbage collector delaying process exit being picked up + }, 2).unref() ); } }; From e857a14380376b5358b4cdfb7c8201c34aba479e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 13:08:57 +0200 Subject: [PATCH 150/504] Ensure consistent return --- bin/test-isolated | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/test-isolated b/bin/test-isolated index afa95f560..b426383d4 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -72,9 +72,10 @@ globby(patterns).then(paths => { cliFooter.updateProgress(Array.from(ongoing)); process.stdout.write(stdoutBuffer); process.stderr.write(stderrBuffer); + return Promise.resolve(); }; } - if (!shouldApplyFsCleanupCheck) return () => {}; + if (!shouldApplyFsCleanupCheck) return () => Promise.resolve(); return () => Promise.all([initialGitStatusDeferred, resolveGitStatus()]).then( ([initialStatus, currentStatus]) => { From 24d1f49e7af52dd50fe39d73990d1cc26ed83f66 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 13:33:46 +0200 Subject: [PATCH 151/504] Ensure to return promise --- lib/classes/CLI.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/classes/CLI.test.js b/lib/classes/CLI.test.js index 7eae2c0a2..06bc86e33 100644 --- a/lib/classes/CLI.test.js +++ b/lib/classes/CLI.test.js @@ -621,13 +621,13 @@ describe('CLI', () => { process.chdir(tmpDir); serverless = new Serverless(); - serverless.init(); + return serverless.init().then(() => { + // Cannot rely on shebang in severless.js to invoke script using NodeJS on Windows. + const execPrefix = os.platform() === 'win32' ? 'node ' : ''; - // Cannot rely on shebang in severless.js to invoke script using NodeJS on Windows. - const execPrefix = os.platform() === 'win32' ? 'node ' : ''; - - that.serverlessExec = execPrefix + path.join(serverless.config.serverlessPath, - '..', 'bin', 'serverless'); + that.serverlessExec = execPrefix + path.join(serverless.config.serverlessPath, + '..', 'bin', 'serverless'); + }); }); after(() => { From 4ed4d723e7e4838104e06a3a74829691f606248e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 14:03:55 +0200 Subject: [PATCH 152/504] Improve async leaks detector So it doesn't produce false positives in Node.js v6 --- tests/mocha-reporter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 31ab2df01..443e5d73f 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -43,9 +43,10 @@ module.exports = class ServerlessSpec extends Spec { // If tests end with any orphaned async call then this callback will be invoked // It's a signal there's some promise chain (or in general async flow) miconfiguration throw new Error('Test ended with unfinished async jobs'); - // Timeout '2' to ensure no false positives, with '1' there are observable rare scenarios + // Timeout '5' to ensure no false positives, with '1' there are observable rare scenarios // of possibly a garbage collector delaying process exit being picked up - }, 2).unref() + // On Node.js v8+ '2' seems safe, while v6 needs '5' + }, 5).unref() ); } }; From 8450e7caca4d380d9fda05e67229750b2e9f008a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 14:06:19 +0200 Subject: [PATCH 153/504] Simplify --- bin/test-isolated | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/test-isolated b/bin/test-isolated index b426383d4..4ce9b8ca1 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -17,7 +17,7 @@ const chalk = require('chalk'); const pLimit = require('p-limit'); const shouldApplyFsCleanupCheck = (() => { - const argIndex = process.argv.findIndex(arg => arg === '--skip-fs-cleanup-check'); + const argIndex = process.argv.indexOf('--skip-fs-cleanup-check'); if (argIndex === -1) return true; process.argv.splice(argIndex, 1); return false; From 9fc9d20efcf8d839545805ba87aa95117289bb8e Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 23 May 2019 08:39:36 -0400 Subject: [PATCH 154/504] sfe bump --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 25efeb438..e32acea0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -465,9 +465,9 @@ } }, "@serverless/enterprise-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.0.tgz", - "integrity": "sha512-m66Wl7kN0aAjM1QFzNPtRCr9LOG33nXWuLrWRXxT93DJE5UEbfq0f/OQ+sk8sCf0m07IdXHzehrvK1dRb+NRlw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.1.tgz", + "integrity": "sha512-iMrS7iuwNY/nrUnuKEyOUgfJ7Gio22orqve24l+k1aHphJxmOLH+xVjYeXxtMKiNVq6SEmJKwHzi2MCiMKsdkg==", "requires": { "@babel/polyfill": "^7.2.5", "@serverless/event-mocks": "^1.1.1", @@ -1892,9 +1892,9 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.7.tgz", - "integrity": "sha512-ydmsQxDVH7lDpYoirXft8S83ddKKfdsrsmWhtyj7xafXVLbLhKOyfD7kAi2ueFfeP7m9rNavjW59O3hLLzzC5A==" + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.8.tgz", + "integrity": "sha512-RWlREFU74TEkdXzyl1bka66O3kYp8jeTXrvJZDzVVMH8AiHUSOFpL1yfhQJ+wHocAm1m+4971W1PPzfLuCv1vg==" }, "core-util-is": { "version": "1.0.2", diff --git a/package.json b/package.json index d431db6ca..7e593e5d0 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "strip-ansi": "^5.2.0" }, "dependencies": { - "@serverless/enterprise-plugin": "^1.0.0", + "@serverless/enterprise-plugin": "^1.0.1", "archiver": "^1.1.0", "async": "^1.5.2", "aws-sdk": "^2.430.0", From 82065121ff56f034e27bc5a242167ac979d78f88 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 23 May 2019 09:08:30 -0400 Subject: [PATCH 155/504] lint --- lib/classes/PluginManager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 1a7d55280..76b8f37f8 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -9,7 +9,6 @@ const updateNotifier = require('update-notifier'); const writeFile = require('../utils/fs/writeFile'); const getCacheFilePath = require('../utils/getCacheFilePath'); const serverlessConfigFileUtils = require('../utils/getServerlessConfigFile'); -const config = require('../utils/config'); const getCommandSuggestion = require('../utils/getCommandSuggestion'); /** @@ -159,7 +158,8 @@ class PluginManager { module.paths.unshift(path.join(__dirname, '../../node_modules')); this.loadPlugins(['@serverless/enterprise-plugin']); // eslint-disable-next-line global-require - const sfePkgJson = require(`@serverless/enterprise-plugin/package.json`); + const sfePkgJson = require('@serverless/enterprise-plugin/package.json'); + if (this.serverless.enterpriseEnabled) { const updates = updateNotifier({ pkg: sfePkgJson, interval: 1 }); if (updates.update) { From 18bc50e406cb256ee908539910b260668a97a1d4 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 15:30:56 +0200 Subject: [PATCH 156/504] Typo --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index c6bf7968e..9eecfdb9f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,7 +4,7 @@ module.exports = { "plugins": [], "rules": { "func-names": "off", - "global-require": "off", // Interferes with optional and eventual circular references + "global-require": "off", // Interfers with optional and eventual circular references // doesn't work in node v4 :( "strict": "off", From be9729904ec67697b71fbba802ec945d90dcc2ea Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 23 May 2019 15:34:33 +0200 Subject: [PATCH 157/504] Improve doc --- tests/mocha-reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 443e5d73f..3e56b36da 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -25,7 +25,7 @@ const Runner = require('mocha/lib/runner'); Runner.immediately = process.nextTick; // Speed up Bluebird's unhandled rejection notifications so it's on par with timing -// observable in native promises, and they do not interfere with an async leaks detector +// we observe with native promises, and so they do not interfere with an async leaks detector const BbPromise = require('bluebird'); /* eslint-disable no-underscore-dangle */ BbPromise.prototype._ensurePossibleRejectionHandled = function () { From 6ab06eb059e45b666c0c237f8963d9f55bc69e98 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 23 May 2019 10:07:52 -0400 Subject: [PATCH 158/504] don't upgrade npm on windows --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e04715b14..4b2e19e7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ matrix: sudo: false install: - travis_retry npm install - - travis_retry npm install -g npm + - [ $TRAVIS_OS_NAME = windows ] || travis_retry npm install -g npm # ensure that Python 2 and Ruby are installed - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python ruby; else choco install python2 ruby; fi - if [ $TRAVIS_OS_NAME = windows ]; then export PATH="/c/Python27:/c/Python27/Scripts:$PATH"; fi From fc5bb754041171c6a278c570b42a8e3855c0dc89 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 23 May 2019 10:34:47 -0400 Subject: [PATCH 159/504] wtf travis From 9df621eaf6a9a9ac1a0c5e73bd01c01219999ba3 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 23 May 2019 11:18:04 -0400 Subject: [PATCH 160/504] oh. stupid yaml broke travis parsing of config --- .travis.yml | 2 +- package-lock.json | 41 ++++++++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4b2e19e7c..c06fcbc68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ matrix: sudo: false install: - travis_retry npm install - - [ $TRAVIS_OS_NAME = windows ] || travis_retry npm install -g npm + - "[ $TRAVIS_OS_NAME = windows ] || travis_retry npm install -g npm" # ensure that Python 2 and Ruby are installed - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python ruby; else choco install python2 ruby; fi - if [ $TRAVIS_OS_NAME = windows ]; then export PATH="/c/Python27:/c/Python27/Scripts:$PATH"; fi diff --git a/package-lock.json b/package-lock.json index a329e0ca4..eef3cf4bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3468,7 +3468,8 @@ "version": "2.1.1", "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -3492,13 +3493,15 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3515,19 +3518,22 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3658,7 +3664,8 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3672,6 +3679,7 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3688,6 +3696,7 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3696,13 +3705,15 @@ "version": "0.0.8", "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3723,6 +3734,7 @@ "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3811,7 +3823,8 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3825,6 +3838,7 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3920,7 +3934,8 @@ "version": "5.1.2", "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3962,6 +3977,7 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3983,6 +3999,7 @@ "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4031,13 +4048,15 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true + "dev": true, + "optional": true } } }, From 43eb30029fe91406f2fffd4dbfb4051b29862a11 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 23 May 2019 12:01:42 -0400 Subject: [PATCH 161/504] upgrade npm before using it --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c06fcbc68..8b3d278e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,8 +36,8 @@ matrix: env: SLS_IGNORE_WARNING=* sudo: false install: - - travis_retry npm install - "[ $TRAVIS_OS_NAME = windows ] || travis_retry npm install -g npm" + - travis_retry npm install # ensure that Python 2 and Ruby are installed - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python ruby; else choco install python2 ruby; fi - if [ $TRAVIS_OS_NAME = windows ]; then export PATH="/c/Python27:/c/Python27/Scripts:$PATH"; fi From c675900b9b63ed99acdd40d7800f96485addfec8 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 23 May 2019 16:52:24 -0400 Subject: [PATCH 162/504] version bumps --- package-lock.json | 39 ++++++++------------------------------- package.json | 4 ++-- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index eef3cf4bc..d7ad55edc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.43.0", + "version": "1.44.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -149,15 +149,6 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/polyfill": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.4.4.tgz", - "integrity": "sha512-WlthFLfhQQhh+A2Gn5NSFl0Huxz36x86Jn+E9OW7ibK8edKPq+KLy4apM1yDpQ8kJOVi1OVjpP4vSDLdrI04dg==", - "requires": { - "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.2" - } - }, "@babel/template": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", @@ -475,11 +466,10 @@ } }, "@serverless/enterprise-plugin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.1.tgz", - "integrity": "sha512-iMrS7iuwNY/nrUnuKEyOUgfJ7Gio22orqve24l+k1aHphJxmOLH+xVjYeXxtMKiNVq6SEmJKwHzi2MCiMKsdkg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.2.tgz", + "integrity": "sha512-NcMxlOilSq/r4KdeUSV2Jeol4NI8IOHnZfZaovpACfctVSNp7nd3mVbrCs42ot8lcKyjgSeS1sEXF+2nqg4dtw==", "requires": { - "@babel/polyfill": "^7.2.5", "@serverless/event-mocks": "^1.1.1", "@serverless/platform-sdk": "^1.0.0", "chalk": "^2.4.2", @@ -492,8 +482,8 @@ "moment": "^2.24.0", "node-dir": "^0.1.17", "node-fetch": "^2.3.0", + "regenerator-runtime": "^0.13.1", "semver": "^5.6.0", - "serialize-error": "^4.1.0", "yamljs": "^0.3.0", "zlib": "^1.0.5" }, @@ -628,9 +618,9 @@ } }, "@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.132", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.132.tgz", + "integrity": "sha512-RNUU1rrh85NgUJcjOOr96YXr+RHwInGbaQCZmlitqOaCKXffj8bh+Zxwuq5rjDy5OgzFldDVoqk4pyLEDiwxIw==" }, "@types/stack-utils": { "version": "1.0.1", @@ -7860,14 +7850,6 @@ } } }, - "serialize-error": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-4.1.0.tgz", - "integrity": "sha512-5j9GgyGsP9vV9Uj1S0lDCvlsd+gc2LEPVK7HHHte7IyPwOD4lVQFeaX143gx3U5AnoCi+wbcb3mvaxVysjpxEw==", - "requires": { - "type-fest": "^0.3.0" - } - }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -8898,11 +8880,6 @@ "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", "dev": true }, - "type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==" - }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/package.json b/package.json index 9edb02558..a0ee45369 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.43.0", + "version": "1.44.0", "engines": { "node": ">=6.0" }, @@ -96,7 +96,7 @@ "strip-ansi": "^5.2.0" }, "dependencies": { - "@serverless/enterprise-plugin": "^1.0.1", + "@serverless/enterprise-plugin": "^1.0.2", "archiver": "^1.1.0", "async": "^1.5.2", "aws-sdk": "^2.430.0", From 119eb7f3b2321efbaa7d114f54ccbb524050b447 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 10:15:32 +0200 Subject: [PATCH 163/504] Upgrade Mocha to v6 --- package-lock.json | 184 +++++++++++++++++++++++++++++++++------- package.json | 2 +- tests/mocha-reporter.js | 5 +- 3 files changed, 159 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 21c4439a4..205700845 100644 --- a/package-lock.json +++ b/package-lock.json @@ -679,6 +679,12 @@ } } }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, "ansi-escapes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", @@ -2098,6 +2104,12 @@ "safer-buffer": "^2.1.0" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "encoding": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", @@ -3008,6 +3020,23 @@ "pinkie-promise": "^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": "1.3.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", @@ -3936,9 +3965,9 @@ } }, "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, "hosted-git-info": { @@ -5613,6 +5642,15 @@ "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", "dev": true }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, "lolex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", @@ -5954,43 +5992,61 @@ } }, "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", + "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", "dev": true, "requires": { + "ansi-colors": "3.2.3", "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", + "debug": "3.2.6", "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.2", + "find-up": "3.0.0", + "glob": "7.1.3", "growl": "1.10.5", - "he": "1.1.1", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "5.4.0" + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" }, "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "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": { - "ms": "2.0.0" + "locate-path": "^3.0.0" } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -6001,20 +6057,60 @@ "path-is-absolute": "^1.0.0" } }, - "ms": { + "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "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", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { "has-flag": "^3.0.0" } + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -6108,6 +6204,16 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", @@ -8671,6 +8777,15 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "widest-line": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", @@ -8900,6 +9015,17 @@ } } }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + } + }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index d46ba67b9..a0d588726 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "markdown-link": "^0.1.1", "markdown-magic": "^0.1.25", "markdown-table": "^1.1.1", - "mocha": "^5.2.0", + "mocha": "^6.1.4", "mocha-lcov-reporter": "^1.2.0", "mock-require": "^1.3.0", "p-limit": "^2.2.0", diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 3e56b36da..e3fd12dba 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -6,8 +6,9 @@ process.on('unhandledRejection', err => { throw err; }); -// Workaround Mocha v5 issue: https://github.com/mochajs/mocha/issues/3226 -// Fixed in v6, but not really: https://github.com/mochajs/mocha/issues/3917 +// If there's an uncaught exception after rest runner wraps up +// Mocha reports it with success exit code: https://github.com/mochajs/mocha/issues/3917 +// Workaround that (otherwise we may end with green CI for failed builds): process.on('uncaughtException', err => { if (!process.listenerCount('exit')) return; // Mocha done it's report, and registered process.exit listener which silences any further From 7c1e5170d8898b148554314b16a9d1a2c43eea1f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 10:45:48 +0200 Subject: [PATCH 164/504] Upgrade to use nyc over istanbul --- .gitignore | 1 + package-lock.json | 523 +++++++++++++++++++++++++++++++++++----------- package.json | 4 +- 3 files changed, 403 insertions(+), 125 deletions(-) diff --git a/.gitignore b/.gitignore index 0d5cd2ea9..79c552a12 100755 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ lib-cov # Coverage directory used by tools like istanbul coverage +/.nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt diff --git a/package-lock.json b/package-lock.json index 205700845..e34a3e796 100644 --- a/package-lock.json +++ b/package-lock.json @@ -534,12 +534,6 @@ "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", "dev": true }, - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, "acorn": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", @@ -630,13 +624,6 @@ "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", "dev": true }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true, - "optional": true - }, "ansi": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", @@ -728,6 +715,15 @@ "normalize-path": "^2.1.1" } }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, "archiver": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", @@ -767,6 +763,12 @@ "readable-stream": "^2.0.0" } }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, "are-we-there-yet": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", @@ -1307,6 +1309,36 @@ "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.2.0.tgz", "integrity": "sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ==" }, + "caching-transform": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "dev": true, + "requires": { + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, "caller-id": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-id/-/caller-id-0.1.0.tgz", @@ -1657,6 +1689,12 @@ "graceful-readlink": ">= 1.0.0" } }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -1760,6 +1798,37 @@ "request": "^2.86.0" } }, + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, "crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -1997,6 +2066,15 @@ "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==", "dev": true }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2171,6 +2249,12 @@ "next-tick": "^1.0.0" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", @@ -2249,43 +2333,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, "escope": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", @@ -3010,6 +3057,53 @@ } } }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "dependencies": { + "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" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } + } + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -3054,6 +3148,28 @@ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, + "foreground-child": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + } + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -3964,6 +4080,15 @@ } } }, + "hasha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "dev": true, + "requires": { + "is-stream": "^1.0.1" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -4459,76 +4584,21 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, "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-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, "istanbul-lib-instrument": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", @@ -5610,6 +5680,12 @@ "integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=", "dev": true }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, "lodash.pad": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", @@ -5895,6 +5971,23 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "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 + } + } + }, "merge-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", @@ -6192,6 +6285,12 @@ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", + "dev": true + }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", @@ -6248,15 +6347,6 @@ "which": "^1.3.0" } }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -6322,6 +6412,158 @@ "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==", "dev": true }, + "nyc": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.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-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "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 + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "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==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" + } + }, + "yargs-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -6628,6 +6870,18 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "package-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, "package-json": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", @@ -7100,6 +7354,15 @@ "rc": "^1.0.1" } }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, "remarkable": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.1.tgz", @@ -7738,6 +8001,20 @@ "os-shim": "^0.1.2" } }, + "spawn-wrap": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.2.tgz", + "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", diff --git a/package.json b/package.json index a0d588726..e8dc1f544 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "scripts": { "test-bare": "node bin/test", "test-isolated": "node bin/test-isolated", - "test": "istanbul cover -x \"**/*.test.js\" bin/test", + "test": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm run test-bare", "lint": "eslint . --cache", "docs": "node scripts/generate-readme.js", "integration-test-cleanup": "node scripts/integration-test-cleanup.js", @@ -81,7 +81,6 @@ "eslint-plugin-import": "^1.13.0", "eslint-plugin-jsx-a11y": "^2.1.0", "eslint-plugin-react": "^6.1.1", - "istanbul": "^0.4.4", "jest-cli": "^24.5.0", "markdown-link": "^0.1.1", "markdown-magic": "^0.1.25", @@ -89,6 +88,7 @@ "mocha": "^6.1.4", "mocha-lcov-reporter": "^1.2.0", "mock-require": "^1.3.0", + "nyc": "^14.1.1", "p-limit": "^2.2.0", "parse-github-url": "^1.0.1", "proxyquire": "^1.7.10", From b07d063f579e49b65fe108c8e3e04c418377c7e9 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 10:51:44 +0200 Subject: [PATCH 165/504] Reconfigure mocha So individual tests can be run as easy as: npx mocha path-to-test-file.js --- bin/test | 11 ----------- bin/test-isolated | 2 +- package.json | 6 +++++- 3 files changed, 6 insertions(+), 13 deletions(-) delete mode 100755 bin/test diff --git a/bin/test b/bin/test deleted file mode 100755 index 0f9173b14..000000000 --- a/bin/test +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -if (process.argv.length <= 2) process.argv.push('!(node_modules)/**/*.test.js'); -if (!process.argv.includes('-R')) process.argv.splice(2, 0, '-R', 'tests/mocha-reporter'); -if (!process.argv.includes('--require=sinon-bluebird')) { - process.argv.splice(2, 0, '--require=sinon-bluebird'); -} - -require('mocha/bin/_mocha'); diff --git a/bin/test-isolated b/bin/test-isolated index 4ce9b8ca1..b3381683b 100755 --- a/bin/test-isolated +++ b/bin/test-isolated @@ -89,7 +89,7 @@ globby(patterns).then(paths => { ); })(); - return spawn('./bin/test', [path], { + return spawn('npx', ['mocha', path], { stdio: isMultiProcessRun ? null : 'inherit', env: { FORCE_COLOR: '1', PATH: process.env.PATH }, }).then(onFinally, error => { diff --git a/package.json b/package.json index e8dc1f544..7a3c339e8 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "sls": "./bin/serverless" }, "scripts": { - "test-bare": "node bin/test", + "test-bare": "mocha '!(node_modules)/**/*.test.js'", "test-isolated": "node bin/test-isolated", "test": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm run test-bare", "lint": "eslint . --cache", @@ -62,6 +62,10 @@ "complex-integration-test": "jest --maxWorkers=5 integration", "postinstall": "node ./scripts/postinstall.js" }, + "mocha": { + "require": "sinon-bluebird", + "-R": "tests/mocha-reporter" + }, "jest": { "testRegex": "(\\.|/)(tests)\\.js$", "testEnvironment": "node", From b9b5e3d4e6fbc5911f4882319c9cbcfaf5e3a2b9 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 10:53:02 +0200 Subject: [PATCH 166/504] Move test-isolated script to scripts /bin should serve only globally exposed binaries --- package.json | 2 +- bin/test-isolated => scripts/test-isolated.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename bin/test-isolated => scripts/test-isolated.js (100%) diff --git a/package.json b/package.json index 7a3c339e8..fc7dcd080 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ }, "scripts": { "test-bare": "mocha '!(node_modules)/**/*.test.js'", - "test-isolated": "node bin/test-isolated", + "test-isolated": "node scripts/test-isolated.js", "test": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm run test-bare", "lint": "eslint . --cache", "docs": "node scripts/generate-readme.js", diff --git a/bin/test-isolated b/scripts/test-isolated.js similarity index 100% rename from bin/test-isolated rename to scripts/test-isolated.js From fb5eb18a36cf1ea259067b7e352b5d39c1072ed0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 12:19:48 +0200 Subject: [PATCH 167/504] Ensure test command works on WIndows --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fc7dcd080..5dde6555b 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "sls": "./bin/serverless" }, "scripts": { - "test-bare": "mocha '!(node_modules)/**/*.test.js'", + "test-bare": "mocha \"!(node_modules)/**/*.test.js\"", "test-isolated": "node scripts/test-isolated.js", "test": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm run test-bare", "lint": "eslint . --cache", From 47ec16930786327a8b2ffd1ee17b0eb4182c4423 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 12:40:53 +0200 Subject: [PATCH 168/504] No need for git history in CI --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0e7d73b41..abc4c79ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: node_js +git: + depth: 1 # no need for commits history matrix: exclude: - os: linux From 71bbbff3152af3af08dd451d5076a2e3dd54b783 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 12:53:56 +0200 Subject: [PATCH 169/504] Remove ineffective configuration --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index abc4c79ef..248d22396 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,6 @@ language: node_js git: depth: 1 # no need for commits history matrix: - exclude: - - os: linux - node_js: "6.2" - env: - - secure: ruPmxtjLY9jX/TQQe1d8vxwL0KsjsTzJK/EL6rZM954JJc+3c5gshxB1Ca9+WRwIGdMjCZgKo4dQcn2DrAap7KoDz+GRCi40kc5AOr2ZLmjxJeCJls+sQ713QJ01GgIA1X2zmz/bDSw5lan8ODR3eYI0fIn37ZV4yxNJ5PObBLCMQW6nnOdMXxcLLi2iJRqu3k0gb85wsUNOB0AF2cBrbxoYmGsrGjGuPjIXPeEdnVJ0jzBY+jDrBraIHEBN9tecIyG77ZgEhlPopqTrj39dEPMwAow+derEVWugPRF3Wps6z+Yx0qtxETptWuOgn0V9GpfZF9vJlHifQUuuJEGtttVCYjZCUOIoIRwWnX+/SygjlgLJ9PpVa6XyZfnqygJXnv7wouGGkiyZEDbayu65qP3Ls/Dj+N2nEtamOPf3dGBeaX8KKF61h80bH7o9JL/t6yxyvm+yDRTkYd9SNk3U7dFXoBcCcjMNNUkKBKtY9G3EZlE0ZV0D7jD33Na4yX8mKBHkYLKXCAn+rh6DIwLNkV8IOtAYD77qan6v4qH9fpPMX4UkLu9SRM1r5RfsZU0gX7sCP9OKFKkAx5BWUqjg+D6Xa0EACtO8bPDHA548hWU2vYx7ghuVJfVNOUlAl97OXclx2SZwXJ46rHaHgrH2I3gi3qyHMib3iUV7SLdRQnY= - - secure: p9ka7mRzo/ecjnDh/dz19g0iVfQdvsGRAtg/4ONeiq75I2+oqHzu+VxUBA1Z2IQbpCEAMo21CarR3fg2I6MFUeazL0nEpqr1PoOAI8nPFeQlg/h+jLXsrPAkDcu2/b8ij7J5MXeLdZXUVqiPcGkr68x/tCMk/rwxftljQhvXPQfc7Lxm/m61ELnC7rLJulhxWZLNIq1hwQ9nh0GMKb4hm0KmPn8ksccVL+wyDikkgXCuvIujhTBjhNivAe4mG8mqnNsW1Ugh++SUe1ld27TtbH7wQj02SSG4Bxfwc3Gz0GFdAL1GyOkWI2WvrqP4a0KYTRUo+pUr9E+HZ1SNlxU5t6QWtmDiy5MKkxzgeTXmkKiJ98vMlF0ja5bpp46NjYarzDafqE8FozHzLtr+uAtqr6gRAgU1rWaG9BE3gKeW/f4B/2MfPI26b7SxuU1MwGVy0I76hb0Ujbgb3X8G4TYTGb6Nhoewc+RZExPwVhfrN8cJjo45masndv5tQAZMSRX/JUFjs4h/QMXNsn0A53GXgf6eIzUu15m+W8TJYFiKQeq9nMejzEE4sWMO3BFnkxueBGVCEurOc1GgdEnKxeqlp+psxHcJRlNCxC1HkUVOzfpkCr/Jy42vM8jQomAMv41Z9zWjOagVphWT25xNeSILfRt4yPku5wfW4CAxp+fl4KQ= include: # Linux tests - os: linux From dce576c0993d5ff4ac71447e978edc08eccdaa50 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 13:46:37 +0200 Subject: [PATCH 170/504] Readability --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 248d22396..765752fe7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: node_js + git: depth: 1 # no need for commits history + matrix: include: # Linux tests @@ -30,20 +32,25 @@ matrix: - os: windows node_js: "10.7" env: SLS_IGNORE_WARNING=* + sudo: false + install: - travis_retry npm install # ensure that Python 2 and Ruby are installed - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python ruby; else choco install python2 ruby; fi - if [ $TRAVIS_OS_NAME = windows ]; then export PATH="/c/Python27:/c/Python27/Scripts:$PATH"; fi + script: - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then npm run lint; fi - if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$INTEGRATION_TEST_SUITE" == "simple" ]]; then npm run simple-integration-test; fi + after_success: - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage + deploy: provider: npm email: services@serverless.com From 08e523f3d7da81eba84cfe590bc6ea42f48162ea Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 13:49:59 +0200 Subject: [PATCH 171/504] Remove ineffective option --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 765752fe7..339eb671a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,8 +33,6 @@ matrix: node_js: "10.7" env: SLS_IGNORE_WARNING=* -sudo: false - install: - travis_retry npm install # ensure that Python 2 and Ruby are installed From 1fcbe80d4cf231d906be3b6682e061ec6bbc51df Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 14:34:57 +0200 Subject: [PATCH 172/504] Document secrets --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 339eb671a..8096ea958 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,9 @@ matrix: - INTEGRATION_TEST=true - INTEGRATION_TEST_SUITE=simple - SLS_IGNORE_WARNING=* + # AWS_ACCESS_KEY_ID - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= + # AWS_SECRET_ACCESS_KEY - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= - os: linux node_js: "10.7" From 295b5c2a4ba8f8095424f85693f673a0c5b57473 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 16:55:15 +0200 Subject: [PATCH 173/504] Prolong timeout Test failed the CI with timeout error --- lib/plugins/aws/invokeLocal/index.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 355f9636e..2a3486b83 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -934,7 +934,8 @@ describe('AwsInvokeLocal', () => { }); describe('context.remainingTimeInMillis', () => { - it('should become lower over time', () => { + it('should become lower over time', function () { + this.timeout(3000); awsInvokeLocal.serverless.config.servicePath = __dirname; return awsInvokeLocal.invokeLocalRuby( From 57ac1d4204b33697a0177c7ab0d855982cf977ff Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 17:20:34 +0200 Subject: [PATCH 174/504] Replace install with before_script To not override good Travis CI default --- .travis.yml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8096ea958..d7943f2a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,23 @@ language: node_js git: depth: 1 # no need for commits history +before_script: + # ensure that Python 2 and Ruby are installed + - | + if [ $TRAVIS_OS_NAME = linux ] + then + sudo apt-get install python ruby + fi + - | + if [ $TRAVIS_OS_NAME = windows ] + then + choco install python2 ruby && + export PATH="/c/Python27:/c/Python27/Scripts:$PATH" + fi + matrix: include: # Linux tests - - os: linux - node_js: "6.2" - env: SLS_IGNORE_WARNING=* - os: linux node_js: "8.9" env: SLS_IGNORE_WARNING=* @@ -35,12 +46,6 @@ matrix: node_js: "10.7" env: SLS_IGNORE_WARNING=* -install: - - travis_retry npm install - # ensure that Python 2 and Ruby are installed - - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python ruby; else choco install python2 ruby; fi - - if [ $TRAVIS_OS_NAME = windows ]; then export PATH="/c/Python27:/c/Python27/Scripts:$PATH"; fi - script: - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then From 18031ac70ac5ecea6e71d7cdfa572584f43e531c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 24 May 2019 17:27:50 +0200 Subject: [PATCH 175/504] Simplify before script --- .travis.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index d7943f2a2..2063f380d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,16 +4,12 @@ git: depth: 1 # no need for commits history before_script: - # ensure that Python 2 and Ruby are installed - - | - if [ $TRAVIS_OS_NAME = linux ] - then - sudo apt-get install python ruby - fi + # Ensure Python 2 in Windows enviroment + # (Ruby is already preinstalled, and on Linux both are preinstalled) - | if [ $TRAVIS_OS_NAME = windows ] then - choco install python2 ruby && + choco install python2 && export PATH="/c/Python27:/c/Python27/Scripts:$PATH" fi From 18befe012e5b2e5baabed879f23bac004a602520 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 24 May 2019 12:48:23 -0400 Subject: [PATCH 176/504] remove unneeded eslint directive --- lib/classes/PluginManager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 76b8f37f8..de219b9ef 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -157,7 +157,6 @@ class PluginManager { loadEnterprisePlugin() { module.paths.unshift(path.join(__dirname, '../../node_modules')); this.loadPlugins(['@serverless/enterprise-plugin']); - // eslint-disable-next-line global-require const sfePkgJson = require('@serverless/enterprise-plugin/package.json'); if (this.serverless.enterpriseEnabled) { From 9e4621cb2388077e562c68e95b3d63444e8cd51a Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 24 May 2019 12:50:58 -0400 Subject: [PATCH 177/504] re add enterprise disable d sflag support --- lib/classes/PluginManager.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index de219b9ef..44cceaf55 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -1,6 +1,7 @@ 'use strict'; const path = require('path'); +const config = require('../utils/config'); const Module = require('module'); const BbPromise = require('bluebird'); const _ = require('lodash'); @@ -155,6 +156,9 @@ class PluginManager { } loadEnterprisePlugin() { + if (config.getGlobalConfig().enterpriseDisabled) { + return; + } module.paths.unshift(path.join(__dirname, '../../node_modules')); this.loadPlugins(['@serverless/enterprise-plugin']); const sfePkgJson = require('@serverless/enterprise-plugin/package.json'); From e335bdb66b4cf9044a0f30c23b176d78d315a333 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 24 May 2019 13:42:19 -0400 Subject: [PATCH 178/504] SFE plugin 1.0.3 removed unneeded zlib dep that was breaking npm installs with old versions of npm --- .travis.yml | 1 - package-lock.json | 14 ++++---------- package.json | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8b3d278e4..0e7d73b41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,6 @@ matrix: env: SLS_IGNORE_WARNING=* sudo: false install: - - "[ $TRAVIS_OS_NAME = windows ] || travis_retry npm install -g npm" - travis_retry npm install # ensure that Python 2 and Ruby are installed - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python ruby; else choco install python2 ruby; fi diff --git a/package-lock.json b/package-lock.json index 264672b03..4634f6f5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -466,9 +466,9 @@ } }, "@serverless/enterprise-plugin": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.2.tgz", - "integrity": "sha512-NcMxlOilSq/r4KdeUSV2Jeol4NI8IOHnZfZaovpACfctVSNp7nd3mVbrCs42ot8lcKyjgSeS1sEXF+2nqg4dtw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.3.tgz", + "integrity": "sha512-qvKaidc3ixuZ1XAa/bzYeKDzuO2jJSEz4KR4syr3+L060wXHJh4i/E7JZxAODQJbFq4gjE+zCUZ1ut6uZvs6ag==", "requires": { "@serverless/event-mocks": "^1.1.1", "@serverless/platform-sdk": "^1.0.0", @@ -484,8 +484,7 @@ "node-fetch": "^2.3.0", "regenerator-runtime": "^0.13.1", "semver": "^5.6.0", - "yamljs": "^0.3.0", - "zlib": "^1.0.5" + "yamljs": "^0.3.0" }, "dependencies": { "fs-extra": { @@ -9588,11 +9587,6 @@ "lodash": "^4.8.0", "readable-stream": "^2.0.0" } - }, - "zlib": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", - "integrity": "sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=" } } } diff --git a/package.json b/package.json index f08ded0b7..cc4c8b0cf 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "strip-ansi": "^5.2.0" }, "dependencies": { - "@serverless/enterprise-plugin": "^1.0.2", + "@serverless/enterprise-plugin": "^1.0.3", "archiver": "^1.1.0", "async": "^1.5.2", "aws-sdk": "^2.430.0", From 2d3427a324e051f586e4ec5a032e4975beef389d Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 24 May 2019 13:57:06 -0400 Subject: [PATCH 179/504] fse.copySync instead --- tests/packaging-suite/packaging.tests.js | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/packaging-suite/packaging.tests.js b/tests/packaging-suite/packaging.tests.js index e849febdb..93da7ee95 100644 --- a/tests/packaging-suite/packaging.tests.js +++ b/tests/packaging-suite/packaging.tests.js @@ -16,8 +16,8 @@ describe('Integration test - Packaging', () => { }); it('packages the default aws template correctly in the zip', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync(`${serverlessExec} package`, { cwd }); return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { @@ -26,8 +26,8 @@ describe('Integration test - Packaging', () => { }); it('packages the default aws template with an npm dep correctly in the zip', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync('npm init --yes', { cwd }); execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -42,8 +42,8 @@ describe('Integration test - Packaging', () => { }); it('doesn\'t package a dev dependency in the zip', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync('npm init --yes', { cwd }); execSync('npm i --save-dev lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -58,8 +58,8 @@ describe('Integration test - Packaging', () => { }); it('ignores package json files per ignore directive in the zip', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync('npm init --yes', { cwd }); execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); execSync('npm i lodash', { cwd }); @@ -75,8 +75,8 @@ describe('Integration test - Packaging', () => { }); it('package artifact directive works', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copyFileSync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')); + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')); execSync('echo \'package: {artifact: artifact.zip}\' >> serverless.yml', { cwd }); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( @@ -112,8 +112,8 @@ describe('Integration test - Packaging', () => { }); it('creates the correct default function resource in cfn template', () => { - fse.copyFileSync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( cwd, '.serverless/cloudformation-template-update-stack.json'))); @@ -152,9 +152,9 @@ describe('Integration test - Packaging', () => { }); it('handles package individually with include/excludes correctly', () => { - fse.copyFileSync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')); - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); - fse.copyFileSync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler2.js')); + fse.copySync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler2.js')); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse(fs.readFileSync(path.join( cwd, '.serverless/cloudformation-template-update-stack.json'))); From ec2795ccb3efdb5e56953893e76e90e1d7723fe0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 10:54:31 +0200 Subject: [PATCH 180/504] Configure default 'env' --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2063f380d..e6d4ac0a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ language: node_js git: depth: 1 # no need for commits history +env: SLS_IGNORE_WARNING=* # Default env + before_script: # Ensure Python 2 in Windows enviroment # (Ruby is already preinstalled, and on Linux both are preinstalled) @@ -18,10 +20,8 @@ matrix: # Linux tests - os: linux node_js: "8.9" - env: SLS_IGNORE_WARNING=* - os: linux node_js: "10.7" - env: SLS_IGNORE_WARNING=* - os: linux node_js: "10.7" env: @@ -40,7 +40,6 @@ matrix: # Windows tests - os: windows node_js: "10.7" - env: SLS_IGNORE_WARNING=* script: - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi From b03347c606d773dc9d55cb2a4065edb437de0c91 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 11:31:04 +0200 Subject: [PATCH 181/504] Reconfigure into stages Additionally: - By default run bare tests (not full coverage) - Reduce number of jobs by using one job per Node.js version - Ensure isolated tests run --- .travis.yml | 87 ++++++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/.travis.yml b/.travis.yml index e6d4ac0a8..32ca5caf9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,58 +5,51 @@ git: env: SLS_IGNORE_WARNING=* # Default env -before_script: - # Ensure Python 2 in Windows enviroment - # (Ruby is already preinstalled, and on Linux both are preinstalled) - - | - if [ $TRAVIS_OS_NAME = windows ] - then - choco install python2 && - export PATH="/c/Python27:/c/Python27/Scripts:$PATH" - fi +script: npm run test-bare # Run bare tests (with no coverage) by default -matrix: +stages: + - name: test + - name: deploy + if: tag =~ ^v\d+\.\d+\.\d+$ + +jobs: include: - # Linux tests - - os: linux - node_js: "8.9" - - os: linux - node_js: "10.7" - - os: linux - node_js: "10.7" + # To speed up Travis build, use one job per Platform + Node.js version combination + - name: "Lint & Unit Tests & Simple Integration tests, Linux, Node.js v12" + node_js: 12 + # Combine with '&&' to abort if any crashes + script: npm run lint && npm run test-bare && npm run simple-integration-test env: - - INTEGRATION_TEST=true - - INTEGRATION_TEST_SUITE=simple - SLS_IGNORE_WARNING=* + - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' # AWS_ACCESS_KEY_ID - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= # AWS_SECRET_ACCESS_KEY - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= - - os: linux - node_js: "10.7" - env: - - DISABLE_TESTS=true - - LINTING=true - # Windows tests - - os: windows - node_js: "10.7" - -script: - - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi - - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then - npm run lint; fi - - if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$INTEGRATION_TEST_SUITE" - == "simple" ]]; then npm run simple-integration-test; fi - -after_success: - - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage - -deploy: - provider: npm - email: services@serverless.com - skip_cleanup: true - on: - tags: true - node: 10.7 - api_key: - secure: EgoetjrRlGfvGnmVp8A0btr1CzB0hl7owVIpbfk4zXJzhGEbHoVu0CG0IdmyLN+JlaZa7EDJTjkDCd6g3fVAh9TT7ZCeaq8YwbZDrql7mAJj7xYQAyM4eSkc95BRzcFJBx7Mxr6H90IDLxKr6ZtB+HEdiHN+59XbepKYYJeb1jHfnKn5xzOqk4BdnZo6pKfudfeO+7/BwJJ0FwlFA40bY2HS/Lp+NG/2IedNR7k3m/5W83/XH5qlWP8jhBKlxrAzks27aNo+42xHkRCVyPViJKq0mfz1hl2bfswChWHgaCuajp+0amNL39pgIX9eXxFc3bNX9Iftox5t31elEhsw06vvuAaVkKEd+VEMaDySbQ9M+irKZeREg+NFYZLnc2WiEE3Sexo6hm9eM2q2KEZ7bleN9B0CQAut1XXLRQEts80rzss4Z2Q7AZb9cOYBQlj9Wf1X0Y74UqvnDn83a4Y38a+lhx7J2q691ZeM1UFSCdO0QfeJRkB55bSyHqUqrLAqUN7eNsKGdBH0kvYIGFREgGgReEpBRAuNqWuJ/5qexp63Kbf+09raG5IvfxSIM5fJ5KE5VxSduBdRnSH0GNKfjuq296/Rg4fmm/bygZ3Yk5L6Wd41SUU8uHzlZFBwtcvxAKDTQe6s+5JU24ilqxOx6J4Ut34X3dIbLLAmoB1ogdM= + - name: "Unit Tests, Windows, Node.js v12" + os: windows + node_js: 12 + before_script: + # Ensure Python 2 in Windows enviroment (Ruby is already preinstalled) + - | + if [ $TRAVIS_OS_NAME = windows ] + then + choco install python2 && + export PATH="/c/Python27:/c/Python27/Scripts:$PATH" + fi + - name: "Isolated Unit Tests, Linux, Node.js v10" + node_js: 10 + script: npm run test-isolated + - name: "Unit Tests & Coverage, Linux, Node.js v8" + node_js: 8 + script: npm test + after_success: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage + - name: "Unit Tests, Linux, Node.js v6" + node_js: 6 + - stage: deploy + script: skip + deploy: + provider: npm + email: services@serverless.com + api_key: + secure: EgoetjrRlGfvGnmVp8A0btr1CzB0hl7owVIpbfk4zXJzhGEbHoVu0CG0IdmyLN+JlaZa7EDJTjkDCd6g3fVAh9TT7ZCeaq8YwbZDrql7mAJj7xYQAyM4eSkc95BRzcFJBx7Mxr6H90IDLxKr6ZtB+HEdiHN+59XbepKYYJeb1jHfnKn5xzOqk4BdnZo6pKfudfeO+7/BwJJ0FwlFA40bY2HS/Lp+NG/2IedNR7k3m/5W83/XH5qlWP8jhBKlxrAzks27aNo+42xHkRCVyPViJKq0mfz1hl2bfswChWHgaCuajp+0amNL39pgIX9eXxFc3bNX9Iftox5t31elEhsw06vvuAaVkKEd+VEMaDySbQ9M+irKZeREg+NFYZLnc2WiEE3Sexo6hm9eM2q2KEZ7bleN9B0CQAut1XXLRQEts80rzss4Z2Q7AZb9cOYBQlj9Wf1X0Y74UqvnDn83a4Y38a+lhx7J2q691ZeM1UFSCdO0QfeJRkB55bSyHqUqrLAqUN7eNsKGdBH0kvYIGFREgGgReEpBRAuNqWuJ/5qexp63Kbf+09raG5IvfxSIM5fJ5KE5VxSduBdRnSH0GNKfjuq296/Rg4fmm/bygZ3Yk5L6Wd41SUU8uHzlZFBwtcvxAKDTQe6s+5JU24ilqxOx6J4Ut34X3dIbLLAmoB1ogdM= From 9b3fc3bc752159d4a55a9bd04179693d88ee0719 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 13:07:27 +0200 Subject: [PATCH 182/504] Refactor to use destructuring --- tests/simple-suite/tests.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/simple-suite/tests.js b/tests/simple-suite/tests.js index 84713c8a4..6421bd0e8 100644 --- a/tests/simple-suite/tests.js +++ b/tests/simple-suite/tests.js @@ -7,11 +7,11 @@ const fse = require('fs-extra'); const BbPromise = require('bluebird'); const execSync = require('child_process').execSync; const AWS = require('aws-sdk'); -const testUtils = require('../utils/index'); +const { getTmpDirPath, replaceTextInFile } = require('../utils/index'); const serverlessExec = path.join(__dirname, '..', '..', 'bin', 'serverless'); -const tmpDir = testUtils.getTmpDirPath(); +const tmpDir = getTmpDirPath(); fse.mkdirsSync(tmpDir); process.chdir(tmpDir); @@ -25,7 +25,7 @@ BbPromise.promisifyAll(CF, { suffix: 'Promised' }); describe('Service Lifecyle Integration Test', () => { it('should create service in tmp directory', () => { execSync(`${serverlessExec} create --template ${templateName}`, { stdio: 'inherit' }); - testUtils.replaceTextInFile('serverless.yml', templateName, newServiceName); + replaceTextInFile('serverless.yml', templateName, newServiceName); expect(fs.existsSync(path.join(tmpDir, 'serverless.yml'))).to.be.equal(true); expect(fs.existsSync(path.join(tmpDir, 'handler.js'))).to.be.equal(true); }); From 51fdfefaa8c36a0c0d40dca885ef760cfef91764 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 13:08:15 +0200 Subject: [PATCH 183/504] execSync test util --- tests/utils/index.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/utils/index.js b/tests/utils/index.js index 687760759..b0b89e99d 100644 --- a/tests/utils/index.js +++ b/tests/utils/index.js @@ -236,4 +236,15 @@ module.exports = { } return originals; }, + + execSync(command, options = null) { + // Same as native but outputs std in case of error + try { + execSync(command, options); + } catch (error) { + if (error.stdout) process.stdout.write(error.stdout); + if (error.stderr) process.stderr.write(error.stderr); + throw error; + } + } }; From 262905768294f29517c76e41cf00600099db0a4e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 13:20:52 +0200 Subject: [PATCH 184/504] execSync test util --- tests/utils/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils/index.js b/tests/utils/index.js index b0b89e99d..5035cae74 100644 --- a/tests/utils/index.js +++ b/tests/utils/index.js @@ -240,11 +240,11 @@ module.exports = { execSync(command, options = null) { // Same as native but outputs std in case of error try { - execSync(command, options); + return execSync(command, options); } catch (error) { if (error.stdout) process.stdout.write(error.stdout); if (error.stderr) process.stderr.write(error.stderr); throw error; } - } + }, }; From 2b077d123035ec6786918101cb47e83b1a0050b7 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 13:08:27 +0200 Subject: [PATCH 185/504] Log output in case of error --- tests/simple-suite/tests.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/simple-suite/tests.js b/tests/simple-suite/tests.js index 6421bd0e8..c56feec2a 100644 --- a/tests/simple-suite/tests.js +++ b/tests/simple-suite/tests.js @@ -5,9 +5,8 @@ const expect = require('chai').expect; const path = require('path'); const fse = require('fs-extra'); const BbPromise = require('bluebird'); -const execSync = require('child_process').execSync; const AWS = require('aws-sdk'); -const { getTmpDirPath, replaceTextInFile } = require('../utils/index'); +const { execSync, getTmpDirPath, replaceTextInFile } = require('../utils/index'); const serverlessExec = path.join(__dirname, '..', '..', 'bin', 'serverless'); From e3956523d35f9da99ba9a73d8914030c20dace84 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 13:23:28 +0200 Subject: [PATCH 186/504] Improve scripts organization --- .travis.yml | 4 +--- package.json | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 32ca5caf9..f65822348 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ git: env: SLS_IGNORE_WARNING=* # Default env -script: npm run test-bare # Run bare tests (with no coverage) by default - stages: - name: test - name: deploy @@ -42,7 +40,7 @@ jobs: script: npm run test-isolated - name: "Unit Tests & Coverage, Linux, Node.js v8" node_js: 8 - script: npm test + script: npm run coverage after_success: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage - name: "Unit Tests, Linux, Node.js v6" node_js: 6 diff --git a/package.json b/package.json index 5dde6555b..8d1ab2cb9 100644 --- a/package.json +++ b/package.json @@ -52,9 +52,9 @@ "sls": "./bin/serverless" }, "scripts": { - "test-bare": "mocha \"!(node_modules)/**/*.test.js\"", + "test": "mocha \"!(node_modules)/**/*.test.js\"", "test-isolated": "node scripts/test-isolated.js", - "test": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm run test-bare", + "coverage": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm test", "lint": "eslint . --cache", "docs": "node scripts/generate-readme.js", "integration-test-cleanup": "node scripts/integration-test-cleanup.js", From 4f3016a756a47d00a98ecb1a82509aa036a5a0b7 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 13:24:27 +0200 Subject: [PATCH 187/504] Whitespace --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f65822348..6827e533e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,6 +44,7 @@ jobs: after_success: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage - name: "Unit Tests, Linux, Node.js v6" node_js: 6 + - stage: deploy script: skip deploy: From edcea7f5f20f99f596ee0297d8d0fede2108af34 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 13:27:15 +0200 Subject: [PATCH 188/504] Improve job names --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6827e533e..05e71e0b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ stages: jobs: include: # To speed up Travis build, use one job per Platform + Node.js version combination - - name: "Lint & Unit Tests & Simple Integration tests, Linux, Node.js v12" + - name: "Lint, Unit Tests, Simple Integration tests - Linux - Node.js v12" node_js: 12 # Combine with '&&' to abort if any crashes script: npm run lint && npm run test-bare && npm run simple-integration-test @@ -24,7 +24,7 @@ jobs: - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= # AWS_SECRET_ACCESS_KEY - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= - - name: "Unit Tests, Windows, Node.js v12" + - name: "Unit Tests - Windows - Node.js v12" os: windows node_js: 12 before_script: @@ -35,14 +35,14 @@ jobs: choco install python2 && export PATH="/c/Python27:/c/Python27/Scripts:$PATH" fi - - name: "Isolated Unit Tests, Linux, Node.js v10" + - name: "Isolated Unit Tests - Linux - Node.js v10" node_js: 10 script: npm run test-isolated - - name: "Unit Tests & Coverage, Linux, Node.js v8" + - name: "Unit Tests, Coverage - Linux - Node.js v8" node_js: 8 script: npm run coverage after_success: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage - - name: "Unit Tests, Linux, Node.js v6" + - name: "Unit Tests - Linux - Node.js v6" node_js: 6 - stage: deploy From 44db2d50797fb652fa46855b708076f9e8f5be0d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 13:31:07 +0200 Subject: [PATCH 189/504] Ensure to expose full stack trace --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 05e71e0b3..3dab3b6a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ jobs: env: - SLS_IGNORE_WARNING=* - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' + - SLS_DEBUG=* # Ensure to expose full stack trace in case of crashes # AWS_ACCESS_KEY_ID - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= # AWS_SECRET_ACCESS_KEY From 0c479bea7d75b2ea0cdadd268281f3faf2b0fcb1 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 27 May 2019 14:02:08 +0200 Subject: [PATCH 190/504] Fix test command --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3dab3b6a0..844d047fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ jobs: - name: "Lint, Unit Tests, Simple Integration tests - Linux - Node.js v12" node_js: 12 # Combine with '&&' to abort if any crashes - script: npm run lint && npm run test-bare && npm run simple-integration-test + script: npm run lint && npm test && npm run simple-integration-test env: - SLS_IGNORE_WARNING=* - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' From 5f6cd0395898fb486729390a823c589204a02d73 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 28 May 2019 07:43:02 -0400 Subject: [PATCH 191/504] Update changelog --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93db86918..482b93f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# 1.44.0 (2019-05-28) + +- [Built in integration of Serverless Enterprise](https://github.com/serverless/serverless/pull/6074) +- [Setup Travis Windows support / Remove AppVeyor](https://github.com/serverless/serverless/pull/6132) +- [Update required Node.js version / Add version check](https://github.com/serverless/serverless/pull/6077) +- [Add scopes for cognito type APIGW referenced authorizer ](https://github.com/serverless/serverless/pull/6150) +- [Do not throw error if authorizer has empty claims](https://github.com/serverless/serverless/pull/6121) +- [Tests: Patch mocha bugs and fix broken async flow cases](https://github.com/serverless/serverless/pull/6157) +- [Fix tagging API Gateway stage fails if tag contains special characters like space](https://github.com/serverless/serverless/pull/6139) +- [Solve the problem of principal format in China region](https://github.com/serverless/serverless/pull/6127) +- [Upgrade mocha, switch from istanbul to nyc, improve tests configuration](https://github.com/serverless/serverless/pull/6169) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.43.0...v1.44.0) + + # 1.43.0 (2019-05-20) - [Update services.md](https://github.com/serverless/serverless/pull/6138) From 6135a1e14c9701aaed2a7c2871e1a117424d2fd2 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 28 May 2019 16:03:01 -0400 Subject: [PATCH 192/504] fix yarn install of SFE integration closes #6181 --- CHANGELOG.md | 7 +++++++ lib/classes/PluginManager.js | 3 +++ package-lock.json | 2 +- package.json | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 482b93f64..129e36e83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.44.1 (2019-05-28) +- [Fix enterprise plugin lookup in global yarn installs](https://github.com/serverless/serverless/pull/TBD) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.0...v1.44.1) + + # 1.44.0 (2019-05-28) - [Built in integration of Serverless Enterprise](https://github.com/serverless/serverless/pull/6074) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 44cceaf55..daf7260e3 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -159,6 +159,9 @@ class PluginManager { if (config.getGlobalConfig().enterpriseDisabled) { return; } + // `yarn global add` support, deps of deps are installed as deps + module.paths.unshift(path.join(__dirname, '../../../../node_modules')); + // `npm -i g` support, deps of deps are installed inside projects module.paths.unshift(path.join(__dirname, '../../node_modules')); this.loadPlugins(['@serverless/enterprise-plugin']); const sfePkgJson = require('@serverless/enterprise-plugin/package.json'); diff --git a/package-lock.json b/package-lock.json index 52b59fde0..287ddc192 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.44.0", + "version": "1.44.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3c17a4b3a..b128a6a4a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.44.0", + "version": "1.44.1", "engines": { "node": ">=6.0" }, From 116d30a9fcbfb7f73cb0260655df63731815b57a Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 28 May 2019 16:13:10 -0400 Subject: [PATCH 193/504] add PR number for this pr --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 129e36e83..2f29cd3a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # 1.44.1 (2019-05-28) -- [Fix enterprise plugin lookup in global yarn installs](https://github.com/serverless/serverless/pull/TBD) +- [Fix enterprise plugin lookup in global yarn installs](https://github.com/serverless/serverless/pull/6183) ## Meta - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.0...v1.44.1) From 531f4600db9a9b65b517f7d8a6418036532567af Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 10:15:18 +0200 Subject: [PATCH 194/504] Isolate process.env toggling in tests --- lib/classes/Error.test.js | 13 +- lib/classes/PluginManager.test.js | 26 +-- lib/classes/Variables.test.js | 21 ++- lib/plugins/aws/invokeLocal/index.test.js | 10 +- lib/plugins/aws/provider/awsProvider.test.js | 167 ++++--------------- lib/plugins/invoke/invoke.test.js | 11 +- package-lock.json | 6 +- package.json | 1 + 8 files changed, 70 insertions(+), 185 deletions(-) diff --git a/lib/classes/Error.test.js b/lib/classes/Error.test.js index c24b50e95..a9eb3bcdd 100644 --- a/lib/classes/Error.test.js +++ b/lib/classes/Error.test.js @@ -2,6 +2,7 @@ const expect = require('chai').expect; const sinon = require('sinon'); +const overrideEnv = require('process-utils/override-env'); const errorReporter = require('../utils/sentry').raven; const ServerlessError = require('./Error').ServerlessError; const logError = require('./Error').logError; @@ -67,17 +68,11 @@ describe('Error', () => { }); describe('#logError()', () => { - let BK_SLS_DEBUG; + let restoreEnv; - beforeEach(() => { - BK_SLS_DEBUG = process.env.SLS_DEBUG; - delete process.env.SLS_DEBUG; - }); + beforeEach(() => ({ restoreEnv } = overrideEnv())); - afterEach(() => { - if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; - else delete process.env.SLS_DEBUG; - }); + afterEach(() => restoreEnv()); it('should log error and exit', () => { const error = new ServerlessError('a message', 'a status code'); diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index b6e978d0a..7a28dac67 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -3,6 +3,7 @@ /* eslint-disable no-unused-expressions */ const chai = require('chai'); +const overrideEnv = require('process-utils/override-env'); let PluginManager = require('../../lib/classes/PluginManager'); const Serverless = require('../../lib/Serverless'); const CLI = require('../../lib/classes/CLI'); @@ -429,13 +430,18 @@ describe('PluginManager', () => { untouched() { return; } } - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + let restoreEnv; + + beforeEach(() => { + ({ restoreEnv } = overrideEnv({ whitelist: ['PATH'] })); serverless = new Serverless(); serverless.cli = new CLI(); pluginManager = new PluginManager(serverless); pluginManager.serverless.config.servicePath = 'foo'; }); + afterEach(() => restoreEnv()); + describe('#constructor()', () => { it('should set the serverless instance', () => { expect(pluginManager.serverless).to.deep.equal(serverless); @@ -698,9 +704,6 @@ describe('PluginManager', () => { _.set(process.env, 'SLS_DEBUG', '*'); expect(() => pluginManager.loadPlugins(servicePlugins)) .to.throw(/Broken plugin/); - }) - .finally(() => { - _.unset(process.env, 'SLS_DEBUG'); }); }); @@ -1052,7 +1055,6 @@ describe('PluginManager', () => { synchronousPluginMockInstance.commands.deploy.aliases = ['info']; _.set(process.env, 'SLS_DEBUG', '*'); pluginManager.loadCommands(synchronousPluginMockInstance); - _.unset(process.env, 'SLS_DEBUG'); expect(consoleLogStub).to.have.been.calledWith(' -> @info'); }); }); @@ -1060,10 +1062,8 @@ describe('PluginManager', () => { describe('#loadHooks()', () => { let deprecatedPluginInstance; let consoleLogStub; - let BK_SLS_DEBUG; beforeEach(() => { - BK_SLS_DEBUG = process.env.SLS_DEBUG; deprecatedPluginInstance = new DeprecatedLifecycleEventsPluginMock(); pluginManager.deprecatedEvents = { 'deprecated:deprecated': 'new:new', @@ -1072,14 +1072,11 @@ describe('PluginManager', () => { }); afterEach(() => { - if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; - else delete process.env.SLS_DEBUG; pluginManager.deprecatedEvents = {}; pluginManager.serverless.cli.log.restore(); }); it('should replace deprecated events with the new ones', () => { - delete process.env.SLS_DEBUG; pluginManager.loadHooks(deprecatedPluginInstance); expect(pluginManager.hooks['deprecated:deprecated']) @@ -1434,7 +1431,6 @@ describe('PluginManager', () => { it('should show warning if in debug mode and the given command has no hooks', () => { const consoleLogStub = sinon.stub(pluginManager.serverless.cli, 'log').returns(); - const BK_SLS_DEBUG = process.env.SLS_DEBUG; process.env.SLS_DEBUG = '*'; class HooklessPlugin { constructor() { @@ -1451,9 +1447,6 @@ describe('PluginManager', () => { return pluginManager.run(commandsArray).then(() => { expect(consoleLogStub.called).is.equal(true); pluginManager.serverless.cli.log.restore(); - }).finally(() => { - if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; - else delete process.env.SLS_DEBUG; }); }); @@ -1727,7 +1720,6 @@ describe('PluginManager', () => { it('should show warning in debug mode and when the given command has no hooks', () => { const consoleLogStub = sinon.stub(pluginManager.serverless.cli, 'log').returns(); - const BK_SLS_DEBUG = process.env.SLS_DEBUG; process.env.SLS_DEBUG = '*'; class HooklessPlugin { @@ -1746,10 +1738,6 @@ describe('PluginManager', () => { .then(() => { expect(consoleLogStub.called).is.equal(true); pluginManager.serverless.cli.log.restore(); - }) - .finally(() => { - if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; - else delete process.env.SLS_DEBUG; }); }); diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 7da83663b..c7eb9b4b2 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -11,6 +11,7 @@ const proxyquire = require('proxyquire'); const sinon = require('sinon'); const YAML = require('js-yaml'); const _ = require('lodash'); +const overrideEnv = require('process-utils/override-env'); const AwsProvider = require('../plugins/aws/provider/awsProvider'); const fse = require('../utils/fs/fse'); @@ -32,9 +33,15 @@ const expect = chai.expect; describe('Variables', () => { let serverless; + let restoreEnv; + beforeEach(() => { + ({ restoreEnv } = overrideEnv()); serverless = new Serverless(); }); + + afterEach(() => restoreEnv()); + describe('#constructor()', () => { it('should attach serverless instance', () => { const variablesInstance = new Variables(serverless); @@ -1187,9 +1194,9 @@ module.exports = { process.env.TEST_VAR = 'dev'; serverless.variables.options = { name: 'TEST_VAR' }; return serverless.variables.populateProperty(property) - .should.eventually.eql('my stage is dev') - .then().finally(() => { delete process.env.TEST_VAR; }); + .should.eventually.eql('my stage is dev'); }); + it('should run recursively through many nested variables', () => { // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${env:${opt:name}}'; @@ -1205,8 +1212,7 @@ module.exports = { lvl6: 'R', }; return serverless.variables.populateProperty(property) - .should.eventually.eql('my stage is dev') - .then().finally(() => { delete process.env.TEST_VAR; }); + .should.eventually.eql('my stage is dev'); }); }); @@ -1514,17 +1520,14 @@ module.exports = { describe('#getValueFromEnv()', () => { it('should get variable from environment variables', () => { process.env.TEST_VAR = 'someValue'; - return serverless.variables.getValueFromEnv('env:TEST_VAR') - .finally(() => { delete process.env.TEST_VAR; }) - .should.become('someValue'); + return serverless.variables.getValueFromEnv('env:TEST_VAR').should.become('someValue'); }); it('should allow top-level references to the environment variables hive', () => { process.env.TEST_VAR = 'someValue'; return serverless.variables.getValueFromEnv('env:').then((valueToPopulate) => { expect(valueToPopulate.TEST_VAR).to.be.equal('someValue'); - }) - .finally(() => { delete process.env.TEST_VAR; }); + }); }); }); diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 355f9636e..78ff4d621 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -9,6 +9,7 @@ const EventEmitter = require('events'); const fse = require('fs-extra'); const proxyquire = require('proxyquire'); const stripAnsi = require('strip-ansi'); +const overrideEnv = require('process-utils/override-env'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); @@ -273,9 +274,9 @@ describe('AwsInvokeLocal', () => { }); describe('#loadEnvVars()', () => { - let BK_AWS_PROFILE; + let restoreEnv; beforeEach(() => { - BK_AWS_PROFILE = process.env.AWS_PROFILE; + ({ restoreEnv } = overrideEnv()); serverless.config.servicePath = true; serverless.service.provider = { environment: { @@ -294,10 +295,7 @@ describe('AwsInvokeLocal', () => { }; }); - afterEach(() => { - if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; - else delete process.env.AWS_PROFILE; - }); + afterEach(() => restoreEnv()); it('it should load provider env vars', () => awsInvokeLocal .loadEnvVars().then(() => { diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 89d2c7ee0..a2ec594c6 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -10,6 +10,7 @@ const sinon = require('sinon'); const fs = require('fs'); const os = require('os'); const path = require('path'); +const overrideEnv = require('process-utils/override-env'); const AwsProvider = require('./awsProvider'); const Serverless = require('../../../Serverless'); @@ -23,16 +24,20 @@ const expect = chai.expect; describe('AwsProvider', () => { let awsProvider; let serverless; + let restoreEnv; const options = { stage: 'dev', region: 'us-east-1', }; beforeEach(() => { + ({ restoreEnv } = overrideEnv()) serverless = new Serverless(options); serverless.cli = new serverless.classes.CLI(); awsProvider = new AwsProvider(serverless, options); }); + afterEach(() => restoreEnv()); + describe('#getProviderName()', () => { it('should return the provider name', () => { @@ -41,17 +46,6 @@ describe('AwsProvider', () => { }); describe('#constructor()', () => { - let backupProxyEnv; - - beforeEach('Environment Variable Cleanup', () => { - backupProxyEnv = process.env.proxy; - delete process.env.proxy; - }); - afterEach('Environment Variable Cleanup', () => { - if (backupProxyEnv) process.env.proxy = backupProxyEnv; - else delete process.env.proxy; - }); - it('should set Serverless instance', () => { expect(typeof awsProvider.serverless).to.not.equal('undefined'); }); @@ -69,16 +63,10 @@ describe('AwsProvider', () => { }); it('should set AWS logger', () => { - const BK_SLS_DEBUG = process.env.SLS_DEBUG; process.env.SLS_DEBUG = 'true'; - try { - const newAwsProvider = new AwsProvider(serverless, options); + const newAwsProvider = new AwsProvider(serverless, options); - expect(typeof newAwsProvider.sdk.config.logger).to.not.equal('undefined'); - } finally { - if (BK_SLS_DEBUG) process.env.SLS_DEBUG = BK_SLS_DEBUG; - else delete process.env.SLS_DEBUG; - } + expect(typeof newAwsProvider.sdk.config.logger).to.not.equal('undefined'); }); it('should set AWS proxy', () => { @@ -89,16 +77,10 @@ describe('AwsProvider', () => { }); it('should set AWS timeout', () => { - const BK_AWS_CLIENT_TIMEOUT = process.env.AWS_CLIENT_TIMEOUT; process.env.AWS_CLIENT_TIMEOUT = '120000'; - try { - const newAwsProvider = new AwsProvider(serverless, options); + const newAwsProvider = new AwsProvider(serverless, options); - expect(typeof newAwsProvider.sdk.config.httpOptions.timeout).to.not.equal('undefined'); - } finally { - if (BK_AWS_CLIENT_TIMEOUT) process.env.AWS_CLIENT_TIMEOUT = BK_AWS_CLIENT_TIMEOUT; - else delete process.env.AWS_CLIENT_TIMEOUT; - } + expect(typeof newAwsProvider.sdk.config.httpOptions.timeout).to.not.equal('undefined'); }); describe('stage name validation', () => { @@ -142,16 +124,6 @@ describe('AwsProvider', () => { }); describe('certificate authority - environment variable', () => { - let backupCaEnv; - - beforeEach('Environment Variable Cleanup', () => { - backupCaEnv = process.env.ca; - delete process.env.ca; - }); - afterEach('Environment Variable Cleanup', () => { - if (backupCaEnv) process.env.ca = backupCaEnv; - else delete process.env.ca; - }); it('should set AWS ca single', () => { process.env.ca = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; const newAwsProvider = new AwsProvider(serverless, options); @@ -182,14 +154,8 @@ describe('AwsProvider', () => { const tmpdir = os.tmpdir(); let file1 = null; let file2 = null; - let backupEnv; + beforeEach('Create CA Files and env vars', () => { - backupEnv = { - ca: process.env.ca, - cafile: process.env.cafile, - }; - delete process.env.ca; - delete process.env.cafile; file1 = path.join(tmpdir, 'ca1.txt'); file2 = path.join(tmpdir, 'ca2.txt'); fs.writeFileSync(file1, certContents); @@ -197,16 +163,9 @@ describe('AwsProvider', () => { }); afterEach('CA File Cleanup', () => { - for (const key of Object.keys(backupEnv)) { - if (backupEnv[key]) process.env[key] = backupEnv[key]; - else delete process.env[key]; - } // delete files fs.unlinkSync(file1); fs.unlinkSync(file2); - // clear env - delete process.env.ca; - delete process.env.cafile; }); it('should set AWS cafile single', () => { @@ -815,11 +774,8 @@ describe('AwsProvider', () => { const relevantEnvironment = { AWS_SHARED_CREDENTIALS_FILE: testUtils.getTmpFilePath('credentials'), }; - let BK_AWS_PROFILE; beforeEach(() => { - BK_AWS_PROFILE = process.env.AWS_PROFILE; - delete process.env.AWS_PROFILE; originalProviderProfile = serverless.service.provider.profile; originalEnvironmentVariables = testUtils.replaceEnv(relevantEnvironment); serverless.utils.writeFileSync( @@ -836,8 +792,6 @@ describe('AwsProvider', () => { }); afterEach(() => { - if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; - else delete process.env.AWS_PROFILE; testUtils.replaceEnv(originalEnvironmentVariables); serverless.service.provider.profile = originalProviderProfile; }); @@ -1046,25 +1000,15 @@ describe('AwsProvider', () => { secretAccessKey: 'secretAccessKey', sessionToken: 'sessionToken', }; - const backup = { - AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID, - AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY, - AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN, - }; process.env.AWS_ACCESS_KEY_ID = testVal.accessKeyId; process.env.AWS_SECRET_ACCESS_KEY = testVal.secretAccessKey; process.env.AWS_SESSION_TOKEN = testVal.sessionToken; - try { - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); - expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); - expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); - } finally { - for (const key of Object.keys(backup)) { - if (backup[key]) process.env[key] = backup[key]; - else delete process.env[key]; - } - } + + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); + expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); + expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); + }); it('should get credentials from environment declared stage specific credentials', () => { @@ -1073,94 +1017,53 @@ describe('AwsProvider', () => { secretAccessKey: 'secretAccessKey', sessionToken: 'sessionToken', }; - const backup = { - AWS_TESTSTAGE_ACCESS_KEY_ID: process.env.AWS_TESTSTAGE_ACCESS_KEY_ID, - AWS_TESTSTAGE_SECRET_ACCESS_KEY: process.env.AWS_TESTSTAGE_SECRET_ACCESS_KEY, - AWS_TESTSTAGE_SESSION_TOKEN: process.env.AWS_TESTSTAGE_SESSION_TOKEN, - }; process.env.AWS_TESTSTAGE_ACCESS_KEY_ID = testVal.accessKeyId; process.env.AWS_TESTSTAGE_SECRET_ACCESS_KEY = testVal.secretAccessKey; process.env.AWS_TESTSTAGE_SESSION_TOKEN = testVal.sessionToken; - try { - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); - expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); - expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); - } finally { - for (const key of Object.keys(backup)) { - if (backup[key]) process.env[key] = backup[key]; - else delete process.env[key]; - } - } + + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); + expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); + expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); }); it('should get credentials from environment declared for-all-stages profile', () => { - const BK_AWS_PROFILE = process.env.AWS_PROFILE; process.env.AWS_PROFILE = 'notDefault'; - try { - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.profile).to.equal('notDefault'); - } finally { - if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; - else delete process.env.AWS_PROFILE; - } + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.profile).to.equal('notDefault'); }); it('should get credentials from environment declared stage-specific profile', () => { - const BK_AWS_TESTSTAGE_PROFILE = process.env.AWS_TESTSTAGE_PROFILE; process.env.AWS_TESTSTAGE_PROFILE = 'notDefault'; - try { - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.profile).to.equal('notDefault'); - } finally { - if (BK_AWS_TESTSTAGE_PROFILE) process.env.AWS_TESTSTAGE_PROFILE = BK_AWS_TESTSTAGE_PROFILE; - else delete process.env.AWS_TESTSTAGE_PROFILE; - } + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.profile).to.equal('notDefault'); }); it('should get credentials when profile is provied via --aws-profile option', () => { - const BK_AWS_PROFILE = process.env.AWS_PROFILE; process.env.AWS_PROFILE = 'notDefaultTemporary'; - try { - newAwsProvider.options['aws-profile'] = 'notDefault'; + newAwsProvider.options['aws-profile'] = 'notDefault'; - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.profile).to.equal('notDefault'); - } finally { - if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; - else delete process.env.AWS_PROFILE; - } + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.profile).to.equal('notDefault'); }); it('should get credentials when profile is provied via --aws-profile option even if profile is defined in serverless.yml', () => { // eslint-disable-line max-len - const BK_AWS_PROFILE = process.env.AWS_PROFILE; process.env.AWS_PROFILE = 'notDefaultTemporary'; - try { - newAwsProvider.options['aws-profile'] = 'notDefault'; + newAwsProvider.options['aws-profile'] = 'notDefault'; - serverless.service.provider.profile = 'notDefaultTemporary2'; + serverless.service.provider.profile = 'notDefaultTemporary2'; - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.profile).to.equal('notDefault'); - } finally { - if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; - else delete process.env.AWS_PROFILE; - } + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.profile).to.equal('notDefault'); }); it('should get credentials when profile is provied via process.env.AWS_PROFILE even if profile is defined in serverless.yml', () => { // eslint-disable-line max-len - const BK_AWS_PROFILE = process.env.AWS_PROFILE; process.env.AWS_PROFILE = 'notDefault'; - try { - serverless.service.provider.profile = 'notDefaultTemporary'; + serverless.service.provider.profile = 'notDefaultTemporary'; - const credentials = newAwsProvider.getCredentials(); - expect(credentials.credentials.profile).to.equal('notDefault'); - } finally { - if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; - else delete process.env.AWS_PROFILE; - } + const credentials = newAwsProvider.getCredentials(); + expect(credentials.credentials.profile).to.equal('notDefault'); }); it('should set the signatureVersion to v4 if the serverSideEncryption is aws:kms', () => { diff --git a/lib/plugins/invoke/invoke.test.js b/lib/plugins/invoke/invoke.test.js index 9dffcde10..b7c891d06 100644 --- a/lib/plugins/invoke/invoke.test.js +++ b/lib/plugins/invoke/invoke.test.js @@ -1,6 +1,7 @@ 'use strict'; const chai = require('chai'); +const overrideEnv = require('process-utils/override-env'); const Invoke = require('./invoke'); const Serverless = require('../../Serverless'); @@ -11,19 +12,15 @@ const expect = chai.expect; describe('Invoke', () => { let invoke; let serverless; - let BK_IS_LOCAL; + let restoreEnv; beforeEach(() => { - BK_IS_LOCAL = process.env.IS_LOCAL; - delete process.env.IS_LOCAL; + ({ restoreEnv } = overrideEnv()); serverless = new Serverless(); invoke = new Invoke(serverless); }); - afterEach(() => { - if (BK_IS_LOCAL) process.env.IS_LOCAL = BK_IS_LOCAL; - else delete process.env.IS_LOCAL; - }); + afterEach(() => restoreEnv()); describe('#constructor()', () => { it('should have commands', () => expect(invoke.commands).to.be.not.empty); diff --git a/package-lock.json b/package-lock.json index 52b59fde0..f2d2d6c5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7596,9 +7596,9 @@ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "process-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/process-utils/-/process-utils-2.1.0.tgz", - "integrity": "sha512-FjkSXOw9XJ8qadsgOQHuwyhvqUq2D9JkEfqExrYe+ik2OHJw0q/hTOgrxybg0lgw3K/HTrBQ3s9nM7yI3Ukk2g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/process-utils/-/process-utils-2.3.0.tgz", + "integrity": "sha512-0i9UzoA8LgDyIk78XKhSbszvCn89vrGPdnQdcotHEjd7hbfRrQQ3pUkyg43+Jy9TuUtD7Syc6/r2LmOmjOLrYA==", "dev": true, "requires": { "type": "^1.0.1" diff --git a/package.json b/package.json index df6b8559e..884df1b81 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "nyc": "^14.1.1", "p-limit": "^2.2.0", "parse-github-url": "^1.0.1", + "process-utils": "^2.3.0", "proxyquire": "^1.7.10", "sinon": "^1.17.5", "sinon-bluebird": "^3.1.0", From bea683c702a211baabd2a69a4567b056b6758682 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 10:34:45 +0200 Subject: [PATCH 195/504] Fix lint issues --- lib/plugins/aws/provider/awsProvider.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index a2ec594c6..47a85389a 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -31,7 +31,7 @@ describe('AwsProvider', () => { }; beforeEach(() => { - ({ restoreEnv } = overrideEnv()) + ({ restoreEnv } = overrideEnv()); serverless = new Serverless(options); serverless.cli = new serverless.classes.CLI(); awsProvider = new AwsProvider(serverless, options); @@ -1008,7 +1008,6 @@ describe('AwsProvider', () => { expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); - }); it('should get credentials from environment declared stage specific credentials', () => { From 0716d258860360226c14c0253e89fb23ead9957c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 12:46:23 +0200 Subject: [PATCH 196/504] Fix mocha configuration (ensure custom reporter) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b128a6a4a..48c093b88 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ }, "mocha": { "require": "sinon-bluebird", - "-R": "tests/mocha-reporter" + "R": "tests/mocha-reporter" }, "jest": { "testRegex": "(\\.|/)(tests)\\.js$", From 24c9b50c5548dead8cd84a44265049833273e34f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 13:01:20 +0200 Subject: [PATCH 197/504] Increase timeout to avoid false positives --- tests/mocha-reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index e3fd12dba..c392ffad6 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -47,7 +47,7 @@ module.exports = class ServerlessSpec extends Spec { // Timeout '5' to ensure no false positives, with '1' there are observable rare scenarios // of possibly a garbage collector delaying process exit being picked up // On Node.js v8+ '2' seems safe, while v6 needs '5' - }, 5).unref() + }, 6).unref() ); } }; From 95a1d7ce21f29a620aeb44cc8a10d13566869212 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 13:20:53 +0200 Subject: [PATCH 198/504] Do not load async leaks detector in Node.js v6 --- tests/mocha-reporter.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index c392ffad6..e09fb13d5 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -36,6 +36,12 @@ BbPromise.prototype._ensurePossibleRejectionHandled = function () { }; /* eslint-enable */ +if (process.version[1] < 8) { + // Async leaks detector is not reliable in Node.js v6 + module.exports = Spec; + return; +} + module.exports = class ServerlessSpec extends Spec { constructor(runner) { super(runner); @@ -44,10 +50,10 @@ module.exports = class ServerlessSpec extends Spec { // If tests end with any orphaned async call then this callback will be invoked // It's a signal there's some promise chain (or in general async flow) miconfiguration throw new Error('Test ended with unfinished async jobs'); - // Timeout '5' to ensure no false positives, with '1' there are observable rare scenarios + // Timeout '2' to ensure no false positives, with '1' there are observable rare scenarios // of possibly a garbage collector delaying process exit being picked up - // On Node.js v8+ '2' seems safe, while v6 needs '5' - }, 6).unref() + }, 2).unref() ); } }; + From 11351925462bdc97686ff4faa80b2d9d4e32312e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 13:22:38 +0200 Subject: [PATCH 199/504] Ensure ESLint treats modules as CJS not ESM --- .eslintrc.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 9eecfdb9f..5c9183b4c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,6 +12,9 @@ module.exports = { "react/require-extension": "off", "import/no-extraneous-dependencies": "off" }, + "parserOptions": { + "sourceType": "script", // Override ESM implied by airbnb + }, "env": { "mocha": true, "jest": true From d8288d057c12d0fd595b074a1b16ccc0ccc2f60d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 13:34:27 +0200 Subject: [PATCH 200/504] Fix eslint configuration of 'strict' option --- .eslintrc.js | 2 +- lib/plugins/aws/lib/getServiceState.js | 2 ++ lib/utils/fs/readFileIfExists.js | 2 ++ lib/utils/renameService.js | 2 ++ lib/utils/sentry.js | 2 ++ tests/setupTests.js | 2 ++ 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5c9183b4c..7bdf7dda3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,9 +5,9 @@ module.exports = { "rules": { "func-names": "off", "global-require": "off", // Interfers with optional and eventual circular references + "strict": ["error", "safe"], // doesn't work in node v4 :( - "strict": "off", "prefer-rest-params": "off", "react/require-extension": "off", "import/no-extraneous-dependencies": "off" diff --git a/lib/plugins/aws/lib/getServiceState.js b/lib/plugins/aws/lib/getServiceState.js index 0feefd37a..fe5b95b01 100644 --- a/lib/plugins/aws/lib/getServiceState.js +++ b/lib/plugins/aws/lib/getServiceState.js @@ -1,3 +1,5 @@ +'use strict'; + const path = require('path'); module.exports = { diff --git a/lib/utils/fs/readFileIfExists.js b/lib/utils/fs/readFileIfExists.js index 4876033db..9c7fdd70e 100644 --- a/lib/utils/fs/readFileIfExists.js +++ b/lib/utils/fs/readFileIfExists.js @@ -1,3 +1,5 @@ +'use strict'; + const fileExists = require('./fileExists'); const readFile = require('./readFile'); const BbPromise = require('bluebird'); diff --git a/lib/utils/renameService.js b/lib/utils/renameService.js index ecb853c07..69dbd4fcf 100644 --- a/lib/utils/renameService.js +++ b/lib/utils/renameService.js @@ -1,3 +1,5 @@ +'use strict'; + const path = require('path'); const fse = require('fs-extra'); diff --git a/lib/utils/sentry.js b/lib/utils/sentry.js index 651a9302f..1a01325dc 100644 --- a/lib/utils/sentry.js +++ b/lib/utils/sentry.js @@ -1,3 +1,5 @@ +'use strict'; + const raven = require('raven'); const ci = require('ci-info'); const configUtils = require('./config'); diff --git a/tests/setupTests.js b/tests/setupTests.js index 4da8b0798..01313fd46 100644 --- a/tests/setupTests.js +++ b/tests/setupTests.js @@ -1,3 +1,5 @@ +'use strict'; + // timeout is set to 5 minutes // eslint-disable-next-line no-undef jasmine.DEFAULT_TIMEOUT_INTERVAL = 300000; From 1608de249103fb4b7089752e067f99c050bebdd6 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 13:39:54 +0200 Subject: [PATCH 201/504] Turn on prefer-rest-params eslint rule --- .eslintrc.js | 3 +-- lib/utils/log/consoleLog.js | 4 ++-- lib/utils/log/fileLog.js | 5 ++--- lib/utils/log/log.js | 4 ++-- lib/utils/userStats.js | 4 ++-- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 7bdf7dda3..da195d61f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,10 +5,9 @@ module.exports = { "rules": { "func-names": "off", "global-require": "off", // Interfers with optional and eventual circular references - "strict": ["error", "safe"], + "strict": ["error", "safe"], // airbnb implies we're transpiling with babel, we're not // doesn't work in node v4 :( - "prefer-rest-params": "off", "react/require-extension": "off", "import/no-extraneous-dependencies": "off" }, diff --git a/lib/utils/log/consoleLog.js b/lib/utils/log/consoleLog.js index 2ba6c7017..4bbe99661 100644 --- a/lib/utils/log/consoleLog.js +++ b/lib/utils/log/consoleLog.js @@ -1,7 +1,7 @@ 'use strict'; -const consoleLog = function () { - console.log(arguments); // eslint-disable-line no-console +const consoleLog = function (...args) { + console.log(args); // eslint-disable-line no-console }; module.exports = consoleLog; diff --git a/lib/utils/log/fileLog.js b/lib/utils/log/fileLog.js index b8dd3549f..4b2c1f070 100644 --- a/lib/utils/log/fileLog.js +++ b/lib/utils/log/fileLog.js @@ -4,11 +4,10 @@ const _ = require('lodash'); const fs = require('fs'); const path = require('path'); -const fileLog = function () { +const fileLog = function (...args) { // TODO BRN: This does not guarentee order, is not multi process safe, // TODO BRN: and is not guarenteed to complete before exit. - fs.appendFileSync(path.join(process.cwd(), 'sls.log'), - _.join(Array.prototype.slice.call(arguments)) + '\n'); // eslint-disable-line prefer-template + fs.appendFileSync(path.join(process.cwd(), 'sls.log'), `${_.join(args)}\n`); }; module.exports = fileLog; diff --git a/lib/utils/log/log.js b/lib/utils/log/log.js index aa59e55ce..c9561f026 100644 --- a/lib/utils/log/log.js +++ b/lib/utils/log/log.js @@ -9,8 +9,8 @@ const loggers = [ fileLog, ]; -const log = function () { - _.each(loggers, (logger) => logger.apply(null, arguments)); // eslint-disable-line prefer-spread +const log = function (...args) { + _.each(loggers, (logger) => logger(...args)); }; module.exports = log; diff --git a/lib/utils/userStats.js b/lib/utils/userStats.js index 435d6a7f9..7700a9b75 100644 --- a/lib/utils/userStats.js +++ b/lib/utils/userStats.js @@ -13,8 +13,8 @@ const TRACK_URL = 'https://serverless.com/api/framework/track'; const IDENTIFY_URL = 'https://serverless.com/api/framework/identify'; const DEBUG = false; -function debug() { - if (DEBUG) console.log(arguments); +function debug(...args) { + if (DEBUG) console.log(args); } /* note tracking swallows errors */ From e4f1b5707ef6e8715c5cf42c9f07e02bd4aa48d9 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 13:52:22 +0200 Subject: [PATCH 202/504] Improve explanation --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index da195d61f..2eb9d17db 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,7 +12,7 @@ module.exports = { "import/no-extraneous-dependencies": "off" }, "parserOptions": { - "sourceType": "script", // Override ESM implied by airbnb + "sourceType": "script", // airbnb assumes ESM, while we're CJS }, "env": { "mocha": true, From 7d96e1e0caaa9e7048bbb821b51478222fd1700a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 13:53:16 +0200 Subject: [PATCH 203/504] Document disabling of rule --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 2eb9d17db..ed670cf65 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,10 +5,10 @@ module.exports = { "rules": { "func-names": "off", "global-require": "off", // Interfers with optional and eventual circular references + "react/require-extension": "off", // Forced by airbnb, not applicable (also deprecated) "strict": ["error", "safe"], // airbnb implies we're transpiling with babel, we're not // doesn't work in node v4 :( - "react/require-extension": "off", "import/no-extraneous-dependencies": "off" }, "parserOptions": { From 8182e9e0c848f8c06013f9c4cae0f2121dd53da0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 13:58:24 +0200 Subject: [PATCH 204/504] Configure "import/no-extraneous-dependencies" rule --- .eslintrc.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ed670cf65..60abf1402 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,11 +5,9 @@ module.exports = { "rules": { "func-names": "off", "global-require": "off", // Interfers with optional and eventual circular references + "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/scripts/**", "**/tests/**"]}], "react/require-extension": "off", // Forced by airbnb, not applicable (also deprecated) "strict": ["error", "safe"], // airbnb implies we're transpiling with babel, we're not - - // doesn't work in node v4 :( - "import/no-extraneous-dependencies": "off" }, "parserOptions": { "sourceType": "script", // airbnb assumes ESM, while we're CJS From ec3d8d2649ea979d962c6eb7a4cf7e3fe630bbb2 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 29 May 2019 15:37:54 +0200 Subject: [PATCH 205/504] Improve tests timeout in face of failures --- lib/plugins/aws/invokeLocal/index.test.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 355f9636e..880f9a385 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -934,7 +934,8 @@ describe('AwsInvokeLocal', () => { }); describe('context.remainingTimeInMillis', () => { - it('should become lower over time', () => { + it('should become lower over time', function () { + this.timeout(3000); // On Windows it tends to timeout with 2000 awsInvokeLocal.serverless.config.servicePath = __dirname; return awsInvokeLocal.invokeLocalRuby( @@ -948,7 +949,9 @@ describe('AwsInvokeLocal', () => { }); describe('calling a class method', () => { - it('should execute', () => { + it('should execute', function () { + this.timeout(3000); // On Windows it tends to timeout with 2000 + awsInvokeLocal.serverless.config.servicePath = __dirname; return awsInvokeLocal.invokeLocalRuby( From b3e6a3e82e9c3ed0c661deb8c019efa3a5e4f418 Mon Sep 17 00:00:00 2001 From: Camilo Sampedro Date: Thu, 30 May 2019 12:35:12 +0900 Subject: [PATCH 206/504] Adding a validation to validation.js script This new check prevents #6185 from happening. - This only checks that the `claims` object is defined, so it can proceed to do the length check. --- .../aws/package/compile/events/apiGateway/lib/validate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js index 08e13da2f..2b6ced95f 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -283,6 +283,7 @@ module.exports = { if (integration === 'AWS_PROXY' && typeof arn === 'string' && awsArnRegExs.cognitoIdpArnExpr.test(arn) + && claims && claims.length > 0) { const errorMessage = [ 'Cognito claims can only be filtered when using the lambda integration type', From 773e9af3c1c55aae0ffb8af20244d91bc36841dd Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Wed, 15 May 2019 16:27:52 +0200 Subject: [PATCH 207/504] Thorough integration testing --- .eslintrc.js | 1 + .gitignore | 5 +- .travis.yml | 8 +- CONTRIBUTING.md | 4 +- lib/Serverless.test.js | 6 +- lib/classes/CLI.test.js | 4 +- lib/classes/PluginManager.test.js | 19 +- lib/classes/Service.test.js | 8 +- lib/classes/Utils.test.js | 41 +-- lib/classes/Variables.test.js | 28 +- lib/classes/YamlParser.test.js | 12 +- lib/plugins/aws/common/lib/artifacts.test.js | 6 +- .../aws/common/lib/cleanupTempDir.test.js | 4 +- .../awsConfigCredentials.test.js | 4 +- .../aws/deploy/lib/createStack.test.js | 4 +- .../aws/deploy/lib/extendedValidate.test.js | 4 +- .../aws/deploy/lib/uploadArtifacts.test.js | 6 +- lib/plugins/aws/deployFunction/index.test.js | 4 +- lib/plugins/aws/invoke/index.test.js | 10 +- lib/plugins/aws/invokeLocal/index.test.js | 12 +- lib/plugins/aws/lib/updateStack.test.js | 4 +- .../package/compile/functions/index.test.js | 4 +- .../aws/package/compile/layers/index.test.js | 4 +- .../package/lib/generateCoreTemplate.test.js | 4 +- lib/plugins/aws/provider/awsProvider.test.js | 15 +- lib/plugins/create/create.test.js | 4 +- lib/plugins/install/install.test.js | 4 +- lib/plugins/package/lib/zipService.test.js | 4 +- lib/plugins/plugin/install/install.test.js | 17 +- lib/plugins/plugin/lib/utils.test.js | 7 +- .../plugin/uninstall/uninstall.test.js | 13 +- lib/utils/downloadTemplateFromRepo.test.js | 8 +- lib/utils/fs/readFile.test.js | 10 +- lib/utils/fs/readFileSync.test.js | 10 +- lib/utils/fs/walkDirSync.test.js | 8 +- lib/utils/fs/writeFile.test.js | 10 +- lib/utils/fs/writeFileSync.test.js | 12 +- lib/utils/getServerlessConfigFile.test.js | 4 +- lib/utils/renameService.test.js | 8 +- lib/utils/yamlAstParser.test.js | 4 +- package-lock.json | 245 ++++++---------- package.json | 14 +- scripts/integration-test-cleanup.js | 97 ------- tests/.eslintrc.js | 5 + .../api-gateway/service/core.js | 51 ++++ .../api-gateway/service/helper.js} | 21 +- .../api-gateway/service/serverless.yml | 62 ++++ tests/integration-all/api-gateway/tests.js | 267 ++++++++++++++++++ .../tests.js | 58 ++-- .../artifact.zip | Bin .../handler.js | 0 .../individually.yml | 0 .../packaging.tests.js | 23 +- .../serverless.yml | 0 .../api-keys/service/handler.js | 13 - .../api-keys/service/serverless.yml | 16 -- .../api-keys/tests.js | 83 ------ .../cors/service/handler.js | 16 -- .../cors/service/serverless.yml | 27 -- .../integration-lambda-proxy/cors/tests.js | 62 ---- .../custom-authorizers/service/handler.js | 45 --- .../custom-authorizers/service/serverless.yml | 16 -- .../custom-authorizers/tests.js | 62 ---- .../simple-api/service/handler.js | 13 - .../simple-api/service/serverless.yml | 37 --- .../simple-api/tests.js | 139 --------- .../api-keys/service/handler.js | 5 - .../api-keys/service/serverless.yml | 17 -- .../integration-lambda/api-keys/tests.js | 83 ------ .../cors/service/handler.js | 5 - .../cors/service/serverless.yml | 29 -- .../integration-lambda/cors/tests.js | 62 ---- .../custom-authorizers/service/serverless.yml | 17 -- .../custom-authorizers/tests.js | 61 ---- .../simple-api/service/handler.js | 5 - .../simple-api/service/serverless.yml | 61 ---- .../integration-lambda/simple-api/tests.js | 139 --------- .../service/handler.js | 11 - .../service/serverless.yml | 29 -- .../tests.js | 29 -- .../service/handler.js | 6 - .../service/serverless.yml | 18 -- .../multiple-events-single-function/tests.js | 26 -- .../service/handler.js | 11 - .../service/serverless.yml | 21 -- .../single-event-multiple-functions/tests.js | 27 -- .../service/handler.js | 6 - .../service/serverless.yml | 14 - .../single-event-single-function/tests.js | 25 -- .../service/handler.js | 27 -- .../service/serverless.yml | 31 -- .../tests.js | 77 ----- .../service/handler.js | 9 - .../service/serverless.yml | 16 -- .../tests.js | 53 ---- .../service/handler.js | 22 -- .../service/serverless.yml | 19 -- .../tests.js | 39 --- .../service/handler.js | 9 - .../service/serverless.yml | 13 - .../tests.js | 29 -- .../custom-resources/service/handler.js | 5 - .../custom-resources/service/serverless.yml | 20 -- .../aws/general/custom-resources/tests.js | 58 ---- .../environment-variables/service/handler.js | 7 - .../service/serverless.yml | 16 -- .../general/environment-variables/tests.js | 33 --- .../service/deeply/nested/handler.js | 6 - .../nested-handlers/service/serverless.yml | 9 - .../aws/general/nested-handlers/tests.js | 25 -- .../overwrite-resources/service/handler.js | 10 - .../service/serverless.yml | 18 -- .../aws/general/overwrite-resources/tests.js | 42 --- .../aws/general/package/service/handler.js | 5 - .../general/package/service/serverless.yml | 9 - .../integration/aws/general/package/tests.js | 35 --- .../service/handler.js | 11 - .../service/serverless.yml | 17 -- .../tests.js | 28 -- .../service/handler.js | 6 - .../service/serverless.yml | 14 - .../multiple-rules-single-function/tests.js | 27 -- .../service/handler.js | 11 - .../service/serverless.yml | 17 -- .../single-rule-multiple-functions/tests.js | 27 -- .../service/handler.js | 6 - .../service/serverless.yml | 12 - .../iot/single-rule-single-function/tests.js | 25 -- .../service/handler.js | 13 - .../service/serverless.yml | 21 -- .../tests.js | 34 --- .../service/handler.js | 13 - .../service/serverless.yml | 17 -- .../tests.js | 30 -- .../service/handler.js | 7 - .../service/serverless.yml | 14 - .../tests.js | 28 -- .../service/handler.js | 7 - .../service/serverless.yml | 12 - .../tests.js | 26 -- .../service/handler.js | 13 - .../service/serverless.yml | 15 - .../tests.js | 30 -- .../aws/sns/existing-topic/service/handler.js | 7 - .../sns/existing-topic/service/serverless.yml | 20 -- .../aws/sns/existing-topic/tests.js | 38 --- .../service/handler.js | 13 - .../service/serverless.yml | 15 - .../tests.js | 31 -- .../service/handler.js | 7 - .../service/serverless.yml | 12 - .../multiple-topics-single-function/tests.js | 28 -- .../service/handler.js | 13 - .../service/serverless.yml | 15 - .../single-topic-multiple-functions/tests.js | 30 -- .../service/handler.js | 7 - .../service/serverless.yml | 11 - .../sns/single-topic-single-function/tests.js | 26 -- .../general/custom-plugins/service/handler.js | 6 - .../custom-plugins/service/package.json | 5 - .../service/serverless-plugin-greeter/main.js | 26 -- .../serverless-plugin-greeter/package.json | 5 - .../custom-plugins/service/serverless.yml | 12 - .../general/custom-plugins/tests.js | 36 --- .../service/.serverless_plugins/one.js | 27 -- .../service/.serverless_plugins/two/index.js | 27 -- .../general/local-plugins/service/handler.js | 6 - .../local-plugins/service/serverless.yml | 13 - .../general/local-plugins/tests.js | 25 -- tests/setup-tests.js | 3 + tests/setupTests.js | 5 - ...{test_all_templates => test-all-templates} | 0 tests/utils/api-gateway/index.js | 65 +++++ tests/utils/aws-cleanup.js | 90 ++++++ tests/utils/cloudformation/index.js | 74 +++++ tests/utils/cloudwatch/index.js | 22 ++ tests/utils/cognito/index.js | 33 +++ tests/utils/fs/index.js | 47 +++ tests/utils/index.js | 244 ---------------- tests/utils/index.test.js | 47 --- tests/utils/iot/index.js | 24 ++ tests/utils/misc/index.js | 118 ++++++++ tests/utils/plugins/index.js | 30 ++ tests/utils/s3/index.js | 54 ++++ tests/utils/sns/index.js | 53 ++++ 185 files changed, 1326 insertions(+), 3590 deletions(-) delete mode 100644 scripts/integration-test-cleanup.js create mode 100644 tests/.eslintrc.js create mode 100644 tests/integration-all/api-gateway/service/core.js rename tests/{integration/aws/api-gateway/integration-lambda/custom-authorizers/service/handler.js => integration-all/api-gateway/service/helper.js} (63%) create mode 100644 tests/integration-all/api-gateway/service/serverless.yml create mode 100644 tests/integration-all/api-gateway/tests.js rename tests/{simple-suite => integration-basic}/tests.js (70%) rename tests/{packaging-suite => integration-package}/artifact.zip (100%) rename tests/{packaging-suite => integration-package}/handler.js (100%) rename tests/{packaging-suite => integration-package}/individually.yml (100%) rename tests/{packaging-suite => integration-package}/packaging.tests.js (90%) rename tests/{packaging-suite => integration-package}/serverless.yml (100%) delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/handler.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/tests.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/handler.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/cors/tests.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/handler.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/tests.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/handler.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/serverless.yml delete mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/tests.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/api-keys/service/handler.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/api-keys/service/serverless.yml delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/api-keys/tests.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/cors/service/handler.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/cors/service/serverless.yml delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/cors/tests.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/serverless.yml delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/tests.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/simple-api/service/handler.js delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/simple-api/service/serverless.yml delete mode 100644 tests/integration/aws/api-gateway/integration-lambda/simple-api/tests.js delete mode 100644 tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/handler.js delete mode 100644 tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/serverless.yml delete mode 100644 tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/tests.js delete mode 100644 tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/handler.js delete mode 100644 tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/serverless.yml delete mode 100644 tests/integration/aws/cloud-watch-event/multiple-events-single-function/tests.js delete mode 100644 tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/handler.js delete mode 100644 tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/serverless.yml delete mode 100644 tests/integration/aws/cloud-watch-event/single-event-multiple-functions/tests.js delete mode 100644 tests/integration/aws/cloud-watch-event/single-event-single-function/service/handler.js delete mode 100644 tests/integration/aws/cloud-watch-event/single-event-single-function/service/serverless.yml delete mode 100644 tests/integration/aws/cloud-watch-event/single-event-single-function/tests.js delete mode 100644 tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/handler.js delete mode 100644 tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml delete mode 100644 tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/tests.js delete mode 100644 tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/handler.js delete mode 100644 tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml delete mode 100644 tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/tests.js delete mode 100644 tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/handler.js delete mode 100644 tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml delete mode 100644 tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/tests.js delete mode 100644 tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/handler.js delete mode 100644 tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml delete mode 100644 tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/tests.js delete mode 100644 tests/integration/aws/general/custom-resources/service/handler.js delete mode 100644 tests/integration/aws/general/custom-resources/service/serverless.yml delete mode 100644 tests/integration/aws/general/custom-resources/tests.js delete mode 100644 tests/integration/aws/general/environment-variables/service/handler.js delete mode 100644 tests/integration/aws/general/environment-variables/service/serverless.yml delete mode 100644 tests/integration/aws/general/environment-variables/tests.js delete mode 100644 tests/integration/aws/general/nested-handlers/service/deeply/nested/handler.js delete mode 100644 tests/integration/aws/general/nested-handlers/service/serverless.yml delete mode 100644 tests/integration/aws/general/nested-handlers/tests.js delete mode 100644 tests/integration/aws/general/overwrite-resources/service/handler.js delete mode 100644 tests/integration/aws/general/overwrite-resources/service/serverless.yml delete mode 100644 tests/integration/aws/general/overwrite-resources/tests.js delete mode 100644 tests/integration/aws/general/package/service/handler.js delete mode 100644 tests/integration/aws/general/package/service/serverless.yml delete mode 100644 tests/integration/aws/general/package/tests.js delete mode 100644 tests/integration/aws/iot/multiple-rules-multiple-functions/service/handler.js delete mode 100644 tests/integration/aws/iot/multiple-rules-multiple-functions/service/serverless.yml delete mode 100644 tests/integration/aws/iot/multiple-rules-multiple-functions/tests.js delete mode 100644 tests/integration/aws/iot/multiple-rules-single-function/service/handler.js delete mode 100644 tests/integration/aws/iot/multiple-rules-single-function/service/serverless.yml delete mode 100644 tests/integration/aws/iot/multiple-rules-single-function/tests.js delete mode 100644 tests/integration/aws/iot/single-rule-multiple-functions/service/handler.js delete mode 100644 tests/integration/aws/iot/single-rule-multiple-functions/service/serverless.yml delete mode 100644 tests/integration/aws/iot/single-rule-multiple-functions/tests.js delete mode 100644 tests/integration/aws/iot/single-rule-single-function/service/handler.js delete mode 100644 tests/integration/aws/iot/single-rule-single-function/service/serverless.yml delete mode 100644 tests/integration/aws/iot/single-rule-single-function/tests.js delete mode 100644 tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/handler.js delete mode 100644 tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/serverless.yml delete mode 100644 tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/tests.js delete mode 100644 tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/handler.js delete mode 100644 tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/serverless.yml delete mode 100644 tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/tests.js delete mode 100644 tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/handler.js delete mode 100644 tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/serverless.yml delete mode 100644 tests/integration/aws/s3/multiple-events-single-function-single-bucket/tests.js delete mode 100644 tests/integration/aws/s3/single-event-single-function-single-bucket/service/handler.js delete mode 100644 tests/integration/aws/s3/single-event-single-function-single-bucket/service/serverless.yml delete mode 100644 tests/integration/aws/s3/single-event-single-function-single-bucket/tests.js delete mode 100644 tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/handler.js delete mode 100644 tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/serverless.yml delete mode 100644 tests/integration/aws/schedule/multiple-schedules-multiple-functions/tests.js delete mode 100644 tests/integration/aws/sns/existing-topic/service/handler.js delete mode 100644 tests/integration/aws/sns/existing-topic/service/serverless.yml delete mode 100644 tests/integration/aws/sns/existing-topic/tests.js delete mode 100644 tests/integration/aws/sns/multiple-topics-multiple-functions/service/handler.js delete mode 100644 tests/integration/aws/sns/multiple-topics-multiple-functions/service/serverless.yml delete mode 100644 tests/integration/aws/sns/multiple-topics-multiple-functions/tests.js delete mode 100644 tests/integration/aws/sns/multiple-topics-single-function/service/handler.js delete mode 100644 tests/integration/aws/sns/multiple-topics-single-function/service/serverless.yml delete mode 100644 tests/integration/aws/sns/multiple-topics-single-function/tests.js delete mode 100644 tests/integration/aws/sns/single-topic-multiple-functions/service/handler.js delete mode 100644 tests/integration/aws/sns/single-topic-multiple-functions/service/serverless.yml delete mode 100644 tests/integration/aws/sns/single-topic-multiple-functions/tests.js delete mode 100644 tests/integration/aws/sns/single-topic-single-function/service/handler.js delete mode 100644 tests/integration/aws/sns/single-topic-single-function/service/serverless.yml delete mode 100644 tests/integration/aws/sns/single-topic-single-function/tests.js delete mode 100644 tests/integration/general/custom-plugins/service/handler.js delete mode 100644 tests/integration/general/custom-plugins/service/package.json delete mode 100644 tests/integration/general/custom-plugins/service/serverless-plugin-greeter/main.js delete mode 100644 tests/integration/general/custom-plugins/service/serverless-plugin-greeter/package.json delete mode 100644 tests/integration/general/custom-plugins/service/serverless.yml delete mode 100644 tests/integration/general/custom-plugins/tests.js delete mode 100644 tests/integration/general/local-plugins/service/.serverless_plugins/one.js delete mode 100644 tests/integration/general/local-plugins/service/.serverless_plugins/two/index.js delete mode 100644 tests/integration/general/local-plugins/service/handler.js delete mode 100644 tests/integration/general/local-plugins/service/serverless.yml delete mode 100644 tests/integration/general/local-plugins/tests.js create mode 100644 tests/setup-tests.js delete mode 100644 tests/setupTests.js rename tests/templates/{test_all_templates => test-all-templates} (100%) create mode 100644 tests/utils/api-gateway/index.js create mode 100644 tests/utils/aws-cleanup.js create mode 100644 tests/utils/cloudformation/index.js create mode 100644 tests/utils/cloudwatch/index.js create mode 100644 tests/utils/cognito/index.js create mode 100644 tests/utils/fs/index.js delete mode 100644 tests/utils/index.js delete mode 100644 tests/utils/index.test.js create mode 100644 tests/utils/iot/index.js create mode 100644 tests/utils/misc/index.js create mode 100644 tests/utils/plugins/index.js create mode 100644 tests/utils/s3/index.js create mode 100644 tests/utils/sns/index.js diff --git a/.eslintrc.js b/.eslintrc.js index 60abf1402..4218e236c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,6 +3,7 @@ module.exports = { "extends": "airbnb", "plugins": [], "rules": { + "arrow-body-style": "off", "func-names": "off", "global-require": "off", // Interfers with optional and eventual circular references "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/scripts/**", "**/tests/**"]}], diff --git a/.gitignore b/.gitignore index 79c552a12..d62ad120c 100755 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ dist # Directory for instrumented libs generated by jscoverage/JSCover lib-cov -# Coverage directory used by tools like istanbul +# Coverage directory used by tools like nyc coverage /.nyc_output @@ -59,3 +59,6 @@ jest # DotNet obj/ [Oo]bj/ + +# Tests +!tests/**/*.zip diff --git a/.travis.yml b/.travis.yml index 0e7d73b41..75084c208 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,6 @@ matrix: node_js: "10.7" env: - INTEGRATION_TEST=true - - INTEGRATION_TEST_SUITE=simple - SLS_IGNORE_WARNING=* - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= @@ -44,8 +43,11 @@ script: - if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi - if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then npm run lint; fi - - if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$INTEGRATION_TEST_SUITE" - == "simple" ]]; then npm run simple-integration-test; fi + - if [[ ! -z "$INTEGRATION_TEST" ]]; then npm run integration-test-run-package; fi + - if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} ]]; then npm run integration-test-run-basic; fi + - if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$TRAVIS_BRANCH" + == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]]; then npm run integration-test-run-all; fi + - if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} ]]; then npm run integration-test-cleanup; fi after_success: - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage deploy: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dacac1823..6f71d94c5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,7 +65,7 @@ If you add a new template or want to test a template after changing it you can r To run all integration tests run: ``` -./tests/templates/test_all_templates +./tests/templates/test-all-templates ``` To run only a specific integration test run: @@ -80,7 +80,7 @@ so for example: tests/templates/integration-test-template aws-java-maven mvn package ``` -If you add a new template make sure to add it to the `test_all_templates` file and configure the `docker-compose.yml` file for your template. +If you add a new template make sure to add it to the `test-all-templates` file and configure the `docker-compose.yml` file for your template. # Our Code of Conduct diff --git a/lib/Serverless.test.js b/lib/Serverless.test.js index a1c42707c..14d33e321 100644 --- a/lib/Serverless.test.js +++ b/lib/Serverless.test.js @@ -13,8 +13,8 @@ const PluginManager = require('../lib/classes/PluginManager'); const Utils = require('../lib/classes/Utils'); const Service = require('../lib/classes/Service'); const CLI = require('../lib/classes/CLI'); -const ServerlessError = require('../lib/classes/Error').ServerlessError; -const testUtils = require('../tests/utils'); +const { ServerlessError } = require('../lib/classes/Error'); +const { getTmpDirPath } = require('../tests/utils/fs'); describe('Serverless', () => { let serverless; @@ -157,7 +157,7 @@ describe('Serverless', () => { it('should resolve after loading the service', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const serverlessYml = { service: 'new-service', provider: 'aws', diff --git a/lib/classes/CLI.test.js b/lib/classes/CLI.test.js index 06bc86e33..cf88a0d7e 100644 --- a/lib/classes/CLI.test.js +++ b/lib/classes/CLI.test.js @@ -9,7 +9,7 @@ const exec = require('child_process').exec; const path = require('path'); const stripAnsi = require('strip-ansi'); const Serverless = require('../../lib/Serverless'); -const testUtils = require('../../tests/utils'); +const { getTmpDirPath } = require('../../tests/utils/fs'); describe('CLI', () => { let cli; @@ -613,7 +613,7 @@ describe('CLI', () => { const that = this; before(() => { - const tmpDir = testUtils.getTmpDirPath(); + const tmpDir = getTmpDirPath(); that.cwd = process.cwd(); diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index b6e978d0a..53dbc1cc4 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -12,14 +12,15 @@ const _ = require('lodash'); const path = require('path'); const fs = require('fs'); const fse = require('fs-extra'); -const execSync = require('child_process').execSync; const mockRequire = require('mock-require'); -const testUtils = require('../../tests/utils'); const os = require('os'); const sinon = require('sinon'); const proxyquire = require('proxyquire'); const BbPromise = require('bluebird'); const getCacheFilePath = require('../utils/getCacheFilePath'); +const { execSync } = require('child_process'); +const { installPlugin } = require('../../tests/utils/plugins'); +const { getTmpDirPath } = require('../../tests/utils/fs'); chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); @@ -1879,7 +1880,7 @@ describe('PluginManager', () => { let serviceDir; let tmpDir; beforeEach(function () { // eslint-disable-line prefer-arrow-callback - tmpDir = testUtils.getTmpDirPath(); + tmpDir = getTmpDirPath(); serviceDir = path.join(tmpDir, 'service'); fse.mkdirsSync(serviceDir); process.chdir(serviceDir); @@ -1888,7 +1889,7 @@ describe('PluginManager', () => { it('should load plugins from .serverless_plugins', () => { const localPluginDir = path.join(serviceDir, '.serverless_plugins', 'local-plugin'); - testUtils.installPlugin(localPluginDir, SynchronousPluginMock); + installPlugin(localPluginDir, SynchronousPluginMock); pluginManager.loadServicePlugins(['local-plugin']); expect(pluginManager.plugins).to.satisfy(plugins => @@ -1897,7 +1898,7 @@ describe('PluginManager', () => { it('should load plugins from custom folder', () => { const localPluginDir = path.join(serviceDir, 'serverless-plugins-custom', 'local-plugin'); - testUtils.installPlugin(localPluginDir, SynchronousPluginMock); + installPlugin(localPluginDir, SynchronousPluginMock); pluginManager.loadServicePlugins({ localPath: path.join(serviceDir, 'serverless-plugins-custom'), @@ -1912,7 +1913,7 @@ describe('PluginManager', () => { it('should load plugins from custom folder outside of serviceDir', () => { serviceDir = path.join(tmpDir, 'serverless-plugins-custom'); const localPluginDir = path.join(serviceDir, 'local-plugin'); - testUtils.installPlugin(localPluginDir, SynchronousPluginMock); + installPlugin(localPluginDir, SynchronousPluginMock); pluginManager.loadServicePlugins({ localPath: serviceDir, @@ -1949,7 +1950,7 @@ describe('PluginManager', () => { const execPrefix = os.platform() === 'win32' ? 'node ' : ''; serverlessExec = execPrefix + path.join(serverlessInstance.config.serverlessPath, '..', 'bin', 'serverless'); - const tmpDir = testUtils.getTmpDirPath(); + const tmpDir = getTmpDirPath(); serviceDir = path.join(tmpDir, 'service'); fse.mkdirsSync(serviceDir); process.chdir(serviceDir); @@ -1968,8 +1969,8 @@ describe('PluginManager', () => { it('should load plugins relatively to the working directory', () => { const localPluginDir = path.join(serviceDir, 'node_modules', 'local-plugin'); const parentPluginDir = path.join(serviceDir, '..', 'node_modules', 'parent-plugin'); - testUtils.installPlugin(localPluginDir, SynchronousPluginMock); - testUtils.installPlugin(parentPluginDir, PromisePluginMock); + installPlugin(localPluginDir, SynchronousPluginMock); + installPlugin(parentPluginDir, PromisePluginMock); fs.appendFileSync(path.join(serviceDir, 'serverless.yml'), 'plugins:\n - local-plugin\n - parent-plugin'); diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index fd77a8eed..746fcb532 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -8,7 +8,7 @@ const sinon = require('sinon'); const Service = require('../../lib/classes/Service'); const Utils = require('../../lib/classes/Utils'); const Serverless = require('../../lib/Serverless'); -const testUtils = require('../../tests/utils'); +const { getTmpDirPath } = require('../../tests/utils/fs'); // Configure chai chai.use(require('chai-as-promised')); @@ -113,7 +113,7 @@ describe('Service', () => { let tmpDirPath; beforeEach(() => { - tmpDirPath = testUtils.getTmpDirPath(); + tmpDirPath = getTmpDirPath(); }); it('should resolve if no servicePath is found', () => { @@ -696,7 +696,7 @@ describe('Service', () => { let tmpDirPath; beforeEach(() => { - tmpDirPath = testUtils.getTmpDirPath(); + tmpDirPath = getTmpDirPath(); }); it('should throw if a function\'s event is not an array or a variable', () => { @@ -972,7 +972,7 @@ describe('Service', () => { let tmpDirPath; beforeEach(() => { - tmpDirPath = testUtils.getTmpDirPath(); + tmpDirPath = getTmpDirPath(); }); it('should make sure function name contains the default stage', () => { diff --git a/lib/classes/Utils.test.js b/lib/classes/Utils.test.js index aa0f2d49f..2ba1bb696 100644 --- a/lib/classes/Utils.test.js +++ b/lib/classes/Utils.test.js @@ -6,13 +6,14 @@ const uuid = require('uuid'); const chai = require('chai'); const sinon = require('sinon'); const Serverless = require('../../lib/Serverless'); -const testUtils = require('../../tests/utils'); const configUtils = require('../utils/config'); -const serverlessVersion = require('../../package.json').version; const segment = require('../utils/segment'); -chai.use(require('chai-as-promised')); -const expect = require('chai').expect; const Utils = require('../../lib/classes/Utils'); +const { expect } = require('chai'); +const { getTmpFilePath, getTmpDirPath } = require('../../tests/utils/fs'); +const serverlessVersion = require('../../package.json').version; + +chai.use(require('chai-as-promised')); describe('Utils', () => { let utils; @@ -53,7 +54,7 @@ describe('Utils', () => { describe('#writeFileDir()', () => { it('should create a directory for the path of the given file', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const rootDir = serverless.utils .writeFileDir(path.join(tmpDirPath, 'foo', 'bar', 'somefile.js')); @@ -66,7 +67,7 @@ describe('Utils', () => { describe('#writeFileSync()', () => { it('should write a .json file synchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + const tmpFilePath = getTmpFilePath('anything.json'); serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' }); const obj = serverless.utils.readFileSync(tmpFilePath); @@ -75,7 +76,7 @@ describe('Utils', () => { }); it('should write a .yml file synchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yml'); + const tmpFilePath = getTmpFilePath('anything.yml'); serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' }); @@ -85,7 +86,7 @@ describe('Utils', () => { }); it('should write a .yaml file synchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yaml'); + const tmpFilePath = getTmpFilePath('anything.yaml'); serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' }); @@ -101,7 +102,7 @@ describe('Utils', () => { describe('#writeFile()', () => { it('should write a file asynchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + const tmpFilePath = getTmpFilePath('anything.json'); // note: use return when testing promises otherwise you'll have unhandled rejection errors return expect(serverless.utils.writeFile(tmpFilePath, { foo: 'bar' })) @@ -115,7 +116,7 @@ describe('Utils', () => { describe('#appendFileSync()', () => { it('should append a line to a text file', () => { - const tmpFilePath = testUtils.getTmpFilePath('appendedfile.txt'); + const tmpFilePath = getTmpFilePath('appendedfile.txt'); serverless.utils.writeFileSync(tmpFilePath, `line 1 ${os.EOL}`); serverless.utils.appendFileSync(tmpFilePath, 'line 2'); @@ -131,7 +132,7 @@ describe('Utils', () => { describe('#readFileSync()', () => { it('should read a file synchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + const tmpFilePath = getTmpFilePath('anything.json'); serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' }); const obj = serverless.utils.readFileSync(tmpFilePath); @@ -140,7 +141,7 @@ describe('Utils', () => { }); it('should read a filename extension .yml', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yml'); + const tmpFilePath = getTmpFilePath('anything.yml'); serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' }); const obj = serverless.utils.readFileSync(tmpFilePath); @@ -149,7 +150,7 @@ describe('Utils', () => { }); it('should read a filename extension .yaml', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yaml'); + const tmpFilePath = getTmpFilePath('anything.yaml'); serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' }); const obj = serverless.utils.readFileSync(tmpFilePath); @@ -158,7 +159,7 @@ describe('Utils', () => { }); it('should throw YAMLException with filename if yml file is invalid format', () => { - const tmpFilePath = testUtils.getTmpFilePath('invalid.yml'); + const tmpFilePath = getTmpFilePath('invalid.yml'); serverless.utils.writeFileSync(tmpFilePath, ': a'); @@ -170,7 +171,7 @@ describe('Utils', () => { describe('#readFile()', () => { it('should read a file asynchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + const tmpFilePath = getTmpFilePath('anything.json'); serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' }); @@ -183,7 +184,7 @@ describe('Utils', () => { describe('#walkDirSync()', () => { it('should return an array with corresponding paths to the found files', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const nestedDir1 = path.join(tmpDirPath, 'foo'); const nestedDir2 = path.join(tmpDirPath, 'foo', 'bar'); @@ -246,7 +247,7 @@ describe('Utils', () => { const testDir = process.cwd(); it('should detect if the CWD is a service directory when using Serverless .yaml files', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const tmpFilePath = path.join(tmpDirPath, 'serverless.yaml'); serverless.utils.writeFileSync(tmpFilePath, 'foo'); @@ -258,7 +259,7 @@ describe('Utils', () => { }); it('should detect if the CWD is a service directory when using Serverless .yml files', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const tmpFilePath = path.join(tmpDirPath, 'serverless.yml'); serverless.utils.writeFileSync(tmpFilePath, 'foo'); @@ -270,7 +271,7 @@ describe('Utils', () => { }); it('should detect if the CWD is a service directory when using Serverless .json files', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const tmpFilePath = path.join(tmpDirPath, 'serverless.json'); serverless.utils.writeFileSync(tmpFilePath, 'foo'); @@ -282,7 +283,7 @@ describe('Utils', () => { }); it('should detect if the CWD is a service directory when using Serverless .js files', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const tmpFilePath = path.join(tmpDirPath, 'serverless.js'); serverless.utils.writeFileSync(tmpFilePath, 'foo'); diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 7da83663b..b655df29c 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -16,9 +16,9 @@ const AwsProvider = require('../plugins/aws/provider/awsProvider'); const fse = require('../utils/fs/fse'); const Serverless = require('../../lib/Serverless'); const slsError = require('./Error'); -const testUtils = require('../../tests/utils'); const Utils = require('../../lib/classes/Utils'); const Variables = require('../../lib/classes/Variables'); +const { getTmpDirPath } = require('../../tests/utils/fs'); BbPromise.longStackTraces(true); @@ -932,7 +932,7 @@ describe('Variables', () => { describe('file reading cases', () => { let tmpDirPath; beforeEach(() => { - tmpDirPath = testUtils.getTmpDirPath(); + tmpDirPath = getTmpDirPath(); fse.mkdirsSync(tmpDirPath); serverless.config.update({ servicePath: tmpDirPath }); }); @@ -1622,7 +1622,7 @@ module.exports = { it('should populate an entire variable file', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const configYml = { test: 1, test2: 'test2', @@ -1638,7 +1638,7 @@ module.exports = { }); it('should get undefined if non existing file and the second argument is true', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); serverless.config.update({ servicePath: tmpDirPath }); const realpathSync = sinon.spy(fse, 'realpathSync'); const existsSync = sinon.spy(fse, 'existsSync'); @@ -1656,7 +1656,7 @@ module.exports = { it('should populate non json/yml files', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), 'hello world'); serverless.config.update({ servicePath: tmpDirPath }); return serverless.variables.getValueFromFile('file(./someFile)') @@ -1665,7 +1665,7 @@ module.exports = { it('should populate symlinks', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const realFilePath = path.join(tmpDirPath, 'someFile'); const symlinkPath = path.join(tmpDirPath, 'refSomeFile'); SUtils.writeFileSync(realFilePath, 'hello world'); @@ -1681,7 +1681,7 @@ module.exports = { it('should trim trailing whitespace and new line character', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), 'hello world \n'); serverless.config.update({ servicePath: tmpDirPath }); return serverless.variables.getValueFromFile('file(./someFile)') @@ -1690,7 +1690,7 @@ module.exports = { it('should populate from another file when variable is of any type', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const configYml = { test0: 0, test1: 'test1', @@ -1707,7 +1707,7 @@ module.exports = { it('should populate from a javascript file', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const jsData = 'module.exports.hello=function(){return "hello world";};'; SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); serverless.config.update({ servicePath: tmpDirPath }); @@ -1717,7 +1717,7 @@ module.exports = { it('should populate an entire variable exported by a javascript file', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const jsData = 'module.exports=function(){return { hello: "hello world" };};'; SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); serverless.config.update({ servicePath: tmpDirPath }); @@ -1727,7 +1727,7 @@ module.exports = { it('should throw if property exported by a javascript file is not a function', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const jsData = 'module.exports={ hello: "hello world" };'; SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); serverless.config.update({ servicePath: tmpDirPath }); @@ -1737,7 +1737,7 @@ module.exports = { it('should populate deep object from a javascript file', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const jsData = `module.exports.hello=function(){ return {one:{two:{three: 'hello world'}}} };`; @@ -1750,7 +1750,7 @@ module.exports = { it('should preserve the exported function context when executing', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const jsData = ` module.exports.one = {two: {three: 'hello world'}} module.exports.hello=function(){ return this; };`; @@ -1763,7 +1763,7 @@ module.exports = { it('should file variable not using ":" syntax', () => { const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const configYml = { test: 1, test2: 'test2', diff --git a/lib/classes/YamlParser.test.js b/lib/classes/YamlParser.test.js index c64331f10..71742ae86 100644 --- a/lib/classes/YamlParser.test.js +++ b/lib/classes/YamlParser.test.js @@ -8,7 +8,7 @@ const chai = require('chai'); const YAML = require('js-yaml'); const path = require('path'); const Serverless = require('../../lib/Serverless'); -const testUtils = require('../../tests/utils'); +const { getTmpFilePath, getTmpDirPath } = require('../../tests/utils/fs'); // Configure chai chai.use(require('chai-as-promised')); @@ -19,7 +19,7 @@ const serverless = new Serverless(); describe('YamlParser', () => { describe('#parse()', () => { it('should parse a simple .yaml file', () => { - const tmpFilePath = testUtils.getTmpFilePath('simple.yaml'); + const tmpFilePath = getTmpFilePath('simple.yaml'); serverless.utils.writeFileSync(tmpFilePath, YAML.dump({ foo: 'bar' })); @@ -28,7 +28,7 @@ describe('YamlParser', () => { }); it('should parse a simple .yml file', () => { - const tmpFilePath = testUtils.getTmpFilePath('simple.yml'); + const tmpFilePath = getTmpFilePath('simple.yml'); serverless.utils.writeFileSync(tmpFilePath, YAML.dump({ foo: 'bar' })); @@ -37,7 +37,7 @@ describe('YamlParser', () => { }); it('should parse a .yml file with JSON-REF to YAML', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); serverless.utils.writeFileSync(path.join(tmpDirPath, 'ref.yml'), { foo: 'bar' }); @@ -54,7 +54,7 @@ describe('YamlParser', () => { }); it('should parse a .yml file with JSON-REF to JSON', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); serverless.utils.writeFileSync(path.join(tmpDirPath, 'ref.json'), { foo: 'bar' }); @@ -71,7 +71,7 @@ describe('YamlParser', () => { }); it('should parse a .yml file with recursive JSON-REF', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); serverless.utils.writeFileSync(path.join(tmpDirPath, 'three.yml'), { foo: 'bar' }); diff --git a/lib/plugins/aws/common/lib/artifacts.test.js b/lib/plugins/aws/common/lib/artifacts.test.js index b61f6fc00..b898d7907 100644 --- a/lib/plugins/aws/common/lib/artifacts.test.js +++ b/lib/plugins/aws/common/lib/artifacts.test.js @@ -5,12 +5,12 @@ const path = require('path'); const fse = require('fs-extra'); const AWSCommon = require('../index'); const Serverless = require('../../../../../lib/Serverless'); -const testUtils = require('../../../../../tests/utils'); +const { getTmpDirPath } = require('../../../../../tests/utils/fs'); describe('#moveArtifactsToPackage()', () => { let serverless; let awsCommon; - const moveBasePath = path.join(testUtils.getTmpDirPath(), 'move'); + const moveBasePath = path.join(getTmpDirPath(), 'move'); const moveServerlessPath = path.join(moveBasePath, '.serverless'); beforeEach(() => { @@ -99,7 +99,7 @@ describe('#moveArtifactsToPackage()', () => { describe('#moveArtifactsToTemp()', () => { let serverless; let awsCommon; - const moveBasePath = path.join(testUtils.getTmpDirPath(), 'move'); + const moveBasePath = path.join(getTmpDirPath(), 'move'); const moveServerlessPath = path.join(moveBasePath, '.serverless'); const moveTargetPath = path.join(moveBasePath, 'target'); diff --git a/lib/plugins/aws/common/lib/cleanupTempDir.test.js b/lib/plugins/aws/common/lib/cleanupTempDir.test.js index 5eb71126c..680f4ceba 100644 --- a/lib/plugins/aws/common/lib/cleanupTempDir.test.js +++ b/lib/plugins/aws/common/lib/cleanupTempDir.test.js @@ -4,7 +4,7 @@ const expect = require('chai').expect; const path = require('path'); const Package = require('../index'); const Serverless = require('../../../../../lib/Serverless'); -const testUtils = require('../../../../../tests/utils'); +const { getTmpDirPath } = require('../../../../../tests/utils/fs'); describe('#cleanupTempDir()', () => { let serverless; @@ -14,7 +14,7 @@ describe('#cleanupTempDir()', () => { serverless = new Serverless(); packageService = new Package(serverless); - serverless.config.servicePath = testUtils.getTmpDirPath(); + serverless.config.servicePath = getTmpDirPath(); }); it('should remove .serverless in the service directory', () => { diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index 025295f1b..ec282dc08 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -7,9 +7,9 @@ const fs = require('fs'); const fse = require('fs-extra'); const os = require('os'); const path = require('path'); -const testUtils = require('../../../../tests/utils'); const AwsConfigCredentials = require('./awsConfigCredentials'); const Serverless = require('../../../Serverless'); +const { getTmpDirPath } = require('../../../../tests/utils/fs'); describe('AwsConfigCredentials', () => { let awsConfigCredentials; @@ -22,7 +22,7 @@ describe('AwsConfigCredentials', () => { beforeEach(() => { sandbox = sinon.sandbox.create(); - tmpDirPath = testUtils.getTmpDirPath(); + tmpDirPath = getTmpDirPath(); credentialsFilePath = path.join(tmpDirPath, '.aws', 'credentials'); credentialsFileContent = '[my-profile]\n'; credentialsFileContent += 'aws_access_key_id = my-old-profile-key\n'; diff --git a/lib/plugins/aws/deploy/lib/createStack.test.js b/lib/plugins/aws/deploy/lib/createStack.test.js index 467061309..1f9d97262 100644 --- a/lib/plugins/aws/deploy/lib/createStack.test.js +++ b/lib/plugins/aws/deploy/lib/createStack.test.js @@ -6,12 +6,12 @@ const path = require('path'); const AwsProvider = require('../../provider/awsProvider'); const AwsDeploy = require('../index'); const Serverless = require('../../../../Serverless'); -const testUtils = require('../../../../../tests/utils'); +const { getTmpDirPath } = require('../../../../../tests/utils/fs'); describe('createStack', () => { let awsDeploy; let sandbox; - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const serverlessYmlPath = path.join(tmpDirPath, 'serverless.yml'); const serverlessYml = { diff --git a/lib/plugins/aws/deploy/lib/extendedValidate.test.js b/lib/plugins/aws/deploy/lib/extendedValidate.test.js index 7cbabdc5f..c34160510 100644 --- a/lib/plugins/aws/deploy/lib/extendedValidate.test.js +++ b/lib/plugins/aws/deploy/lib/extendedValidate.test.js @@ -6,7 +6,7 @@ const path = require('path'); const AwsProvider = require('../../provider/awsProvider'); const AwsDeploy = require('../index'); const Serverless = require('../../../../Serverless'); -const testUtils = require('../../../../../tests/utils'); +const { getTmpDirPath } = require('../../../../../tests/utils/fs'); chai.use(require('sinon-chai')); @@ -14,7 +14,7 @@ const expect = chai.expect; describe('extendedValidate', () => { let awsDeploy; - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const serverlessYmlPath = path.join(tmpDirPath, 'serverless.yml'); const serverlessYml = { diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js index 532a8bac2..bdc72583c 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js @@ -11,7 +11,7 @@ const normalizeFiles = require('../../lib/normalizeFiles'); const AwsProvider = require('../../provider/awsProvider'); const AwsDeploy = require('../index'); const Serverless = require('../../../../Serverless'); -const testUtils = require('../../../../../tests/utils'); +const { getTmpDirPath } = require('../../../../../tests/utils/fs'); chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); @@ -171,7 +171,7 @@ describe('uploadArtifacts', () => { it('should upload the .zip file to the S3 bucket', () => { cryptoStub.createHash().update().digest.onCall(0).returns('local-hash-zip-file'); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const artifactFilePath = path.join(tmpDirPath, 'artifact.zip'); serverless.utils.writeFileSync(artifactFilePath, 'artifact.zip file content'); @@ -197,7 +197,7 @@ describe('uploadArtifacts', () => { it('should upload the .zip file to a bucket with SSE bucket policy', () => { cryptoStub.createHash().update().digest.onCall(0).returns('local-hash-zip-file'); - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const artifactFilePath = path.join(tmpDirPath, 'artifact.zip'); serverless.utils.writeFileSync(artifactFilePath, 'artifact.zip file content'); awsDeploy.serverless.service.provider.deploymentBucketObject = { diff --git a/lib/plugins/aws/deployFunction/index.test.js b/lib/plugins/aws/deployFunction/index.test.js index 015e930b0..d456cc390 100644 --- a/lib/plugins/aws/deployFunction/index.test.js +++ b/lib/plugins/aws/deployFunction/index.test.js @@ -7,7 +7,7 @@ const fs = require('fs'); const proxyquire = require('proxyquire'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); -const testUtils = require('../../../../tests/utils'); +const { getTmpDirPath } = require('../../../../tests/utils/fs'); chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); @@ -442,7 +442,7 @@ describe('AwsDeployFunction', () => { beforeEach(() => { // write a file to disc to simulate that the deployment artifact exists - awsDeployFunction.packagePath = testUtils.getTmpDirPath(); + awsDeployFunction.packagePath = getTmpDirPath(); artifactFilePath = path.join(awsDeployFunction.packagePath, 'first.zip'); serverless.utils.writeFileSync(artifactFilePath, 'first.zip file content'); updateFunctionCodeStub = sinon diff --git a/lib/plugins/aws/invoke/index.test.js b/lib/plugins/aws/invoke/index.test.js index 3e1211ef4..2716dca8e 100644 --- a/lib/plugins/aws/invoke/index.test.js +++ b/lib/plugins/aws/invoke/index.test.js @@ -6,7 +6,7 @@ const path = require('path'); const proxyquire = require('proxyquire'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); -const testUtils = require('../../../../tests/utils'); +const { getTmpDirPath } = require('../../../../tests/utils/fs'); chai.use(require('chai-as-promised')); @@ -143,7 +143,7 @@ describe('AwsInvoke', () => { }); it('it should parse file if relative file path is provided', () => { - serverless.config.servicePath = testUtils.getTmpDirPath(); + serverless.config.servicePath = getTmpDirPath(); const data = { testProp: 'testValue', }; @@ -158,7 +158,7 @@ describe('AwsInvoke', () => { }); it('it should parse file if absolute file path is provided', () => { - serverless.config.servicePath = testUtils.getTmpDirPath(); + serverless.config.servicePath = getTmpDirPath(); const data = { testProp: 'testValue', }; @@ -173,7 +173,7 @@ describe('AwsInvoke', () => { }); it('it should parse a yaml file if file path is provided', () => { - serverless.config.servicePath = testUtils.getTmpDirPath(); + serverless.config.servicePath = getTmpDirPath(); const yamlContent = 'testProp: testValue'; serverless.utils.writeFileSync(path @@ -194,7 +194,7 @@ describe('AwsInvoke', () => { }); it('it should throw error if file path does not exist', () => { - serverless.config.servicePath = testUtils.getTmpDirPath(); + serverless.config.servicePath = getTmpDirPath(); awsInvoke.options.path = 'some/path'; return expect(awsInvoke.extendedValidate()) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 880f9a385..0dcca6430 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -12,7 +12,7 @@ const stripAnsi = require('strip-ansi'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); -const testUtils = require('../../../../tests/utils'); +const { getTmpDirPath } = require('../../../../tests/utils/fs'); chai.use(require('chai-as-promised')); @@ -185,7 +185,7 @@ describe('AwsInvokeLocal', () => { }); it('it should parse file if relative file path is provided', () => { - serverless.config.servicePath = testUtils.getTmpDirPath(); + serverless.config.servicePath = getTmpDirPath(); const data = { testProp: 'testValue', }; @@ -200,7 +200,7 @@ describe('AwsInvokeLocal', () => { }); it('it should parse file if absolute file path is provided', () => { - serverless.config.servicePath = testUtils.getTmpDirPath(); + serverless.config.servicePath = getTmpDirPath(); const data = { event: { testProp: 'testValue', @@ -218,7 +218,7 @@ describe('AwsInvokeLocal', () => { }); it('it should parse a yaml file if file path is provided', () => { - serverless.config.servicePath = testUtils.getTmpDirPath(); + serverless.config.servicePath = getTmpDirPath(); const yamlContent = 'event: data'; serverless.utils.writeFileSync(path @@ -232,7 +232,7 @@ describe('AwsInvokeLocal', () => { }); it('it should require a js file if file path is provided', () => { - serverless.config.servicePath = testUtils.getTmpDirPath(); + serverless.config.servicePath = getTmpDirPath(); const jsContent = [ 'module.exports = {', ' headers: { "Content-Type" : "application/json" },', @@ -260,7 +260,7 @@ describe('AwsInvokeLocal', () => { }); it('it should reject error if file path does not exist', () => { - serverless.config.servicePath = testUtils.getTmpDirPath(); + serverless.config.servicePath = getTmpDirPath(); awsInvokeLocal.options.path = 'some/path'; return expect(awsInvokeLocal.extendedValidate()).to.be.rejected; diff --git a/lib/plugins/aws/lib/updateStack.test.js b/lib/plugins/aws/lib/updateStack.test.js index dbde9a841..1421a7355 100644 --- a/lib/plugins/aws/lib/updateStack.test.js +++ b/lib/plugins/aws/lib/updateStack.test.js @@ -5,12 +5,12 @@ const sinon = require('sinon'); const AwsProvider = require('../provider/awsProvider'); const AwsDeploy = require('../deploy'); const Serverless = require('../../../Serverless'); -const testUtils = require('../../../../tests/utils'); +const { getTmpDirPath } = require('../../../../tests/utils/fs'); describe('updateStack', () => { let serverless; let awsDeploy; - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); beforeEach(() => { const options = { diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index 51a8cc132..9d67915f5 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -5,8 +5,8 @@ const path = require('path'); const chai = require('chai'); const AwsProvider = require('../../../provider/awsProvider'); const AwsCompileFunctions = require('./index'); -const testUtils = require('../../../../../../tests/utils'); const Serverless = require('../../../../../Serverless'); +const { getTmpDirPath } = require('../../../../../../tests/utils/fs'); chai.use(require('chai-as-promised')); @@ -34,7 +34,7 @@ describe('AwsCompileFunctions', () => { const serviceArtifact = 'new-service.zip'; const individualArtifact = 'test.zip'; - awsCompileFunctions.packagePath = testUtils.getTmpDirPath(); + awsCompileFunctions.packagePath = getTmpDirPath(); // The contents of the test artifacts need to be predictable so the hashes stay the same serverless.utils.writeFileSync(path.join(awsCompileFunctions.packagePath, serviceArtifact), 'foobar'); diff --git a/lib/plugins/aws/package/compile/layers/index.test.js b/lib/plugins/aws/package/compile/layers/index.test.js index 22245035b..0687ce350 100644 --- a/lib/plugins/aws/package/compile/layers/index.test.js +++ b/lib/plugins/aws/package/compile/layers/index.test.js @@ -5,8 +5,8 @@ const path = require('path'); const chai = require('chai'); const AwsProvider = require('../../../provider/awsProvider'); const AwsCompileLayers = require('./index'); -const testUtils = require('../../../../../../tests/utils'); const Serverless = require('../../../../../Serverless'); +const { getTmpDirPath } = require('../../../../../../tests/utils/fs'); chai.use(require('chai-as-promised')); @@ -35,7 +35,7 @@ describe('AwsCompileLayers', () => { const serviceArtifact = 'new-service.zip'; const individualArtifact = 'test.zip'; - awsCompileLayers.packagePath = testUtils.getTmpDirPath(); + awsCompileLayers.packagePath = getTmpDirPath(); // The contents of the test artifacts need to be predictable so the hashes stay the same serverless.utils.writeFileSync(path.join(awsCompileLayers.packagePath, serviceArtifact), 'foobar'); diff --git a/lib/plugins/aws/package/lib/generateCoreTemplate.test.js b/lib/plugins/aws/package/lib/generateCoreTemplate.test.js index 177693e10..5d487b32d 100644 --- a/lib/plugins/aws/package/lib/generateCoreTemplate.test.js +++ b/lib/plugins/aws/package/lib/generateCoreTemplate.test.js @@ -7,8 +7,8 @@ const AwsProvider = require('../../provider/awsProvider'); const Serverless = require('../../../../Serverless'); const validate = require('../../lib/validate'); const generateCoreTemplate = require('./generateCoreTemplate'); -const testUtils = require('../../../../../tests/utils'); const expect = require('chai').expect; +const { getTmpDirPath } = require('../../../../../tests/utils/fs'); chai.use(require('chai-as-promised')); @@ -29,7 +29,7 @@ describe('#generateCoreTemplate()', () => { Object.assign(awsPlugin, generateCoreTemplate, validate); awsPlugin.serverless.cli = new serverless.classes.CLI(); - awsPlugin.serverless.config.servicePath = testUtils.getTmpDirPath(); + awsPlugin.serverless.config.servicePath = getTmpDirPath(); awsPlugin.serverless.service.provider.compiledCloudFormationTemplate = { Resources: {}, diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 89d2c7ee0..f7b69e8aa 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -13,7 +13,8 @@ const path = require('path'); const AwsProvider = require('./awsProvider'); const Serverless = require('../../../Serverless'); -const testUtils = require('../../../../tests/utils'); +const { replaceEnv } = require('../../../../tests/utils/misc'); +const { getTmpFilePath } = require('../../../../tests/utils/fs'); chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); @@ -813,7 +814,7 @@ describe('AwsProvider', () => { let originalProviderProfile; let originalEnvironmentVariables; const relevantEnvironment = { - AWS_SHARED_CREDENTIALS_FILE: testUtils.getTmpFilePath('credentials'), + AWS_SHARED_CREDENTIALS_FILE: getTmpFilePath('credentials'), }; let BK_AWS_PROFILE; @@ -821,7 +822,7 @@ describe('AwsProvider', () => { BK_AWS_PROFILE = process.env.AWS_PROFILE; delete process.env.AWS_PROFILE; originalProviderProfile = serverless.service.provider.profile; - originalEnvironmentVariables = testUtils.replaceEnv(relevantEnvironment); + originalEnvironmentVariables = replaceEnv(relevantEnvironment); serverless.utils.writeFileSync( relevantEnvironment.AWS_SHARED_CREDENTIALS_FILE, '[default]\n' + @@ -838,7 +839,7 @@ describe('AwsProvider', () => { afterEach(() => { if (BK_AWS_PROFILE) process.env.AWS_PROFILE = BK_AWS_PROFILE; else delete process.env.AWS_PROFILE; - testUtils.replaceEnv(originalEnvironmentVariables); + replaceEnv(originalEnvironmentVariables); serverless.service.provider.profile = originalProviderProfile; }); @@ -899,7 +900,7 @@ describe('AwsProvider', () => { AWS_TESTSTAGE_ACCESS_KEY_ID: 'undefined', AWS_TESTSTAGE_SECRET_ACCESS_KEY: 'undefined', AWS_TESTSTAGE_SESSION_TOKEN: 'undefined', - AWS_SHARED_CREDENTIALS_FILE: testUtils.getTmpFilePath('credentials'), + AWS_SHARED_CREDENTIALS_FILE: getTmpFilePath('credentials'), AWS_PROFILE: 'undefined', AWS_TESTSTAGE_PROFILE: 'undefined', }; @@ -924,7 +925,7 @@ describe('AwsProvider', () => { beforeEach(() => { originalProviderCredentials = serverless.service.provider.credentials; originalProviderProfile = serverless.service.provider.profile; - originalEnvironmentVariables = testUtils.replaceEnv(relevantEnvironment); + originalEnvironmentVariables = replaceEnv(relevantEnvironment); // make temporary credentials file serverless.utils.writeFileSync( relevantEnvironment.AWS_SHARED_CREDENTIALS_FILE, @@ -945,7 +946,7 @@ describe('AwsProvider', () => { }); afterEach(() => { - testUtils.replaceEnv(originalEnvironmentVariables); + replaceEnv(originalEnvironmentVariables); serverless.service.provider.profile = originalProviderProfile; serverless.service.provider.credentials = originalProviderCredentials; }); diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index 76e5482aa..315974333 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -7,10 +7,10 @@ const fse = require('fs-extra'); const Create = require('./create'); const Serverless = require('../../Serverless'); const sinon = require('sinon'); -const testUtils = require('../../../tests/utils'); const walkDirSync = require('../../utils/fs/walkDirSync'); const download = require('../../utils/downloadTemplateFromRepo'); const userStats = require('../../utils/userStats'); +const { getTmpDirPath } = require('../../../tests/utils/fs'); describe('Create', () => { let create; @@ -51,7 +51,7 @@ describe('Create', () => { let cwd; beforeEach(() => { - tmpDir = testUtils.getTmpDirPath(); + tmpDir = getTmpDirPath(); fse.mkdirsSync(tmpDir); cwd = process.cwd(); }); diff --git a/lib/plugins/install/install.test.js b/lib/plugins/install/install.test.js index 6ae38ba95..be6763911 100644 --- a/lib/plugins/install/install.test.js +++ b/lib/plugins/install/install.test.js @@ -4,11 +4,11 @@ const expect = require('chai').expect; const Serverless = require('../../Serverless'); const Install = require('./install.js'); const sinon = require('sinon'); -const testUtils = require('../../../tests/utils'); const download = require('../../utils/downloadTemplateFromRepo'); const userStats = require('../../utils/userStats'); const fse = require('fs-extra'); const path = require('path'); +const { getTmpDirPath } = require('../../../tests/utils/fs'); describe('Install', () => { let install; @@ -19,7 +19,7 @@ describe('Install', () => { let servicePath; beforeEach(() => { - const tmpDir = testUtils.getTmpDirPath(); + const tmpDir = getTmpDirPath(); cwd = process.cwd(); fse.mkdirsSync(tmpDir); diff --git a/lib/plugins/package/lib/zipService.test.js b/lib/plugins/package/lib/zipService.test.js index 562663229..ef8fc194e 100644 --- a/lib/plugins/package/lib/zipService.test.js +++ b/lib/plugins/package/lib/zipService.test.js @@ -14,7 +14,7 @@ const childProcess = BbPromise.promisifyAll(require('child_process')); const sinon = require('sinon'); const Package = require('../package'); const Serverless = require('../../../Serverless'); -const testUtils = require('../../../../tests/utils'); +const { getTmpDirPath } = require('../../../../tests/utils/fs'); // Configure chai chai.use(require('chai-as-promised')); @@ -28,7 +28,7 @@ describe('zipService', () => { let params; beforeEach(() => { - tmpDirPath = testUtils.getTmpDirPath(); + tmpDirPath = getTmpDirPath(); serverless = new Serverless(); serverless.service.service = 'first-service'; serverless.config.servicePath = tmpDirPath; diff --git a/lib/plugins/plugin/install/install.test.js b/lib/plugins/plugin/install/install.test.js index 7e49c19ad..2999ded74 100644 --- a/lib/plugins/plugin/install/install.test.js +++ b/lib/plugins/plugin/install/install.test.js @@ -11,11 +11,12 @@ const fse = require('fs-extra'); const PluginInstall = require('./install'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); -const testUtils = require('../../../../tests/utils'); -const userStats = require('../../../utils/userStats'); -chai.use(require('chai-as-promised')); -const expect = require('chai').expect; const _ = require('lodash'); +const userStats = require('../../../utils/userStats'); +const { expect } = require('chai'); +const { getTmpDirPath } = require('../../../../tests/utils/fs'); + +chai.use(require('chai-as-promised')); describe('PluginInstall', () => { let pluginInstall; @@ -110,7 +111,7 @@ describe('PluginInstall', () => { let installPeerDependenciesStub; beforeEach(() => { - servicePath = testUtils.getTmpDirPath(); + servicePath = getTmpDirPath(); pluginInstall.serverless.config.servicePath = servicePath; fse.ensureDirSync(servicePath); serverlessYmlFilePath = path.join(servicePath, 'serverless.yml'); @@ -261,7 +262,7 @@ describe('PluginInstall', () => { beforeEach(() => { pluginInstall.options.pluginName = 'serverless-plugin-1'; pluginInstall.options.pluginVersion = 'latest'; - servicePath = testUtils.getTmpDirPath(); + servicePath = getTmpDirPath(); pluginInstall.serverless.config.servicePath = servicePath; fse.ensureDirSync(servicePath); packageJsonFilePath = path.join(servicePath, 'package.json'); @@ -327,7 +328,7 @@ describe('PluginInstall', () => { let serverlessYmlFilePath; beforeEach(() => { - servicePath = testUtils.getTmpDirPath(); + servicePath = getTmpDirPath(); pluginInstall.serverless.config.servicePath = servicePath; serverlessYmlFilePath = path.join(servicePath, 'serverless.yml'); }); @@ -507,7 +508,7 @@ describe('PluginInstall', () => { beforeEach(() => { pluginName = 'some-plugin'; pluginInstall.options.pluginName = pluginName; - servicePath = testUtils.getTmpDirPath(); + servicePath = getTmpDirPath(); fse.ensureDirSync(servicePath); pluginInstall.serverless.config.servicePath = servicePath; servicePackageJsonFilePath = path.join(servicePath, 'package.json'); diff --git a/lib/plugins/plugin/lib/utils.test.js b/lib/plugins/plugin/lib/utils.test.js index 018883a2f..75dd046b1 100644 --- a/lib/plugins/plugin/lib/utils.test.js +++ b/lib/plugins/plugin/lib/utils.test.js @@ -10,9 +10,10 @@ const chalk = require('chalk'); const PluginInstall = require('./../install/install'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); -const testUtils = require('../../../../tests/utils'); +const { expect } = require('chai'); +const { getTmpDirPath } = require('../../../../tests/utils/fs'); + chai.use(require('chai-as-promised')); -const expect = require('chai').expect; describe('PluginUtils', () => { let pluginUtils; @@ -66,7 +67,7 @@ describe('PluginUtils', () => { let servicePath; beforeEach(() => { - servicePath = testUtils.getTmpDirPath(); + servicePath = getTmpDirPath(); pluginUtils.serverless.config.servicePath = servicePath; }); diff --git a/lib/plugins/plugin/uninstall/uninstall.test.js b/lib/plugins/plugin/uninstall/uninstall.test.js index ab6b418c8..d7e272e94 100644 --- a/lib/plugins/plugin/uninstall/uninstall.test.js +++ b/lib/plugins/plugin/uninstall/uninstall.test.js @@ -10,10 +10,11 @@ const fse = require('fs-extra'); const PluginUninstall = require('./uninstall'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); -const testUtils = require('../../../../tests/utils'); const userStats = require('../../../utils/userStats'); +const { expect } = require('chai'); +const { getTmpDirPath } = require('../../../../tests/utils/fs'); + chai.use(require('chai-as-promised')); -const expect = require('chai').expect; describe('PluginUninstall', () => { let pluginUninstall; @@ -105,7 +106,7 @@ describe('PluginUninstall', () => { let uninstallPeerDependenciesStub; beforeEach(() => { - servicePath = testUtils.getTmpDirPath(); + servicePath = getTmpDirPath(); pluginUninstall.serverless.config.servicePath = servicePath; fse.ensureDirSync(servicePath); serverlessYmlFilePath = path.join(servicePath, 'serverless.yml'); @@ -203,7 +204,7 @@ describe('PluginUninstall', () => { beforeEach(() => { pluginUninstall.options.pluginName = 'serverless-plugin-1'; - servicePath = testUtils.getTmpDirPath(); + servicePath = getTmpDirPath(); pluginUninstall.serverless.config.servicePath = servicePath; fse.ensureDirSync(servicePath); packageJsonFilePath = path.join(servicePath, 'package.json'); @@ -249,7 +250,7 @@ describe('PluginUninstall', () => { let serverlessYmlFilePath; beforeEach(() => { - servicePath = testUtils.getTmpDirPath(); + servicePath = getTmpDirPath(); pluginUninstall.serverless.config.servicePath = servicePath; serverlessYmlFilePath = path.join(servicePath, 'serverless.yml'); }); @@ -477,7 +478,7 @@ describe('PluginUninstall', () => { beforeEach(() => { pluginName = 'some-plugin'; pluginUninstall.options.pluginName = pluginName; - servicePath = testUtils.getTmpDirPath(); + servicePath = getTmpDirPath(); pluginUninstall.serverless.config.servicePath = servicePath; pluginPath = path.join( servicePath, 'node_modules', pluginName); diff --git a/lib/utils/downloadTemplateFromRepo.test.js b/lib/utils/downloadTemplateFromRepo.test.js index dcafe3a39..8fad468f5 100644 --- a/lib/utils/downloadTemplateFromRepo.test.js +++ b/lib/utils/downloadTemplateFromRepo.test.js @@ -1,19 +1,19 @@ 'use strict'; -const expect = require('chai').expect; const sinon = require('sinon'); const BbPromise = require('bluebird'); -const testUtils = require('../../tests/utils'); const fse = require('fs-extra'); const path = require('path'); const proxyquire = require('proxyquire'); +const { expect } = require('chai'); +const { getTmpDirPath } = require('../../tests/utils/fs'); const writeFileSync = require('./fs/writeFileSync'); const readFileSync = require('./fs/readFileSync'); const remove = BbPromise.promisify(fse.remove); -const parseRepoURL = require('./downloadTemplateFromRepo').parseRepoURL; +const { parseRepoURL } = require('./downloadTemplateFromRepo'); describe('downloadTemplateFromRepo', () => { let downloadTemplateFromRepo; @@ -24,7 +24,7 @@ describe('downloadTemplateFromRepo', () => { let newServicePath; beforeEach(() => { - const tmpDir = testUtils.getTmpDirPath(); + const tmpDir = getTmpDirPath(); cwd = process.cwd(); fse.mkdirsSync(tmpDir); diff --git a/lib/utils/fs/readFile.test.js b/lib/utils/fs/readFile.test.js index 87625df4d..9809d808c 100644 --- a/lib/utils/fs/readFile.test.js +++ b/lib/utils/fs/readFile.test.js @@ -1,9 +1,9 @@ 'use strict'; -const testUtils = require('../../../tests/utils'); const chai = require('chai'); const writeFile = require('./writeFile'); const readFile = require('./readFile'); +const { getTmpFilePath } = require('../../../tests/utils/fs'); // Configure chai chai.use(require('chai-as-promised')); @@ -12,28 +12,28 @@ const expect = require('chai').expect; describe('#readFile()', () => { it('should read a file asynchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + const tmpFilePath = getTmpFilePath('anything.json'); return writeFile(tmpFilePath, { foo: 'bar' }) .then(() => expect(readFile(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' })); }); it('should read a filename extension .yml', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yml'); + const tmpFilePath = getTmpFilePath('anything.yml'); return writeFile(tmpFilePath, { foo: 'bar' }) .then(() => expect(readFile(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' })); }); it('should read a filename extension .yaml', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yaml'); + const tmpFilePath = getTmpFilePath('anything.yaml'); return writeFile(tmpFilePath, { foo: 'bar' }) .then(() => expect(readFile(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' })); }); it('should throw YAMLException with filename if yml file is invalid format', () => { - const tmpFilePath = testUtils.getTmpFilePath('invalid.yml'); + const tmpFilePath = getTmpFilePath('invalid.yml'); return writeFile(tmpFilePath, ': a').then(() => readFile(tmpFilePath)) .catch(e => { expect(e.name).to.equal('YAMLException'); diff --git a/lib/utils/fs/readFileSync.test.js b/lib/utils/fs/readFileSync.test.js index 652432040..328d38028 100644 --- a/lib/utils/fs/readFileSync.test.js +++ b/lib/utils/fs/readFileSync.test.js @@ -1,13 +1,13 @@ 'use strict'; -const testUtils = require('../../../tests/utils'); const expect = require('chai').expect; const writeFileSync = require('./writeFileSync'); const readFileSync = require('./readFileSync'); +const { getTmpFilePath } = require('../../../tests/utils/fs'); describe('#readFileSync()', () => { it('should read a file synchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + const tmpFilePath = getTmpFilePath('anything.json'); writeFileSync(tmpFilePath, { foo: 'bar' }); const obj = readFileSync(tmpFilePath); @@ -16,7 +16,7 @@ describe('#readFileSync()', () => { }); it('should read a filename extension .yml', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yml'); + const tmpFilePath = getTmpFilePath('anything.yml'); writeFileSync(tmpFilePath, { foo: 'bar' }); const obj = readFileSync(tmpFilePath); @@ -25,7 +25,7 @@ describe('#readFileSync()', () => { }); it('should read a filename extension .yaml', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yaml'); + const tmpFilePath = getTmpFilePath('anything.yaml'); writeFileSync(tmpFilePath, { foo: 'bar' }); const obj = readFileSync(tmpFilePath); @@ -34,7 +34,7 @@ describe('#readFileSync()', () => { }); it('should throw YAMLException with filename if yml file is invalid format', () => { - const tmpFilePath = testUtils.getTmpFilePath('invalid.yml'); + const tmpFilePath = getTmpFilePath('invalid.yml'); writeFileSync(tmpFilePath, ': a'); diff --git a/lib/utils/fs/walkDirSync.test.js b/lib/utils/fs/walkDirSync.test.js index 2ef3a2600..27f3466cd 100644 --- a/lib/utils/fs/walkDirSync.test.js +++ b/lib/utils/fs/walkDirSync.test.js @@ -2,14 +2,14 @@ const fs = require('fs'); const path = require('path'); -const expect = require('chai').expect; -const testUtils = require('../../../tests/utils'); const writeFileSync = require('./writeFileSync'); const walkDirSync = require('./walkDirSync'); +const { expect } = require('chai'); +const { getTmpDirPath } = require('../../../tests/utils/fs'); describe('#walkDirSync()', () => { it('should return an array with corresponding paths to the found files', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const nestedDir1 = path.join(tmpDirPath, 'foo'); const nestedDir2 = path.join(tmpDirPath, 'foo', 'bar'); @@ -31,7 +31,7 @@ describe('#walkDirSync()', () => { }); it('should check noLinks option', () => { - const tmpDirPath = testUtils.getTmpDirPath(); + const tmpDirPath = getTmpDirPath(); const realFile = path.join(tmpDirPath, 'real'); writeFileSync(realFile, 'content'); diff --git a/lib/utils/fs/writeFile.test.js b/lib/utils/fs/writeFile.test.js index 966c928d4..01c4addda 100644 --- a/lib/utils/fs/writeFile.test.js +++ b/lib/utils/fs/writeFile.test.js @@ -1,11 +1,11 @@ 'use strict'; const fse = require('./fse'); -const testUtils = require('../../../tests/utils'); const Serverless = require('../../../lib/Serverless'); const chai = require('chai'); const writeFile = require('./writeFile'); const readFile = require('./readFile'); +const { getTmpFilePath } = require('../../../tests/utils/fs'); // Configure chai chai.use(require('chai-as-promised')); @@ -21,14 +21,14 @@ describe('#writeFile()', function () { }); it('should write a .json file asynchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + const tmpFilePath = getTmpFilePath('anything.json'); return writeFile(tmpFilePath, { foo: 'bar' }) .then(() => expect(readFile(tmpFilePath)) .to.eventually.deep.equal({ foo: 'bar' })); }); it('should write a .yml file synchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yml'); + const tmpFilePath = getTmpFilePath('anything.yml'); return writeFile(tmpFilePath, { foo: 'bar' }) .then(() => expect(serverless.yamlParser.parse(tmpFilePath)) @@ -36,7 +36,7 @@ describe('#writeFile()', function () { }); it('should write a .yaml file synchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yaml'); + const tmpFilePath = getTmpFilePath('anything.yaml'); return writeFile(tmpFilePath, { foo: 'bar' }) .then(() => expect(serverless.yamlParser.parse(tmpFilePath)) @@ -44,7 +44,7 @@ describe('#writeFile()', function () { }); it('should be able to write an object with circular references', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + const tmpFilePath = getTmpFilePath('anything.json'); const bar = {}; bar.foo = bar; const expected = '{\n "foo": {\n "$ref": "$"\n }\n}'; diff --git a/lib/utils/fs/writeFileSync.test.js b/lib/utils/fs/writeFileSync.test.js index 87ec760a5..c43337fb8 100644 --- a/lib/utils/fs/writeFileSync.test.js +++ b/lib/utils/fs/writeFileSync.test.js @@ -1,11 +1,11 @@ 'use strict'; const fse = require('./fse'); -const testUtils = require('../../../tests/utils'); const Serverless = require('../../../lib/Serverless'); -const expect = require('chai').expect; const writeFileSync = require('./writeFileSync'); const readFileSync = require('./readFileSync'); +const { expect } = require('chai'); +const { getTmpFilePath } = require('../../../tests/utils/fs'); describe('#writeFileSync()', () => { let serverless; @@ -15,7 +15,7 @@ describe('#writeFileSync()', () => { }); it('should write a .json file synchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + const tmpFilePath = getTmpFilePath('anything.json'); writeFileSync(tmpFilePath, { foo: 'bar' }); const obj = readFileSync(tmpFilePath); @@ -24,7 +24,7 @@ describe('#writeFileSync()', () => { }); it('should write a .yml file synchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yml'); + const tmpFilePath = getTmpFilePath('anything.yml'); writeFileSync(tmpFilePath, { foo: 'bar' }); @@ -34,7 +34,7 @@ describe('#writeFileSync()', () => { }); it('should write a .yaml file synchronously', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.yaml'); + const tmpFilePath = getTmpFilePath('anything.yaml'); writeFileSync(tmpFilePath, { foo: 'bar' }); @@ -48,7 +48,7 @@ describe('#writeFileSync()', () => { }); it('should be able to write an object with circular references', () => { - const tmpFilePath = testUtils.getTmpFilePath('anything.json'); + const tmpFilePath = getTmpFilePath('anything.json'); const bar = {}; bar.foo = bar; const expected = '{\n "foo": {\n "$ref": "$"\n }\n}'; diff --git a/lib/utils/getServerlessConfigFile.test.js b/lib/utils/getServerlessConfigFile.test.js index 33b9661fb..2c4dc1236 100644 --- a/lib/utils/getServerlessConfigFile.test.js +++ b/lib/utils/getServerlessConfigFile.test.js @@ -2,9 +2,9 @@ const path = require('path'); const chai = require('chai'); -const testUtils = require('../../tests/utils'); const writeFileSync = require('./fs/writeFileSync'); const serverlessConfigFileUtils = require('./getServerlessConfigFile'); +const { getTmpDirPath } = require('../../tests/utils/fs'); const getServerlessConfigFile = serverlessConfigFileUtils.getServerlessConfigFile; @@ -16,7 +16,7 @@ describe('#getServerlessConfigFile()', () => { let tmpDirPath; beforeEach(() => { - tmpDirPath = testUtils.getTmpDirPath(); + tmpDirPath = getTmpDirPath(); }); it('should return an empty string if no serverless file is found', () => { diff --git a/lib/utils/renameService.test.js b/lib/utils/renameService.test.js index d580a8cfc..c6e50fdbe 100644 --- a/lib/utils/renameService.test.js +++ b/lib/utils/renameService.test.js @@ -1,12 +1,12 @@ 'use strict'; -const expect = require('chai').expect; const Serverless = require('../Serverless'); -const testUtils = require('../../tests/utils'); const fse = require('fs-extra'); const path = require('path'); +const { expect } = require('chai'); +const { getTmpDirPath } = require('../../tests/utils/fs'); -const renameService = require('./renameService').renameService; +const { renameService } = require('./renameService'); describe('renameService', () => { let serverless; @@ -15,7 +15,7 @@ describe('renameService', () => { let servicePath; beforeEach(() => { - const tmpDir = testUtils.getTmpDirPath(); + const tmpDir = getTmpDirPath(); cwd = process.cwd(); fse.mkdirsSync(tmpDir); diff --git a/lib/utils/yamlAstParser.test.js b/lib/utils/yamlAstParser.test.js index 3dbbb2361..84e083390 100644 --- a/lib/utils/yamlAstParser.test.js +++ b/lib/utils/yamlAstParser.test.js @@ -2,12 +2,12 @@ const path = require('path'); const chai = require('chai'); -const testUtils = require('../../tests/utils'); const writeFileSync = require('./fs/writeFileSync'); const readFileSync = require('./fs/readFileSync'); const yamlAstParser = require('./yamlAstParser'); const _ = require('lodash'); const chaiAsPromised = require('chai-as-promised'); +const { getTmpDirPath } = require('../../tests/utils/fs'); chai.use(chaiAsPromised); const expect = require('chai').expect; @@ -16,7 +16,7 @@ describe('#yamlAstParser', () => { let tmpDirPath; beforeEach(() => { - tmpDirPath = testUtils.getTmpDirPath(); + tmpDirPath = getTmpDirPath(); }); describe('#addNewArrayItem()', () => { diff --git a/package-lock.json b/package-lock.json index 287ddc192..9b885bf9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -274,21 +274,6 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true - }, - "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 - }, - "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" - } } } }, @@ -522,11 +507,10 @@ } }, "@serverless/platform-sdk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@serverless/platform-sdk/-/platform-sdk-1.0.0.tgz", - "integrity": "sha512-+MYfXA36tXERShJDbfMQcEJKt4eRHQBbUeAzrY7emzJ/RYViPTw49Z8iHvYXhsMIi0v0DOlGHma7xNtGaKNm6Q==", + "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==", "requires": { - "babel-polyfill": "^6.26.0", "body-parser": "^1.19.0", "chalk": "^2.4.1", "cors": "^2.8.4", @@ -538,6 +522,7 @@ "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" @@ -1021,9 +1006,9 @@ "dev": true }, "aws-sdk": { - "version": "2.452.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.452.0.tgz", - "integrity": "sha512-l6J2NmUg12xpDKG9YdlPje5+Z+nNvqyWMA85ookzPqwx8RcD28D3vg4K1aGi27/oo/3NsngR3XfKUBPSqUpUMA==", + "version": "2.454.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.454.0.tgz", + "integrity": "sha512-1vB9DNIwh+mqKD2IZspYTQapCD6f5VnMT5V2VPlXJ1CNcUdFSU8FFyxKmYApNs+S3re1h3fhWDjpwTreS+XLRQ==", "requires": { "buffer": "4.9.1", "events": "1.1.1", @@ -1098,17 +1083,15 @@ "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" } }, "supports-color": { @@ -1173,23 +1156,6 @@ "@types/babel__traverse": "^7.0.6" } }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" - } - } - }, "babel-preset-jest": { "version": "24.6.0", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz", @@ -1200,22 +1166,6 @@ "babel-plugin-jest-hoist": "^24.6.0" } }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - } - } - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1865,9 +1815,9 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { "delayed-stream": "~1.0.0" } @@ -1988,11 +1938,6 @@ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, - "core-js": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.8.tgz", - "integrity": "sha512-RWlREFU74TEkdXzyl1bka66O3kYp8jeTXrvJZDzVVMH8AiHUSOFpL1yfhQJ+wHocAm1m+4971W1PPzfLuCv1vg==" - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -2653,17 +2598,6 @@ "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, "debug": { @@ -2694,17 +2628,6 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.0", "through": "^2.3.6" - }, - "dependencies": { - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, "ms": { @@ -2722,6 +2645,15 @@ "once": "^1.3.0" } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -3135,9 +3067,9 @@ } }, "express": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.0.tgz", - "integrity": "sha512-1Z7/t3Z5ZnBG252gKUPyItc4xdeaA0X934ca2ewckAsVsw9EG71i++ZHZPYnus8g/s5Bty8IMpSVEuRkmwwPRQ==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "requires": { "accepts": "~1.3.7", "array-flatten": "1.1.1", @@ -3612,8 +3544,7 @@ "version": "2.1.1", "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3637,15 +3568,13 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3662,22 +3591,19 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3808,8 +3734,7 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3823,7 +3748,6 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3840,7 +3764,6 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3849,15 +3772,13 @@ "version": "0.0.8", "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3878,7 +3799,6 @@ "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3967,8 +3887,7 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3982,7 +3901,6 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4078,8 +3996,7 @@ "version": "5.1.2", "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -4121,7 +4038,6 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4143,7 +4059,6 @@ "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4192,15 +4107,13 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true, - "optional": true + "dev": true } } }, @@ -5230,6 +5143,30 @@ } } }, + "jest-circus": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-24.8.0.tgz", + "integrity": "sha512-2QASG3QuDdk0SMP2O73D8u3/lc/A/E2G7q23v5WhbUR+hCGzWZXwRMKif18f11dSLfL1wcrMbwE4IorvV0DRVw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.8.0", + "@jest/test-result": "^24.8.0", + "@jest/types": "^24.8.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.8.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.8.0", + "jest-matcher-utils": "^24.8.0", + "jest-message-util": "^24.8.0", + "jest-snapshot": "^24.8.0", + "jest-util": "^24.8.0", + "pretty-format": "^24.8.0", + "stack-utils": "^1.0.1", + "throat": "^4.0.0" + } + }, "jest-cli": { "version": "24.8.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.8.0.tgz", @@ -6750,9 +6687,9 @@ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "neo-async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", - "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, "nested-error-stacks": { @@ -8968,12 +8905,6 @@ "json-stable-stringify": "^1.0.1" } }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -8991,23 +8922,6 @@ "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, "is-fullwidth-code-point": { @@ -9026,6 +8940,12 @@ "strip-ansi": "^4.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 + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -9037,6 +8957,15 @@ } } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -9337,9 +9266,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "uglify-js": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.11.tgz", - "integrity": "sha512-izPJg8RsSyqxbdnqX36ExpbH3K7tDBsAU/VfNv89VkMFy3z39zFjunQGsSHOlGlyIfGLGprGeosgQno3bo2/Kg==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.12.tgz", + "integrity": "sha512-KeQesOpPiZNgVwJj8Ge3P4JYbQHUdZzpx6Fahy6eKAYRSV4zhVmLXoC+JtOeYxcHCHTve8RG1ZGdTvpeOUM26Q==", "dev": true, "optional": true, "requires": { diff --git a/package.json b/package.json index 48c093b88..13cfd03ac 100644 --- a/package.json +++ b/package.json @@ -57,10 +57,10 @@ "test": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm run test-bare", "lint": "eslint . --cache", "docs": "node scripts/generate-readme.js", - "integration-test-cleanup": "node scripts/integration-test-cleanup.js", - "packaging-integration-test": "jest --maxWorkers=5 packaging-suite", - "simple-integration-test": "jest --maxWorkers=5 simple-suite", - "complex-integration-test": "jest --maxWorkers=5 integration", + "integration-test-run-package": "jest --maxWorkers=5 integration-package", + "integration-test-run-basic": "jest --maxWorkers=5 integration-basic", + "integration-test-run-all": "jest --maxWorkers=5 integration-all", + "integration-test-cleanup": "node tests/utils/aws-cleanup.js", "postinstall": "node ./scripts/postinstall.js" }, "mocha": { @@ -71,8 +71,9 @@ "testRegex": "(\\.|/)(tests)\\.js$", "testEnvironment": "node", "setupFilesAfterEnv": [ - "/tests/setupTests.js" - ] + "/tests/setup-tests.js" + ], + "testRunner": "jest-circus/runner" }, "devDependencies": { "chai": "^3.5.0", @@ -86,6 +87,7 @@ "eslint-plugin-import": "^1.13.0", "eslint-plugin-jsx-a11y": "^2.1.0", "eslint-plugin-react": "^6.1.1", + "jest-circus": "^24.8.0", "jest-cli": "^24.5.0", "markdown-link": "^0.1.1", "markdown-magic": "^0.1.25", diff --git a/scripts/integration-test-cleanup.js b/scripts/integration-test-cleanup.js deleted file mode 100644 index 35276c64f..000000000 --- a/scripts/integration-test-cleanup.js +++ /dev/null @@ -1,97 +0,0 @@ -'use strict'; - -const BbPromise = require('bluebird'); -const AWS = require('aws-sdk'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -const S3 = new AWS.S3({ region: 'us-east-1' }); - -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); -BbPromise.promisifyAll(S3, { suffix: 'Promised' }); - -const logger = console; - -const emptyS3Bucket = (bucket) => ( - S3.listObjectsPromised({ Bucket: bucket }) - .then(data => { - logger.log('Bucket', bucket, 'has', data.Contents.length, 'items'); - if (data.Contents.length) { - const keys = data.Contents.map(item => Object.assign({}, { Key: item.Key })); - return S3.deleteObjectsPromised({ - Bucket: bucket, - Delete: { - Objects: keys, - }, - }); - } - return BbPromise.resolve(); - }) -); - -const deleteS3Bucket = (bucket) => ( - emptyS3Bucket(bucket) - .then(() => { - logger.log('Bucket', bucket, 'is now empty, deleting ...'); - return S3.deleteBucketPromised({ Bucket: bucket }); - }) -); - -const cleanupS3Buckets = (token) => { - logger.log('Looking through buckets ...'); - - const params = {}; - - if (token) { - params.NextToken = token; - } - - return S3.listBucketsPromised() - .then(response => - response.Buckets.reduce((memo, bucket) => memo - .then(() => deleteS3Bucket(bucket.Name)), BbPromise.resolve()) - .then(() => { - if (response.NextToken) { - return cleanupS3Buckets(response.NextToken); - } - return BbPromise.resolve(); - }) - ); -}; - -const cleanupCFStacks = (token) => { - const params = { - StackStatusFilter: [ - 'CREATE_FAILED', - 'CREATE_COMPLETE', - 'ROLLBACK_FAILED', - 'ROLLBACK_COMPLETE', - 'DELETE_FAILED', - 'UPDATE_ROLLBACK_FAILED', - 'UPDATE_ROLLBACK_COMPLETE', - ], - }; - - if (token) { - params.NextToken = token; - } - - logger.log('Looking through stacks ...'); - return CF.listStacksPromised(params) - .then(response => - response.StackSummaries.reduce((memo, stack) => { - if (['DELETE_COMPLETE', 'DELETE_IN_PROGRESS'].indexOf(stack.StackStatus) === -1) { - logger.log('Deleting stack', stack.StackName); - return memo.then(() => CF.deleteStackPromised({ StackName: stack.StackName })); - } - return memo; - }, BbPromise.resolve()) - .then(() => { - if (response.NextToken) { - return cleanupCFStacks(response.NextToken); - } - return BbPromise.resolve(); - }) - ); -}; - -cleanupS3Buckets().then(cleanupCFStacks); diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js new file mode 100644 index 000000000..162340ca5 --- /dev/null +++ b/tests/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + "parserOptions": { + "ecmaVersion": 2017, + } +}; diff --git a/tests/integration-all/api-gateway/service/core.js b/tests/integration-all/api-gateway/service/core.js new file mode 100644 index 000000000..326d614cb --- /dev/null +++ b/tests/integration-all/api-gateway/service/core.js @@ -0,0 +1,51 @@ +'use strict'; + +async function minimal(event) { + return { + statusCode: 200, + body: JSON.stringify({ + message: 'Hello from API Gateway! - (minimal)', + event, + }), + }; +} + +async function cors(event) { + return { + statusCode: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + message: 'Hello from API Gateway! - (cors)', + event, + }), + }; +} + +async function customAuthorizers(event) { + return ({ + statusCode: 200, + body: JSON.stringify({ + message: 'Hello from API Gateway! - (customAuthorizers)', + event, + }), + }); +} + +async function apiKeys(event) { + return { + statusCode: 200, + body: JSON.stringify({ + message: 'Hello from API Gateway! - (apiKeys)', + event, + }), + }; +} + +module.exports = { + minimal, + cors, + customAuthorizers, + apiKeys, +}; diff --git a/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/handler.js b/tests/integration-all/api-gateway/service/helper.js similarity index 63% rename from tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/handler.js rename to tests/integration-all/api-gateway/service/helper.js index d47160ae6..000efbc5c 100644 --- a/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/handler.js +++ b/tests/integration-all/api-gateway/service/helper.js @@ -1,6 +1,7 @@ 'use strict'; -const generatePolicy = (principalId, effect, resource) => { +// custom authorizer +function generatePolicy(principalId, effect, resource) { const authResponse = {}; authResponse.principalId = principalId; @@ -18,20 +19,18 @@ const generatePolicy = (principalId, effect, resource) => { } return authResponse; -}; +} -// protected function -module.exports.hello = (event, context, callback) => { - callback(null, { message: 'Successfully authorized!', event }); -}; - -// auth function -module.exports.auth = (event, context) => { +async function auth(event, context) { const token = event.authorizationToken.split(' '); if (token[0] === 'Bearer' && token[1] === 'ShouldBeAuthorized') { - context.succeed(generatePolicy('SomeRandomId', 'Allow', '*')); + return context.succeed(generatePolicy('SomeRandomId', 'Allow', '*')); } - context.fail('Unauthorized'); + return context.fail('Unauthorized'); +} + +module.exports = { + auth, }; diff --git a/tests/integration-all/api-gateway/service/serverless.yml b/tests/integration-all/api-gateway/service/serverless.yml new file mode 100644 index 000000000..75bdd3fd2 --- /dev/null +++ b/tests/integration-all/api-gateway/service/serverless.yml @@ -0,0 +1,62 @@ +service: aws-nodejs # NOTE: update this with your service name + +provider: + name: aws + runtime: nodejs10.x + versionFunctions: false + apiKeys: + - name: api-key-1 + value: 0p3ns3s4m3-0p3ns3s4m3-0p3ns3s4m3 + +functions: + # core functions + minimal: + handler: core.minimal + events: + - http: GET / + - http: + method: POST + path: minimal-1 + - http: + method: PUT + path: /minimal-2 + - http: + method: DELETE + path: /minimal-3/ + cors: + handler: core.cors + events: + - http: + method: GET + path: simple-cors + cors: true + - http: + method: GET + path: complex-cors + cors: + origin: '*' + headers: + - Content-Type + - X-Amz-Date + - Authorization + - X-Api-Key + - X-Amz-Security-Token + - X-Amz-User-Agent + allowCredentials: true + customAuthorizers: + handler: core.customAuthorizers + events: + - http: + path: custom-auth + method: GET + authorizer: authorizer + apiKeys: + handler: core.apiKeys + events: + - http: + path: api-keys + method: GET + private: true + # helper functions + authorizer: + handler: helper.auth diff --git a/tests/integration-all/api-gateway/tests.js b/tests/integration-all/api-gateway/tests.js new file mode 100644 index 000000000..bdd1d5578 --- /dev/null +++ b/tests/integration-all/api-gateway/tests.js @@ -0,0 +1,267 @@ +'use strict'; + +const path = require('path'); +const AWS = require('aws-sdk'); +const _ = require('lodash'); +const fetch = require('node-fetch'); +const { expect } = require('chai'); + +const { getTmpDirPath, readYamlFile, writeYamlFile } = require('../../utils/fs'); +const { region, createTestService, deployService, removeService } = require('../../utils/misc'); +const { createRestApi, deleteRestApi, getResources } = require('../../utils/api-gateway'); + +const CF = new AWS.CloudFormation({ region }); + +describe('AWS - API Gateway Integration Test', () => { + let serviceName; + let endpoint; + let StackName; + let tmpDirPath; + let serverlessFilePath; + let restApiId; + let restApiRootResourceId; + const stage = 'dev'; + + beforeAll(() => { + tmpDirPath = getTmpDirPath(); + serverlessFilePath = path.join(tmpDirPath, 'serverless.yml'); + serviceName = createTestService('aws-nodejs', tmpDirPath, path.join(__dirname, 'service')); + StackName = `${serviceName}-${stage}`; + deployService(); + // create an external REST API + const externalRestApiName = `${stage}-${serviceName}-ext-api`; + return createRestApi(externalRestApiName) + .then((restApiMeta) => { + restApiId = restApiMeta.id; + return getResources(restApiId); + }) + .then((resources) => { + restApiRootResourceId = resources[0].id; + }); + }); + + afterAll(() => { + // NOTE: deleting the references to the old, external REST API + const serverless = readYamlFile(serverlessFilePath); + delete serverless.provider.apiGateway.restApiId; + delete serverless.provider.apiGateway.restApiRootResourceId; + writeYamlFile(serverlessFilePath, serverless); + // NOTE: deploying once again to get the stack into the original state + deployService(); + removeService(); + return deleteRestApi(restApiId); + }); + + beforeEach(() => { + return CF.describeStacks({ StackName }).promise() + .then((result) => _.find(result.Stacks[0].Outputs, + { OutputKey: 'ServiceEndpoint' }).OutputValue) + .then((endpointOutput) => { + endpoint = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; + endpoint = `${endpoint}`; + }); + }); + + describe('Minimal Setup', () => { + const expectedMessage = 'Hello from API Gateway! - (minimal)'; + + it('should expose an accessible GET HTTP endpoint', () => { + const testEndpoint = `${endpoint}`; + + return fetch(testEndpoint, { method: 'GET' }) + .then(response => response.json()) + .then((json) => expect(json.message).to.equal(expectedMessage)); + }); + + it('should expose an accessible POST HTTP endpoint', () => { + const testEndpoint = `${endpoint}/minimal-1`; + + return fetch(testEndpoint, { method: 'POST' }) + .then(response => response.json()) + .then((json) => expect(json.message).to.equal(expectedMessage)); + }); + + it('should expose an accessible PUT HTTP endpoint', () => { + const testEndpoint = `${endpoint}/minimal-2`; + + return fetch(testEndpoint, { method: 'PUT' }) + .then(response => response.json()) + .then((json) => expect(json.message).to.equal(expectedMessage)); + }); + + it('should expose an accessible DELETE HTTP endpoint', () => { + const testEndpoint = `${endpoint}/minimal-3`; + + return fetch(testEndpoint, { method: 'DELETE' }) + .then(response => response.json()) + .then((json) => expect(json.message).to.equal(expectedMessage)); + }); + }); + + describe('CORS', () => { + it('should setup simple CORS support via cors: true config', () => { + const testEndpoint = `${endpoint}/simple-cors`; + + return fetch(testEndpoint, { method: 'OPTIONS' }) + .then((response) => { + const headers = response.headers; + const allowHeaders = [ + 'Content-Type', + 'X-Amz-Date', + 'Authorization', + 'X-Api-Key', + 'X-Amz-Security-Token', + 'X-Amz-User-Agent', + ].join(','); + expect(headers.get('access-control-allow-headers')).to.equal(allowHeaders); + expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); + expect(headers.get('access-control-allow-credentials')).to.equal('false'); + // TODO: for some reason this test fails for now... + // expect(headers.get('access-control-allow-origin')).to.equal('*'); + }); + }); + + it('should setup CORS support with complex object config', () => { + const testEndpoint = `${endpoint}/complex-cors`; + + return fetch(testEndpoint, { method: 'OPTIONS' }) + .then((response) => { + const headers = response.headers; + const allowHeaders = [ + 'Content-Type', + 'X-Amz-Date', + 'Authorization', + 'X-Api-Key', + 'X-Amz-Security-Token', + 'X-Amz-User-Agent', + ].join(','); + expect(headers.get('access-control-allow-headers')).to.equal(allowHeaders); + expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); + expect(headers.get('access-control-allow-credentials')).to.equal('true'); + expect(headers.get('access-control-allow-origin')).to.equal('*'); + }); + }); + }); + + describe('Custom Authorizers', () => { + let testEndpoint; + + beforeEach(() => { + testEndpoint = `${endpoint}/custom-auth`; + }); + + it('should reject requests without authorization', () => { + return fetch(testEndpoint) + .then((response) => { + expect(response.status).to.equal(401); + }); + }); + + it('should reject requests with wrong authorization', () => { + return fetch(testEndpoint, { headers: { Authorization: 'Bearer ShouldNotBeAuthorized' } }) + .then((response) => { + expect(response.status).to.equal(401); + }); + }); + + it('should authorize requests with correct authorization', () => { + return fetch(testEndpoint, { headers: { Authorization: 'Bearer ShouldBeAuthorized' } }) + .then(response => response.json()) + .then((json) => { + expect(json.message).to.equal('Hello from API Gateway! - (customAuthorizers)'); + expect(json.event.requestContext.authorizer.principalId).to.equal('SomeRandomId'); + expect(json.event.headers.Authorization).to.equal('Bearer ShouldBeAuthorized'); + }); + }); + }); + + describe('API Keys', () => { + let testEndpoint; + + beforeEach(() => { + testEndpoint = `${endpoint}/api-keys`; + }); + + it('should reject a request with an invalid API Key', () => { + return fetch(testEndpoint) + .then((response) => { + expect(response.status).to.equal(403); + }); + }); + + it('should succeed if correct API key is given', () => { + const apiKey = '0p3ns3s4m3-0p3ns3s4m3-0p3ns3s4m3'; + + return fetch(testEndpoint, { headers: { 'X-API-Key': apiKey } }) + .then(response => response.json()) + .then((json) => { + expect(json.message).to.equal('Hello from API Gateway! - (apiKeys)'); + }); + }); + }); + + describe('Using stage specific configuration', () => { + beforeAll(() => { + const serverless = readYamlFile(serverlessFilePath); + // enable Logs, Tags and Tracing + _.merge(serverless.provider, { + tags: { + foo: 'bar', + baz: 'qux', + }, + tracing: { + apiGateway: true, + }, + logs: { + restApi: true, + }, + }); + writeYamlFile(serverlessFilePath, serverless); + deployService(); + }); + + it('should update the stage without service interruptions', () => { + // re-using the endpoint from the "minimal" test case + const testEndpoint = `${endpoint}`; + + return fetch(testEndpoint, { method: 'GET' }) + .then(response => response.json()) + .then((json) => expect(json.message).to.equal('Hello from API Gateway! - (minimal)')); + }); + }); + + // NOTE: this test should be at the very end because we're using an external REST API here + describe('when using an existing REST API with stage specific configuration', () => { + beforeAll(() => { + const serverless = readYamlFile(serverlessFilePath); + // enable Logs, Tags and Tracing + _.merge(serverless.provider, { + apiGateway: { + restApiId, + restApiRootResourceId, + }, + tags: { + foo: 'bar', + baz: 'qux', + }, + tracing: { + apiGateway: true, + }, + logs: { + restApi: true, + }, + }); + writeYamlFile(serverlessFilePath, serverless); + deployService(); + }); + + it('should update the stage without service interruptions', () => { + // re-using the endpoint from the "minimal" test case + const testEndpoint = `${endpoint}/minimal-1`; + + return fetch(testEndpoint, { method: 'POST' }) + .then(response => response.json()) + .then((json) => expect(json.message).to.equal('Hello from API Gateway! - (minimal)')); + }); + }); +}); diff --git a/tests/simple-suite/tests.js b/tests/integration-basic/tests.js similarity index 70% rename from tests/simple-suite/tests.js rename to tests/integration-basic/tests.js index 84713c8a4..e1eadf416 100644 --- a/tests/simple-suite/tests.js +++ b/tests/integration-basic/tests.js @@ -1,45 +1,55 @@ 'use strict'; -const fs = require('fs'); -const expect = require('chai').expect; const path = require('path'); +const fs = require('fs'); const fse = require('fs-extra'); const BbPromise = require('bluebird'); -const execSync = require('child_process').execSync; const AWS = require('aws-sdk'); -const testUtils = require('../utils/index'); +const { expect } = require('chai'); +const { execSync } = require('child_process'); +const { getTmpDirPath, replaceTextInFile } = require('../utils/fs'); +const { region, getServiceName } = require('../utils/misc'); const serverlessExec = path.join(__dirname, '..', '..', 'bin', 'serverless'); -const tmpDir = testUtils.getTmpDirPath(); -fse.mkdirsSync(tmpDir); -process.chdir(tmpDir); - -const templateName = 'aws-nodejs'; -const newServiceName = `service-${(new Date()).getTime().toString()}`; -const stackName = `${newServiceName}-dev`; - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); +const CF = new AWS.CloudFormation({ region }); describe('Service Lifecyle Integration Test', () => { + const templateName = 'aws-nodejs'; + const tmpDir = getTmpDirPath(); + let oldCwd; + let serviceName; + let StackName; + + beforeAll(() => { + oldCwd = process.cwd(); + serviceName = getServiceName(); + StackName = `${serviceName}-dev`; + fse.mkdirsSync(tmpDir); + process.chdir(tmpDir); + }); + + afterAll(() => { + process.chdir(oldCwd); + }); + it('should create service in tmp directory', () => { - execSync(`${serverlessExec} create --template ${templateName}`, { stdio: 'inherit' }); - testUtils.replaceTextInFile('serverless.yml', templateName, newServiceName); + execSync(`${serverlessExec} create --template ${templateName}`); + replaceTextInFile('serverless.yml', templateName, serviceName); expect(fs.existsSync(path.join(tmpDir, 'serverless.yml'))).to.be.equal(true); expect(fs.existsSync(path.join(tmpDir, 'handler.js'))).to.be.equal(true); }); it('should deploy service to aws', () => { - execSync(`${serverlessExec} deploy`, { stdio: 'inherit' }); + execSync(`${serverlessExec} deploy`); - return CF.describeStacksPromised({ StackName: stackName }) + return CF.describeStacks({ StackName }).promise() .then(d => expect(d.Stacks[0].StackStatus).to.be.equal('UPDATE_COMPLETE')); }); it('should invoke function from aws', () => { const invoked = execSync(`${serverlessExec} invoke --function hello --noGreeting true`); - const result = JSON.parse(new Buffer(invoked, 'base64').toString()); + const result = JSON.parse(Buffer.from(invoked, 'base64').toString()); // parse it once again because the body is stringified to be LAMBDA-PROXY ready const message = JSON.parse(result.body).message; expect(message).to.be.equal('Go Serverless v1.0! Your function executed successfully!'); @@ -56,12 +66,12 @@ describe('Service Lifecyle Integration Test', () => { `; fs.writeFileSync(path.join(tmpDir, 'handler.js'), newHandler); - execSync(`${serverlessExec} deploy`, { stdio: 'inherit' }); + execSync(`${serverlessExec} deploy`); }); it('should invoke updated function from aws', () => { const invoked = execSync(`${serverlessExec} invoke --function hello --noGreeting true`); - const result = JSON.parse(new Buffer(invoked, 'base64').toString()); + const result = JSON.parse(Buffer.from(invoked, 'base64').toString()); expect(result.message).to.be.equal('Service Update Succeeded'); }); @@ -79,16 +89,16 @@ describe('Service Lifecyle Integration Test', () => { execSync(`${serverlessExec} rollback -t ${timestamp}`); const invoked = execSync(`${serverlessExec} invoke --function hello --noGreeting true`); - const result = JSON.parse(new Buffer(invoked, 'base64').toString()); + const result = JSON.parse(Buffer.from(invoked, 'base64').toString()); // parse it once again because the body is stringified to be LAMBDA-PROXY ready const message = JSON.parse(result.body).message; expect(message).to.be.equal('Go Serverless v1.0! Your function executed successfully!'); }); it('should remove service from aws', () => { - execSync(`${serverlessExec} remove`, { stdio: 'inherit' }); + execSync(`${serverlessExec} remove`); - return CF.describeStacksPromised({ StackName: stackName }) + return CF.describeStacks({ StackName }).promise() .then(d => expect(d.Stacks[0].StackStatus).to.be.equal('DELETE_COMPLETE')) .catch(error => { if (error.message.indexOf('does not exist') > -1) return BbPromise.resolve(); diff --git a/tests/packaging-suite/artifact.zip b/tests/integration-package/artifact.zip similarity index 100% rename from tests/packaging-suite/artifact.zip rename to tests/integration-package/artifact.zip diff --git a/tests/packaging-suite/handler.js b/tests/integration-package/handler.js similarity index 100% rename from tests/packaging-suite/handler.js rename to tests/integration-package/handler.js diff --git a/tests/packaging-suite/individually.yml b/tests/integration-package/individually.yml similarity index 100% rename from tests/packaging-suite/individually.yml rename to tests/integration-package/individually.yml diff --git a/tests/packaging-suite/packaging.tests.js b/tests/integration-package/packaging.tests.js similarity index 90% rename from tests/packaging-suite/packaging.tests.js rename to tests/integration-package/packaging.tests.js index 93da7ee95..6203525dd 100644 --- a/tests/packaging-suite/packaging.tests.js +++ b/tests/integration-package/packaging.tests.js @@ -2,16 +2,15 @@ const fs = require('fs'); const path = require('path'); -const execSync = require('child_process').execSync; const fse = require('fs-extra'); -const testUtils = require('../utils/index'); - -const serverlessExec = path.join(__dirname, '..', '..', 'bin', 'serverless'); +const { execSync } = require('child_process'); +const { serverlessExec } = require('../utils/misc'); +const { getTmpDirPath, listZipFiles } = require('../utils/fs'); describe('Integration test - Packaging', () => { let cwd; beforeEach(() => { - cwd = testUtils.getTmpDirPath(); + cwd = getTmpDirPath(); fse.mkdirsSync(cwd); }); @@ -19,7 +18,7 @@ describe('Integration test - Packaging', () => { fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync(`${serverlessExec} package`, { cwd }); - return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { expect(zipfiles).toEqual(['handler.js']); }); @@ -31,7 +30,7 @@ describe('Integration test - Packaging', () => { execSync('npm init --yes', { cwd }); execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); - return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { const nodeModules = new Set( zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); @@ -47,7 +46,7 @@ describe('Integration test - Packaging', () => { execSync('npm init --yes', { cwd }); execSync('npm i --save-dev lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); - return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { const nodeModules = new Set( zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); @@ -64,7 +63,7 @@ describe('Integration test - Packaging', () => { execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); - return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { const nodeModules = new Set( zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); @@ -145,7 +144,7 @@ describe('Integration test - Packaging', () => { 'IamRoleLambdaExecution', ], }); - return testUtils.listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) .then(zipfiles => { expect(zipfiles).toEqual(['handler.js']); }); @@ -188,9 +187,9 @@ describe('Integration test - Packaging', () => { 'IamRoleLambdaExecution', ], }); - return testUtils.listZipFiles(path.join(cwd, '.serverless/hello.zip')) + return listZipFiles(path.join(cwd, '.serverless/hello.zip')) .then(zipfiles => expect(zipfiles).toEqual(['handler.js'])) - .then(() => testUtils.listZipFiles(path.join(cwd, '.serverless/hello2.zip'))) + .then(() => listZipFiles(path.join(cwd, '.serverless/hello2.zip'))) .then(zipfiles => expect(zipfiles).toEqual(['handler2.js'])); }); }); diff --git a/tests/packaging-suite/serverless.yml b/tests/integration-package/serverless.yml similarity index 100% rename from tests/packaging-suite/serverless.yml rename to tests/integration-package/serverless.yml diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/handler.js deleted file mode 100644 index a33dec9e5..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/handler.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - const response = { - statusCode: 200, - body: JSON.stringify({ - message: 'Hello from API Gateway!', - event, - }), - }; - - callback(null, response); -}; diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml deleted file mode 100644 index 19f08adba..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml +++ /dev/null @@ -1,16 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - apiKeys: - - WillBeReplacedBeforeDeployment - -functions: - hello: - handler: handler.hello - events: - - http: - path: hello - method: GET - private: true diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/tests.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/tests.js deleted file mode 100644 index a3048eda1..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/tests.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const BbPromise = require('bluebird'); -const execSync = require('child_process').execSync; -const AWS = require('aws-sdk'); -const _ = require('lodash'); -const fetch = require('node-fetch'); -const fse = require('fs-extra'); -const crypto = require('crypto'); - -const Utils = require('../../../../../utils/index'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -const APIG = new AWS.APIGateway({ region: 'us-east-1' }); -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); -BbPromise.promisifyAll(APIG, { suffix: 'Promised' }); - -describe('AWS - API Gateway (Integration: Lambda Proxy): API keys test', () => { - let stackName; - let endpoint; - let apiKey; - - beforeAll(() => { - stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - - // replace name of the API key with something unique - const serverlessYmlFilePath = path.join(process.cwd(), 'serverless.yml'); - let serverlessYmlFileContent = fse.readFileSync(serverlessYmlFilePath).toString(); - - const apiKeyName = crypto.randomBytes(8).toString('hex'); - - serverlessYmlFileContent = serverlessYmlFileContent - .replace(/WillBeReplacedBeforeDeployment/, apiKeyName); - - fse.writeFileSync(serverlessYmlFilePath, serverlessYmlFileContent); - - Utils.deployService(); - }); - - beforeAll(() => { - const info = execSync(`${Utils.serverlessExec} info`); - const stringifiedOutput = (new Buffer(info, 'base64').toString()); - // some regex magic to extract the first API key value from the info output - apiKey = stringifiedOutput.match(/(api keys:\n)(\s*)(.+):(\s*)(.+)/)[5]; - }); - - it('should expose the endpoint(s) in the CloudFormation Outputs', () => - CF.describeStacksPromised({ StackName: stackName }) - .then((result) => _.find(result.Stacks[0].Outputs, - { OutputKey: 'ServiceEndpoint' }).OutputValue) - .then((endpointOutput) => { - const matched = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; - endpoint = `${matched}/hello`; - }) - ); - - it('should expose the API key(s) with its values when running the info command', () => { - expect(apiKey.length).to.be.above(0); - }); - - it('should reject a request with an invalid API Key', () => - fetch(endpoint) - .then((response) => { - expect(response.status).to.equal(403); - }) - ); - - it('should succeed if correct API key is given', () => - fetch(endpoint, { headers: { 'x-api-key': apiKey } }) - .then(response => response.json()) - .then((json) => { - expect(json.message).to.equal('Hello from API Gateway!'); - expect(json.event.requestContext.identity.apiKey).to.equal(apiKey); - expect(json.event.headers['x-api-key']).to.equal(apiKey); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/handler.js deleted file mode 100644 index af4dfe714..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/handler.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - const response = { - statusCode: 200, - headers: { - 'Access-Control-Allow-Origin': '*', - }, - body: JSON.stringify({ - message: 'Hello from API Gateway!', - input: event, - }), - }; - - callback(null, response); -}; diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml deleted file mode 100644 index b0f885660..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml +++ /dev/null @@ -1,27 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - http: - method: GET - path: simple-cors - cors: true - - http: - method: GET - path: complex-cors - cors: - origins: - - '*' - headers: - - Content-Type - - X-Amz-Date - - Authorization - - X-Api-Key - - X-Amz-Security-Token - - X-Amz-User-Agent diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/tests.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/tests.js deleted file mode 100644 index 92095960c..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/tests.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const BbPromise = require('bluebird'); -const AWS = require('aws-sdk'); -const _ = require('lodash'); -const fetch = require('node-fetch'); - -const Utils = require('../../../../../utils/index'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); - -describe('AWS - API Gateway (Integration: Lambda Proxy): CORS test', () => { - let stackName; - let endpointBase; - - beforeAll(() => { - stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should expose the endpoint(s) in the CloudFormation Outputs', () => - CF.describeStacksPromised({ StackName: stackName }) - .then((result) => _.find(result.Stacks[0].Outputs, - { OutputKey: 'ServiceEndpoint' }).OutputValue) - .then((endpointOutput) => { - endpointBase = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; - }) - ); - - it('should setup CORS support with simple string config', () => - fetch(`${endpointBase}/simple-cors`, { method: 'OPTIONS' }) - .then((response) => { - const headers = response.headers; - - expect(headers.get('access-control-allow-headers')) - .to.equal('Content-Type,X-Amz-Date,Authorization,X-Api-Key,' - + 'X-Amz-Security-Token,X-Amz-User-Agent'); - expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); - expect(headers.get('access-control-allow-origin')).to.equal('*'); - }) - ); - - it('should setup CORS support with complex object config', () => - fetch(`${endpointBase}/complex-cors`, { method: 'OPTIONS' }) - .then((response) => { - const headers = response.headers; - - expect(headers.get('access-control-allow-headers')) - .to.equal('Content-Type,X-Amz-Date,Authorization,X-Api-Key,' - + 'X-Amz-Security-Token,X-Amz-User-Agent'); - expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); - expect(headers.get('access-control-allow-origin')).to.equal('*'); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/handler.js deleted file mode 100644 index 08f981b24..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/handler.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -const generatePolicy = (principalId, effect, resource) => { - const authResponse = {}; - authResponse.principalId = principalId; - - if (effect && resource) { - const policyDocument = {}; - policyDocument.Version = '2012-10-17'; - policyDocument.Statement = []; - - const statementOne = {}; - statementOne.Action = 'execute-api:Invoke'; - statementOne.Effect = effect; - statementOne.Resource = resource; - policyDocument.Statement[0] = statementOne; - authResponse.policyDocument = policyDocument; - } - - return authResponse; -}; - -// protected function -module.exports.hello = (event, context, callback) => { - const response = { - statusCode: 200, - body: JSON.stringify({ - message: 'Successfully authorized!', - event, - }), - }; - - callback(null, response); -}; - -// auth function -module.exports.auth = (event, context) => { - const token = event.authorizationToken.split(' '); - - if (token[0] === 'Bearer' && token[1] === 'ShouldBeAuthorized') { - context.succeed(generatePolicy('SomeRandomId', 'Allow', '*')); - } - - context.fail('Unauthorized'); -}; diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml deleted file mode 100644 index c5d573a40..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml +++ /dev/null @@ -1,16 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - http: - path: hello - method: GET - authorizer: auth - auth: - handler: handler.auth diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/tests.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/tests.js deleted file mode 100644 index ffe2ec57b..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/tests.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const BbPromise = require('bluebird'); -const AWS = require('aws-sdk'); -const _ = require('lodash'); -const fetch = require('node-fetch'); - -const Utils = require('../../../../../utils/index'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); - - -describe('AWS - API Gateway (Integration: Lambda Proxy): Custom authorizers test', () => { - let stackName; - let endpoint; - - beforeAll(() => { - stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should expose the endpoint(s) in the CloudFormation Outputs', () => - CF.describeStacksPromised({ StackName: stackName }) - .then((result) => _.find(result.Stacks[0].Outputs, - { OutputKey: 'ServiceEndpoint' }).OutputValue) - .then((endpointOutput) => { - endpoint = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; - endpoint = `${endpoint}/hello`; - }) - ); - - it('should reject requests without authorization', () => - fetch(endpoint) - .then((response) => { - expect(response.status).to.equal(401); - }) - ); - - it('should reject requests with wrong authorization', () => - fetch(endpoint, { headers: { Authorization: 'Bearer ShouldNotBeAuthorized' } }) - .then((response) => { - expect(response.status).to.equal(401); - }) - ); - - it('should authorize requests with correct authorization', () => - fetch(endpoint, { headers: { Authorization: 'Bearer ShouldBeAuthorized' } }) - .then(response => response.json()) - .then((json) => { - expect(json.message).to.equal('Successfully authorized!'); - expect(json.event.requestContext.authorizer.principalId).to.equal('SomeRandomId'); - expect(json.event.headers.Authorization).to.equal('Bearer ShouldBeAuthorized'); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/handler.js deleted file mode 100644 index 60924d588..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/handler.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - const response = { - statusCode: 200, - body: JSON.stringify({ - message: 'Hello from API Gateway!', - input: event, - }), - }; - - callback(null, response); -}; diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/serverless.yml deleted file mode 100644 index b72f5a4c1..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/serverless.yml +++ /dev/null @@ -1,37 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - # paths without a slash - - http: POST without-slash - - http: GET without-slash - - http: - method: PUT - path: without-slash - - http: - method: DELETE - path: without-slash - # paths with a slash - - http: POST /with-slash - - http: GET /with-slash - - http: - method: PUT - path: /with-slash - - http: - method: DELETE - path: /with-slash - # root only paths - - http: POST / - - http: GET / - - http: - method: PUT - path: / - - http: - method: DELETE - path: / diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/tests.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/tests.js deleted file mode 100644 index 49b09b77e..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/tests.js +++ /dev/null @@ -1,139 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const BbPromise = require('bluebird'); -const AWS = require('aws-sdk'); -const _ = require('lodash'); -const fetch = require('node-fetch'); - -const Utils = require('../../../../../utils/index'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); - -describe('AWS - API Gateway (Integration: Lambda Proxy): Simple API test', () => { - let stackName; - let endpoint; - - beforeAll(() => { - stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should expose the endpoint(s) in the CloudFormation Outputs', () => - CF.describeStacksPromised({ StackName: stackName }) - .then((result) => _.find(result.Stacks[0].Outputs, - { OutputKey: 'ServiceEndpoint' }).OutputValue) - .then((endpointOutput) => { - endpoint = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; - endpoint = `${endpoint}`; - }) - ); - - describe('when having a "without-slash" path setup', () => { - it('should expose an accessible POST HTTP endpoint', () => { - const testEndpoint = `${endpoint}/without-slash`; - - return fetch(testEndpoint, { method: 'POST' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible GET HTTP endpoint', () => { - const testEndpoint = `${endpoint}/without-slash`; - - return fetch(testEndpoint) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible PUT HTTP endpoint', () => { - const testEndpoint = `${endpoint}/without-slash`; - - return fetch(testEndpoint, { method: 'PUT' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible DELETE HTTP endpoint', () => { - const testEndpoint = `${endpoint}/without-slash`; - - return fetch(testEndpoint, { method: 'DELETE' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - }); - - describe('when having a "/with-slash" path setup', () => { - it('should expose an accessible POST HTTP endpoint', () => { - const testEndpoint = `${endpoint}/with-slash`; - - return fetch(testEndpoint, { method: 'POST' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible GET HTTP endpoint', () => { - const testEndpoint = `${endpoint}/with-slash`; - - return fetch(testEndpoint) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible PUT HTTP endpoint', () => { - const testEndpoint = `${endpoint}/with-slash`; - - return fetch(testEndpoint, { method: 'PUT' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible DELETE HTTP endpoint', () => { - const testEndpoint = `${endpoint}/with-slash`; - - return fetch(testEndpoint, { method: 'DELETE' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - }); - - describe('when having a "/" path setup', () => { - it('should expose an accessible POST HTTP endpoint', () => { - const testEndpoint = `${endpoint}`; - - return fetch(testEndpoint, { method: 'POST' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible GET HTTP endpoint', () => { - const testEndpoint = `${endpoint}`; - - return fetch(testEndpoint) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible PUT HTTP endpoint', () => { - const testEndpoint = `${endpoint}`; - - return fetch(testEndpoint, { method: 'PUT' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible DELETE HTTP endpoint', () => { - const testEndpoint = `${endpoint}`; - - return fetch(testEndpoint, { method: 'DELETE' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - }); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/api-gateway/integration-lambda/api-keys/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda/api-keys/service/handler.js deleted file mode 100644 index a05213486..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/api-keys/service/handler.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - callback(null, { message: 'Hello from API Gateway!', event }); -}; diff --git a/tests/integration/aws/api-gateway/integration-lambda/api-keys/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda/api-keys/service/serverless.yml deleted file mode 100644 index dca0516ba..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/api-keys/service/serverless.yml +++ /dev/null @@ -1,17 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - apiKeys: - - WillBeReplacedBeforeDeployment - -functions: - hello: - handler: handler.hello - events: - - http: - path: hello - method: GET - integration: lambda - private: true diff --git a/tests/integration/aws/api-gateway/integration-lambda/api-keys/tests.js b/tests/integration/aws/api-gateway/integration-lambda/api-keys/tests.js deleted file mode 100644 index 3f21813db..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/api-keys/tests.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const BbPromise = require('bluebird'); -const execSync = require('child_process').execSync; -const AWS = require('aws-sdk'); -const _ = require('lodash'); -const fetch = require('node-fetch'); -const fse = require('fs-extra'); -const crypto = require('crypto'); - -const Utils = require('../../../../../utils/index'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -const APIG = new AWS.APIGateway({ region: 'us-east-1' }); -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); -BbPromise.promisifyAll(APIG, { suffix: 'Promised' }); - -describe('AWS - API Gateway (Integration: Lambda): API keys test', () => { - let stackName; - let endpoint; - let apiKey; - - beforeAll(() => { - stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - - // replace name of the API key with something unique - const serverlessYmlFilePath = path.join(process.cwd(), 'serverless.yml'); - let serverlessYmlFileContent = fse.readFileSync(serverlessYmlFilePath).toString(); - - const apiKeyName = crypto.randomBytes(8).toString('hex'); - - serverlessYmlFileContent = serverlessYmlFileContent - .replace(/WillBeReplacedBeforeDeployment/, apiKeyName); - - fse.writeFileSync(serverlessYmlFilePath, serverlessYmlFileContent); - - Utils.deployService(); - }); - - it('should expose the endpoint(s) in the CloudFormation Outputs', () => - CF.describeStacksPromised({ StackName: stackName }) - .then((result) => _.find(result.Stacks[0].Outputs, - { OutputKey: 'ServiceEndpoint' }).OutputValue) - .then((endpointOutput) => { - endpoint = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; - endpoint = `${endpoint}/hello`; - }) - ); - - it('should expose the API key(s) with its values when running the info command', () => { - const info = execSync(`${Utils.serverlessExec} info`); - - const stringifiedOutput = (new Buffer(info, 'base64').toString()); - - // some regex magic to extract the first API key value from the info output - apiKey = stringifiedOutput.match(/(api keys:\n)(\s*)(.+):(\s*)(.+)/)[5]; - - expect(apiKey.length).to.be.above(0); - }); - - it('should reject a request with an invalid API Key', () => - fetch(endpoint) - .then((response) => { - expect(response.status).to.equal(403); - }) - ); - - it('should succeed if correct API key is given', () => - fetch(endpoint, { headers: { 'x-api-key': apiKey } }) - .then(response => response.json()) - .then((json) => { - expect(json.message).to.equal('Hello from API Gateway!'); - expect(json.event.identity.apiKey).to.equal(apiKey); - expect(json.event.headers['x-api-key']).to.equal(apiKey); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/api-gateway/integration-lambda/cors/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda/cors/service/handler.js deleted file mode 100644 index a05213486..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/cors/service/handler.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - callback(null, { message: 'Hello from API Gateway!', event }); -}; diff --git a/tests/integration/aws/api-gateway/integration-lambda/cors/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda/cors/service/serverless.yml deleted file mode 100644 index 89d6fc2d6..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/cors/service/serverless.yml +++ /dev/null @@ -1,29 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - http: - method: GET - path: simple-cors - integration: lambda - cors: true - - http: - method: GET - path: complex-cors - integration: lambda - cors: - origins: - - '*' - headers: - - Content-Type - - X-Amz-Date - - Authorization - - X-Api-Key - - X-Amz-Security-Token - - X-Amz-User-Agent diff --git a/tests/integration/aws/api-gateway/integration-lambda/cors/tests.js b/tests/integration/aws/api-gateway/integration-lambda/cors/tests.js deleted file mode 100644 index 7b8171559..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/cors/tests.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const BbPromise = require('bluebird'); -const AWS = require('aws-sdk'); -const _ = require('lodash'); -const fetch = require('node-fetch'); - -const Utils = require('../../../../../utils/index'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); - -describe('AWS - API Gateway (Integration: Lambda): CORS test', () => { - let stackName; - let endpointBase; - - beforeAll(() => { - stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should expose the endpoint(s) in the CloudFormation Outputs', () => - CF.describeStacksPromised({ StackName: stackName }) - .then((result) => _.find(result.Stacks[0].Outputs, - { OutputKey: 'ServiceEndpoint' }).OutputValue) - .then((endpointOutput) => { - endpointBase = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; - }) - ); - - it('should setup CORS support with simple string config', () => - fetch(`${endpointBase}/simple-cors`, { method: 'OPTIONS' }) - .then((response) => { - const headers = response.headers; - - expect(headers.get('access-control-allow-headers')) - .to.equal('Content-Type,X-Amz-Date,Authorization,X-Api-Key,' - + 'X-Amz-Security-Token,X-Amz-User-Agent'); - expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); - expect(headers.get('access-control-allow-origin')).to.equal('*'); - }) - ); - - it('should setup CORS support with complex object config', () => - fetch(`${endpointBase}/complex-cors`, { method: 'OPTIONS' }) - .then((response) => { - const headers = response.headers; - - expect(headers.get('access-control-allow-headers')) - .to.equal('Content-Type,X-Amz-Date,Authorization,X-Api-Key,' - + 'X-Amz-Security-Token,X-Amz-User-Agent'); - expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); - expect(headers.get('access-control-allow-origin')).to.equal('*'); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/serverless.yml deleted file mode 100644 index c0b5a7250..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/service/serverless.yml +++ /dev/null @@ -1,17 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - http: - path: hello - method: GET - integration: lambda - authorizer: auth - auth: - handler: handler.auth diff --git a/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/tests.js b/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/tests.js deleted file mode 100644 index 33a4f9706..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/custom-authorizers/tests.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const BbPromise = require('bluebird'); -const AWS = require('aws-sdk'); -const _ = require('lodash'); -const fetch = require('node-fetch'); - -const Utils = require('../../../../../utils/index'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); - -describe('AWS - API Gateway (Integration: Lambda): Custom authorizers test', () => { - let stackName; - let endpoint; - - beforeAll(() => { - stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should expose the endpoint(s) in the CloudFormation Outputs', () => - CF.describeStacksPromised({ StackName: stackName }) - .then((result) => _.find(result.Stacks[0].Outputs, - { OutputKey: 'ServiceEndpoint' }).OutputValue) - .then((endpointOutput) => { - endpoint = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; - endpoint = `${endpoint}/hello`; - }) - ); - - it('should reject requests without authorization', () => - fetch(endpoint) - .then((response) => { - expect(response.status).to.equal(401); - }) - ); - - it('should reject requests with wrong authorization', () => - fetch(endpoint, { headers: { Authorization: 'Bearer ShouldNotBeAuthorized' } }) - .then((response) => { - expect(response.status).to.equal(401); - }) - ); - - it('should authorize requests with correct authorization', () => - fetch(endpoint, { headers: { Authorization: 'Bearer ShouldBeAuthorized' } }) - .then(response => response.json()) - .then((json) => { - expect(json.message).to.equal('Successfully authorized!'); - expect(json.event.principalId).to.equal('SomeRandomId'); - expect(json.event.headers.Authorization).to.equal('Bearer ShouldBeAuthorized'); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/api-gateway/integration-lambda/simple-api/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda/simple-api/service/handler.js deleted file mode 100644 index a05213486..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/simple-api/service/handler.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - callback(null, { message: 'Hello from API Gateway!', event }); -}; diff --git a/tests/integration/aws/api-gateway/integration-lambda/simple-api/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda/simple-api/service/serverless.yml deleted file mode 100644 index b4ef6a1a2..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/simple-api/service/serverless.yml +++ /dev/null @@ -1,61 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - # paths without a slash - - http: - method: POST - path: without-slash - integration: lambda - - http: - method: GET - path: without-slash - integration: lambda - - http: - method: PUT - path: without-slash - integration: lambda - - http: - method: DELETE - path: without-slash - integration: lambda - # paths with a slash - - http: - method: POST - path: /with-slash - integration: lambda - - http: - method: GET - path: /with-slash - integration: lambda - - http: - method: PUT - path: /with-slash - integration: lambda - - http: - method: DELETE - path: /with-slash - integration: lambda - # root only paths - - http: - method: POST - path: / - integration: lambda - - http: - method: GET - path: / - integration: lambda - - http: - method: PUT - path: / - integration: lambda - - http: - method: DELETE - path: / - integration: lambda diff --git a/tests/integration/aws/api-gateway/integration-lambda/simple-api/tests.js b/tests/integration/aws/api-gateway/integration-lambda/simple-api/tests.js deleted file mode 100644 index 6050c13f3..000000000 --- a/tests/integration/aws/api-gateway/integration-lambda/simple-api/tests.js +++ /dev/null @@ -1,139 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const BbPromise = require('bluebird'); -const AWS = require('aws-sdk'); -const _ = require('lodash'); -const fetch = require('node-fetch'); - -const Utils = require('../../../../../utils/index'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); - -describe('AWS - API Gateway (Integration: Lambda): Simple API test', () => { - let stackName; - let endpoint; - - beforeAll(() => { - stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should expose the endpoint(s) in the CloudFormation Outputs', () => - CF.describeStacksPromised({ StackName: stackName }) - .then((result) => _.find(result.Stacks[0].Outputs, - { OutputKey: 'ServiceEndpoint' }).OutputValue) - .then((endpointOutput) => { - endpoint = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; - endpoint = `${endpoint}`; - }) - ); - - describe('when having a "without-slash" path setup', () => { - it('should expose an accessible POST HTTP endpoint', () => { - const testEndpoint = `${endpoint}/without-slash`; - - return fetch(testEndpoint, { method: 'POST' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible GET HTTP endpoint', () => { - const testEndpoint = `${endpoint}/without-slash`; - - return fetch(testEndpoint) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible PUT HTTP endpoint', () => { - const testEndpoint = `${endpoint}/without-slash`; - - return fetch(testEndpoint, { method: 'PUT' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible DELETE HTTP endpoint', () => { - const testEndpoint = `${endpoint}/without-slash`; - - return fetch(testEndpoint, { method: 'DELETE' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - }); - - describe('when having a "/with-slash" path setup', () => { - it('should expose an accessible POST HTTP endpoint', () => { - const testEndpoint = `${endpoint}/with-slash`; - - return fetch(testEndpoint, { method: 'POST' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible GET HTTP endpoint', () => { - const testEndpoint = `${endpoint}/with-slash`; - - return fetch(testEndpoint) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible PUT HTTP endpoint', () => { - const testEndpoint = `${endpoint}/with-slash`; - - return fetch(testEndpoint, { method: 'PUT' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible DELETE HTTP endpoint', () => { - const testEndpoint = `${endpoint}/with-slash`; - - return fetch(testEndpoint, { method: 'DELETE' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - }); - - describe('when having a "/" path setup', () => { - it('should expose an accessible POST HTTP endpoint', () => { - const testEndpoint = `${endpoint}`; - - return fetch(testEndpoint, { method: 'POST' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible GET HTTP endpoint', () => { - const testEndpoint = `${endpoint}`; - - return fetch(testEndpoint) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible PUT HTTP endpoint', () => { - const testEndpoint = `${endpoint}`; - - return fetch(testEndpoint, { method: 'PUT' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - - it('should expose an accessible DELETE HTTP endpoint', () => { - const testEndpoint = `${endpoint}`; - - return fetch(testEndpoint, { method: 'DELETE' }) - .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway!')); - }); - }); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/handler.js b/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/handler.js deleted file mode 100644 index 8ea67e830..000000000 --- a/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/handler.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports.cwe1 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; - -module.exports.cwe2 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; diff --git a/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/serverless.yml b/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/serverless.yml deleted file mode 100644 index ab1075c76..000000000 --- a/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/service/serverless.yml +++ /dev/null @@ -1,29 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - cwe1: - handler: handler.cwe1 - events: - - cloudwatchEvent: - event: - source: - - serverless.testapp1 - - cloudwatchEvent: - event: - source: - - serverless.testapp2 - cwe2: - handler: handler.cwe2 - events: - - cloudwatchEvent: - event: - source: - - serverless.testapp1 - - cloudwatchEvent: - event: - source: - - serverless.testapp2 diff --git a/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/tests.js b/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/tests.js deleted file mode 100644 index 33d4d59b9..000000000 --- a/tests/integration/aws/cloud-watch-event/multiple-events-multiple-functions/tests.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - CloudWatch Event: Multiple events with multiple functions', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger functions when cloudwatchEvent runs', () => Utils - .putCloudWatchEvents(['serverless.testapp1', 'serverless.testapp2']) - .delay(60000) - .then(() => { - const logs1 = Utils.getFunctionLogs('cwe1'); - const logs2 = Utils.getFunctionLogs('cwe2'); - expect(/serverless\.testapp1/g.test(logs1)).to.equal(true); - expect(/serverless\.testapp1/g.test(logs2)).to.equal(true); - expect(/serverless\.testapp2/g.test(logs1)).to.equal(true); - expect(/serverless\.testapp2/g.test(logs2)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/handler.js b/tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/handler.js deleted file mode 100644 index 53d42c6dc..000000000 --- a/tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/handler.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports.cwe1 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; diff --git a/tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/serverless.yml b/tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/serverless.yml deleted file mode 100644 index b17378649..000000000 --- a/tests/integration/aws/cloud-watch-event/multiple-events-single-function/service/serverless.yml +++ /dev/null @@ -1,18 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - cwe1: - handler: handler.cwe1 - events: - - cloudwatchEvent: - event: - source: - - serverless.testapp1 - - cloudwatchEvent: - event: - source: - - serverless.testapp2 diff --git a/tests/integration/aws/cloud-watch-event/multiple-events-single-function/tests.js b/tests/integration/aws/cloud-watch-event/multiple-events-single-function/tests.js deleted file mode 100644 index 9b662b686..000000000 --- a/tests/integration/aws/cloud-watch-event/multiple-events-single-function/tests.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - CloudWatch Event: Multiple events with single function', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when cloudwatchEvent runs', () => Utils - .putCloudWatchEvents(['serverless.testapp1', 'serverless.testapp2']) - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('cwe1'); - expect(/serverless\.testapp1/g.test(logs)).to.equal(true); - expect(/serverless\.testapp2/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/handler.js b/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/handler.js deleted file mode 100644 index 8ea67e830..000000000 --- a/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/handler.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports.cwe1 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; - -module.exports.cwe2 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; diff --git a/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/serverless.yml b/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/serverless.yml deleted file mode 100644 index ac28afdca..000000000 --- a/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/service/serverless.yml +++ /dev/null @@ -1,21 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - cwe1: - handler: handler.cwe1 - events: - - cloudwatchEvent: - event: - source: - - serverless.testapp1 - cwe2: - handler: handler.cwe2 - events: - - cloudwatchEvent: - event: - source: - - serverless.testapp1 diff --git a/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/tests.js b/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/tests.js deleted file mode 100644 index 433708734..000000000 --- a/tests/integration/aws/cloud-watch-event/single-event-multiple-functions/tests.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - CloudWatch Event: Single event with multiple functions', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger functions when cloudwatchEvent runs', () => Utils - .putCloudWatchEvents(['serverless.testapp1']) - .delay(60000) - .then(() => { - const logs1 = Utils.getFunctionLogs('cwe1'); - const logs2 = Utils.getFunctionLogs('cwe2'); - expect(/serverless\.testapp1/g.test(logs1)).to.equal(true); - expect(/serverless\.testapp1/g.test(logs2)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/cloud-watch-event/single-event-single-function/service/handler.js b/tests/integration/aws/cloud-watch-event/single-event-single-function/service/handler.js deleted file mode 100644 index 53d42c6dc..000000000 --- a/tests/integration/aws/cloud-watch-event/single-event-single-function/service/handler.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports.cwe1 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; diff --git a/tests/integration/aws/cloud-watch-event/single-event-single-function/service/serverless.yml b/tests/integration/aws/cloud-watch-event/single-event-single-function/service/serverless.yml deleted file mode 100644 index 09df4393c..000000000 --- a/tests/integration/aws/cloud-watch-event/single-event-single-function/service/serverless.yml +++ /dev/null @@ -1,14 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - cwe1: - handler: handler.cwe1 - events: - - cloudwatchEvent: - event: - source: - - serverless.testapp1 diff --git a/tests/integration/aws/cloud-watch-event/single-event-single-function/tests.js b/tests/integration/aws/cloud-watch-event/single-event-single-function/tests.js deleted file mode 100644 index c0ab408d8..000000000 --- a/tests/integration/aws/cloud-watch-event/single-event-single-function/tests.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - CloudWatch Event: Single event with single function', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when cloudwatchEvent runs', () => Utils - .putCloudWatchEvents(['serverless.testapp1']) - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('cwe1'); - expect(/serverless\.testapp1/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/handler.js b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/handler.js deleted file mode 100644 index a1e2afe62..000000000 --- a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/handler.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const preSignUp = (event, context, callback) => { - const nextEvent = Object.assign({}, event); - nextEvent.response.autoConfirmUser = true; - - process.stdout.write(JSON.stringify(nextEvent)); - callback(null, nextEvent); -}; - -const customMessage = (event, context, callback) => { - const nextEvent = Object.assign({}, event); - if (event.triggerSource === 'CustomMessage_SignUp') { - nextEvent.response.smsMessage = `Welcome to the service. Your confirmation code is ${ - event.request.codeParameter}`; - nextEvent.response.emailSubject = 'Welcome to the service'; - nextEvent.response.emailMessage = `Thank you for signing up. ${ - event.request.codeParameter} is your verification code`; - } - process.stdout.write(JSON.stringify(nextEvent)); - callback(null, nextEvent); -}; - -module.exports.preSignUp1 = preSignUp; -module.exports.preSignUp2 = preSignUp; -module.exports.customMessage1 = customMessage; -module.exports.customMessage2 = customMessage; diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml deleted file mode 100644 index 9335d606a..000000000 --- a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/service/serverless.yml +++ /dev/null @@ -1,31 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - preSignUp1: - handler: handler.preSignUp1 - events: - - cognitoUserPool: - pool: ${env:COGNITO_USER_POOL_1} - trigger: PreSignUp - customMessage1: - handler: handler.customMessage1 - events: - - cognitoUserPool: - pool: ${env:COGNITO_USER_POOL_1} - trigger: CustomMessage - preSignUp2: - handler: handler.preSignUp2 - events: - - cognitoUserPool: - pool: ${env:COGNITO_USER_POOL_2} - trigger: PreSignUp - customMessage2: - handler: handler.customMessage2 - events: - - cognitoUserPool: - pool: ${env:COGNITO_USER_POOL_2} - trigger: CustomMessage diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/tests.js b/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/tests.js deleted file mode 100644 index df2ba5d40..000000000 --- a/tests/integration/aws/cognito-user-pool/multiple-pools-multiple-events-multiple-functions/tests.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - Cognito User Pool: Multiple User Pools with multiple ' + - 'events with multiple functions', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should call the specified function on the first User Pool when PreSignUp ' + - 'event is triggered', () => Utils - .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) - .then((poolId) => - Promise.all([ - poolId, - Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!'), - ]) - ) - .delay(60000) - .then((promiseResponse) => { - const poolId = promiseResponse[0]; - const logs = Utils.getFunctionLogs('preSignUp1'); - - expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); - expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); - }) - ); - - it('should call the specified function on the first User Pool when CustomMessage ' + - 'event is triggered', () => Utils - .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) - .then((poolId) => { - const logs = Utils.getFunctionLogs('customMessage1'); - - expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); - expect(/"triggerSource":"CustomMessage_AdminCreateUser"/g.test(logs)).to.equal(true); - }) - ); - - it('should call the specified function on the second User Pool when PreSignUp ' + - 'event is triggered', () => Utils - .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_2) - .then((poolId) => - Promise.all([ - poolId, - Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!'), - ]) - ) - .delay(60000) - .then((promiseResponse) => { - const poolId = promiseResponse[0]; - const logs = Utils.getFunctionLogs('preSignUp2'); - - expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); - expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); - }) - ); - - it('should call the specified function on the second User Pool when CustomMessage ' + - 'event is triggered', () => Utils - .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_2) - .then((poolId) => { - const logs = Utils.getFunctionLogs('customMessage2'); - - expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); - expect(/"triggerSource":"CustomMessage_AdminCreateUser"/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/handler.js b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/handler.js deleted file mode 100644 index b7f044321..000000000 --- a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/handler.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -module.exports.preSignUp = (event, context, callback) => { - const nextEvent = Object.assign({}, event); - nextEvent.response.autoConfirmUser = true; - - process.stdout.write(JSON.stringify(nextEvent)); - callback(null, nextEvent); -}; diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml deleted file mode 100644 index e2d62ae50..000000000 --- a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/service/serverless.yml +++ /dev/null @@ -1,16 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - preSignUp: - handler: handler.preSignUp - events: - - cognitoUserPool: - pool: ${env:COGNITO_USER_POOL_1} - trigger: PreSignUp - - cognitoUserPool: - pool: ${env:COGNITO_USER_POOL_2} - trigger: PreSignUp diff --git a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/tests.js b/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/tests.js deleted file mode 100644 index 9a9477545..000000000 --- a/tests/integration/aws/cognito-user-pool/multiple-pools-single-event-single-function/tests.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - Cognito User Pool: Multiple User Pools with single ' + - 'event with single function', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should call the specified function on the first User Pool when PreSignUp ' + - 'event is triggered', () => Utils - .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) - .then((poolId) => - Promise.all([ - poolId, - Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!'), - ]) - ) - .delay(60000) - .then((promiseResponse) => { - const poolId = promiseResponse[0]; - const logs = Utils.getFunctionLogs('preSignUp'); - expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); - expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); - }) - ); - - it('should call the specified function on the second User Pool when PreSignUp ' + - 'event is triggered', () => Utils - .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_2) - .then((poolId) => - Promise.all([ - poolId, - Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!'), - ]) - ) - .delay(60000) - .then((promiseResponse) => { - const poolId = promiseResponse[0]; - const logs = Utils.getFunctionLogs('preSignUp'); - expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); - expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/handler.js b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/handler.js deleted file mode 100644 index 2801cf8c5..000000000 --- a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/handler.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -module.exports.preSignUp = (event, context, callback) => { - const nextEvent = Object.assign({}, event); - nextEvent.response.autoConfirmUser = true; - - process.stdout.write(JSON.stringify(nextEvent)); - callback(null, nextEvent); -}; - -module.exports.customMessage = (event, context, callback) => { - const nextEvent = Object.assign({}, event); - if (event.triggerSource === 'CustomMessage_SignUp') { - nextEvent.response.smsMessage = `Welcome to the service. Your confirmation code is ${ - event.request.codeParameter}`; - nextEvent.response.emailSubject = 'Welcome to the service'; - nextEvent.response.emailMessage = `Thank you for signing up. ${ - event.request.codeParameter} is your verification code`; - } - process.stdout.write(JSON.stringify(nextEvent)); - callback(null, nextEvent); -}; diff --git a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml deleted file mode 100644 index b3d78c5b9..000000000 --- a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/service/serverless.yml +++ /dev/null @@ -1,19 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - preSignUp: - handler: handler.preSignUp - events: - - cognitoUserPool: - pool: ${env:COGNITO_USER_POOL_1} - trigger: PreSignUp - customMessage: - handler: handler.customMessage - events: - - cognitoUserPool: - pool: ${env:COGNITO_USER_POOL_1} - trigger: CustomMessage diff --git a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/tests.js b/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/tests.js deleted file mode 100644 index 96d25c39b..000000000 --- a/tests/integration/aws/cognito-user-pool/single-pool-multiple-events-multiple-functions/tests.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - Cognito User Pool: Single User Pool with multiple ' + - 'events with multiple functions', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should call the specified function when PreSignUp event is triggered', () => Utils - .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) - .then((poolId) => - Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!') - ) - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('preSignUp'); - expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); - }) - ); - - it('should call the specified function when CustomMessage event is triggered', () => Utils - .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) - .then((poolId) => { - const logs = Utils.getFunctionLogs('customMessage'); - - expect(RegExp(`"userPoolId":"${poolId}"`, 'g').test(logs)).to.equal(true); - expect(/"triggerSource":"CustomMessage_AdminCreateUser"/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/handler.js b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/handler.js deleted file mode 100644 index b7f044321..000000000 --- a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/handler.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -module.exports.preSignUp = (event, context, callback) => { - const nextEvent = Object.assign({}, event); - nextEvent.response.autoConfirmUser = true; - - process.stdout.write(JSON.stringify(nextEvent)); - callback(null, nextEvent); -}; diff --git a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml deleted file mode 100644 index 4e5dcd826..000000000 --- a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/service/serverless.yml +++ /dev/null @@ -1,13 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - preSignUp: - handler: handler.preSignUp - events: - - cognitoUserPool: - pool: ${env:COGNITO_USER_POOL_1} - trigger: PreSignUp diff --git a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/tests.js b/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/tests.js deleted file mode 100644 index b77837821..000000000 --- a/tests/integration/aws/cognito-user-pool/single-pool-single-event-single-function/tests.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - Cognito User Pool: Single User Pool with single ' + - 'event with single function', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should call the specified function when PreSignUp event is triggered', () => Utils - .getCognitoUserPoolId(process.env.COGNITO_USER_POOL_1) - .then((poolId) => - Utils.createCognitoUser(poolId, 'test@test.com', 'Password123!') - ) - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('preSignUp'); - expect(/"triggerSource":"PreSignUp_\w+"/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/general/custom-resources/service/handler.js b/tests/integration/aws/general/custom-resources/service/handler.js deleted file mode 100644 index 57b45f44f..000000000 --- a/tests/integration/aws/general/custom-resources/service/handler.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); -}; diff --git a/tests/integration/aws/general/custom-resources/service/serverless.yml b/tests/integration/aws/general/custom-resources/service/serverless.yml deleted file mode 100644 index 30d1b454f..000000000 --- a/tests/integration/aws/general/custom-resources/service/serverless.yml +++ /dev/null @@ -1,20 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - -resources: - Resources: - MyCustomS3Bucket: - Type: 'AWS::S3::Bucket' - Properties: - BucketName: WillBeReplacedBeforeDeployment - Outputs: - MyCustomOutput: - Value: SomeValue - Description: Used to test custom outputs diff --git a/tests/integration/aws/general/custom-resources/tests.js b/tests/integration/aws/general/custom-resources/tests.js deleted file mode 100644 index 66222e495..000000000 --- a/tests/integration/aws/general/custom-resources/tests.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const AWS = require('aws-sdk'); -const BbPromise = require('bluebird'); -const _ = require('lodash'); -const fse = require('fs-extra'); -const crypto = require('crypto'); - -const Utils = require('../../../../utils/index'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -const S3 = new AWS.S3({ region: 'us-east-1' }); -BbPromise.promisifyAll(CF, { suffix: 'Promised' }); -BbPromise.promisifyAll(S3, { suffix: 'Promised' }); - -describe('AWS - General: Custom resources test', () => { - let stackName; - let s3BucketName; - - beforeAll(() => { - stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - - // replace name of bucket which is created through custom resources with something unique - const serverlessYmlFilePath = path.join(process.cwd(), 'serverless.yml'); - let serverlessYmlFileContent = fse.readFileSync(serverlessYmlFilePath).toString(); - - s3BucketName = crypto.randomBytes(8).toString('hex'); - - serverlessYmlFileContent = serverlessYmlFileContent - .replace(/WillBeReplacedBeforeDeployment/, s3BucketName); - - fse.writeFileSync(serverlessYmlFilePath, serverlessYmlFileContent); - - Utils.deployService(); - }); - - it('should add the custom outputs to the Outputs section', () => - CF.describeStacksPromised({ StackName: stackName }) - .then((result) => _.find(result.Stacks[0].Outputs, - { OutputKey: 'MyCustomOutput' }).OutputValue) - .then((endpointOutput) => { - expect(endpointOutput).to.equal('SomeValue'); - }) - ); - - it('should create the custom resources (a S3 bucket)', () => - S3.listBucketsPromised() - .then((result) => !!_.find(result.Buckets, - { Name: s3BucketName })) - .then((found) => expect(found).to.equal(true)) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/general/environment-variables/service/handler.js b/tests/integration/aws/general/environment-variables/service/handler.js deleted file mode 100644 index 6f5d68a82..000000000 --- a/tests/integration/aws/general/environment-variables/service/handler.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - callback(null, { - environment_variables: process.env, - }); -}; diff --git a/tests/integration/aws/general/environment-variables/service/serverless.yml b/tests/integration/aws/general/environment-variables/service/serverless.yml deleted file mode 100644 index 00cc3e865..000000000 --- a/tests/integration/aws/general/environment-variables/service/serverless.yml +++ /dev/null @@ -1,16 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - environment: - provider_level_variable_1: provider_level_1 - provider_level_variable_2: provider_level_2 - -functions: - hello: - handler: handler.hello - environment: - function_level_variable_1: function_level_1 - function_level_variable_2: function_level_2 - provider_level_variable_2: overwritten_by_function diff --git a/tests/integration/aws/general/environment-variables/tests.js b/tests/integration/aws/general/environment-variables/tests.js deleted file mode 100644 index af1dcb368..000000000 --- a/tests/integration/aws/general/environment-variables/tests.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const execSync = require('child_process').execSync; - -const Utils = require('../../../../utils/index'); - -describe('AWS - General: Environment variables test', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should expose environment variables', () => { - const invoked = execSync(`${Utils.serverlessExec} invoke --function hello --noGreeting true`); - - const result = JSON.parse(new Buffer(invoked, 'base64').toString()); - - expect(result.environment_variables.provider_level_variable_1) - .to.be.equal('provider_level_1'); - expect(result.environment_variables.function_level_variable_1) - .to.be.equal('function_level_1'); - expect(result.environment_variables.function_level_variable_2) - .to.be.equal('function_level_2'); - expect(result.environment_variables.provider_level_variable_2) - .to.be.equal('overwritten_by_function'); - }); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/general/nested-handlers/service/deeply/nested/handler.js b/tests/integration/aws/general/nested-handlers/service/deeply/nested/handler.js deleted file mode 100644 index 6313e306f..000000000 --- a/tests/integration/aws/general/nested-handlers/service/deeply/nested/handler.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -// Your first function handler -module.exports.hello = (event, context, callback) => { - callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); -}; diff --git a/tests/integration/aws/general/nested-handlers/service/serverless.yml b/tests/integration/aws/general/nested-handlers/service/serverless.yml deleted file mode 100644 index 870644b34..000000000 --- a/tests/integration/aws/general/nested-handlers/service/serverless.yml +++ /dev/null @@ -1,9 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: deeply/nested/handler.hello diff --git a/tests/integration/aws/general/nested-handlers/tests.js b/tests/integration/aws/general/nested-handlers/tests.js deleted file mode 100644 index ed4083d95..000000000 --- a/tests/integration/aws/general/nested-handlers/tests.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const execSync = require('child_process').execSync; - -const Utils = require('../../../../utils/index'); - -describe('AWS - General: Nested handlers test', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should invoke the nested handler function from AWS', () => { - const invoked = execSync(`${Utils.serverlessExec} invoke --function hello --noGreeting true`); - - const result = JSON.parse(new Buffer(invoked, 'base64').toString()); - expect(result.message).to.be.equal('Go Serverless v1.0! Your function executed successfully!'); - }); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/general/overwrite-resources/service/handler.js b/tests/integration/aws/general/overwrite-resources/service/handler.js deleted file mode 100644 index c45399eaa..000000000 --- a/tests/integration/aws/general/overwrite-resources/service/handler.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); -}; - -module.exports.world = (event, context, callback) => { - callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); -}; - diff --git a/tests/integration/aws/general/overwrite-resources/service/serverless.yml b/tests/integration/aws/general/overwrite-resources/service/serverless.yml deleted file mode 100644 index 19d78e077..000000000 --- a/tests/integration/aws/general/overwrite-resources/service/serverless.yml +++ /dev/null @@ -1,18 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - world: - handler: handler.world - -resources: - Resources: - HelloLambdaFunction: - Type: "AWS::Lambda::Function" - Properties: - Timeout: 10 diff --git a/tests/integration/aws/general/overwrite-resources/tests.js b/tests/integration/aws/general/overwrite-resources/tests.js deleted file mode 100644 index 06e615005..000000000 --- a/tests/integration/aws/general/overwrite-resources/tests.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const AWS = require('aws-sdk'); -const BbPromise = require('bluebird'); - -const Utils = require('../../../../utils/index'); - -const Lambda = new AWS.Lambda({ region: 'us-east-1' }); -BbPromise.promisifyAll(Lambda, { suffix: 'Promised' }); - -describe('AWS - General: Overwrite resources test', () => { - let stackName; - - beforeAll(() => { - stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should overwrite timeout config for hello function', () => { - const helloFunctionName = `${stackName}-hello`; - return Lambda.getFunctionPromised({ FunctionName: helloFunctionName }) - .then(data => { - const timeout = data.Configuration.Timeout; - expect(timeout).to.equal(10); - }); - }); - - it('should NOT overwrite timeout config for world function', () => { - const worldFunctionName = `${stackName}-world`; - return Lambda.getFunctionPromised({ FunctionName: worldFunctionName }) - .then(data => { - const timeout = data.Configuration.Timeout; - expect(timeout).to.equal(6); - }); - }); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/general/package/service/handler.js b/tests/integration/aws/general/package/service/handler.js deleted file mode 100644 index 57b45f44f..000000000 --- a/tests/integration/aws/general/package/service/handler.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); -}; diff --git a/tests/integration/aws/general/package/service/serverless.yml b/tests/integration/aws/general/package/service/serverless.yml deleted file mode 100644 index b01bfe299..000000000 --- a/tests/integration/aws/general/package/service/serverless.yml +++ /dev/null @@ -1,9 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello diff --git a/tests/integration/aws/general/package/tests.js b/tests/integration/aws/general/package/tests.js deleted file mode 100644 index 2e466cd7a..000000000 --- a/tests/integration/aws/general/package/tests.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const execSync = require('child_process').execSync; -const AWS = require('aws-sdk'); -const fs = require('fs'); - -const CF = new AWS.CloudFormation({ region: 'us-east-1' }); -const Utils = require('../../../../utils/index'); - -describe('AWS - General: Package', () => { - let serviceName; - - beforeAll(() => { - serviceName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - execSync(`${Utils.serverlessExec} package`); - }); - - it('should have create cloudformation files and functions zip', () => { - const deployedFiles = fs.readdirSync(path.join(process.cwd(), '.serverless')); - expect(deployedFiles[0]).to.equal('cloudformation-template-create-stack.json'); - expect(deployedFiles[1]).to.equal('cloudformation-template-update-stack.json'); - expect(deployedFiles[2]).to.equal('serverless-state.json'); - // Note: noticed the seconds section can vary a lot - expect(deployedFiles[3]).to.match(/test-[0-9]{1,}-[0-9]{1,}.zip/); - }); - - it('should not found stack from AWS', (done) => { - CF.describeStackResources({ StackName: serviceName }, (error) => { - expect(error.message).to.equal(`Stack with id ${serviceName} does not exist`); - done(); - }); - }); -}); diff --git a/tests/integration/aws/iot/multiple-rules-multiple-functions/service/handler.js b/tests/integration/aws/iot/multiple-rules-multiple-functions/service/handler.js deleted file mode 100644 index cdfd5c5b9..000000000 --- a/tests/integration/aws/iot/multiple-rules-multiple-functions/service/handler.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports.iot1 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; - -module.exports.iot2 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; diff --git a/tests/integration/aws/iot/multiple-rules-multiple-functions/service/serverless.yml b/tests/integration/aws/iot/multiple-rules-multiple-functions/service/serverless.yml deleted file mode 100644 index c018fbd45..000000000 --- a/tests/integration/aws/iot/multiple-rules-multiple-functions/service/serverless.yml +++ /dev/null @@ -1,17 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - iot1: - handler: handler.iot1 - events: - - iot: - sql: "SELECT * FROM 'mybutton'" - iot2: - handler: handler.iot2 - events: - - iot: - sql: "SELECT * FROM 'weather'" diff --git a/tests/integration/aws/iot/multiple-rules-multiple-functions/tests.js b/tests/integration/aws/iot/multiple-rules-multiple-functions/tests.js deleted file mode 100644 index a54ace65c..000000000 --- a/tests/integration/aws/iot/multiple-rules-multiple-functions/tests.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - IoT: Multiple rules with multiple functions', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when ruletopic message is published', () => Utils - .publishIotData('mybutton', '{"message":"hello serverless"}') - .then(() => Utils.publishIotData('weather', '{"message":"sunny today"}')) - .delay(60000) - .then(() => { - const iot1Logs = Utils.getFunctionLogs('iot1'); - const iot2Logs = Utils.getFunctionLogs('iot2'); - expect(/{"message":"hello serverless"}/g.test(iot1Logs)).to.equal(true); - expect(/{"message":"sunny today"}/g.test(iot2Logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/iot/multiple-rules-single-function/service/handler.js b/tests/integration/aws/iot/multiple-rules-single-function/service/handler.js deleted file mode 100644 index 0f770c560..000000000 --- a/tests/integration/aws/iot/multiple-rules-single-function/service/handler.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports.iot1 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; diff --git a/tests/integration/aws/iot/multiple-rules-single-function/service/serverless.yml b/tests/integration/aws/iot/multiple-rules-single-function/service/serverless.yml deleted file mode 100644 index 5e13e0c96..000000000 --- a/tests/integration/aws/iot/multiple-rules-single-function/service/serverless.yml +++ /dev/null @@ -1,14 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - iot1: - handler: handler.iot1 - events: - - iot: - sql: "SELECT * FROM 'mybutton'" - - iot: - sql: "SELECT * FROM 'weather'" diff --git a/tests/integration/aws/iot/multiple-rules-single-function/tests.js b/tests/integration/aws/iot/multiple-rules-single-function/tests.js deleted file mode 100644 index 11065591f..000000000 --- a/tests/integration/aws/iot/multiple-rules-single-function/tests.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - IoT: Multiple rules with single function', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when ruletopic message is published', () => Utils - .publishIotData('mybutton', '{"message":"hello serverless"}') - .then(() => Utils.publishIotData('weather', '{"message":"sunny today"}')) - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('iot1'); - expect(/{"message":"hello serverless"}/g.test(logs)).to.equal(true); - expect(/{"message":"sunny today"}/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/iot/single-rule-multiple-functions/service/handler.js b/tests/integration/aws/iot/single-rule-multiple-functions/service/handler.js deleted file mode 100644 index cdfd5c5b9..000000000 --- a/tests/integration/aws/iot/single-rule-multiple-functions/service/handler.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports.iot1 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; - -module.exports.iot2 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; diff --git a/tests/integration/aws/iot/single-rule-multiple-functions/service/serverless.yml b/tests/integration/aws/iot/single-rule-multiple-functions/service/serverless.yml deleted file mode 100644 index 0d8e4d393..000000000 --- a/tests/integration/aws/iot/single-rule-multiple-functions/service/serverless.yml +++ /dev/null @@ -1,17 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - iot1: - handler: handler.iot1 - events: - - iot: - sql: "SELECT * FROM 'mybutton'" - iot2: - handler: handler.iot2 - events: - - iot: - sql: "SELECT * FROM 'mybutton'" diff --git a/tests/integration/aws/iot/single-rule-multiple-functions/tests.js b/tests/integration/aws/iot/single-rule-multiple-functions/tests.js deleted file mode 100644 index cddc707f0..000000000 --- a/tests/integration/aws/iot/single-rule-multiple-functions/tests.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - IoT: Single rule with multiple functions', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when ruletopic message is published', () => Utils - .publishIotData('mybutton', '{"message":"hello serverless"}') - .delay(60000) - .then(() => { - const iot1Logs = Utils.getFunctionLogs('iot1'); - const iot2Logs = Utils.getFunctionLogs('iot2'); - expect(/{"message":"hello serverless"}/g.test(iot1Logs)).to.equal(true); - expect(/{"message":"hello serverless"}/g.test(iot2Logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/iot/single-rule-single-function/service/handler.js b/tests/integration/aws/iot/single-rule-single-function/service/handler.js deleted file mode 100644 index 0f770c560..000000000 --- a/tests/integration/aws/iot/single-rule-single-function/service/handler.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports.iot1 = (event, context, callback) => { - process.stdout.write(JSON.stringify(event)); - callback(null, {}); -}; diff --git a/tests/integration/aws/iot/single-rule-single-function/service/serverless.yml b/tests/integration/aws/iot/single-rule-single-function/service/serverless.yml deleted file mode 100644 index cb7ecf35f..000000000 --- a/tests/integration/aws/iot/single-rule-single-function/service/serverless.yml +++ /dev/null @@ -1,12 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - iot1: - handler: handler.iot1 - events: - - iot: - sql: "SELECT * FROM 'mybutton'" diff --git a/tests/integration/aws/iot/single-rule-single-function/tests.js b/tests/integration/aws/iot/single-rule-single-function/tests.js deleted file mode 100644 index eaf3cb11f..000000000 --- a/tests/integration/aws/iot/single-rule-single-function/tests.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - IoT: Single rule with single function', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when ruletopic message is published', () => Utils - .publishIotData('mybutton', '{"message":"hello serverless"}') - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('iot1'); - expect(/{"message":"hello serverless"}/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/handler.js b/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/handler.js deleted file mode 100644 index 5a96f3a31..000000000 --- a/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/handler.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - process.stdout.write(event.Records[0].eventSource); - process.stdout.write(event.Records[0].eventName); - callback(null, { message: 'Hello from S3!', event }); -}; - -module.exports.world = (event, context, callback) => { - process.stdout.write(event.Records[0].eventSource); - process.stdout.write(event.Records[0].eventName); - callback(null, { message: 'Hello from S3!', event }); -}; diff --git a/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/serverless.yml b/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/serverless.yml deleted file mode 100644 index 05cee4c24..000000000 --- a/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/service/serverless.yml +++ /dev/null @@ -1,21 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - s3: ${env:BUCKET_1} - - s3: - bucket: ${env:BUCKET_1} - event: s3:ObjectRemoved:* - world: - handler: handler.world - events: - - s3: ${env:BUCKET_2} - - s3: - bucket: ${env:BUCKET_2} - event: s3:ObjectRemoved:* diff --git a/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/tests.js b/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/tests.js deleted file mode 100644 index 1b667c068..000000000 --- a/tests/integration/aws/s3/multiple-events-multiple-functions-multiple-buckets/tests.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - S3: Multiple events in multiple functions with multiple buckets', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger functions when object created or deleted in buckets', () => Utils - .createAndRemoveInBucket(process.env.BUCKET_1) - .then(() => Utils.createAndRemoveInBucket(process.env.BUCKET_2)) - .delay(60000) - .then(() => { - const helloLogs = Utils.getFunctionLogs('hello'); - const worldLogs = Utils.getFunctionLogs('world'); - - expect(/aws:s3/g.test(helloLogs)).to.equal(true); - expect(/ObjectCreated:Put/g.test(helloLogs)).to.equal(true); - expect(/ObjectRemoved:Delete/g.test(helloLogs)).to.equal(true); - - expect(/aws:s3/g.test(worldLogs)).to.equal(true); - expect(/ObjectCreated:Put/g.test(worldLogs)).to.equal(true); - expect(/ObjectRemoved:Delete/g.test(worldLogs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/handler.js b/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/handler.js deleted file mode 100644 index d742ed551..000000000 --- a/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/handler.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports.create = (event, context, callback) => { - process.stdout.write(event.Records[0].eventSource); - process.stdout.write(event.Records[0].eventName); - callback(null, { message: 'Hello from S3!', event }); -}; - -module.exports.remove = (event, context, callback) => { - process.stdout.write(event.Records[0].eventSource); - process.stdout.write(event.Records[0].eventName); - callback(null, { message: 'Hello from S3!', event }); -}; diff --git a/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/serverless.yml b/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/serverless.yml deleted file mode 100644 index b7d95c417..000000000 --- a/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/service/serverless.yml +++ /dev/null @@ -1,17 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - create: - handler: handler.create - events: - - s3: ${env:BUCKET_1} - remove: - handler: handler.remove - events: - - s3: - bucket: ${env:BUCKET_1} - event: s3:ObjectRemoved:* diff --git a/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/tests.js b/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/tests.js deleted file mode 100644 index 03a0aab68..000000000 --- a/tests/integration/aws/s3/multiple-events-multiple-functions-single-bucket/tests.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - S3: Multiple events in multiple functions with a single bucket', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger create/remove functions on object create / remove', () => Utils - .createAndRemoveInBucket(process.env.BUCKET_1) - .delay(60000) - .then(() => { - const createLogs = Utils.getFunctionLogs('create'); - const removeLogs = Utils.getFunctionLogs('remove'); - - expect(/aws:s3/g.test(createLogs)).to.equal(true); - expect(/aws:s3/g.test(removeLogs)).to.equal(true); - expect(/ObjectCreated:Put/g.test(createLogs)).to.equal(true); - expect(/ObjectRemoved:Delete/g.test(removeLogs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/handler.js b/tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/handler.js deleted file mode 100644 index aaa093a8c..000000000 --- a/tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/handler.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - process.stdout.write(event.Records[0].eventSource); - process.stdout.write(event.Records[0].eventName); - callback(null, { message: 'Hello from S3!', event }); -}; diff --git a/tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/serverless.yml b/tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/serverless.yml deleted file mode 100644 index 212cb8ab2..000000000 --- a/tests/integration/aws/s3/multiple-events-single-function-single-bucket/service/serverless.yml +++ /dev/null @@ -1,14 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - s3: ${env:BUCKET_1} - - s3: - bucket: ${env:BUCKET_1} - event: s3:ObjectRemoved:* diff --git a/tests/integration/aws/s3/multiple-events-single-function-single-bucket/tests.js b/tests/integration/aws/s3/multiple-events-single-function-single-bucket/tests.js deleted file mode 100644 index e7db28191..000000000 --- a/tests/integration/aws/s3/multiple-events-single-function-single-bucket/tests.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - S3: Multiple events in a single function with a single bucket', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when object created or deleted in bucket', () => Utils - .createAndRemoveInBucket(process.env.BUCKET_1) - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('hello'); - - expect(/aws:s3/g.test(logs)).to.equal(true); - expect(/ObjectCreated:Put/g.test(logs)).to.equal(true); - expect(/ObjectRemoved:Delete/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/s3/single-event-single-function-single-bucket/service/handler.js b/tests/integration/aws/s3/single-event-single-function-single-bucket/service/handler.js deleted file mode 100644 index aaa093a8c..000000000 --- a/tests/integration/aws/s3/single-event-single-function-single-bucket/service/handler.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - process.stdout.write(event.Records[0].eventSource); - process.stdout.write(event.Records[0].eventName); - callback(null, { message: 'Hello from S3!', event }); -}; diff --git a/tests/integration/aws/s3/single-event-single-function-single-bucket/service/serverless.yml b/tests/integration/aws/s3/single-event-single-function-single-bucket/service/serverless.yml deleted file mode 100644 index 17446857c..000000000 --- a/tests/integration/aws/s3/single-event-single-function-single-bucket/service/serverless.yml +++ /dev/null @@ -1,12 +0,0 @@ - -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - s3: ${env:BUCKET_1} diff --git a/tests/integration/aws/s3/single-event-single-function-single-bucket/tests.js b/tests/integration/aws/s3/single-event-single-function-single-bucket/tests.js deleted file mode 100644 index 7c09075a2..000000000 --- a/tests/integration/aws/s3/single-event-single-function-single-bucket/tests.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - S3: Single event in a single function with a single bucket', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when object created in bucket', () => Utils - .createAndRemoveInBucket(process.env.BUCKET_1) - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('hello'); - expect(/aws:s3/g.test(logs)).to.equal(true); - expect(/ObjectCreated:Put/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/handler.js b/tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/handler.js deleted file mode 100644 index 424b93300..000000000 --- a/tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/handler.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - process.stdout.write(event.source); - process.stdout.write(event['detail-type']); - callback(null, { message: 'Hello from Schedule!', event }); -}; - -module.exports.world = (event, context, callback) => { - process.stdout.write(event.source); - process.stdout.write(event['detail-type']); - callback(null, { message: 'Hello from Schedule!', event }); -}; diff --git a/tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/serverless.yml b/tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/serverless.yml deleted file mode 100644 index 6bcb6fe38..000000000 --- a/tests/integration/aws/schedule/multiple-schedules-multiple-functions/service/serverless.yml +++ /dev/null @@ -1,15 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - schedule: rate(1 minute) - world: - handler: handler.world - events: - - schedule: rate(1 minute) diff --git a/tests/integration/aws/schedule/multiple-schedules-multiple-functions/tests.js b/tests/integration/aws/schedule/multiple-schedules-multiple-functions/tests.js deleted file mode 100644 index 51ae9cb57..000000000 --- a/tests/integration/aws/schedule/multiple-schedules-multiple-functions/tests.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); -const BbPromise = require('bluebird'); - -describe('AWS - Schedule: Multiple schedules with multiple functions', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger functions every minute', () => BbPromise.resolve() - .delay(100000) - .then(() => { - const helloLogs = Utils.getFunctionLogs('hello'); - const worldLogs = Utils.getFunctionLogs('world'); - - expect(/Scheduled Event/g.test(helloLogs)).to.equal(true); - expect(/aws\.events/g.test(helloLogs)).to.equal(true); - expect(/Scheduled Event/g.test(worldLogs)).to.equal(true); - expect(/aws\.events/g.test(worldLogs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/sns/existing-topic/service/handler.js b/tests/integration/aws/sns/existing-topic/service/handler.js deleted file mode 100644 index f3f54b9ab..000000000 --- a/tests/integration/aws/sns/existing-topic/service/handler.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - process.stdout.write(event.Records[0].EventSource); - process.stdout.write(event.Records[0].Sns.Message); - callback(null, { message: 'Hello from SNS!', event }); -}; diff --git a/tests/integration/aws/sns/existing-topic/service/serverless.yml b/tests/integration/aws/sns/existing-topic/service/serverless.yml deleted file mode 100644 index 8be85bc03..000000000 --- a/tests/integration/aws/sns/existing-topic/service/serverless.yml +++ /dev/null @@ -1,20 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - sns: - arn: - Fn::Join: - - ':' - - - - 'arn:aws:sns' - - ${env:EXISTING_TOPIC_REGION} - - ${env:EXISTING_TOPIC_ACCOUNT} - - ${env:EXISTING_TOPIC_NAME} - topicName: ${env:EXISTING_TOPIC_NAME} diff --git a/tests/integration/aws/sns/existing-topic/tests.js b/tests/integration/aws/sns/existing-topic/tests.js deleted file mode 100644 index 414bd3e76..000000000 --- a/tests/integration/aws/sns/existing-topic/tests.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); -const uuid = require('uuid'); - -describe('AWS - SNS: Existing topic with single function', () => { - const snsTopic = uuid.v4(); - - beforeAll(() => Utils.createSnsTopic(snsTopic) - .then((result) => { - const splitTopicArn = result.topicArn.split(':'); - process.env.EXISTING_TOPIC_REGION = splitTopicArn[3]; - process.env.EXISTING_TOPIC_ACCOUNT = splitTopicArn[4]; - process.env.EXISTING_TOPIC_NAME = splitTopicArn[5]; - }) - .then(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }) - ); - - it('should trigger function when new message is published', () => Utils - .publishSnsMessage(snsTopic, 'hello world') - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('hello'); - expect(/aws:sns/g.test(logs)).to.equal(true); - expect(/hello world/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - Utils.removeSnsTopic(snsTopic); - }); -}); diff --git a/tests/integration/aws/sns/multiple-topics-multiple-functions/service/handler.js b/tests/integration/aws/sns/multiple-topics-multiple-functions/service/handler.js deleted file mode 100644 index 9d0517bea..000000000 --- a/tests/integration/aws/sns/multiple-topics-multiple-functions/service/handler.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - process.stdout.write(event.Records[0].EventSource); - process.stdout.write(event.Records[0].Sns.Message); - callback(null, { message: 'Hello from SNS!', event }); -}; - -module.exports.world = (event, context, callback) => { - process.stdout.write(event.Records[0].EventSource); - process.stdout.write(event.Records[0].Sns.Message); - callback(null, { message: 'Hello from SNS!', event }); -}; diff --git a/tests/integration/aws/sns/multiple-topics-multiple-functions/service/serverless.yml b/tests/integration/aws/sns/multiple-topics-multiple-functions/service/serverless.yml deleted file mode 100644 index 209f3cba8..000000000 --- a/tests/integration/aws/sns/multiple-topics-multiple-functions/service/serverless.yml +++ /dev/null @@ -1,15 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - sns: ${env:TOPIC_1} - world: - handler: handler.world - events: - - sns: ${env:TOPIC_2} diff --git a/tests/integration/aws/sns/multiple-topics-multiple-functions/tests.js b/tests/integration/aws/sns/multiple-topics-multiple-functions/tests.js deleted file mode 100644 index df3420898..000000000 --- a/tests/integration/aws/sns/multiple-topics-multiple-functions/tests.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - SNS: Multiple topics with multiple functions', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when new message is published', () => Utils - .publishSnsMessage(process.env.TOPIC_1, 'topic1') - .then(() => Utils.publishSnsMessage(process.env.TOPIC_2, 'topic2')) - .delay(60000) - .then(() => { - const helloLogs = Utils.getFunctionLogs('hello'); - const worldLogs = Utils.getFunctionLogs('world'); - - expect(/aws:sns/g.test(helloLogs)).to.equal(true); - expect(/topic1/g.test(helloLogs)).to.equal(true); - expect(/aws:sns/g.test(worldLogs)).to.equal(true); - expect(/topic2/g.test(worldLogs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/sns/multiple-topics-single-function/service/handler.js b/tests/integration/aws/sns/multiple-topics-single-function/service/handler.js deleted file mode 100644 index f3f54b9ab..000000000 --- a/tests/integration/aws/sns/multiple-topics-single-function/service/handler.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - process.stdout.write(event.Records[0].EventSource); - process.stdout.write(event.Records[0].Sns.Message); - callback(null, { message: 'Hello from SNS!', event }); -}; diff --git a/tests/integration/aws/sns/multiple-topics-single-function/service/serverless.yml b/tests/integration/aws/sns/multiple-topics-single-function/service/serverless.yml deleted file mode 100644 index c0f1f29f9..000000000 --- a/tests/integration/aws/sns/multiple-topics-single-function/service/serverless.yml +++ /dev/null @@ -1,12 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - sns: ${env:TOPIC_1} - - sns: ${env:TOPIC_2} diff --git a/tests/integration/aws/sns/multiple-topics-single-function/tests.js b/tests/integration/aws/sns/multiple-topics-single-function/tests.js deleted file mode 100644 index 6c6122cce..000000000 --- a/tests/integration/aws/sns/multiple-topics-single-function/tests.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - SNS: Multiple topics single function', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when new message is published', () => Utils - .publishSnsMessage(process.env.TOPIC_1, 'topic1') - .then(() => Utils.publishSnsMessage(process.env.TOPIC_2, 'topic2')) - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('hello'); - expect(/aws:sns/g.test(logs)).to.equal(true); - expect(/topic1/g.test(logs)).to.equal(true); - expect(/topic2/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/sns/single-topic-multiple-functions/service/handler.js b/tests/integration/aws/sns/single-topic-multiple-functions/service/handler.js deleted file mode 100644 index 9d0517bea..000000000 --- a/tests/integration/aws/sns/single-topic-multiple-functions/service/handler.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - process.stdout.write(event.Records[0].EventSource); - process.stdout.write(event.Records[0].Sns.Message); - callback(null, { message: 'Hello from SNS!', event }); -}; - -module.exports.world = (event, context, callback) => { - process.stdout.write(event.Records[0].EventSource); - process.stdout.write(event.Records[0].Sns.Message); - callback(null, { message: 'Hello from SNS!', event }); -}; diff --git a/tests/integration/aws/sns/single-topic-multiple-functions/service/serverless.yml b/tests/integration/aws/sns/single-topic-multiple-functions/service/serverless.yml deleted file mode 100644 index e81c8c2c6..000000000 --- a/tests/integration/aws/sns/single-topic-multiple-functions/service/serverless.yml +++ /dev/null @@ -1,15 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - sns: ${env:TOPIC_1} - world: - handler: handler.world - events: - - sns: ${env:TOPIC_1} diff --git a/tests/integration/aws/sns/single-topic-multiple-functions/tests.js b/tests/integration/aws/sns/single-topic-multiple-functions/tests.js deleted file mode 100644 index 03b4977ca..000000000 --- a/tests/integration/aws/sns/single-topic-multiple-functions/tests.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - SNS: Single topic with multiple functions', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when new message is published', () => Utils - .publishSnsMessage(process.env.TOPIC_1, 'hello world') - .delay(60000) - .then(() => { - const helloLogs = Utils.getFunctionLogs('hello'); - const worldLogs = Utils.getFunctionLogs('world'); - - expect(/aws:sns/g.test(helloLogs)).to.equal(true); - expect(/hello world/g.test(helloLogs)).to.equal(true); - expect(/aws:sns/g.test(worldLogs)).to.equal(true); - expect(/hello world/g.test(worldLogs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/aws/sns/single-topic-single-function/service/handler.js b/tests/integration/aws/sns/single-topic-single-function/service/handler.js deleted file mode 100644 index f3f54b9ab..000000000 --- a/tests/integration/aws/sns/single-topic-single-function/service/handler.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports.hello = (event, context, callback) => { - process.stdout.write(event.Records[0].EventSource); - process.stdout.write(event.Records[0].Sns.Message); - callback(null, { message: 'Hello from SNS!', event }); -}; diff --git a/tests/integration/aws/sns/single-topic-single-function/service/serverless.yml b/tests/integration/aws/sns/single-topic-single-function/service/serverless.yml deleted file mode 100644 index 3430ea40c..000000000 --- a/tests/integration/aws/sns/single-topic-single-function/service/serverless.yml +++ /dev/null @@ -1,11 +0,0 @@ -service: aws-nodejs - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - events: - - sns: ${env:TOPIC_1} diff --git a/tests/integration/aws/sns/single-topic-single-function/tests.js b/tests/integration/aws/sns/single-topic-single-function/tests.js deleted file mode 100644 index fcf655144..000000000 --- a/tests/integration/aws/sns/single-topic-single-function/tests.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const Utils = require('../../../../utils/index'); - -describe('AWS - SNS: Single topic with single function', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - Utils.deployService(); - }); - - it('should trigger function when new message is published', () => Utils - .publishSnsMessage(process.env.TOPIC_1, 'hello world') - .delay(60000) - .then(() => { - const logs = Utils.getFunctionLogs('hello'); - expect(/aws:sns/g.test(logs)).to.equal(true); - expect(/hello world/g.test(logs)).to.equal(true); - }) - ); - - afterAll(() => { - Utils.removeService(); - }); -}); diff --git a/tests/integration/general/custom-plugins/service/handler.js b/tests/integration/general/custom-plugins/service/handler.js deleted file mode 100644 index 6313e306f..000000000 --- a/tests/integration/general/custom-plugins/service/handler.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -// Your first function handler -module.exports.hello = (event, context, callback) => { - callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); -}; diff --git a/tests/integration/general/custom-plugins/service/package.json b/tests/integration/general/custom-plugins/service/package.json deleted file mode 100644 index 2970090d2..000000000 --- a/tests/integration/general/custom-plugins/service/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "test-service", - "version": "0.1.0", - "dependencies": {} -} diff --git a/tests/integration/general/custom-plugins/service/serverless-plugin-greeter/main.js b/tests/integration/general/custom-plugins/service/serverless-plugin-greeter/main.js deleted file mode 100644 index 0d028622a..000000000 --- a/tests/integration/general/custom-plugins/service/serverless-plugin-greeter/main.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -class Greeter { - constructor(serverless, options) { - this.serverless = serverless; - this.options = options; - - this.commands = { - greet: { - lifecycleEvents: [ - 'greet', - ], - }, - }; - - this.hooks = { - 'greet:greet': this.greet.bind(this), - }; - } - - greet() { - process.stdout.write('Hello from the greeter plugin!'); - } -} - -module.exports = Greeter; diff --git a/tests/integration/general/custom-plugins/service/serverless-plugin-greeter/package.json b/tests/integration/general/custom-plugins/service/serverless-plugin-greeter/package.json deleted file mode 100644 index ff4faea7e..000000000 --- a/tests/integration/general/custom-plugins/service/serverless-plugin-greeter/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "serverless-plugin-greeter", - "version": "0.1.0", - "main": "main.js" -} diff --git a/tests/integration/general/custom-plugins/service/serverless.yml b/tests/integration/general/custom-plugins/service/serverless.yml deleted file mode 100644 index b572235a4..000000000 --- a/tests/integration/general/custom-plugins/service/serverless.yml +++ /dev/null @@ -1,12 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - -plugins: - - serverless-plugin-greeter diff --git a/tests/integration/general/custom-plugins/tests.js b/tests/integration/general/custom-plugins/tests.js deleted file mode 100644 index 6da6213f6..000000000 --- a/tests/integration/general/custom-plugins/tests.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const execSync = require('child_process').execSync; - -const Utils = require('../../../utils/index'); - -describe('General: Custom plugins test', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - - // cd into the plugins directory - execSync('cd serverless-plugin-greeter'); - - // link and install the npm package / plugin - execSync('npm link serverless-plugin-greeter && npm install --save serverless-plugin-greeter'); - - // cd back into the service directory - execSync('cd ..'); - }); - - it('should successfully run the greet command of the custom plugin', () => { - const pluginExecution = execSync(`${Utils.serverlessExec} greet`); - - // note: the result will return a newline at the end - const result = new Buffer(pluginExecution, 'base64').toString(); - - expect(result).to.equal('Hello from the greeter plugin!'); - }); - - afterAll(() => { - // unlink the npm package - execSync('npm r serverless-plugin-greeter -g'); - }); -}); diff --git a/tests/integration/general/local-plugins/service/.serverless_plugins/one.js b/tests/integration/general/local-plugins/service/.serverless_plugins/one.js deleted file mode 100644 index 016b2416e..000000000 --- a/tests/integration/general/local-plugins/service/.serverless_plugins/one.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -class ServerlessPlugin { - constructor(serverless, options) { - this.serverless = serverless; - this.options = options; - - this.commands = { - one: { - usage: 'test plugin', - lifecycleEvents: [ - 'hello', - ], - }, - }; - - this.hooks = { - 'before:one:hello': this.beforeWelcome.bind(this), - }; - } - - beforeWelcome() { - this.serverless.cli.log('plugin one ran successfully!'); - } -} - -module.exports = ServerlessPlugin; diff --git a/tests/integration/general/local-plugins/service/.serverless_plugins/two/index.js b/tests/integration/general/local-plugins/service/.serverless_plugins/two/index.js deleted file mode 100644 index 566e6647a..000000000 --- a/tests/integration/general/local-plugins/service/.serverless_plugins/two/index.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -class ServerlessPlugin { - constructor(serverless, options) { - this.serverless = serverless; - this.options = options; - - this.commands = { - two: { - usage: 'test plugin', - lifecycleEvents: [ - 'hello', - ], - }, - }; - - this.hooks = { - 'before:two:hello': this.beforeWelcome.bind(this), - }; - } - - beforeWelcome() { - this.serverless.cli.log('plugin two ran successfully!'); - } -} - -module.exports = ServerlessPlugin; diff --git a/tests/integration/general/local-plugins/service/handler.js b/tests/integration/general/local-plugins/service/handler.js deleted file mode 100644 index 6313e306f..000000000 --- a/tests/integration/general/local-plugins/service/handler.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -// Your first function handler -module.exports.hello = (event, context, callback) => { - callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); -}; diff --git a/tests/integration/general/local-plugins/service/serverless.yml b/tests/integration/general/local-plugins/service/serverless.yml deleted file mode 100644 index c89417975..000000000 --- a/tests/integration/general/local-plugins/service/serverless.yml +++ /dev/null @@ -1,13 +0,0 @@ -service: aws-nodejs # NOTE: update this with your service name - -provider: - name: aws - runtime: nodejs10.x - -functions: - hello: - handler: handler.hello - -plugins: - - one - - two diff --git a/tests/integration/general/local-plugins/tests.js b/tests/integration/general/local-plugins/tests.js deleted file mode 100644 index 54594cf22..000000000 --- a/tests/integration/general/local-plugins/tests.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const path = require('path'); -const expect = require('chai').expect; -const execSync = require('child_process').execSync; - -const Utils = require('../../../utils/index'); - -describe('General: Local plugins test', () => { - beforeAll(() => { - Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); - }); - - it('should successfully run the one command', () => { - const pluginExecution = execSync(`${Utils.serverlessExec} one`); - const result = new Buffer(pluginExecution, 'base64').toString(); - expect(/plugin one ran successfully/g.test(result)).to.equal(true); - }); - - it('should successfully run the two command', () => { - const pluginExecution = execSync(`${Utils.serverlessExec} two`); - const result = new Buffer(pluginExecution, 'base64').toString(); - expect(/plugin two ran successfully/g.test(result)).to.equal(true); - }); -}); diff --git a/tests/setup-tests.js b/tests/setup-tests.js new file mode 100644 index 000000000..32c0f7f96 --- /dev/null +++ b/tests/setup-tests.js @@ -0,0 +1,3 @@ +'use strict'; + +jest.setTimeout(300000); diff --git a/tests/setupTests.js b/tests/setupTests.js deleted file mode 100644 index 01313fd46..000000000 --- a/tests/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -// timeout is set to 5 minutes -// eslint-disable-next-line no-undef -jasmine.DEFAULT_TIMEOUT_INTERVAL = 300000; diff --git a/tests/templates/test_all_templates b/tests/templates/test-all-templates similarity index 100% rename from tests/templates/test_all_templates rename to tests/templates/test-all-templates diff --git a/tests/utils/api-gateway/index.js b/tests/utils/api-gateway/index.js new file mode 100644 index 000000000..212f9ef32 --- /dev/null +++ b/tests/utils/api-gateway/index.js @@ -0,0 +1,65 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const _ = require('lodash'); +const { region, persistentRequest } = require('../misc'); + +function createRestApi(name) { + const APIG = new AWS.APIGateway({ region }); + + const params = { + name, + }; + + return APIG.createRestApi(params).promise(); +} + +function deleteRestApi(restApiId) { + const APIG = new AWS.APIGateway({ region }); + + const params = { + restApiId, + }; + + return APIG.deleteRestApi(params).promise(); +} + +function getResources(restApiId) { + const APIG = new AWS.APIGateway({ region }); + + const params = { + restApiId, + }; + + return APIG.getResources(params).promise() + .then((data) => data.items); +} + +function findRestApis(name) { + const APIG = new AWS.APIGateway({ region }); + + const params = { + limit: 500, + }; + + function recursiveFind(found, position) { + if (position) params.position = position; + return APIG.getRestApis(params).promise().then(result => { + const matches = result.items.filter(restApi => restApi.name.match(name)); + if (matches.length) { + _.merge(found, matches); + } + if (result.position) return recursiveFind(found, result.position); + return found; + }); + } + + return recursiveFind([]); +} + +module.exports = { + createRestApi: persistentRequest.bind(this, createRestApi), + deleteRestApi: persistentRequest.bind(this, deleteRestApi), + getResources: persistentRequest.bind(this, getResources), + findRestApis: persistentRequest.bind(this, findRestApis), +}; diff --git a/tests/utils/aws-cleanup.js b/tests/utils/aws-cleanup.js new file mode 100644 index 000000000..85167e65f --- /dev/null +++ b/tests/utils/aws-cleanup.js @@ -0,0 +1,90 @@ +'use strict'; + +// NOTE: This script requires Node.js > 8 to run since it uses +// modern Node.js / JavaScript features such as async / await + +const { logger, testServiceIdentifier } = require('./misc'); +const { findStacks, deleteStack, listStackResources } = require('./cloudformation'); +const { findRestApis, deleteRestApi } = require('./api-gateway'); +const { deleteBucket } = require('./s3'); + +async function findDeploymentBuckets(stacks) { + const buckets = []; + for (const stack of stacks) { + const stackResources = await listStackResources(stack.StackId); + const bucket = stackResources.filter((resource) => { + return resource.LogicalResourceId === 'ServerlessDeploymentBucket'; + }); + buckets.push(...bucket); + } + return buckets; +} + +async function cleanup() { + const date = new Date(); + const yesterday = date.setDate(date.getDate() - 1); + + const status = [ + 'CREATE_FAILED', + 'CREATE_COMPLETE', + 'UPDATE_COMPLETE', + 'ROLLBACK_FAILED', + 'ROLLBACK_COMPLETE', + 'DELETE_FAILED', + 'UPDATE_ROLLBACK_FAILED', + 'UPDATE_ROLLBACK_COMPLETE', + ]; + + // find all the resources + const stacks = await findStacks(testServiceIdentifier, status); + const apis = await findRestApis(testServiceIdentifier); + + let bucketsToRemove = []; + const stacksToRemove = stacks.filter((stack) => +new Date(stack.CreationTime) < yesterday); + const apisToRemove = apis.filter((api) => +new Date(api.createdDate) < yesterday); + if (stacksToRemove) { + bucketsToRemove = await findDeploymentBuckets(stacksToRemove); + } + + logger.log(`${bucketsToRemove.length} Buckets to remove...`); + logger.log(`${stacksToRemove.length} Stacks to remove...`); + logger.log(`${apisToRemove.length} APIs to remove...`); + + if (bucketsToRemove.length) { + logger.log('Removing Buckets...'); + const promises = bucketsToRemove + .map(bucket => deleteBucket(bucket.PhysicalResourceId)); + try { + await Promise.all(promises); + } catch (error) { + // do nothing... try to continue with cleanup + } + } + + if (stacksToRemove.length) { + logger.log('Removing Stacks...'); + const promises = stacksToRemove + .map(stack => deleteStack(stack.StackName)); + try { + await Promise.all(promises); + } catch (error) { + // do nothing... try to continue with cleanup + } + } + + if (apisToRemove.length) { + logger.log('Removing APIs...'); + const promises = apisToRemove.map(api => deleteRestApi(api.id)); + try { + await Promise.all(promises); + } catch (error) { + // do nothing... try to continue with cleanup + } + } +} + +cleanup().catch((error) => { + // eslint-disable-next-line no-console + console.error(error); + process.exit(1); +}); diff --git a/tests/utils/cloudformation/index.js b/tests/utils/cloudformation/index.js new file mode 100644 index 000000000..4d651acef --- /dev/null +++ b/tests/utils/cloudformation/index.js @@ -0,0 +1,74 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const { region, persistentRequest } = require('../misc'); + +function findStacks(name, status) { + const CF = new AWS.CloudFormation({ region }); + + const params = {}; + if (status) { + params.StackStatusFilter = status; + } + + function recursiveFind(found, token) { + if (token) params.NextToken = token; + return CF.listStacks(params).promise().then(result => { + const matches = result.StackSummaries.filter(stack => stack.StackName.match(name)); + if (matches.length) { + found.push(...matches); + } + if (result.NextToken) return recursiveFind(found, result.NextToken); + return found; + }); + } + + return recursiveFind([]); +} + +function deleteStack(stack) { + const CF = new AWS.CloudFormation({ region }); + + const params = { + StackName: stack, + }; + + return CF.deleteStack(params).promise(); +} + +function listStackResources(stack) { + const CF = new AWS.CloudFormation({ region }); + + const params = { + StackName: stack, + }; + + function recursiveFind(resources, token) { + if (token) params.NextToken = token; + return CF.listStackResources(params).promise().then(result => { + resources.push(...result.StackResourceSummaries); + if (result.NextToken) return recursiveFind(resources, result.NextToken); + return resources; + }); + } + + return recursiveFind([]); +} + +function listStacks(status) { + const CF = new AWS.CloudFormation({ region }); + + const params = {}; + if (status) { + params.StackStatusFilter = status; + } + + return CF.listStacks(params).promise(); +} + +module.exports = { + findStacks: persistentRequest.bind(this, findStacks), + deleteStack: persistentRequest.bind(this, deleteStack), + listStackResources: persistentRequest.bind(this, listStackResources), + listStacks: persistentRequest.bind(this, listStacks), +}; diff --git a/tests/utils/cloudwatch/index.js b/tests/utils/cloudwatch/index.js new file mode 100644 index 000000000..d9150ad25 --- /dev/null +++ b/tests/utils/cloudwatch/index.js @@ -0,0 +1,22 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const { region, persistentRequest } = require('../misc'); + +function putCloudWatchEvents(sources) { + const cwe = new AWS.CloudWatchEvents({ region }); + + const entries = sources.map(source => ({ + Source: source, + DetailType: 'serverlessDetailType', + Detail: '{ "key1": "value1" }', + })); + const params = { + Entries: entries, + }; + return cwe.putEvents(params).promise(); +} + +module.exports = { + putCloudWatchEvents: persistentRequest.bind(this, putCloudWatchEvents), +}; diff --git a/tests/utils/cognito/index.js b/tests/utils/cognito/index.js new file mode 100644 index 000000000..ab942ac2d --- /dev/null +++ b/tests/utils/cognito/index.js @@ -0,0 +1,33 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const { region, persistentRequest } = require('../misc'); + +function getCognitoUserPoolId(userPoolName) { + const cisp = new AWS.CognitoIdentityServiceProvider({ region }); + + const params = { + MaxResults: 50, + }; + + return cisp.listUserPools(params).promise() + .then((data) => data.UserPools.find((userPool) => + RegExp(userPoolName, 'g').test(userPool.Name)).Id + ); +} + +function createCognitoUser(userPoolId, username, password) { + const cisp = new AWS.CognitoIdentityServiceProvider({ region }); + + const params = { + UserPoolId: userPoolId, + Username: username, + TemporaryPassword: password, + }; + return cisp.adminCreateUser(params).promise(); +} + +module.exports = { + getCognitoUserPoolId: persistentRequest.bind(this, getCognitoUserPoolId), + createCognitoUser: persistentRequest.bind(this, createCognitoUser), +}; diff --git a/tests/utils/fs/index.js b/tests/utils/fs/index.js new file mode 100644 index 000000000..cc895de8f --- /dev/null +++ b/tests/utils/fs/index.js @@ -0,0 +1,47 @@ +'use strict'; + +const os = require('os'); +const path = require('path'); +const fs = require('fs'); +const crypto = require('crypto'); +const YAML = require('js-yaml'); +const JSZip = require('jszip'); + +function getTmpDirPath() { + return path.join(os.tmpdir(), + 'tmpdirs-serverless', crypto.randomBytes(8).toString('hex')); +} + +function getTmpFilePath(fileName) { + return path.join(getTmpDirPath(), fileName); +} + +function replaceTextInFile(filePath, subString, newSubString) { + const fileContent = fs.readFileSync(filePath).toString(); + fs.writeFileSync(filePath, fileContent.replace(subString, newSubString)); +} + +function readYamlFile(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + return YAML.safeLoad(content); +} + +function writeYamlFile(filePath, content) { + const yaml = YAML.safeDump(content); + fs.writeFileSync(filePath, yaml); + return yaml; +} + +function listZipFiles(filename) { + return new JSZip().loadAsync(fs.readFileSync(filename)) + .then(zip => Object.keys(zip.files)); +} + +module.exports = { + getTmpDirPath, + getTmpFilePath, + replaceTextInFile, + readYamlFile, + writeYamlFile, + listZipFiles, +}; diff --git a/tests/utils/index.js b/tests/utils/index.js deleted file mode 100644 index 081ea7a56..000000000 --- a/tests/utils/index.js +++ /dev/null @@ -1,244 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const os = require('os'); -const path = require('path'); -const crypto = require('crypto'); -const JSZip = require('jszip'); -const BbPromise = require('bluebird'); -const fse = require('fs-extra'); -const execSync = require('child_process').execSync; -const AWS = require('aws-sdk'); - -// mock to test functionality bound to a serverless plugin -class ServerlessPlugin { - constructor(serverless, options, testSubject) { - this.options = options; - this.serverless = serverless; - - Object.assign( - this, - testSubject - ); - } -} - -const serverlessExec = path.join(__dirname, '..', '..', 'bin', 'serverless'); - -const getTmpDirPath = () => path.join(os.tmpdir(), - 'tmpdirs-serverless', 'serverless', crypto.randomBytes(8).toString('hex')); - -const getTmpFilePath = (fileName) => path.join(getTmpDirPath(), fileName); - -const replaceTextInFile = (filePath, subString, newSubString) => { - const fileContent = fs.readFileSync(filePath).toString(); - fs.writeFileSync(filePath, fileContent.replace(subString, newSubString)); -}; - -const listZipFiles = filename => new JSZip().loadAsync(fs.readFileSync(filename)) - .then(zip => Object.keys(zip.files)); - -module.exports = { - serverlessExec, - getTmpDirPath, - getTmpFilePath, - replaceTextInFile, - ServerlessPlugin, - listZipFiles, - - createTestService: (templateName, testServiceDir) => { - const hrtime = process.hrtime(); - const serviceName = `test-${hrtime[0]}-${hrtime[1]}`; - const tmpDir = path.join(os.tmpdir(), - 'tmpdirs-serverless', - 'integration-test-suite', - crypto.randomBytes(8).toString('hex')); - - fse.mkdirsSync(tmpDir); - process.chdir(tmpDir); - - // create a new Serverless service - execSync(`${serverlessExec} create --template ${templateName}`, { stdio: 'inherit' }); - - if (testServiceDir) { - fse.copySync(testServiceDir, tmpDir, { clobber: true, preserveTimestamps: true }); - } - - replaceTextInFile('serverless.yml', templateName, serviceName); - - process.env.TOPIC_1 = `${serviceName}-1`; - process.env.TOPIC_2 = `${serviceName}-1`; - process.env.BUCKET_1 = `${serviceName}-1`; - process.env.BUCKET_2 = `${serviceName}-2`; - process.env.COGNITO_USER_POOL_1 = `${serviceName}-1`; - process.env.COGNITO_USER_POOL_2 = `${serviceName}-2`; - - // return the name of the CloudFormation stack - return `${serviceName}-dev`; - }, - - createAndRemoveInBucket(bucketName) { - const S3 = new AWS.S3({ region: 'us-east-1' }); - BbPromise.promisifyAll(S3, { suffix: 'Promised' }); - - const params = { - Bucket: bucketName, - Key: 'object', - Body: 'hello world', - }; - - return S3.putObjectPromised(params) - .then(() => { - delete params.Body; - return S3.deleteObjectPromised(params); - }); - }, - - createSnsTopic(topicName) { - const SNS = new AWS.SNS({ region: 'us-east-1' }); - BbPromise.promisifyAll(SNS, { suffix: 'Promised' }); - - const params = { - Name: topicName, - }; - - return SNS.createTopicPromised(params); - }, - - installPlugin: (installDir, PluginClass) => { - const pluginPkg = { name: path.basename(installDir), version: '0.0.0' }; - const className = (new PluginClass()).constructor.name; - fse.outputFileSync(path.join(installDir, 'package.json'), JSON.stringify(pluginPkg), 'utf8'); - fse.outputFileSync(path.join(installDir, 'index.js'), - `"use strict";\n${PluginClass.toString()}\nmodule.exports = ${className}`, 'utf8'); - }, - - removeSnsTopic(topicName) { - const SNS = new AWS.SNS({ region: 'us-east-1' }); - BbPromise.promisifyAll(SNS, { suffix: 'Promised' }); - - return SNS.listTopicsPromised() - .then(data => { - const topicArn = data.Topics.find(topic => RegExp(topicName, 'g') - .test(topic.TopicArn)).TopicArn; - - const params = { - TopicArn: topicArn, - }; - - return SNS.deleteTopicPromised(params); - }); - }, - - publishSnsMessage(topicName, message) { - const SNS = new AWS.SNS({ region: 'us-east-1' }); - BbPromise.promisifyAll(SNS, { suffix: 'Promised' }); - - return SNS.listTopicsPromised() - .then(data => { - const topicArn = data.Topics.find(topic => RegExp(topicName, 'g') - .test(topic.TopicArn)).TopicArn; - - const params = { - Message: message, - TopicArn: topicArn, - }; - - return SNS.publishPromised(params); - }); - }, - - publishIotData(topic, message) { - const Iot = new AWS.Iot({ region: 'us-east-1' }); - BbPromise.promisifyAll(Iot, { suffix: 'Promised' }); - - return Iot.describeEndpointPromised() - .then(data => { - const IotData = new AWS.IotData({ region: 'us-east-1', endpoint: data.endpointAddress }); - BbPromise.promisifyAll(IotData, { suffix: 'Promised' }); - - const params = { - topic, - payload: new Buffer(message), - }; - - return IotData.publishPromised(params); - }); - }, - - putCloudWatchEvents(sources) { - const cwe = new AWS.CloudWatchEvents({ region: 'us-east-1' }); - BbPromise.promisifyAll(cwe, { suffix: 'Promised' }); - - const entries = []; - sources.forEach(source => { - entries.push({ - Source: source, - DetailType: 'serverlessDetailType', - Detail: '{ "key1": "value1" }', - }); - }); - const params = { - Entries: entries, - }; - return cwe.putEventsPromised(params); - }, - - getCognitoUserPoolId(userPoolName) { - const cisp = new AWS.CognitoIdentityServiceProvider({ region: 'us-east-1' }); - BbPromise.promisifyAll(cisp, { suffix: 'Promised' }); - - const params = { - MaxResults: 50, - }; - - return cisp.listUserPoolsPromised(params) - .then((data) => data.UserPools.find((userPool) => - RegExp(userPoolName, 'g').test(userPool.Name)).Id - ); - }, - - createCognitoUser(userPoolId, username, password) { - const cisp = new AWS.CognitoIdentityServiceProvider({ region: 'us-east-1' }); - BbPromise.promisifyAll(cisp, { suffix: 'Promised' }); - - const params = { - UserPoolId: userPoolId, - Username: username, - TemporaryPassword: password, - }; - return cisp.adminCreateUserPromised(params); - }, - - getFunctionLogs(functionName) { - const logs = execSync(`${serverlessExec} logs --function ${functionName} --noGreeting true`); - const logsString = new Buffer(logs, 'base64').toString(); - process.stdout.write(logsString); - return logsString; - }, - - deployService() { - execSync(`${serverlessExec} deploy`, { stdio: 'inherit' }); - }, - - removeService() { - execSync(`${serverlessExec} remove`, { stdio: 'inherit' }); - }, - - replaceEnv(values) { - const originals = {}; - for (const key of Object.keys(values)) { - if (process.env[key]) { - originals[key] = process.env[key]; - } else { - originals[key] = 'undefined'; - } - if (values[key] === 'undefined') { - delete process.env[key]; - } else { - process.env[key] = values[key]; - } - } - return originals; - }, -}; diff --git a/tests/utils/index.test.js b/tests/utils/index.test.js deleted file mode 100644 index 16efb247e..000000000 --- a/tests/utils/index.test.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -const BbPromise = require('bluebird'); -const Serverless = require('../../lib/Serverless'); -const expect = require('chai').expect; -const testUtils = require('./index'); - -describe('Test utils', () => { - describe('#getTmpDirPath()', () => { - it('should return a valid tmpDir path', () => { - const tmpDirPath = testUtils.getTmpDirPath(); - - expect(tmpDirPath).to.match(/.+.{16}/); - }); - }); - - describe('#getTmpFilePath()', () => { - it('should return a valid tmpFile path', () => { - const fileName = 'foo.bar'; - const tmpFilePath = testUtils.getTmpFilePath(fileName); - - expect(tmpFilePath).to.match(/.+.{16}.{1}foo\.bar/); - }); - }); - - describe('ServerlessPlugin', () => { - it('should create a new ServerlessPlugin mock instance', () => { - const ServerlessPlugin = testUtils.ServerlessPlugin; - - const serverless = new Serverless(); - const options = { - stage: 'production', - region: 'my-test-region', - }; - const functionUnderTest = () => BbPromise.resolve('function under test'); - - const serverlessPlugin = new ServerlessPlugin( - serverless, - options, - functionUnderTest - ); - - expect(serverlessPlugin.serverless).to.be.instanceof(Serverless); - expect(serverlessPlugin.options).to.deep.equal(options); - }); - }); -}); diff --git a/tests/utils/iot/index.js b/tests/utils/iot/index.js new file mode 100644 index 000000000..0cadbff95 --- /dev/null +++ b/tests/utils/iot/index.js @@ -0,0 +1,24 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const { region, persistentRequest } = require('../misc'); + +function publishIotData(topic, message) { + const Iot = new AWS.Iot({ region }); + + return Iot.describeEndpoint().promise() + .then(data => { + const IotData = new AWS.IotData({ region, endpoint: data.endpointAddress }); + + const params = { + topic, + payload: Buffer.from(message), + }; + + return IotData.publish(params).promise(); + }); +} + +module.exports = { + publishIotData: persistentRequest.bind(this, publishIotData), +}; diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js new file mode 100644 index 000000000..4dd68fc7d --- /dev/null +++ b/tests/utils/misc/index.js @@ -0,0 +1,118 @@ +'use strict'; + +const path = require('path'); +const fse = require('fs-extra'); +const BbPromise = require('bluebird'); +const { execSync } = require('child_process'); +const { replaceTextInFile } = require('../fs'); + +const logger = console; + +const region = 'us-east-1'; + +const testServiceIdentifier = 'integ-test'; + +const serverlessExec = path.resolve(__dirname, '..', '..', '..', 'bin', 'serverless'); + +const serviceNameRegex = new RegExp(`${testServiceIdentifier}-d+`); + +function getServiceName() { + const hrtime = process.hrtime(); + return `${testServiceIdentifier}-${hrtime[1]}`; +} + +function deployService() { + execSync(`${serverlessExec} deploy`); +} + +function removeService() { + execSync(`${serverlessExec} remove`); +} + +function replaceEnv(values) { + const originals = {}; + for (const key of Object.keys(values)) { + if (process.env[key]) { + originals[key] = process.env[key]; + } else { + originals[key] = 'undefined'; + } + if (values[key] === 'undefined') { + delete process.env[key]; + } else { + process.env[key] = values[key]; + } + } + return originals; +} + +function createTestService(templateName, tmpDir, testServiceDir) { + const serviceName = getServiceName(); + + fse.mkdirsSync(tmpDir); + process.chdir(tmpDir); + + // create a new Serverless service + execSync(`${serverlessExec} create --template ${templateName}`); + + if (testServiceDir) { + fse.copySync(testServiceDir, tmpDir, { clobber: true, preserveTimestamps: true }); + } + + replaceTextInFile('serverless.yml', templateName, serviceName); + + process.env.TOPIC_1 = `${serviceName}-1`; + process.env.TOPIC_2 = `${serviceName}-1`; + process.env.BUCKET_1 = `${serviceName}-1`; + process.env.BUCKET_2 = `${serviceName}-2`; + process.env.COGNITO_USER_POOL_1 = `${serviceName}-1`; + process.env.COGNITO_USER_POOL_2 = `${serviceName}-2`; + + // return the name of the CloudFormation stack + return serviceName; +} + +function getFunctionLogs(functionName) { + const logs = execSync(`${serverlessExec} logs --function ${functionName} --noGreeting true`); + const logsString = Buffer.from(logs, 'base64').toString(); + process.stdout.write(logsString); + return logsString; +} + +function persistentRequest(...args) { + const func = args[0]; + const funcArgs = args.slice(1); + const MAX_TRIES = 5; + return new BbPromise((resolve, reject) => { + const doCall = (numTry) => { + return func.apply(this, funcArgs).then(resolve, e => { + if (numTry < MAX_TRIES && + ((e.providerError && e.providerError.retryable) || e.statusCode === 429)) { + logger.log( + [`Recoverable error occurred (${e.message}), sleeping for 5 seconds.`, + `Try ${numTry + 1} of ${MAX_TRIES}`].join(' ') + ); + setTimeout(doCall, 5000, numTry + 1); + } else { + reject(e); + } + }); + }; + return doCall(0); + }); +} + +module.exports = { + logger, + region, + testServiceIdentifier, + serverlessExec, + serviceNameRegex, + getServiceName, + deployService, + removeService, + replaceEnv, + createTestService, + getFunctionLogs, + persistentRequest, +}; diff --git a/tests/utils/plugins/index.js b/tests/utils/plugins/index.js new file mode 100644 index 000000000..6f422d1eb --- /dev/null +++ b/tests/utils/plugins/index.js @@ -0,0 +1,30 @@ +'use strict'; + +const path = require('path'); +const fse = require('fs-extra'); + +// mock to test functionality bound to a serverless plugin +class ServerlessPlugin { + constructor(serverless, options, testSubject) { + this.options = options; + this.serverless = serverless; + + Object.assign( + this, + testSubject + ); + } +} + +function installPlugin(installDir, PluginClass) { + const pluginPkg = { name: path.basename(installDir), version: '0.0.0' }; + const className = (new PluginClass()).constructor.name; + fse.outputFileSync(path.join(installDir, 'package.json'), JSON.stringify(pluginPkg), 'utf8'); + fse.outputFileSync(path.join(installDir, 'index.js'), + `"use strict";\n${PluginClass.toString()}\nmodule.exports = ${className}`, 'utf8'); +} + +module.exports = { + ServerlessPlugin, + installPlugin, +}; diff --git a/tests/utils/s3/index.js b/tests/utils/s3/index.js new file mode 100644 index 000000000..1e48930ab --- /dev/null +++ b/tests/utils/s3/index.js @@ -0,0 +1,54 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const { region, persistentRequest } = require('../misc'); + +function createAndRemoveInBucket(bucketName) { + const S3 = new AWS.S3({ region }); + + const params = { + Bucket: bucketName, + Key: 'object', + Body: 'hello world', + }; + + return S3.putObject(params).promise() + .then(() => { + delete params.Body; + return S3.deleteObject(params); + }); +} + +function emptyBucket(bucket) { + const S3 = new AWS.S3({ region }); + + return S3.listObjects({ Bucket: bucket }) + .promise() + .then(data => { + const items = data.Contents; + const numItems = items.length; + if (numItems) { + const keys = items.map(item => Object.assign({}, { Key: item.Key })); + return S3.deleteObjects({ + Bucket: bucket, + Delete: { + Objects: keys, + }, + }).promise(); + } + return null; + }); +} + +function deleteBucket(bucket) { + const S3 = new AWS.S3({ region }); + + return emptyBucket(bucket).then(() => + S3.deleteBucket({ Bucket: bucket }).promise()); +} + +module.exports = { + createAndRemoveInBucket: persistentRequest.bind(this, createAndRemoveInBucket), + emptyBucket: persistentRequest.bind(this, emptyBucket), + deleteBucket: persistentRequest.bind(this, deleteBucket), +}; diff --git a/tests/utils/sns/index.js b/tests/utils/sns/index.js new file mode 100644 index 000000000..012293b76 --- /dev/null +++ b/tests/utils/sns/index.js @@ -0,0 +1,53 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const { region, persistentRequest } = require('../misc'); + +function createSnsTopic(topicName) { + const SNS = new AWS.SNS({ region }); + + const params = { + Name: topicName, + }; + + return SNS.createTopic(params).promise(); +} + +function removeSnsTopic(topicName) { + const SNS = new AWS.SNS({ region }); + + return SNS.listTopics().promise() + .then(data => { + const topicArn = data.Topics.find(topic => RegExp(topicName, 'g') + .test(topic.TopicArn)).TopicArn; + + const params = { + TopicArn: topicArn, + }; + + return SNS.deleteTopic(params).promise(); + }); +} + +function publishSnsMessage(topicName, message) { + const SNS = new AWS.SNS({ region }); + + return SNS.listTopics().promise() + .then(data => { + const topicArn = data.Topics.find(topic => RegExp(topicName, 'g') + .test(topic.TopicArn)).TopicArn; + + const params = { + Message: message, + TopicArn: topicArn, + }; + + return SNS.publish(params).promise(); + }); +} + +module.exports = { + createSnsTopic: persistentRequest.bind(this, createSnsTopic), + removeSnsTopic: persistentRequest.bind(this, removeSnsTopic), + publishSnsMessage: persistentRequest.bind(this, publishSnsMessage), +}; From f931ecca81ca208fccf5fe24c417198242c5decb Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 7 May 2019 14:59:00 +0200 Subject: [PATCH 208/504] Add support for Websocket Logs --- .eslintrc.js | 1 + docs/providers/aws/events/websocket.md | 14 ++ docs/providers/aws/guide/serverless.yml.md | 1 + lib/plugins/aws/deploy/lib/uploadArtifacts.js | 2 - lib/plugins/aws/lib/naming.js | 9 ++ lib/plugins/aws/lib/naming.test.js | 18 +++ .../events/apiGateway/lib/hack/updateStage.js | 2 - .../compile/events/apiGateway/lib/stage.js | 5 +- .../compile/events/websockets/lib/stage.js | 143 ++++++++++++++-- .../events/websockets/lib/stage.test.js | 153 +++++++++++++++++- lib/plugins/package/lib/zipService.js | 2 - scripts/postinstall.js | 1 - 12 files changed, 320 insertions(+), 31 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 4218e236c..b95dee02b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,6 +7,7 @@ module.exports = { "func-names": "off", "global-require": "off", // Interfers with optional and eventual circular references "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/scripts/**", "**/tests/**"]}], + "no-use-before-define": "off", "react/require-extension": "off", // Forced by airbnb, not applicable (also deprecated) "strict": ["error", "safe"], // airbnb implies we're transpiling with babel, we're not }, diff --git a/docs/providers/aws/events/websocket.md b/docs/providers/aws/events/websocket.md index 8d88f4681..7df2f1327 100644 --- a/docs/providers/aws/events/websocket.md +++ b/docs/providers/aws/events/websocket.md @@ -186,3 +186,17 @@ module.exports.defaultHandler = async (event, context) => { }; } ``` + +## Logs + +Use the following configuration to enable Websocket logs: + +```yml +# serverless.yml +provider: + name: aws + logs: + websocket: true +``` + +The log streams will be generated in a dedicated log group which follows the naming schema `/aws/websocket/{service}-{stage}`. diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 413602b47..ff129c57f 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -128,6 +128,7 @@ provider: lambda: true # Optional, can be true (true equals 'Active'), 'Active' or 'PassThrough' logs: restApi: true # Optional configuration which specifies if API Gateway logs are used + websocket: true # Optional configuration which specifies if Websockets logs are used package: # Optional deployment packaging configuration include: # Specify the directories and files which should be included in the deployment package diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.js index e921fce18..c941f9b42 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.js @@ -1,7 +1,5 @@ 'use strict'; -/* eslint-disable no-use-before-define */ - const _ = require('lodash'); const fs = require('fs'); const path = require('path'); diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index 7ad93bc53..694fd1021 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -204,6 +204,15 @@ module.exports = { getWebsocketsAuthorizerLogicalId(functionName) { return `${this.getNormalizedAuthorizerName(functionName)}WebsocketsAuthorizer`; }, + getWebsocketsLogGroupLogicalId() { + return 'WebsocketsLogGroup'; + }, + getWebsocketsLogsRoleLogicalId() { + return 'IamRoleWebsocketsLogs'; + }, + getWebsocketsAccountLogicalId() { + return 'WebsocketsAccount'; + }, // API Gateway getApiGatewayName() { diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 78f9a6b23..1b10d0198 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -302,6 +302,24 @@ describe('#naming()', () => { }); }); + describe('#getWebsocketsLogGroupLogicalId()', () => { + it('should return the Websockets log group logical id', () => { + expect(sdk.naming.getWebsocketsLogGroupLogicalId()).to.equal('WebsocketsLogGroup'); + }); + }); + + describe('#getWebsocketsLogsRoleLogicalId()', () => { + it('should return the Websockets logs IAM role logical id', () => { + expect(sdk.naming.getWebsocketsLogsRoleLogicalId()).to.equal('IamRoleWebsocketsLogs'); + }); + }); + + describe('#getWebsocketsAccountLogicalId()', () => { + it('should return the Websockets account logical id', () => { + expect(sdk.naming.getWebsocketsAccountLogicalId()).to.equal('WebsocketsAccount'); + }); + }); + describe('#getApiGatewayName()', () => { it('should return the composition of stage & service name if custom name not provided', () => { serverless.service.service = 'myService'; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js index 525a08198..0aa14e06b 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js @@ -1,5 +1,3 @@ -/* eslint-disable no-use-before-define */ - 'use strict'; const _ = require('lodash'); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js index 3fb978548..e32df1ad3 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js @@ -1,8 +1,7 @@ -/* eslint-disable no-use-before-define */ -/* eslint-disable max-len */ - 'use strict'; +/* eslint-disable max-len */ + // NOTE: --> Keep this file in sync with ./hack/updateStage.js const _ = require('lodash'); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/stage.js b/lib/plugins/aws/package/compile/events/websockets/lib/stage.js index eecaa1579..ae2472565 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/stage.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/stage.js @@ -1,30 +1,139 @@ 'use strict'; -const _ = require('lodash'); const BbPromise = require('bluebird'); module.exports = { compileStage() { - const websocketsStageLogicalId = this.provider.naming - .getWebsocketsStageLogicalId(); + const { service, provider } = this.serverless.service; + const stage = this.options.stage; + const cfTemplate = provider.compiledCloudFormationTemplate; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { - [websocketsStageLogicalId]: { - Type: 'AWS::ApiGatewayV2::Stage', - Properties: { - ApiId: { - Ref: this.websocketsApiLogicalId, - }, - DeploymentId: { - Ref: this.websocketsDeploymentLogicalId, - }, - StageName: this.provider.getStage(), - Description: this.serverless.service.provider - .websocketsDescription || 'Serverless Websockets', + // logs + const logsEnabled = provider.logs && provider.logs.websocket; + + const stageLogicalId = this.provider.naming + .getWebsocketsStageLogicalId(); + const logGroupLogicalId = this.provider.naming + .getWebsocketsLogGroupLogicalId(); + const logsRoleLogicalId = this.provider.naming + .getWebsocketsLogsRoleLogicalId(); + const accountLogicalid = this.provider.naming + .getWebsocketsAccountLogicalId(); + + const stageResource = { + Type: 'AWS::ApiGatewayV2::Stage', + Properties: { + ApiId: { + Ref: this.websocketsApiLogicalId, }, + DeploymentId: { + Ref: this.websocketsDeploymentLogicalId, + }, + StageName: this.provider.getStage(), + Description: this.serverless.service.provider + .websocketsDescription || 'Serverless Websockets', }, - }); + }; + + // create log-specific resources + if (logsEnabled) { + Object.assign(stageResource.Properties, { + AccessLogSettings: { + DestinationArn: { + 'Fn::GetAtt': [ + logGroupLogicalId, + 'Arn', + ], + }, + Format: [ + '$context.identity.sourceIp', + '$context.identity.caller', + '$context.identity.user', + '[$context.requestTime]', + '"$context.eventType $context.routeKey $context.connectionId"', + '$context.status', + '$context.requestId', + ].join(' '), + }, + DefaultRouteSettings: { + DataTraceEnabled: true, + LoggingLevel: 'INFO', + }, + }); + + Object.assign(cfTemplate.Resources, { + [logGroupLogicalId]: getLogGroupResource(service, stage), + [logsRoleLogicalId]: getIamRoleResource(service, stage), + [accountLogicalid]: getAccountResource(logsRoleLogicalId), + }); + } + + Object.assign(cfTemplate.Resources, { [stageLogicalId]: stageResource }); return BbPromise.resolve(); }, }; + +function getLogGroupResource(service, stage) { + return ({ + Type: 'AWS::Logs::LogGroup', + Properties: { + LogGroupName: `/aws/websocket/${service}-${stage}`, + }, + }); +} + +function getIamRoleResource(service, stage) { + return ({ + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { + Service: [ + 'apigateway.amazonaws.com', + ], + }, + Action: [ + 'sts:AssumeRole', + ], + }, + ], + }, + ManagedPolicyArns: [ + 'arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs', + ], + Path: '/', + RoleName: { + 'Fn::Join': [ + '-', + [ + service, + stage, + { + Ref: 'AWS::Region', + }, + 'apiGatewayLogsRole', + ], + ], + }, + }, + }); +} + +function getAccountResource(logsRoleLogicalId) { + return ({ + Type: 'AWS::ApiGateway::Account', + Properties: { + CloudWatchRoleArn: { + 'Fn::GetAtt': [ + logsRoleLogicalId, + 'Arn', + ], + }, + }, + }); +} diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js index 01dff2d81..1dc2bdb0e 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js @@ -7,14 +7,30 @@ const AwsProvider = require('../../../../../provider/awsProvider'); describe('#compileStage()', () => { let awsCompileWebsocketsEvents; + let stageLogicalId; + let accountLogicalid; + let logsRoleLogicalId; + let logGroupLogicalId; beforeEach(() => { + const options = { + stage: 'dev', + region: 'us-east-1', + }; const serverless = new Serverless(); serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.service.service = 'my-service'; serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; - awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); - + awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless, options); + stageLogicalId = awsCompileWebsocketsEvents.provider.naming + .getWebsocketsStageLogicalId(); + accountLogicalid = awsCompileWebsocketsEvents.provider.naming + .getWebsocketsAccountLogicalId(); + logsRoleLogicalId = awsCompileWebsocketsEvents.provider.naming + .getWebsocketsLogsRoleLogicalId(); + logGroupLogicalId = awsCompileWebsocketsEvents.provider.naming + .getWebsocketsLogGroupLogicalId(); awsCompileWebsocketsEvents.websocketsApiLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); awsCompileWebsocketsEvents.websocketsDeploymentLogicalId @@ -26,13 +42,142 @@ describe('#compileStage()', () => { .compiledCloudFormationTemplate.Resources; const resourceKeys = Object.keys(resources); - expect(resourceKeys[0]).to.equal('WebsocketsDeploymentStage'); + expect(resourceKeys[0]).to.equal(stageLogicalId); expect(resources.WebsocketsDeploymentStage.Type).to.equal('AWS::ApiGatewayV2::Stage'); expect(resources.WebsocketsDeploymentStage.Properties.ApiId).to.deep.equal({ - Ref: 'WebsocketsApi', + Ref: awsCompileWebsocketsEvents.websocketsApiLogicalId, + }); + expect(resources.WebsocketsDeploymentStage.Properties.DeploymentId).to.deep.equal({ + Ref: awsCompileWebsocketsEvents.websocketsDeploymentLogicalId, }); expect(resources.WebsocketsDeploymentStage.Properties.StageName).to.equal('dev'); expect(resources.WebsocketsDeploymentStage.Properties.Description) .to.equal('Serverless Websockets'); })); + + describe('logs', () => { + beforeEach(() => { + // setting up Websocket logs + awsCompileWebsocketsEvents.serverless.service.provider.logs = { + websocket: true, + }; + }); + + it('should create a dedicated stage resource if logs are configured', () => + awsCompileWebsocketsEvents.compileStage().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources[stageLogicalId]).to.deep.equal({ + Type: 'AWS::ApiGatewayV2::Stage', + Properties: { + ApiId: { + Ref: awsCompileWebsocketsEvents.websocketsApiLogicalId, + }, + DeploymentId: { + Ref: awsCompileWebsocketsEvents.websocketsDeploymentLogicalId, + }, + StageName: 'dev', + Description: 'Serverless Websockets', + AccessLogSettings: { + DestinationArn: { + 'Fn::GetAtt': [ + logGroupLogicalId, + 'Arn', + ], + }, + Format: [ + '$context.identity.sourceIp', + '$context.identity.caller', + '$context.identity.user', + '[$context.requestTime]', + '"$context.eventType $context.routeKey $context.connectionId"', + '$context.status', + '$context.requestId', + ].join(' '), + }, + DefaultRouteSettings: { + DataTraceEnabled: true, + LoggingLevel: 'INFO', + }, + }, + }); + })); + + it('should create a Log Group resource', () => + awsCompileWebsocketsEvents.compileStage().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources[logGroupLogicalId]).to.deep.equal({ + Type: 'AWS::Logs::LogGroup', + Properties: { + LogGroupName: '/aws/websocket/my-service-dev', + }, + }); + })); + + it('should create a IAM Role resource', () => + awsCompileWebsocketsEvents.compileStage().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources[logsRoleLogicalId]).to.deep.equal({ + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: [ + 'sts:AssumeRole', + ], + Effect: 'Allow', + Principal: { + Service: [ + 'apigateway.amazonaws.com', + ], + }, + }, + ], + Version: '2012-10-17', + }, + ManagedPolicyArns: [ + 'arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs', + ], + Path: '/', + RoleName: { + 'Fn::Join': [ + '-', + [ + 'my-service', + 'dev', + { + Ref: 'AWS::Region', + }, + 'apiGatewayLogsRole', + ], + ], + }, + }, + }); + })); + + it('should create an Account resource', () => + awsCompileWebsocketsEvents.compileStage().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources[accountLogicalid]).to.deep.equal({ + Type: 'AWS::ApiGateway::Account', + Properties: { + CloudWatchRoleArn: { + 'Fn::GetAtt': [ + logsRoleLogicalId, + 'Arn', + ], + }, + }, + }); + })); + }); }); diff --git a/lib/plugins/package/lib/zipService.js b/lib/plugins/package/lib/zipService.js index b72381942..9b7137c27 100644 --- a/lib/plugins/package/lib/zipService.js +++ b/lib/plugins/package/lib/zipService.js @@ -1,7 +1,5 @@ 'use strict'; -/* eslint-disable no-use-before-define */ - const BbPromise = require('bluebird'); const archiver = require('archiver'); const os = require('os'); diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 1a6324cef..823bed854 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -3,7 +3,6 @@ const path = require('path'); /* eslint-disable no-console */ -/* eslint-disable no-use-before-define */ const Serverless = require('../lib/Serverless'); const execSync = require('child_process').execSync; From 6c833818bab28976e142c116945081c9a6a9f21e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 16:29:16 +0200 Subject: [PATCH 209/504] Whitespace --- tests/mocha-reporter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index e09fb13d5..f8d26fdb9 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -56,4 +56,3 @@ module.exports = class ServerlessSpec extends Spec { ); } }; - From 32d46f3b5655f49bde2d9f98099805de993fcc6d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 16:32:49 +0200 Subject: [PATCH 210/504] Ensure current directory change in cleaned up --- tests/mocha-reporter.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index f8d26fdb9..27ed9f9a2 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -36,15 +36,27 @@ BbPromise.prototype._ensurePossibleRejectionHandled = function () { }; /* eslint-enable */ -if (process.version[1] < 8) { - // Async leaks detector is not reliable in Node.js v6 - module.exports = Spec; - return; -} +const startCwd = process.cwd(); module.exports = class ServerlessSpec extends Spec { constructor(runner) { super(runner); + + // Check for not cleaned current directory change + runner.on('suite end', suite => { + if (!suite.parent || !suite.parent.root) return; // Apply just on top level suites + if (process.cwd() !== startCwd) { + runner._abort = true; + throw new Error( + `Tests in ${suite.file.slice(startCwd.length + 1)} didn't revert ` + + 'current directory change. This may affect resuls of upcoming tests.' + ); + } + }); + + if (process.version[1] < 8) return; // Async leaks detector is not reliable in Node.js v6 + + // Async leaks detection runner.on('end', () => setTimeout(() => { // If tests end with any orphaned async call then this callback will be invoked From f14d870ea16892987d918ef36d1699727c0c21c1 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 17:13:38 +0200 Subject: [PATCH 211/504] Ensure to cleanup temporary files --- lib/classes/Utils.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/classes/Utils.test.js b/lib/classes/Utils.test.js index aa0f2d49f..6315ed6f4 100644 --- a/lib/classes/Utils.test.js +++ b/lib/classes/Utils.test.js @@ -5,6 +5,7 @@ const os = require('os'); const uuid = require('uuid'); const chai = require('chai'); const sinon = require('sinon'); +const fse = require('fs-extra') const Serverless = require('../../lib/Serverless'); const testUtils = require('../../tests/utils'); const configUtils = require('../utils/config'); @@ -227,6 +228,8 @@ describe('Utils', () => { expect(serverless.utils.fileExistsSync(destFile1)).to.equal(true); expect(serverless.utils.fileExistsSync(destFile2)).to.equal(true); expect(serverless.utils.fileExistsSync(destFile3)).to.equal(true); + fse.removeSync(tmpSrcDirPath); + fse.removeSync(tmpDestDirPath); }); }); From 7f1b507bcee7e65ccc7d8cb420caa9ea2417844f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 17:19:23 +0200 Subject: [PATCH 212/504] Prevent change of current directory Yaml parser was introducing side effects, that affected test results. Refactor so it doesn't depend on current directory --- lib/classes/YamlParser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/YamlParser.js b/lib/classes/YamlParser.js index 35907f337..723e39e0f 100644 --- a/lib/classes/YamlParser.js +++ b/lib/classes/YamlParser.js @@ -14,7 +14,6 @@ class YamlParser { let parentDir = yamlFilePath.split(path.sep); parentDir.pop(); parentDir = parentDir.join('/'); - process.chdir(parentDir); const root = this.serverless.utils.readFileSync(yamlFilePath); const options = { @@ -24,6 +23,7 @@ class YamlParser { callback(null, YAML.load(res.text)); }, }, + relativeBase: parentDir }; return resolve(root, options).then((res) => (res.resolved)); } From 64a1b95a29f3e37c1868871d2c8064473493f4c0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 17:31:10 +0200 Subject: [PATCH 213/504] Ensure to not silence uncaught exceptions --- tests/mocha-reporter.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 27ed9f9a2..ed31529f3 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -10,7 +10,10 @@ process.on('unhandledRejection', err => { // Mocha reports it with success exit code: https://github.com/mochajs/mocha/issues/3917 // Workaround that (otherwise we may end with green CI for failed builds): process.on('uncaughtException', err => { - if (!process.listenerCount('exit')) return; + if (!process.listenerCount('exit')) { + if (process.listenerCount('uncaughtException') === 1) throw err; + return; + } // Mocha done it's report, and registered process.exit listener which silences any further // eventual crashes. Recover by unregistering the listener process.removeAllListeners('exit'); From fb4335de255af6054484bbe70e8f66a0042783f1 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 17:31:54 +0200 Subject: [PATCH 214/504] Ensure to revert current directory change --- lib/utils/getServerlessConfigFile.test.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/utils/getServerlessConfigFile.test.js b/lib/utils/getServerlessConfigFile.test.js index 33b9661fb..21c5d8ba4 100644 --- a/lib/utils/getServerlessConfigFile.test.js +++ b/lib/utils/getServerlessConfigFile.test.js @@ -101,13 +101,12 @@ describe('#getServerlessConfigFile()', () => { const cwd = process.cwd(); process.chdir(tmpDirPath); return expect(getServerlessConfigFile()).to.be.fulfilled - .then(result => result) - .catch((ex) => { - process.chdir(cwd); - throw ex; - }) .then((result) => { + process.chdir(cwd); expect(result).to.deep.equal({ service: 'my-yml-service' }); + }, error => { + process.chdir(cwd); + throw error; }); }); }); From c389e74bac5afa342a663ccb68bd8ed2a68adad0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 17:44:16 +0200 Subject: [PATCH 215/504] Cleanup --- tests/mocha-reporter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index ed31529f3..db501e7e8 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -39,13 +39,12 @@ BbPromise.prototype._ensurePossibleRejectionHandled = function () { }; /* eslint-enable */ -const startCwd = process.cwd(); - module.exports = class ServerlessSpec extends Spec { constructor(runner) { super(runner); // Check for not cleaned current directory change + const startCwd = process.cwd(); runner.on('suite end', suite => { if (!suite.parent || !suite.parent.root) return; // Apply just on top level suites if (process.cwd() !== startCwd) { From d76480329ac0dd1cf0b82f2980076c6e8b562a67 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 21:32:24 +0200 Subject: [PATCH 216/504] Move require to top It seems there's no reason to have it nested --- lib/utils/config/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/utils/config/index.js b/lib/utils/config/index.js index 71012d8c3..304693a0c 100644 --- a/lib/utils/config/index.js +++ b/lib/utils/config/index.js @@ -4,6 +4,7 @@ const p = require('path'); const os = require('os'); const _ = require('lodash'); +const rc = require('rc'); const writeFileAtomic = require('write-file-atomic'); const fileExistsSync = require('../fs/fileExistsSync'); const readFileSync = require('../fs/readFileSync'); @@ -52,7 +53,7 @@ function getConfig() { } // then return config merged via rc module - return require('rc')(rcFileBase); // eslint-disable-line + return rc(rcFileBase); } function getGlobalConfig() { From 56a3a4e10535db482bacac76f39717e19658e966 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 21:40:31 +0200 Subject: [PATCH 217/504] Expose tmp dir common path --- tests/utils/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/utils/index.js b/tests/utils/index.js index 081ea7a56..bc2e8867f 100644 --- a/tests/utils/index.js +++ b/tests/utils/index.js @@ -25,8 +25,9 @@ class ServerlessPlugin { const serverlessExec = path.join(__dirname, '..', '..', 'bin', 'serverless'); -const getTmpDirPath = () => path.join(os.tmpdir(), - 'tmpdirs-serverless', 'serverless', crypto.randomBytes(8).toString('hex')); +const tmpDirCommonPath = path.join(os.tmpdir(), 'tmpdirs-serverless', 'serverless'); + +const getTmpDirPath = () => path.join(tmpDirCommonPath, crypto.randomBytes(8).toString('hex')); const getTmpFilePath = (fileName) => path.join(getTmpDirPath(), fileName); @@ -40,6 +41,7 @@ const listZipFiles = filename => new JSZip().loadAsync(fs.readFileSync(filename) module.exports = { serverlessExec, + tmpDirCommonPath, getTmpDirPath, getTmpFilePath, replaceTextInFile, From 9b1c474c2d761c2f3712927016a7c8ed9e8d88f3 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 21:41:50 +0200 Subject: [PATCH 218/504] Ensure to mock homedir for tests --- tests/mocha-reporter.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index db501e7e8..2c89ea842 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -20,8 +20,11 @@ process.on('uncaughtException', err => { throw err; }); +const { join } = require('path'); +const os = require('os'); const Spec = require('mocha/lib/reporters/spec'); const Runner = require('mocha/lib/runner'); +const { tmpDirCommonPath } = require('../tests/utils') // Ensure faster tests propagation // It's to expose errors otherwise hidden by race conditions @@ -39,6 +42,13 @@ BbPromise.prototype._ensurePossibleRejectionHandled = function () { }; /* eslint-enable */ +// Ensure to not mess with real homedir +// Tests do not mock config handling, which generates and edits user's serverlessrc +// By overriding homedir resolution the file, we prevent updates to real ~/.serverlessrc +os.homedir = () => tmpDirCommonPath; +if (process.env.USERPROFILE) process.env.USERPROFILE = tmpDirCommonPath; +if (process.env.HOME) process.env.HOME = tmpDirCommonPath; + module.exports = class ServerlessSpec extends Spec { constructor(runner) { super(runner); From 94065246c1cbb5ff9788cf6d944a1fbd300dae12 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 21:44:25 +0200 Subject: [PATCH 219/504] Fix lint issues --- tests/mocha-reporter.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 2c89ea842..7d6257cdb 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -20,11 +20,10 @@ process.on('uncaughtException', err => { throw err; }); -const { join } = require('path'); const os = require('os'); const Spec = require('mocha/lib/reporters/spec'); const Runner = require('mocha/lib/runner'); -const { tmpDirCommonPath } = require('../tests/utils') +const { tmpDirCommonPath } = require('../tests/utils'); // Ensure faster tests propagation // It's to expose errors otherwise hidden by race conditions @@ -58,7 +57,7 @@ module.exports = class ServerlessSpec extends Spec { runner.on('suite end', suite => { if (!suite.parent || !suite.parent.root) return; // Apply just on top level suites if (process.cwd() !== startCwd) { - runner._abort = true; + runner._abort = true; // eslint-disable-line no-underscore-dangle,no-param-reassign throw new Error( `Tests in ${suite.file.slice(startCwd.length + 1)} didn't revert ` + 'current directory change. This may affect resuls of upcoming tests.' From 37e19c429ab6ac73d9a3ee106625d5aff58526cf Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 21:50:25 +0200 Subject: [PATCH 220/504] Ensure to not respect CLI argumenst for SLS config --- lib/utils/config/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/config/index.js b/lib/utils/config/index.js index 304693a0c..786eb11de 100644 --- a/lib/utils/config/index.js +++ b/lib/utils/config/index.js @@ -53,7 +53,7 @@ function getConfig() { } // then return config merged via rc module - return rc(rcFileBase); + return rc(rcFileBase, null, /* Ensure to not read options from CLI */ {}); } function getGlobalConfig() { From 2b125cbdece195a79be0496ec4fc775363d1b6ba Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 21:55:36 +0200 Subject: [PATCH 221/504] Ensure to reset created user config between tests --- tests/mocha-reporter.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 7d6257cdb..2a7507846 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -20,9 +20,11 @@ process.on('uncaughtException', err => { throw err; }); +const { join } = require('path'); const os = require('os'); const Spec = require('mocha/lib/reporters/spec'); const Runner = require('mocha/lib/runner'); +const { removeSync } = require('fs-extra'); const { tmpDirCommonPath } = require('../tests/utils'); // Ensure faster tests propagation @@ -52,10 +54,18 @@ module.exports = class ServerlessSpec extends Spec { constructor(runner) { super(runner); - // Check for not cleaned current directory change + // After tests for given files finalize: + // - Enforce eventual current directory change was reverted + // - Ensure to reset of eventually created user config file const startCwd = process.cwd(); + const userConfig = join(tmpDirCommonPath, '.serverlessrc'); runner.on('suite end', suite => { if (!suite.parent || !suite.parent.root) return; // Apply just on top level suites + try { + removeSync(userConfig); + } catch (error) { + if (error.code !== 'ENOENT') throw error; + } if (process.cwd() !== startCwd) { runner._abort = true; // eslint-disable-line no-underscore-dangle,no-param-reassign throw new Error( From eed9d54c6511c4067e3e9641daf31994a61ea635 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 22:04:49 +0200 Subject: [PATCH 222/504] Recognize error code 1 --- scripts/test-isolated.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-isolated.js b/scripts/test-isolated.js index b3381683b..6df678167 100755 --- a/scripts/test-isolated.js +++ b/scripts/test-isolated.js @@ -96,7 +96,7 @@ globby(patterns).then(paths => { if (isMultiProcessRun) ongoing.clear(); return onFinally(error).then(() => { process.stderr.write(chalk.red.bold(`${path} failed\n\n`)); - if (error.code === 2) process.exit(2); + if (error.code <= 2) process.exit(error.code); throw error; }); }); From 5dec45f5903964145aab9467aaf94dd5d3884871 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 22:06:36 +0200 Subject: [PATCH 223/504] Fix lint issues --- lib/classes/Utils.test.js | 2 +- lib/classes/YamlParser.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/classes/Utils.test.js b/lib/classes/Utils.test.js index 6315ed6f4..1fa14073c 100644 --- a/lib/classes/Utils.test.js +++ b/lib/classes/Utils.test.js @@ -5,7 +5,7 @@ const os = require('os'); const uuid = require('uuid'); const chai = require('chai'); const sinon = require('sinon'); -const fse = require('fs-extra') +const fse = require('fs-extra'); const Serverless = require('../../lib/Serverless'); const testUtils = require('../../tests/utils'); const configUtils = require('../utils/config'); diff --git a/lib/classes/YamlParser.js b/lib/classes/YamlParser.js index 723e39e0f..00b84a5ba 100644 --- a/lib/classes/YamlParser.js +++ b/lib/classes/YamlParser.js @@ -23,7 +23,7 @@ class YamlParser { callback(null, YAML.load(res.text)); }, }, - relativeBase: parentDir + relativeBase: parentDir, }; return resolve(root, options).then((res) => (res.resolved)); } From e82eed38c1a58706dfc98f97381107d3c705dcef Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 22:08:33 +0200 Subject: [PATCH 224/504] Expose process output in case of crash --- lib/classes/PluginManager.test.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 7a28dac67..25aa814e5 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -1942,7 +1942,14 @@ describe('PluginManager', () => { fse.mkdirsSync(serviceDir); process.chdir(serviceDir); - execSync(`${serverlessExec} create --template aws-nodejs`); + try { + execSync(`${serverlessExec} create --template aws-nodejs`); + } catch (error) { + // Expose process output in case of crash + process.stdout.write(error.stdout); + process.stderr.write(error.stderr) + throw error; + } }); }); From 9c5a13badd8e3d895a0307baf2ec505e0236a2f8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 30 May 2019 22:20:36 +0200 Subject: [PATCH 225/504] Lint --- lib/classes/PluginManager.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 25aa814e5..90fd282e0 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -1947,7 +1947,7 @@ describe('PluginManager', () => { } catch (error) { // Expose process output in case of crash process.stdout.write(error.stdout); - process.stderr.write(error.stderr) + process.stderr.write(error.stderr); throw error; } }); From 54346998b799b93f86d8d40c426c32609d7bf964 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 10:07:11 +0200 Subject: [PATCH 226/504] Upgrade process-utils --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 74acca6cd..9eeb7af7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7596,9 +7596,9 @@ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "process-utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/process-utils/-/process-utils-2.3.0.tgz", - "integrity": "sha512-0i9UzoA8LgDyIk78XKhSbszvCn89vrGPdnQdcotHEjd7hbfRrQQ3pUkyg43+Jy9TuUtD7Syc6/r2LmOmjOLrYA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/process-utils/-/process-utils-2.3.1.tgz", + "integrity": "sha512-ZTNuK06C/RMIh85Yk9OFTq8h7WG4mXK3NJkqnHm63QH5by82KNU7MvEMPzYsrlYbvcJflUsor63uLB7mSx30Yg==", "dev": true, "requires": { "type": "^1.0.1" diff --git a/package.json b/package.json index ccff59f5c..3324c3ba0 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "nyc": "^14.1.1", "p-limit": "^2.2.0", "parse-github-url": "^1.0.1", - "process-utils": "^2.3.0", + "process-utils": "^2.3.1", "proxyquire": "^1.7.10", "sinon": "^1.17.5", "sinon-bluebird": "^3.1.0", From 23eaa401ca5524a1061cf08a3c95363b3c797fd3 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 10:40:02 +0200 Subject: [PATCH 227/504] Ensure to pass HOME in test isolated script --- scripts/test-isolated.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/test-isolated.js b/scripts/test-isolated.js index 6df678167..83c08cc74 100755 --- a/scripts/test-isolated.js +++ b/scripts/test-isolated.js @@ -91,7 +91,12 @@ globby(patterns).then(paths => { return spawn('npx', ['mocha', path], { stdio: isMultiProcessRun ? null : 'inherit', - env: { FORCE_COLOR: '1', PATH: process.env.PATH }, + env: { + FORCE_COLOR: '1', + PATH: process.env.PATH, + HOME: process.env.HOME, + USERPROFILE: process.env.USERPROFILE, + }, }).then(onFinally, error => { if (isMultiProcessRun) ongoing.clear(); return onFinally(error).then(() => { From 78c2025f13351b837c8dba3d9ed99c66afd0464e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 12:03:40 +0200 Subject: [PATCH 228/504] Ensure to mirror mandatory windows env var --- lib/classes/PluginManager.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 90fd282e0..b95a08ed4 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -433,7 +433,7 @@ describe('PluginManager', () => { let restoreEnv; beforeEach(() => { - ({ restoreEnv } = overrideEnv({ whitelist: ['PATH'] })); + ({ restoreEnv } = overrideEnv({ whitelist: ['APPDATA', 'PATH'] })); serverless = new Serverless(); serverless.cli = new CLI(); pluginManager = new PluginManager(serverless); From 6c674572f08475c1ae21ea5bc91b02d5b2664340 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 12:57:41 +0200 Subject: [PATCH 229/504] Improve comments --- tests/mocha-reporter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 0a02da0ee..f329d2034 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -44,8 +44,8 @@ BbPromise.prototype._ensurePossibleRejectionHandled = function () { /* eslint-enable */ // Ensure to not mess with real homedir -// Tests do not mock config handling, which generates and edits user's serverlessrc -// By overriding homedir resolution the file, we prevent updates to real ~/.serverlessrc +// Tests do not mock config handling, which during tests generates and edits user's serverlessrc +// By overriding homedir resolution we prevent updates to real ~/.serverlessrc os.homedir = () => tmpDirCommonPath; if (process.env.USERPROFILE) process.env.USERPROFILE = tmpDirCommonPath; if (process.env.HOME) process.env.HOME = tmpDirCommonPath; From 6847ce3386d83a9903a7d2eff8508fd2332de8d8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 12:58:11 +0200 Subject: [PATCH 230/504] Improve comments --- tests/mocha-reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index f329d2034..755e1fe6b 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -54,7 +54,7 @@ module.exports = class ServerlessSpec extends Spec { constructor(runner) { super(runner); - // After tests for given files finalize: + // After test run for given file finalizes: // - Enforce eventual current directory change was reverted // - Ensure to reset of eventually created user config file const startCwd = process.cwd(); From e827c28a71e194515752690261cb3bb454244e93 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 14:08:05 +0200 Subject: [PATCH 231/504] Improve title --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 99264b732..7f3868a37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ stages: jobs: include: # To speed up Travis build, use one job per Platform + Node.js version combination - - name: "Lint, Unit Tests, Basic Integration tests - Linux - Node.js v12" + - name: "Lint, Unit Tests, Basic Integration Tests - Linux - Node.js v12" node_js: 12 # Combine with '&&' to abort early if any crashes env: From 06dafbeba263df2c69d0b02784c9f5a4b53ab2f1 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 21:12:52 +0200 Subject: [PATCH 232/504] Expose process output in case of errors --- tests/integration-basic/tests.js | 2 +- tests/utils/child-process.js | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/utils/child-process.js diff --git a/tests/integration-basic/tests.js b/tests/integration-basic/tests.js index e1eadf416..8462c064f 100644 --- a/tests/integration-basic/tests.js +++ b/tests/integration-basic/tests.js @@ -6,7 +6,7 @@ const fse = require('fs-extra'); const BbPromise = require('bluebird'); const AWS = require('aws-sdk'); const { expect } = require('chai'); -const { execSync } = require('child_process'); +const { execSync } = require('../utils/child-process'); const { getTmpDirPath, replaceTextInFile } = require('../utils/fs'); const { region, getServiceName } = require('../utils/misc'); diff --git a/tests/utils/child-process.js b/tests/utils/child-process.js new file mode 100644 index 000000000..93f2a716a --- /dev/null +++ b/tests/utils/child-process.js @@ -0,0 +1,16 @@ +'use strict'; + +const { execSync: originalExecSync } = require('child_process'); + +function execSync(command, options = null) { + // Same as native but outputs std in case of error + try { + return originalExecSync(command, options); + } catch (error) { + if (error.stdout) process.stdout.write(error.stdout); + if (error.stderr) process.stderr.write(error.stderr); + throw error; + } +} + +module.exports = { execSync }; From ce8e24d9dfd2ab41b39712c29c469844f2d9ef4d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 22:20:01 +0200 Subject: [PATCH 233/504] Ensure to test non colored output --- tests/integration-basic/tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration-basic/tests.js b/tests/integration-basic/tests.js index 8462c064f..5bfb551d7 100644 --- a/tests/integration-basic/tests.js +++ b/tests/integration-basic/tests.js @@ -5,6 +5,7 @@ const fs = require('fs'); const fse = require('fs-extra'); const BbPromise = require('bluebird'); const AWS = require('aws-sdk'); +const stripAnsi = require('strip-ansi'); const { expect } = require('chai'); const { execSync } = require('../utils/child-process'); const { getTmpDirPath, replaceTextInFile } = require('../utils/fs'); @@ -78,7 +79,7 @@ describe('Service Lifecyle Integration Test', () => { it('should list existing deployments and roll back to first deployment', () => { let timestamp; const listDeploys = execSync(`${serverlessExec} deploy list`); - const output = listDeploys.toString(); + const output = stripAnsi(listDeploys.toString()); const match = output.match(new RegExp('Datetime: (.+)')); if (match) { timestamp = match[1]; From fb99bee2b475bacfd3509da175c6c53b46522de1 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 22:33:21 +0200 Subject: [PATCH 234/504] Do not build PR branches --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7f3868a37..58a0f6899 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,9 @@ language: node_js git: depth: 1 # no need for commits history +branches: + only: master # Do not build PR branches + env: SLS_IGNORE_WARNING=* # Default env stages: From e2043fd239e61a72fe4676de7162aa9a2d6008d6 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 22:33:33 +0200 Subject: [PATCH 235/504] Improve comments --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 58a0f6899..f33159af3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: node_js git: - depth: 1 # no need for commits history + depth: 1 # No need for commits history branches: only: master # Do not build PR branches @@ -20,7 +20,6 @@ jobs: # To speed up Travis build, use one job per Platform + Node.js version combination - name: "Lint, Unit Tests, Basic Integration Tests - Linux - Node.js v12" node_js: 12 - # Combine with '&&' to abort early if any crashes env: - SLS_IGNORE_WARNING=* - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' @@ -29,6 +28,7 @@ jobs: # AWS_SECRET_ACCESS_KEY - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= script: + # Combine with '&&' to not continue on fail - npm run lint && npm test && npm run integration-test-run-basic - npm run integration-test-cleanup - name: "Unit Tests - Windows - Node.js v12" @@ -47,6 +47,7 @@ jobs: env: - SLS_IGNORE_WARNING=* - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' + # Combine with '&&' to not continue on fail script: npm run test-isolated && npm run integration-test-run-package - name: "Unit Tests, Coverage - Linux - Node.js v8" node_js: 8 From b3d25d4f553fdb058ce0ed7c0d7f15e1171379d2 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 22:45:03 +0200 Subject: [PATCH 236/504] Configure "lint-updated" script --- package-lock.json | 60 ++++++++++++++++++++++++++++++++++++++--------- package.json | 2 ++ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52fa814f9..5c3c71cdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2825,6 +2825,12 @@ "estraverse": "^4.1.0" } }, + "essentials": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/essentials/-/essentials-1.0.0.tgz", + "integrity": "sha512-Xq511rzvYFFDnUDFXqfA6LxyYAlp3BM2qCm9+ml3Xcs28RPmJczKUluYiGYUUEu4yTwU5jsm0TSzUxUxZxf3Zw==", + "dev": true + }, "estraverse": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", @@ -3544,7 +3550,8 @@ "version": "2.1.1", "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -3568,13 +3575,15 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3591,19 +3600,22 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3734,7 +3746,8 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3748,6 +3761,7 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3764,6 +3778,7 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3772,13 +3787,15 @@ "version": "0.0.8", "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3799,6 +3816,7 @@ "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3887,7 +3905,8 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3901,6 +3920,7 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3996,7 +4016,8 @@ "version": "5.1.2", "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -4038,6 +4059,7 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4059,6 +4081,7 @@ "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4107,13 +4130,15 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true + "dev": true, + "optional": true } } }, @@ -4191,6 +4216,19 @@ "assert-plus": "^1.0.0" } }, + "git-list-updated": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/git-list-updated/-/git-list-updated-1.1.2.tgz", + "integrity": "sha512-PGmbGbjttcyTNUIK1Ecef9nUvlz7tb+zOI7/hkrHgl1G9V2bRCF78XE1ddWNdshXVklVEqdXRM5l/iJvLIjk0Q==", + "dev": true, + "requires": { + "2-thenable": "1", + "child-process-ext": "2", + "es5-ext": "^0.10.46", + "essentials": "1", + "minimist": "^1.2" + } + }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", diff --git a/package.json b/package.json index 0e5023f32..fd01b53c7 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "test-isolated": "node scripts/test-isolated.js", "coverage": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm test", "lint": "eslint . --cache", + "lint-updated": "pipe-git-updated --ext=js -- eslint --cache", "docs": "node scripts/generate-readme.js", "integration-test-run-package": "jest --maxWorkers=5 integration-package", "integration-test-run-basic": "jest --maxWorkers=5 integration-basic", @@ -87,6 +88,7 @@ "eslint-plugin-import": "^1.13.0", "eslint-plugin-jsx-a11y": "^2.1.0", "eslint-plugin-react": "^6.1.1", + "git-list-updated": "^1.1.2", "jest-circus": "^24.8.0", "jest-cli": "^24.5.0", "markdown-link": "^0.1.1", From 02a494cf1de3cc7e8466e08c0bfabf3725880b72 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 31 May 2019 22:46:55 +0200 Subject: [PATCH 237/504] In CI run lint only on updated files --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f33159af3..80c68fc51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ jobs: - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= script: # Combine with '&&' to not continue on fail - - npm run lint && npm test && npm run integration-test-run-basic + - npm run lint-updated && npm test && npm run integration-test-run-basic - npm run integration-test-cleanup - name: "Unit Tests - Windows - Node.js v12" os: windows From cc34e916a6cd5802ffef427d731dea4f8da5594c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 11:44:06 +0200 Subject: [PATCH 238/504] Update package-lock.json --- package-lock.json | 56 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 59d2c7634..4dd516540 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3544,7 +3544,8 @@ "version": "2.1.1", "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -3568,13 +3569,15 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3591,19 +3594,22 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3734,7 +3740,8 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3748,6 +3755,7 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3764,6 +3772,7 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3772,13 +3781,15 @@ "version": "0.0.8", "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3799,6 +3810,7 @@ "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3887,7 +3899,8 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3901,6 +3914,7 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3996,7 +4010,8 @@ "version": "5.1.2", "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -4038,6 +4053,7 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4059,6 +4075,7 @@ "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4107,13 +4124,15 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true + "dev": true, + "optional": true } } }, @@ -8288,6 +8307,21 @@ "type-detect": "^4.0.0" }, "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", From a873a9150a6244ab35648f06d4b64dc818f55f09 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 12:18:38 +0200 Subject: [PATCH 239/504] Sync function stub may throw not reject --- lib/plugins/package/lib/zipService.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/package/lib/zipService.test.js b/lib/plugins/package/lib/zipService.test.js index ef8fc194e..2c2d09d6b 100644 --- a/lib/plugins/package/lib/zipService.test.js +++ b/lib/plugins/package/lib/zipService.test.js @@ -207,7 +207,7 @@ describe('zipService', () => { }); it('should return excludes and includes if an error is thrown in the global scope', () => { - globbySyncStub.rejects(); + globbySyncStub.throws(); return expect(packagePlugin.excludeDevDependencies(params)).to.be .fulfilled.then((updatedParams) => { From 621584bdedbe1d750f9b638ca72ff94cd1c75c99 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 12:31:34 +0200 Subject: [PATCH 240/504] Update Sinon to v3 --- package-lock.json | 379 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 372 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4dd516540..e20803c57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -535,6 +535,50 @@ } } }, + "@sinonjs/commons": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", + "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + }, + "dependencies": { + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + } + } + }, + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", + "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/babel__core": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.1.tgz", @@ -912,6 +956,12 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -1418,6 +1468,32 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "build": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/build/-/build-0.1.4.tgz", + "integrity": "sha1-cH/gJv/O3crL/c3zVur9pk8VEEY=", + "dev": true, + "requires": { + "cssmin": "0.3.x", + "jsmin": "1.x", + "jxLoader": "*", + "moo-server": "*", + "promised-io": "*", + "timespan": "2.x", + "uglify-js": "1.x", + "walker": "1.x", + "winston": "*", + "wrench": "1.3.x" + }, + "dependencies": { + "uglify-js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.3.5.tgz", + "integrity": "sha1-S1v/+Rhu/7qoiOTJ6UvZ/EyUkp0=", + "dev": true + } + } + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -1801,6 +1877,16 @@ "object-visit": "^1.0.0" } }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1814,6 +1900,38 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=", + "dev": true + }, + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", + "dev": true + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "dev": true, + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2037,6 +2155,12 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, + "cssmin": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/cssmin/-/cssmin-0.3.2.tgz", + "integrity": "sha1-3c5MVHtRCuDVlKjx+/iq+OLFwA0=", + "dev": true + }, "cssom": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", @@ -2293,6 +2417,17 @@ "integrity": "sha1-bfwP+dAQAKLt8oZTccrDFulJd68=", "dev": true }, + "diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "dev": true, + "requires": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -2371,6 +2506,15 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "dev": true, + "requires": { + "env-variable": "0.0.x" + } + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -2392,6 +2536,12 @@ "once": "^1.4.0" } }, + "env-variable": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", + "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3212,6 +3362,12 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fast-safe-stringify": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", + "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==", + "dev": true + }, "fb-watchman": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", @@ -3229,6 +3385,12 @@ "pend": "~1.2.0" } }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==", + "dev": true + }, "figures": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", @@ -5751,6 +5913,12 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "jsmin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jsmin/-/jsmin-1.0.1.tgz", + "integrity": "sha1-570NzWSWw79IYyNb9GGj2YqjuYw=", + "dev": true + }, "json-cycle": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/json-cycle/-/json-cycle-1.3.0.tgz", @@ -5872,11 +6040,37 @@ "set-immediate-shim": "~1.0.1" } }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, "jwt-decode": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" }, + "jxLoader": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jxLoader/-/jxLoader-0.1.1.tgz", + "integrity": "sha1-ATTqUUTlM7WU/B/yX/GU4jXFPs0=", + "dev": true, + "requires": { + "js-yaml": "0.3.x", + "moo-server": "1.3.x", + "promised-io": "*", + "walker": "1.x" + }, + "dependencies": { + "js-yaml": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-0.3.7.tgz", + "integrity": "sha1-1znY7oZGHlSzVNan19HyrZoWf2I=", + "dev": true + } + } + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -5896,6 +6090,15 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "dev": true, + "requires": { + "colornames": "^1.1.1" + } + }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -6091,6 +6294,12 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "lodash.pad": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", @@ -6132,10 +6341,23 @@ "chalk": "^2.0.1" } }, + "logform": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "dev": true, + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + } + }, "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", "dev": true }, "loose-envify": { @@ -6642,6 +6864,12 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, + "moo-server": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz", + "integrity": "sha1-XceVaVZaENbv7VQ5SR5p0jkuWPE=", + "dev": true + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -6717,6 +6945,36 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nise": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", + "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "node-dir": { "version": "0.1.17", "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", @@ -7123,6 +7381,12 @@ "wrappy": "1" } }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=", + "dev": true + }, "onetime": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", @@ -7556,6 +7820,12 @@ "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=" }, + "promised-io": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/promised-io/-/promised-io-0.3.5.tgz", + "integrity": "sha1-StIXuzZYvKrplGsXqGaOzYUeE1Y=", + "dev": true + }, "prompts": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.4.tgz", @@ -8291,16 +8561,36 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, - "sinon": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", - "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", "dev": true, "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + } + } + }, + "sinon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-3.3.0.tgz", + "integrity": "sha512-/flfGfIxIRXSvZBHJzIf3iAyGYkmMQq6SQjA0cx9SOuVuq+4ZPPO4LJtH1Ce0Lznax1KSG1U6Dad85wIcSW19w==", + "dev": true, + "requires": { + "build": "^0.1.4", "diff": "^3.1.0", "formatio": "1.2.0", - "lolex": "^1.6.0", + "lodash.get": "^4.4.2", + "lolex": "^2.1.2", "native-promise-only": "^0.8.1", + "nise": "^1.0.1", "path-to-regexp": "^1.7.0", "samsam": "^1.1.3", "text-encoding": "0.6.4", @@ -9060,6 +9350,12 @@ "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", "dev": true }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "dev": true + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -9114,6 +9410,12 @@ "next-tick": "1" } }, + "timespan": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/timespan/-/timespan-2.3.0.tgz", + "integrity": "sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk=", + "dev": true + }, "tmp": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", @@ -9238,6 +9540,12 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -9680,6 +9988,55 @@ } } }, + "winston": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", + "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "dev": true, + "requires": { + "async": "^2.6.1", + "diagnostics": "^1.1.1", + "is-stream": "^1.1.0", + "logform": "^2.1.1", + "one-time": "0.0.4", + "readable-stream": "^3.1.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.3.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" + } + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "winston-transport": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", + "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", + "dev": true, + "requires": { + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" + } + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -9712,6 +10069,12 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "wrench": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.3.9.tgz", + "integrity": "sha1-bxPsNRRTF+spLKX2UxORskQRFBE=", + "dev": true + }, "write": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", diff --git a/package.json b/package.json index 05034b436..a9e6279e7 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "parse-github-url": "^1.0.1", "process-utils": "^2.3.1", "proxyquire": "^1.7.10", - "sinon": "^2.4.1", + "sinon": "^3.3.0", "sinon-chai": "^2.9.0", "strip-ansi": "^5.2.0" }, From 89472d2b552da22d14f0c12ca13514d1d39bb943 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 12:41:03 +0200 Subject: [PATCH 241/504] Update to new version of Sinon --- lib/plugins/aws/provider/awsProvider.test.js | 2 +- lib/plugins/plugin/install/install.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 70019e990..c89524c42 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -244,7 +244,7 @@ describe('AwsProvider', () => { describe('#request()', () => { beforeEach(() => { const originalSetTimeout = setTimeout; - sinon.stub(global, 'setTimeout', (cb, timeout) => + sinon.stub(global, 'setTimeout').callsFake((cb, timeout) => originalSetTimeout(cb, Math.min(timeout || 0, 10))); }); diff --git a/lib/plugins/plugin/install/install.test.js b/lib/plugins/plugin/install/install.test.js index 2999ded74..9b5a59c17 100644 --- a/lib/plugins/plugin/install/install.test.js +++ b/lib/plugins/plugin/install/install.test.js @@ -266,7 +266,7 @@ describe('PluginInstall', () => { pluginInstall.serverless.config.servicePath = servicePath; fse.ensureDirSync(servicePath); packageJsonFilePath = path.join(servicePath, 'package.json'); - npmInstallStub = sinon.stub(childProcess, 'execAsync', () => { + npmInstallStub = sinon.stub(childProcess, 'execAsync').callsFake(() => { const packageJson = serverless.utils.readFileSync(packageJsonFilePath, 'utf8'); packageJson.devDependencies = { From b48b094cc3d6461d10549f26aa1d04540f950509 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 12:42:16 +0200 Subject: [PATCH 242/504] Upgrade Sinon to v4 --- package-lock.json | 337 +++------------------------------------------- package.json | 2 +- 2 files changed, 23 insertions(+), 316 deletions(-) diff --git a/package-lock.json b/package-lock.json index e20803c57..317647d86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -553,13 +553,12 @@ } }, "@sinonjs/formatio": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", - "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", "dev": true, "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "samsam": "1.3.0" } }, "@sinonjs/samsam": { @@ -1468,32 +1467,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, - "build": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/build/-/build-0.1.4.tgz", - "integrity": "sha1-cH/gJv/O3crL/c3zVur9pk8VEEY=", - "dev": true, - "requires": { - "cssmin": "0.3.x", - "jsmin": "1.x", - "jxLoader": "*", - "moo-server": "*", - "promised-io": "*", - "timespan": "2.x", - "uglify-js": "1.x", - "walker": "1.x", - "winston": "*", - "wrench": "1.3.x" - }, - "dependencies": { - "uglify-js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.3.5.tgz", - "integrity": "sha1-S1v/+Rhu/7qoiOTJ6UvZ/EyUkp0=", - "dev": true - } - } - }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -1877,16 +1850,6 @@ "object-visit": "^1.0.0" } }, - "color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", - "dev": true, - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1900,38 +1863,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "dev": true, - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "colornames": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", - "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=", - "dev": true - }, - "colors": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", - "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", - "dev": true - }, - "colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", - "dev": true, - "requires": { - "color": "3.0.x", - "text-hex": "1.0.x" - } - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2155,12 +2086,6 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, - "cssmin": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/cssmin/-/cssmin-0.3.2.tgz", - "integrity": "sha1-3c5MVHtRCuDVlKjx+/iq+OLFwA0=", - "dev": true - }, "cssom": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", @@ -2417,17 +2342,6 @@ "integrity": "sha1-bfwP+dAQAKLt8oZTccrDFulJd68=", "dev": true }, - "diagnostics": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", - "dev": true, - "requires": { - "colorspace": "1.1.x", - "enabled": "1.0.x", - "kuler": "1.0.x" - } - }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -2506,15 +2420,6 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "dev": true, - "requires": { - "env-variable": "0.0.x" - } - }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -2536,12 +2441,6 @@ "once": "^1.4.0" } }, - "env-variable": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", - "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==", - "dev": true - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3362,12 +3261,6 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, - "fast-safe-stringify": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", - "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==", - "dev": true - }, "fb-watchman": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", @@ -3385,12 +3278,6 @@ "pend": "~1.2.0" } }, - "fecha": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", - "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==", - "dev": true - }, "figures": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", @@ -3630,15 +3517,6 @@ "mime-types": "^2.1.12" } }, - "formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", - "dev": true, - "requires": { - "samsam": "1.x" - } - }, "formidable": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", @@ -5913,12 +5791,6 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, - "jsmin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jsmin/-/jsmin-1.0.1.tgz", - "integrity": "sha1-570NzWSWw79IYyNb9GGj2YqjuYw=", - "dev": true - }, "json-cycle": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/json-cycle/-/json-cycle-1.3.0.tgz", @@ -6051,26 +5923,6 @@ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" }, - "jxLoader": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jxLoader/-/jxLoader-0.1.1.tgz", - "integrity": "sha1-ATTqUUTlM7WU/B/yX/GU4jXFPs0=", - "dev": true, - "requires": { - "js-yaml": "0.3.x", - "moo-server": "1.3.x", - "promised-io": "*", - "walker": "1.x" - }, - "dependencies": { - "js-yaml": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-0.3.7.tgz", - "integrity": "sha1-1znY7oZGHlSzVNan19HyrZoWf2I=", - "dev": true - } - } - }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -6090,15 +5942,6 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, - "kuler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "dev": true, - "requires": { - "colornames": "^1.1.1" - } - }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -6341,19 +6184,6 @@ "chalk": "^2.0.1" } }, - "logform": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", - "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", - "dev": true, - "requires": { - "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", - "fecha": "^2.3.3", - "ms": "^2.1.1", - "triple-beam": "^1.3.0" - } - }, "lolex": { "version": "2.7.5", "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", @@ -6864,12 +6694,6 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, - "moo-server": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz", - "integrity": "sha1-XceVaVZaENbv7VQ5SR5p0jkuWPE=", - "dev": true - }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -6958,6 +6782,16 @@ "path-to-regexp": "^1.7.0" }, "dependencies": { + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -7381,12 +7215,6 @@ "wrappy": "1" } }, - "one-time": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", - "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=", - "dev": true - }, "onetime": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", @@ -7820,12 +7648,6 @@ "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=" }, - "promised-io": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/promised-io/-/promised-io-0.3.5.tgz", - "integrity": "sha1-StIXuzZYvKrplGsXqGaOzYUeE1Y=", - "dev": true - }, "prompts": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.4.tgz", @@ -8561,57 +8383,21 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dev": true, - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - } - } - }, "sinon": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-3.3.0.tgz", - "integrity": "sha512-/flfGfIxIRXSvZBHJzIf3iAyGYkmMQq6SQjA0cx9SOuVuq+4ZPPO4LJtH1Ce0Lznax1KSG1U6Dad85wIcSW19w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", + "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", "dev": true, "requires": { - "build": "^0.1.4", + "@sinonjs/formatio": "^2.0.0", "diff": "^3.1.0", - "formatio": "1.2.0", "lodash.get": "^4.4.2", - "lolex": "^2.1.2", - "native-promise-only": "^0.8.1", - "nise": "^1.0.1", - "path-to-regexp": "^1.7.0", - "samsam": "^1.1.3", - "text-encoding": "0.6.4", - "type-detect": "^4.0.0" + "lolex": "^2.2.0", + "nise": "^1.2.0", + "supports-color": "^5.1.0", + "type-detect": "^4.0.5" }, "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "dev": true, - "requires": { - "isarray": "0.0.1" - } - }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -9344,18 +9130,6 @@ "require-main-filename": "^2.0.0" } }, - "text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", - "dev": true - }, - "text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", - "dev": true - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -9410,12 +9184,6 @@ "next-tick": "1" } }, - "timespan": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/timespan/-/timespan-2.3.0.tgz", - "integrity": "sha1-SQLOBAvRPYRcj1myfp1ZutbzmSk=", - "dev": true - }, "tmp": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", @@ -9540,12 +9308,6 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, - "triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==", - "dev": true - }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -9988,55 +9750,6 @@ } } }, - "winston": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", - "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", - "dev": true, - "requires": { - "async": "^2.6.1", - "diagnostics": "^1.1.1", - "is-stream": "^1.1.0", - "logform": "^2.1.1", - "one-time": "0.0.4", - "readable-stream": "^3.1.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.3.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" - } - }, - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "winston-transport": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", - "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", - "dev": true, - "requires": { - "readable-stream": "^2.3.6", - "triple-beam": "^1.2.0" - } - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -10069,12 +9782,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "wrench": { - "version": "1.3.9", - "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.3.9.tgz", - "integrity": "sha1-bxPsNRRTF+spLKX2UxORskQRFBE=", - "dev": true - }, "write": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", diff --git a/package.json b/package.json index a9e6279e7..f7c1fe750 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "parse-github-url": "^1.0.1", "process-utils": "^2.3.1", "proxyquire": "^1.7.10", - "sinon": "^3.3.0", + "sinon": "^4.5.0", "sinon-chai": "^2.9.0", "strip-ansi": "^5.2.0" }, From 575957af5d458404350267c8c9b8537ab5d5a4e7 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 12:44:56 +0200 Subject: [PATCH 243/504] Upgrade Sinon to v5 --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 317647d86..e441770d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8384,18 +8384,18 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "sinon": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", - "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-5.1.1.tgz", + "integrity": "sha512-h/3uHscbt5pQNxkf7Y/Lb9/OM44YNCicHakcq73ncbrIS8lXg+ZGOZbtuU+/km4YnyiCYfQQEwANaReJz7KDfw==", "dev": true, "requires": { "@sinonjs/formatio": "^2.0.0", - "diff": "^3.1.0", + "diff": "^3.5.0", "lodash.get": "^4.4.2", - "lolex": "^2.2.0", - "nise": "^1.2.0", - "supports-color": "^5.1.0", - "type-detect": "^4.0.5" + "lolex": "^2.4.2", + "nise": "^1.3.3", + "supports-color": "^5.4.0", + "type-detect": "^4.0.8" }, "dependencies": { "type-detect": { diff --git a/package.json b/package.json index f7c1fe750..ad5532f4c 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "parse-github-url": "^1.0.1", "process-utils": "^2.3.1", "proxyquire": "^1.7.10", - "sinon": "^4.5.0", + "sinon": "^5.1.1", "sinon-chai": "^2.9.0", "strip-ansi": "^5.2.0" }, From 17af9d74bab2fc73758e5976162b9acd8ccb8a15 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 12:51:31 +0200 Subject: [PATCH 244/504] Upgrade Sinon to v6 --- package-lock.json | 67 ++++++++++++++++++++++------------------------- package.json | 2 +- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index e441770d3..6331c7339 100644 --- a/package-lock.json +++ b/package-lock.json @@ -553,24 +553,33 @@ } }, "@sinonjs/formatio": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", - "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", "dev": true, "requires": { - "samsam": "1.3.0" + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + }, + "dependencies": { + "@sinonjs/samsam": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", + "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } + } } }, "@sinonjs/samsam": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", - "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.0.2", - "array-from": "^2.1.1", - "lodash": "^4.17.11" - } + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-2.1.3.tgz", + "integrity": "sha512-8zNeBkSKhU9a5cRNbpCKau2WWPfan+Q2zDlcXvXyhn9EsMqgYs4qzo0XHNVlXC6ABQL8fT6nV+zzo5RTHJzyXw==", + "dev": true }, "@sinonjs/text-encoding": { "version": "0.7.1", @@ -6782,16 +6791,6 @@ "path-to-regexp": "^1.7.0" }, "dependencies": { - "@sinonjs/formatio": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", - "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -8156,12 +8155,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", - "dev": true - }, "sane": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", @@ -8384,17 +8377,19 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "sinon": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-5.1.1.tgz", - "integrity": "sha512-h/3uHscbt5pQNxkf7Y/Lb9/OM44YNCicHakcq73ncbrIS8lXg+ZGOZbtuU+/km4YnyiCYfQQEwANaReJz7KDfw==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.3.5.tgz", + "integrity": "sha512-xgoZ2gKjyVRcF08RrIQc+srnSyY1JDJtxu3Nsz07j1ffjgXoY6uPLf/qja6nDBZgzYYEovVkFryw2+KiZz11xQ==", "dev": true, "requires": { - "@sinonjs/formatio": "^2.0.0", + "@sinonjs/commons": "^1.0.2", + "@sinonjs/formatio": "^3.0.0", + "@sinonjs/samsam": "^2.1.2", "diff": "^3.5.0", "lodash.get": "^4.4.2", - "lolex": "^2.4.2", - "nise": "^1.3.3", - "supports-color": "^5.4.0", + "lolex": "^2.7.5", + "nise": "^1.4.5", + "supports-color": "^5.5.0", "type-detect": "^4.0.8" }, "dependencies": { diff --git a/package.json b/package.json index ad5532f4c..db4eeecfb 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "parse-github-url": "^1.0.1", "process-utils": "^2.3.1", "proxyquire": "^1.7.10", - "sinon": "^5.1.1", + "sinon": "^6.3.5", "sinon-chai": "^2.9.0", "strip-ansi": "^5.2.0" }, From 0738f7b8f4d8a36a40c7ed34b390bb2bc06cb6d3 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Wed, 1 May 2019 14:29:55 +0200 Subject: [PATCH 245/504] Add Application Load Balancer event source --- docs/providers/aws/README.md | 1 + docs/providers/aws/events/alb.md | 47 +++++++++++ docs/providers/aws/events/alexa-skill.md | 2 +- docs/providers/aws/events/alexa-smart-home.md | 2 +- docs/providers/aws/events/cloudwatch-event.md | 2 +- docs/providers/aws/events/cloudwatch-log.md | 2 +- .../providers/aws/events/cognito-user-pool.md | 2 +- docs/providers/aws/events/iot.md | 2 +- docs/providers/aws/guide/serverless.yml.md | 5 ++ lib/plugins/Plugins.json | 1 + lib/plugins/aws/lib/naming.js | 11 +++ lib/plugins/aws/lib/naming.test.js | 21 +++++ .../aws/package/compile/events/alb/index.js | 41 +++++++++ .../package/compile/events/alb/index.test.js | 81 ++++++++++++++++++ .../compile/events/alb/lib/listeners.js | 52 ++++++++++++ .../compile/events/alb/lib/listeners.test.js | 83 +++++++++++++++++++ .../compile/events/alb/lib/permissions.js | 31 +++++++ .../events/alb/lib/permissions.test.js | 74 +++++++++++++++++ .../compile/events/alb/lib/targetGroups.js | 36 ++++++++ .../events/alb/lib/targetGroups.test.js | 80 ++++++++++++++++++ .../compile/events/alb/lib/validate.js | 31 +++++++ .../compile/events/alb/lib/validate.test.js | 67 +++++++++++++++ .../aws-clojure-gradle/serverless.yml | 5 ++ .../aws-clojurescript-gradle/serverless.yml | 5 ++ .../templates/aws-csharp/serverless.yml | 5 ++ .../templates/aws-fsharp/serverless.yml | 5 ++ .../templates/aws-go-dep/serverless.yml | 5 ++ .../templates/aws-go-mod/serverless.yml | 5 ++ .../create/templates/aws-go/serverless.yml | 5 ++ .../aws-groovy-gradle/serverless.yml | 5 ++ .../templates/aws-java-gradle/serverless.yml | 5 ++ .../templates/aws-java-maven/serverless.yml | 5 ++ .../aws-kotlin-jvm-gradle/serverless.yml | 5 ++ .../aws-kotlin-jvm-maven/serverless.yml | 5 ++ .../aws-kotlin-nodejs-gradle/serverless.yml | 5 ++ .../templates/aws-nodejs/serverless.yml | 5 ++ .../templates/aws-provided/serverless.yml | 5 ++ .../templates/aws-python/serverless.yml | 5 ++ .../templates/aws-python3/serverless.yml | 5 ++ .../create/templates/aws-ruby/serverless.yml | 5 ++ .../templates/aws-scala-sbt/serverless.yml | 5 ++ 41 files changed, 763 insertions(+), 6 deletions(-) create mode 100644 docs/providers/aws/events/alb.md create mode 100644 lib/plugins/aws/package/compile/events/alb/index.js create mode 100644 lib/plugins/aws/package/compile/events/alb/index.test.js create mode 100644 lib/plugins/aws/package/compile/events/alb/lib/listeners.js create mode 100644 lib/plugins/aws/package/compile/events/alb/lib/listeners.test.js create mode 100644 lib/plugins/aws/package/compile/events/alb/lib/permissions.js create mode 100644 lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js create mode 100644 lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js create mode 100644 lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js create mode 100644 lib/plugins/aws/package/compile/events/alb/lib/validate.js create mode 100644 lib/plugins/aws/package/compile/events/alb/lib/validate.test.js diff --git a/docs/providers/aws/README.md b/docs/providers/aws/README.md index 77374cacb..a194ca32b 100644 --- a/docs/providers/aws/README.md +++ b/docs/providers/aws/README.md @@ -93,6 +93,7 @@ If you have any questions, [search the forums](https://forum.serverless.com?utm_
  • Schedule
  • SNS
  • SQS
  • +
  • ALB
  • Alexa Skill
  • Alexa Smart Home
  • IoT
  • diff --git a/docs/providers/aws/events/alb.md b/docs/providers/aws/events/alb.md new file mode 100644 index 000000000..001fa3d78 --- /dev/null +++ b/docs/providers/aws/events/alb.md @@ -0,0 +1,47 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/alb) + + +# Application Load Balancer + +[Application Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) can be used to re-route requests when certain traffic patterns are met. While traffic can be routed to services such as EC2 it [can also be routed to Lambda functions](https://aws.amazon.com/de/blogs/networking-and-content-delivery/lambda-functions-as-targets-for-application-load-balancers/) which can in turn be used process incoming requests. + +The Serverless Framework makes it possible to setup the connection between Application Load Balancers and Lamdba functions with the help of the `alb` event. + +## Event definition + +The following code will setup an alb using a HTTPS-based listener. + +```yml +functions: + albEventConsumer: + handler: handler.hello + events: + - alb: + loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 + certificateArn: arn:aws:iam::123456:server-certificate/ProdServerCert + name: alb-handler-https + listener: HTTPS:443 +``` + +The Application Load Balancer `arn` can also be referenced via `Ref`. HTTP listeners don't require the `certificateArn` property to be set. + +```yml +functions: + albEventConsumer: + handler: handler.hello + events: + - alb: + loadBalancerArn: + Ref: MyApplicationLoadBalancer + name: alb-handler-http + listener: HTTP:80 +``` diff --git a/docs/providers/aws/events/alexa-skill.md b/docs/providers/aws/events/alexa-skill.md index b7355ba09..e4e76acc4 100644 --- a/docs/providers/aws/events/alexa-skill.md +++ b/docs/providers/aws/events/alexa-skill.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/events/alexa-smart-home.md b/docs/providers/aws/events/alexa-smart-home.md index a451a4b2e..cff1a86c1 100644 --- a/docs/providers/aws/events/alexa-smart-home.md +++ b/docs/providers/aws/events/alexa-smart-home.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/events/cloudwatch-event.md b/docs/providers/aws/events/cloudwatch-event.md index fc9d32dfb..0236e6f1b 100644 --- a/docs/providers/aws/events/cloudwatch-event.md +++ b/docs/providers/aws/events/cloudwatch-event.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/events/cloudwatch-log.md b/docs/providers/aws/events/cloudwatch-log.md index 7942545f0..629dc5a35 100644 --- a/docs/providers/aws/events/cloudwatch-log.md +++ b/docs/providers/aws/events/cloudwatch-log.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/events/cognito-user-pool.md b/docs/providers/aws/events/cognito-user-pool.md index 0d2662848..a4751364e 100644 --- a/docs/providers/aws/events/cognito-user-pool.md +++ b/docs/providers/aws/events/cognito-user-pool.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/events/iot.md b/docs/providers/aws/events/iot.md index c927ff832..095b9c2a2 100644 --- a/docs/providers/aws/events/iot.md +++ b/docs/providers/aws/events/iot.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index ff129c57f..ffc517636 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -269,6 +269,11 @@ functions: - cognitoUserPool: pool: MyUserPool trigger: PreSignUp + - alb: + loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 + certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert # only required when HTTPS is used + name: alb-handler-https + listener: HTTPS:443 layers: hello: # A Lambda layer diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json index 1e63fd66d..253d0a750 100644 --- a/lib/plugins/Plugins.json +++ b/lib/plugins/Plugins.json @@ -38,6 +38,7 @@ "./aws/package/compile/events/websockets/index.js", "./aws/package/compile/events/sns/index.js", "./aws/package/compile/events/stream/index.js", + "./aws/package/compile/events/alb/index.js", "./aws/package/compile/events/alexaSkill/index.js", "./aws/package/compile/events/alexaSmartHome/index.js", "./aws/package/compile/events/iot/index.js", diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index 694fd1021..c99c5af61 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -369,6 +369,14 @@ module.exports = { }`; }, + // ALB + getAlbTargetGroupLogicalId(functionName) { + return `${this.getNormalizedFunctionName(functionName)}AlbTargetGroup`; + }, + getAlbListenerLogicalId(functionName, idx) { + return `${this.getNormalizedFunctionName(functionName)}AlbListener${idx}`; + }, + // Permissions getLambdaS3PermissionLogicalId(functionName, bucketName) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermission${this @@ -411,4 +419,7 @@ module.exports = { .getNormalizedFunctionName(functionName)}LambdaPermissionCognitoUserPool${ this.normalizeNameToAlphaNumericOnly(poolId)}TriggerSource${triggerSource}`; }, + getLambdaAlbPermissionLogicalId(functionName) { + return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlb`; + }, }; diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 1b10d0198..a96536417 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -674,6 +674,13 @@ describe('#naming()', () => { 'CustomMessage' )).to.equal('FunctionNameLambdaPermissionCognitoUserPoolPool1TriggerSourceCustomMessage'); }); + + describe('#getLambdaAlbPermissionLogicalId()', () => { + it('should normalize the function name', () => { + expect(sdk.naming.getLambdaAlbPermissionLogicalId('functionName')) + .to.equal('FunctionNameLambdaPermissionAlb'); + }); + }); }); describe('#getQueueLogicalId()', () => { @@ -682,4 +689,18 @@ describe('#naming()', () => { .to.equal('FunctionNameEventSourceMappingSQSMyQueue'); }); }); + + describe('#getAlbTargetGroupLogicalId()', () => { + it('should normalize the function name', () => { + expect(sdk.naming.getAlbTargetGroupLogicalId('functionName')) + .to.equal('FunctionNameAlbTargetGroup'); + }); + }); + + describe('#getAlbListenerLogicalId()', () => { + it('should normalize the function name and add an index', () => { + expect(sdk.naming.getAlbListenerLogicalId('functionName', 0)) + .to.equal('FunctionNameAlbListener0'); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/alb/index.js b/lib/plugins/aws/package/compile/events/alb/index.js new file mode 100644 index 000000000..00cc1c54a --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/index.js @@ -0,0 +1,41 @@ +'use strict'; + +const BbPromise = require('bluebird'); + +const validate = require('./lib/validate'); +const compileTargetGroups = require('./lib/targetGroups'); +const compileListeners = require('./lib/listeners'); +const compilePermissions = require('./lib/permissions'); + +class AwsCompileAlbEvents { + constructor(serverless, options) { + this.serverless = serverless; + this.options = options; + this.provider = this.serverless.getProvider('aws'); + + Object.assign( + this, + validate, + compileTargetGroups, + compileListeners, + compilePermissions + ); + + this.hooks = { + 'package:compileEvents': () => { + this.validated = this.validate(); + + if (this.validated.events.length === 0) { + return BbPromise.resolve(); + } + + return BbPromise.bind(this) + .then(this.compileTargetGroups) + .then(this.compileListeners) + .then(this.compilePermissions); + }, + }; + } +} + +module.exports = AwsCompileAlbEvents; diff --git a/lib/plugins/aws/package/compile/events/alb/index.test.js b/lib/plugins/aws/package/compile/events/alb/index.test.js new file mode 100644 index 000000000..ba1a8b912 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/index.test.js @@ -0,0 +1,81 @@ +'use strict'; + +const expect = require('chai').expect; +const sinon = require('sinon'); +const AwsProvider = require('../../../../provider/awsProvider'); +const AwsCompileAlbEvents = require('./index'); +const Serverless = require('../../../../../../Serverless'); + +describe('AwsCompileAlbEvents', () => { + let awsCompileAlbEvents; + + beforeEach(() => { + const serverless = new Serverless(); + const options = { + stage: 'dev', + region: 'us-east-1', + }; + serverless.setProvider('aws', new AwsProvider(serverless, options)); + awsCompileAlbEvents = new AwsCompileAlbEvents(serverless, options); + }); + + describe('#constructor()', () => { + let compileTargetGroupsStub; + let compileListenersStub; + let compilePermissionsStub; + + beforeEach(() => { + compileTargetGroupsStub = sinon + .stub(awsCompileAlbEvents, 'compileTargetGroups').resolves(); + compileListenersStub = sinon + .stub(awsCompileAlbEvents, 'compileListeners').resolves(); + compilePermissionsStub = sinon + .stub(awsCompileAlbEvents, 'compilePermissions').resolves(); + }); + + afterEach(() => { + awsCompileAlbEvents.compileTargetGroups.restore(); + awsCompileAlbEvents.compileListeners.restore(); + awsCompileAlbEvents.compilePermissions.restore(); + }); + + it('should have hooks', () => expect(awsCompileAlbEvents.hooks).to.be.not.empty); + + it('should set the provider variable to be an instanceof AwsProvider', () => + expect(awsCompileAlbEvents.provider).to.be.instanceof(AwsProvider)); + + describe('"package:compileEvents" promise chain', () => { + afterEach(() => { + awsCompileAlbEvents.validate.restore(); + }); + + it('should run the promise chain in order', () => { + const validateStub = sinon + .stub(awsCompileAlbEvents, 'validate').returns({ + events: [ + { + functionName: 'first', + listener: 'HTTPS:443', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + name: 'some-alb-event-1', + }, + ], + }); + + return awsCompileAlbEvents.hooks['package:compileEvents']().then(() => { + expect(validateStub.calledOnce).to.be.equal(true); + expect(compileTargetGroupsStub.calledAfter(validateStub)).to.be.equal(true); + expect(compileListenersStub.calledAfter(compileTargetGroupsStub)).to.be.equal(true); + expect(compilePermissionsStub.calledAfter(compileListenersStub)).to.be.equal(true); + }); + }); + }); + + it('should resolve if no functions are given', () => { + awsCompileAlbEvents.serverless.service.functions = {}; + + return awsCompileAlbEvents.hooks['package:compileEvents'](); + }); + }); +}); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/listeners.js b/lib/plugins/aws/package/compile/events/alb/lib/listeners.js new file mode 100644 index 000000000..a852af554 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/lib/listeners.js @@ -0,0 +1,52 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const _ = require('lodash'); + +module.exports = { + compileListeners() { + this.validated.events.forEach((event, idx) => { + const targetGroupLogicalId = this.provider.naming + .getAlbTargetGroupLogicalId(event.name); + const listenerLogicalId = this.provider.naming + .getAlbListenerLogicalId(event.functionName, idx); + + const listener = event.listener; + const Protocol = listener.split(':')[0].toUpperCase(); + const Port = listener.split(':')[1]; + const LoadBalancerArn = event.loadBalancerArn; + const CertificateArn = event.certificateArn || null; + + let Certificates = []; + if (CertificateArn) { + Certificates = [ + { + CertificateArn, + }, + ]; + } + + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { + [listenerLogicalId]: { + Type: 'AWS::ElasticLoadBalancingV2::Listener', + Properties: { + DefaultActions: [ + { + TargetGroupArn: { + Ref: targetGroupLogicalId, + }, + Type: 'forward', + }, + ], + LoadBalancerArn, + Certificates, + Protocol, + Port, + }, + }, + }); + }); + + return BbPromise.resolve(); + }, +}; diff --git a/lib/plugins/aws/package/compile/events/alb/lib/listeners.test.js b/lib/plugins/aws/package/compile/events/alb/lib/listeners.test.js new file mode 100644 index 000000000..f1796b7a6 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/lib/listeners.test.js @@ -0,0 +1,83 @@ +'use strict'; + +const expect = require('chai').expect; +const AwsCompileAlbEvents = require('../index'); +const Serverless = require('../../../../../../../Serverless'); +const AwsProvider = require('../../../../../provider/awsProvider'); + +describe('#compileListeners()', () => { + let awsCompileAlbEvents; + + beforeEach(() => { + const serverless = new Serverless(); + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.service.service = 'some-service'; + serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; + + awsCompileAlbEvents = new AwsCompileAlbEvents(serverless); + }); + + it('should create ELB listener resources', () => { + awsCompileAlbEvents.validated = { + events: [ + { + functionName: 'first', + listener: 'HTTPS:443', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + name: 'some-alb-event-1', + }, + { + functionName: 'second', + listener: 'HTTP:80', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + name: 'some-alb-event-2', + }, + ], + }; + + return awsCompileAlbEvents.compileListeners().then(() => { + const resources = awsCompileAlbEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources.FirstAlbListener0).to.deep.equal({ + Type: 'AWS::ElasticLoadBalancingV2::Listener', + Properties: { + Certificates: [ + { + CertificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + }, + ], + DefaultActions: [ + { + TargetGroupArn: { + Ref: 'SomeDashalbDasheventDash1AlbTargetGroup', + }, + Type: 'forward', + }, + ], + LoadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + Port: '443', + Protocol: 'HTTPS', + }, + }); + expect(resources.SecondAlbListener1).to.deep.equal({ + Type: 'AWS::ElasticLoadBalancingV2::Listener', + Properties: { + Certificates: [], + DefaultActions: [ + { + TargetGroupArn: { + Ref: 'SomeDashalbDasheventDash2AlbTargetGroup', + }, + Type: 'forward', + }, + ], + LoadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + Port: '80', + Protocol: 'HTTP', + }, + }); + }); + }); +}); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/permissions.js b/lib/plugins/aws/package/compile/events/alb/lib/permissions.js new file mode 100644 index 000000000..46b6c2426 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/lib/permissions.js @@ -0,0 +1,31 @@ +'use strict'; + +const _ = require('lodash'); +const BbPromise = require('bluebird'); + +module.exports = { + compilePermissions() { + this.validated.events.forEach((event) => { + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(event.functionName); + + const albPermissionLogicalId = this.provider.naming + .getLambdaAlbPermissionLogicalId(event.functionName); + + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { + [albPermissionLogicalId]: { + Type: 'AWS::Lambda::Permission', + Properties: { + FunctionName: { + 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], + }, + Action: 'lambda:InvokeFunction', + Principal: 'elasticloadbalancing.amazonaws.com', + }, + DependsOn: [lambdaLogicalId], + }, + }); + }); + + return BbPromise.resolve(); + }, +}; diff --git a/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js b/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js new file mode 100644 index 000000000..82f8d3692 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js @@ -0,0 +1,74 @@ +'use strict'; + +const expect = require('chai').expect; +const AwsCompileAlbEvents = require('../index'); +const Serverless = require('../../../../../../../Serverless'); +const AwsProvider = require('../../../../../provider/awsProvider'); + +describe('#compilePermissions()', () => { + let awsCompileAlbEvents; + + beforeEach(() => { + const serverless = new Serverless(); + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.service.service = 'some-service'; + serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; + + awsCompileAlbEvents = new AwsCompileAlbEvents(serverless); + }); + + it('should create Lambda permission resources', () => { + awsCompileAlbEvents.validated = { + events: [ + { + functionName: 'first', + listener: 'HTTPS:443', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + name: 'some-alb-event-1', + }, + { + functionName: 'second', + listener: 'HTTP:80', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + name: 'some-alb-event-2', + }, + ], + }; + + return awsCompileAlbEvents.compilePermissions().then(() => { + const resources = awsCompileAlbEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources.FirstLambdaPermissionAlb).to.deep.equal({ + Type: 'AWS::Lambda::Permission', + Properties: { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'FirstLambdaFunction', + 'Arn', + ], + }, + Principal: 'elasticloadbalancing.amazonaws.com', + }, + DependsOn: ['FirstLambdaFunction'], + }); + expect(resources.SecondLambdaPermissionAlb).to.deep.equal({ + Type: 'AWS::Lambda::Permission', + Properties: { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'SecondLambdaFunction', + 'Arn', + ], + }, + Principal: 'elasticloadbalancing.amazonaws.com', + }, + DependsOn: ['SecondLambdaFunction'], + }); + }); + }); +}); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js new file mode 100644 index 000000000..3f85da001 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js @@ -0,0 +1,36 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const _ = require('lodash'); + +module.exports = { + compileTargetGroups() { + this.validated.events.forEach((event) => { + const targetGroupLogicalId = this.provider.naming + .getAlbTargetGroupLogicalId(event.name); + const lambdaLogicalId = this.provider.naming + .getLambdaLogicalId(event.functionName); + const lambdaPermissionLogicalId = this.provider.naming + .getLambdaAlbPermissionLogicalId(event.functionName); + + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { + [targetGroupLogicalId]: { + Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', + Properties: { + TargetType: 'lambda', + Targets: [ + { + Id: { + 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], + }, + }, + ], + }, + DependsOn: [lambdaPermissionLogicalId], + }, + }); + }); + + return BbPromise.resolve(); + }, +}; diff --git a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js new file mode 100644 index 000000000..dd9b28237 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js @@ -0,0 +1,80 @@ +'use strict'; + +const expect = require('chai').expect; +const AwsCompileAlbEvents = require('../index'); +const Serverless = require('../../../../../../../Serverless'); +const AwsProvider = require('../../../../../provider/awsProvider'); + +describe('#compileTargetGroups()', () => { + let awsCompileAlbEvents; + + beforeEach(() => { + const serverless = new Serverless(); + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.service.service = 'some-service'; + serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; + + awsCompileAlbEvents = new AwsCompileAlbEvents(serverless); + }); + + it('should create ELB target group resources', () => { + awsCompileAlbEvents.validated = { + events: [ + { + functionName: 'first', + listener: 'HTTPS:443', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + name: 'some-alb-event-1', + }, + { + functionName: 'second', + listener: 'HTTP:80', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + name: 'some-alb-event-2', + }, + ], + }; + + return awsCompileAlbEvents.compileTargetGroups().then(() => { + const resources = awsCompileAlbEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources.SomeDashalbDasheventDash1AlbTargetGroup).to.deep.equal({ + Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', + Properties: { + TargetType: 'lambda', + Targets: [ + { + Id: { + 'Fn::GetAtt': [ + 'FirstLambdaFunction', + 'Arn', + ], + }, + }, + ], + }, + DependsOn: ['FirstLambdaPermissionAlb'], + }); + expect(resources.SomeDashalbDasheventDash2AlbTargetGroup).to.deep.equal({ + Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', + Properties: { + TargetType: 'lambda', + Targets: [ + { + Id: { + 'Fn::GetAtt': [ + 'SecondLambdaFunction', + 'Arn', + ], + }, + }, + ], + }, + DependsOn: ['SecondLambdaPermissionAlb'], + }); + }); + }); +}); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/validate.js b/lib/plugins/aws/package/compile/events/alb/lib/validate.js new file mode 100644 index 000000000..e3102edb1 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/lib/validate.js @@ -0,0 +1,31 @@ +'use strict'; + +const _ = require('lodash'); + +module.exports = { + validate() { + const events = []; + + _.forEach(this.serverless.service.functions, (functionObject, functionName) => { + _.forEach(functionObject.events, (event) => { + if (_.has(event, 'alb')) { + if (_.isObject(event.alb)) { + const albObj = { + name: event.alb.name, + loadBalancerArn: event.alb.loadBalancerArn, + certificateArn: event.alb.certificateArn, + listener: event.alb.listener, + // the following is data which is not defined on the event-level + functionName, + }; + events.push(albObj); + } + } + }); + }); + + return { + events, + }; + }, +}; diff --git a/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js b/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js new file mode 100644 index 000000000..8be6969f5 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js @@ -0,0 +1,67 @@ +'use strict'; + +const expect = require('chai').expect; +const AwsCompileAlbEvents = require('../index'); +const Serverless = require('../../../../../../../Serverless'); +const AwsProvider = require('../../../../../provider/awsProvider'); + +describe('#validate()', () => { + let awsCompileAlbEvents; + + beforeEach(() => { + const serverless = new Serverless(); + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.service.service = 'some-service'; + serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; + + awsCompileAlbEvents = new AwsCompileAlbEvents(serverless); + }); + + it('should detect alb event definitions', () => { + awsCompileAlbEvents.serverless.service.functions = { + first: { + events: [ + { + alb: { + listener: 'HTTPS:443', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + name: 'some-alb-event-1', + }, + }, + ], + }, + second: { + events: [ + { + alb: { + listener: 'HTTP:80', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + name: 'some-alb-event-2', + }, + }, + ], + }, + }; + + const validated = awsCompileAlbEvents.validate(); + + expect(validated.events).to.deep.equal([ + { + functionName: 'first', + listener: 'HTTPS:443', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + name: 'some-alb-event-1', + }, + { + functionName: 'second', + listener: 'HTTP:80', + loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line + certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', + name: 'some-alb-event-2', + }, + ]); + }); +}); diff --git a/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml b/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml index b167892d0..3dd357308 100644 --- a/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml @@ -90,6 +90,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml index a297fee2a..993def7e6 100644 --- a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml @@ -99,6 +99,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-csharp/serverless.yml b/lib/plugins/create/templates/aws-csharp/serverless.yml index 81c9930c5..48ce3064f 100644 --- a/lib/plugins/create/templates/aws-csharp/serverless.yml +++ b/lib/plugins/create/templates/aws-csharp/serverless.yml @@ -90,6 +90,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-fsharp/serverless.yml b/lib/plugins/create/templates/aws-fsharp/serverless.yml index 2c304a440..4d2757657 100644 --- a/lib/plugins/create/templates/aws-fsharp/serverless.yml +++ b/lib/plugins/create/templates/aws-fsharp/serverless.yml @@ -87,6 +87,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-go-dep/serverless.yml b/lib/plugins/create/templates/aws-go-dep/serverless.yml index b36eca0f8..b8ba76d50 100644 --- a/lib/plugins/create/templates/aws-go-dep/serverless.yml +++ b/lib/plugins/create/templates/aws-go-dep/serverless.yml @@ -98,6 +98,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-go-mod/serverless.yml b/lib/plugins/create/templates/aws-go-mod/serverless.yml index 7cc8abe00..65082379d 100644 --- a/lib/plugins/create/templates/aws-go-mod/serverless.yml +++ b/lib/plugins/create/templates/aws-go-mod/serverless.yml @@ -98,6 +98,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-go/serverless.yml b/lib/plugins/create/templates/aws-go/serverless.yml index fe0c0492a..9ee090fdb 100644 --- a/lib/plugins/create/templates/aws-go/serverless.yml +++ b/lib/plugins/create/templates/aws-go/serverless.yml @@ -98,6 +98,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml index 27bbd89ba..105257433 100644 --- a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml @@ -84,6 +84,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-java-gradle/serverless.yml b/lib/plugins/create/templates/aws-java-gradle/serverless.yml index faff3364a..4d9e011c2 100644 --- a/lib/plugins/create/templates/aws-java-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-java-gradle/serverless.yml @@ -84,6 +84,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.yml b/lib/plugins/create/templates/aws-java-maven/serverless.yml index 807128ade..c39589916 100644 --- a/lib/plugins/create/templates/aws-java-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-java-maven/serverless.yml @@ -84,6 +84,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml index 92bb8538f..973f28286 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml @@ -84,6 +84,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml index 3702074f7..12e4b9fd4 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml @@ -84,6 +84,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml index a2c045481..e59789ad2 100644 --- a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml @@ -80,6 +80,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index cb2f09e97..15f12f583 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -89,6 +89,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-provided/serverless.yml b/lib/plugins/create/templates/aws-provided/serverless.yml index 90922dea2..757c86e78 100644 --- a/lib/plugins/create/templates/aws-provided/serverless.yml +++ b/lib/plugins/create/templates/aws-provided/serverless.yml @@ -89,6 +89,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-python/serverless.yml b/lib/plugins/create/templates/aws-python/serverless.yml index d77ff1625..7c9111f3f 100644 --- a/lib/plugins/create/templates/aws-python/serverless.yml +++ b/lib/plugins/create/templates/aws-python/serverless.yml @@ -89,6 +89,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-python3/serverless.yml b/lib/plugins/create/templates/aws-python3/serverless.yml index da05941bb..f2e656d84 100644 --- a/lib/plugins/create/templates/aws-python3/serverless.yml +++ b/lib/plugins/create/templates/aws-python3/serverless.yml @@ -89,6 +89,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-ruby/serverless.yml b/lib/plugins/create/templates/aws-ruby/serverless.yml index de18084b9..079186796 100644 --- a/lib/plugins/create/templates/aws-ruby/serverless.yml +++ b/lib/plugins/create/templates/aws-ruby/serverless.yml @@ -89,6 +89,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml index 1b4684faa..d737b96d6 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml +++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml @@ -86,6 +86,11 @@ functions: # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp +# - alb: +# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 +# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert +# name: alb-handler-https +# listener: HTTPS:443 # Define function environment variables here # environment: From ac2a6682f5cfa6253ae02b5a0ad6ba1f894018e5 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 13:02:09 +0200 Subject: [PATCH 246/504] Upgrade Sinon to v7 --- package-lock.json | 72 ++++++++++++++++++----------------------------- package.json | 2 +- 2 files changed, 28 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51e66bee1..a6f34d441 100644 --- a/package-lock.json +++ b/package-lock.json @@ -560,26 +560,18 @@ "requires": { "@sinonjs/commons": "^1", "@sinonjs/samsam": "^3.1.0" - }, - "dependencies": { - "@sinonjs/samsam": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", - "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.0.2", - "array-from": "^2.1.1", - "lodash": "^4.17.11" - } - } } }, "@sinonjs/samsam": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-2.1.3.tgz", - "integrity": "sha512-8zNeBkSKhU9a5cRNbpCKau2WWPfan+Q2zDlcXvXyhn9EsMqgYs4qzo0XHNVlXC6ABQL8fT6nV+zzo5RTHJzyXw==", - "dev": true + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", + "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } }, "@sinonjs/text-encoding": { "version": "0.7.1", @@ -6165,12 +6157,6 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, "lodash.pad": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", @@ -6213,9 +6199,9 @@ } }, "lolex": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", - "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.0.1.tgz", + "integrity": "sha512-UHuOBZ5jjsKuzbB/gRNNW8Vg8f00Emgskdq2kvZxgBJCS0aqquAuXai/SkWORlKeZEiNQWZjFZOqIUcH9LqKCw==", "dev": true }, "loose-envify": { @@ -6816,6 +6802,12 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + }, "path-to-regexp": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", @@ -8396,28 +8388,18 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "sinon": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.3.5.tgz", - "integrity": "sha512-xgoZ2gKjyVRcF08RrIQc+srnSyY1JDJtxu3Nsz07j1ffjgXoY6uPLf/qja6nDBZgzYYEovVkFryw2+KiZz11xQ==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", + "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", "dev": true, "requires": { - "@sinonjs/commons": "^1.0.2", - "@sinonjs/formatio": "^3.0.0", - "@sinonjs/samsam": "^2.1.2", + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.1", "diff": "^3.5.0", - "lodash.get": "^4.4.2", - "lolex": "^2.7.5", - "nise": "^1.4.5", - "supports-color": "^5.5.0", - "type-detect": "^4.0.8" - }, - "dependencies": { - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - } + "lolex": "^4.0.1", + "nise": "^1.4.10", + "supports-color": "^5.5.0" } }, "sinon-chai": { diff --git a/package.json b/package.json index 3222d25be..1449ed88f 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "parse-github-url": "^1.0.1", "process-utils": "^2.3.1", "proxyquire": "^1.7.10", - "sinon": "^6.3.5", + "sinon": "^7.3.2", "sinon-chai": "^2.9.0", "strip-ansi": "^5.2.0" }, From 492bae3df00a8fa126e105545d3f311ef2c3abf4 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 13:02:45 +0200 Subject: [PATCH 247/504] Improve stages naming --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 80c68fc51..dadd0b98f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,10 +9,10 @@ branches: env: SLS_IGNORE_WARNING=* # Default env stages: - - name: test - - name: integration-test + - name: Test + - name: Integration Test if: branch = master AND type = push - - name: deploy + - name: Deploy if: tag =~ ^v\d+\.\d+\.\d+$ jobs: @@ -56,7 +56,7 @@ jobs: - name: "Unit Tests - Linux - Node.js v6" node_js: 6 - - stage: integration-test + - stage: Integration Test name: "Integration Tests - Linux - Node.js v12" node_js: 12 env: @@ -69,7 +69,7 @@ jobs: - npm run integration-test-run-all - npm run integration-test-cleanup - - stage: deploy + - stage: Deploy script: skip deploy: provider: npm From 0ddfec479dae5b9bc00466f7a6e2af345bd6a949 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 13:37:57 +0200 Subject: [PATCH 248/504] Update up to sinon upgrade --- lib/classes/Error.test.js | 4 +-- .../awsConfigCredentials.test.js | 7 ++-- .../aws/deploy/lib/checkForChanges.test.js | 33 +++++++++---------- .../aws/deploy/lib/createStack.test.js | 8 ++--- 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/lib/classes/Error.test.js b/lib/classes/Error.test.js index a9eb3bcdd..1d6dc113b 100644 --- a/lib/classes/Error.test.js +++ b/lib/classes/Error.test.js @@ -1,7 +1,7 @@ 'use strict'; const expect = require('chai').expect; -const sinon = require('sinon'); +const sandbox = require('sinon'); const overrideEnv = require('process-utils/override-env'); const errorReporter = require('../utils/sentry').raven; const ServerlessError = require('./Error').ServerlessError; @@ -50,13 +50,11 @@ describe('ServerlessError', () => { }); describe('Error', () => { - let sandbox; let consoleLogSpy; let processExitStub; let captureExceptionStub; beforeEach(() => { - sandbox = sinon.sandbox.create(); consoleLogSpy = sandbox.spy(console, 'log'); // the following is used so that the process exiting never interrupts tests processExitStub = sandbox.stub(process, 'exit').withArgs(1).returns(); diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index ec282dc08..05dd68021 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -1,7 +1,7 @@ 'use strict'; const expect = require('chai').expect; -const sinon = require('sinon'); +const sandbox = require('sinon'); const constants = require('constants'); const fs = require('fs'); const fse = require('fs-extra'); @@ -16,12 +16,9 @@ describe('AwsConfigCredentials', () => { let credentialsFileContent; let credentialsFilePath; let serverless; - let sandbox; let tmpDirPath; beforeEach(() => { - sandbox = sinon.sandbox.create(); - tmpDirPath = getTmpDirPath(); credentialsFilePath = path.join(tmpDirPath, '.aws', 'credentials'); credentialsFileContent = '[my-profile]\n'; @@ -78,7 +75,7 @@ describe('AwsConfigCredentials', () => { }); it('should run promise chain in order for "config:credentials:config" hook', () => { - const awsConfigCredentialsStub = sinon + const awsConfigCredentialsStub = sandbox .stub(awsConfigCredentials, 'configureCredentials').resolves(); return awsConfigCredentials.hooks['config:credentials:config']().then(() => { diff --git a/lib/plugins/aws/deploy/lib/checkForChanges.test.js b/lib/plugins/aws/deploy/lib/checkForChanges.test.js index 72df78397..7c03a593b 100644 --- a/lib/plugins/aws/deploy/lib/checkForChanges.test.js +++ b/lib/plugins/aws/deploy/lib/checkForChanges.test.js @@ -5,7 +5,7 @@ const fs = require('fs'); const path = require('path'); const globby = require('globby'); -const sinon = require('sinon'); +const sandbox = require('sinon'); const chai = require('chai'); const BbPromise = require('bluebird'); const proxyquire = require('proxyquire'); @@ -42,11 +42,11 @@ describe('checkForChanges', () => { foo: 'bar', }; s3Key = `serverless/${serverless.service.service}/${provider.getStage()}`; - awsDeploy.serverless.cli = { log: sinon.spy() }; + awsDeploy.serverless.cli = { log: sandbox.spy() }; cryptoStub = { createHash: function () { return this; }, // eslint-disable-line update: function () { return this; }, // eslint-disable-line - digest: sinon.stub(), + digest: sandbox.stub(), }; const checkForChanges = proxyquire('./checkForChanges.js', { crypto: cryptoStub, @@ -64,13 +64,13 @@ describe('checkForChanges', () => { let checkLogGroupSubscriptionFilterResourceLimitExceededStub; beforeEach(() => { - getMostRecentObjectsStub = sinon + getMostRecentObjectsStub = sandbox .stub(awsDeploy, 'getMostRecentObjects').resolves(); - getObjectMetadataStub = sinon + getObjectMetadataStub = sandbox .stub(awsDeploy, 'getObjectMetadata').resolves(); - checkIfDeploymentIsNecessaryStub = sinon + checkIfDeploymentIsNecessaryStub = sandbox .stub(awsDeploy, 'checkIfDeploymentIsNecessary').resolves(); - checkLogGroupSubscriptionFilterResourceLimitExceededStub = sinon + checkLogGroupSubscriptionFilterResourceLimitExceededStub = sandbox .stub(awsDeploy, 'checkLogGroupSubscriptionFilterResourceLimitExceeded').resolves(); }); @@ -111,7 +111,7 @@ describe('checkForChanges', () => { let listObjectsV2Stub; beforeEach(() => { - listObjectsV2Stub = sinon + listObjectsV2Stub = sandbox .stub(awsDeploy.provider, 'request'); }); @@ -205,7 +205,7 @@ describe('checkForChanges', () => { let headObjectStub; beforeEach(() => { - headObjectStub = sinon + headObjectStub = sandbox .stub(awsDeploy.provider, 'request').resolves(); }); @@ -281,12 +281,12 @@ describe('checkForChanges', () => { let readFileStub; beforeEach(() => { - normalizeCloudFormationTemplateStub = sinon + normalizeCloudFormationTemplateStub = sandbox .stub(normalizeFiles, 'normalizeCloudFormationTemplate') .returns(); - globbySyncStub = sinon + globbySyncStub = sandbox .stub(globby, 'sync'); - readFileStub = sinon + readFileStub = sandbox .stub(fs, 'readFile') .yields(null, undefined); }); @@ -508,18 +508,15 @@ describe('checkForChanges', () => { const serviceName = 'my-service'; const region = 'us-east-1'; let describeSubscriptionFiltersResponse = {}; - let sandbox; beforeEach(() => { - sandbox = sinon.sandbox.create(); - CloudWatchLogsStub = class { constructor() { - this.deleteSubscriptionFilter = deleteSubscriptionFilterStub = sinon.spy(() => ({ + this.deleteSubscriptionFilter = deleteSubscriptionFilterStub = sandbox.spy(() => ({ promise: () => BbPromise.resolve(), })); - this.describeSubscriptionFilters = sinon.spy(() => ({ + this.describeSubscriptionFilters = sandbox.spy(() => ({ promise: () => BbPromise.resolve(describeSubscriptionFiltersResponse), })); } @@ -551,7 +548,7 @@ describe('checkForChanges', () => { resolve(); })); - const spy = sinon.spy(awsDeploy, 'checkLogGroupSubscriptionFilterResourceLimitExceeded'); + const spy = sandbox.spy(awsDeploy, 'checkLogGroupSubscriptionFilterResourceLimitExceeded'); return awsDeploy.checkForChanges().then(() => expect(spy).to.not.have.been.called); }); diff --git a/lib/plugins/aws/deploy/lib/createStack.test.js b/lib/plugins/aws/deploy/lib/createStack.test.js index 1f9d97262..b9e33cb39 100644 --- a/lib/plugins/aws/deploy/lib/createStack.test.js +++ b/lib/plugins/aws/deploy/lib/createStack.test.js @@ -1,7 +1,7 @@ 'use strict'; const expect = require('chai').expect; -const sinon = require('sinon'); +const sandbox = require('sinon'); const path = require('path'); const AwsProvider = require('../../provider/awsProvider'); const AwsDeploy = require('../index'); @@ -10,7 +10,6 @@ const { getTmpDirPath } = require('../../../../../tests/utils/fs'); describe('createStack', () => { let awsDeploy; - let sandbox; const tmpDirPath = getTmpDirPath(); const serverlessYmlPath = path.join(tmpDirPath, 'serverless.yml'); @@ -25,7 +24,6 @@ describe('createStack', () => { }; beforeEach(() => { - sandbox = sinon.sandbox.create(); const serverless = new Serverless(); serverless.setProvider('aws', new AwsProvider(serverless, {})); serverless.utils.writeFileSync(serverlessYmlPath, serverlessYml); @@ -91,9 +89,9 @@ describe('createStack', () => { const mytopicArn = 'arn:aws:sns::123456789012:mytopic'; awsDeploy.serverless.service.provider.notificationArns = [mytopicArn]; - const createStackStub = sinon + const createStackStub = sandbox .stub(awsDeploy.provider, 'request').resolves(); - sinon.stub(awsDeploy, 'monitorStack').resolves(); + sandbox.stub(awsDeploy, 'monitorStack').resolves(); return awsDeploy.create().then(() => { expect(createStackStub.args[0][2].NotificationARNs) From 8b9815ba2e20a1e44327e50c593c787245af147d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 13:56:37 +0200 Subject: [PATCH 249/504] Upgrade chai --- package-lock.json | 49 +++++++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6f34d441..f6537d37c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1591,14 +1591,17 @@ } }, "chai": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", - "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { - "assertion-error": "^1.0.1", - "deep-eql": "^0.1.3", - "type-detect": "^1.0.0" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" } }, "chai-as-promised": { @@ -2251,20 +2254,12 @@ } }, "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "0.1.1" - }, - "dependencies": { - "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", - "dev": true - } + "type-detect": "^4.0.0" } }, "deep-extend": { @@ -4225,6 +4220,12 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "get-proxy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", @@ -7519,6 +7520,12 @@ } } }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -9334,9 +9341,9 @@ } }, "type-detect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", - "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "type-is": { diff --git a/package.json b/package.json index 1449ed88f..42b6561b3 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "testRunner": "jest-circus/runner" }, "devDependencies": { - "chai": "^3.5.0", + "chai": "^4.2.0", "chai-as-promised": "^6.0.0", "child-process-ext": "^2.0.0", "cli-progress-footer": "^1.1.1", From 663f74da68140642b717420f08e18ce10f0d0d8f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 13:56:56 +0200 Subject: [PATCH 250/504] Fix equality test --- lib/classes/Error.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/classes/Error.test.js b/lib/classes/Error.test.js index 1d6dc113b..9ebc18b45 100644 --- a/lib/classes/Error.test.js +++ b/lib/classes/Error.test.js @@ -131,8 +131,6 @@ describe('Error', () => { }); it('should re-throw error when handling raises an exception itself', () => { - const handlingError = new Error('an unexpected error'); - let thrownError = null; try { logError('INVALID INPUT'); @@ -140,7 +138,7 @@ describe('Error', () => { thrownError = e; } - expect(thrownError).to.deep.equal(handlingError); + expect(thrownError.message).to.equal('INVALID INPUT'); }); }); From 2023e5038681d759b530b08dd652039c29c7fdef Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 27 May 2019 16:26:39 +0200 Subject: [PATCH 251/504] Update code to use an existing listener --- docs/providers/aws/events/alb.md | 25 +--- docs/providers/aws/guide/serverless.yml.md | 9 +- lib/plugins/aws/lib/naming.js | 4 +- lib/plugins/aws/lib/naming.test.js | 6 +- .../aws/package/compile/events/alb/index.js | 20 ++-- .../package/compile/events/alb/index.test.js | 30 ++--- .../compile/events/alb/lib/listenerRules.js | 44 +++++++ .../events/alb/lib/listenerRules.test.js | 109 ++++++++++++++++++ .../compile/events/alb/lib/listeners.js | 52 --------- .../compile/events/alb/lib/listeners.test.js | 83 ------------- .../compile/events/alb/lib/permissions.js | 13 +-- .../events/alb/lib/permissions.test.js | 81 +++++++------ .../compile/events/alb/lib/targetGroups.js | 22 ++-- .../events/alb/lib/targetGroups.test.js | 95 ++++++++------- .../compile/events/alb/lib/validate.js | 12 +- .../compile/events/alb/lib/validate.test.js | 46 +++++--- .../aws-clojure-gradle/serverless.yml | 9 +- .../aws-clojurescript-gradle/serverless.yml | 9 +- .../templates/aws-csharp/serverless.yml | 9 +- .../templates/aws-fsharp/serverless.yml | 9 +- .../templates/aws-go-dep/serverless.yml | 9 +- .../templates/aws-go-mod/serverless.yml | 9 +- .../create/templates/aws-go/serverless.yml | 9 +- .../aws-groovy-gradle/serverless.yml | 9 +- .../templates/aws-java-gradle/serverless.yml | 9 +- .../templates/aws-java-maven/serverless.yml | 9 +- .../aws-kotlin-jvm-gradle/serverless.yml | 9 +- .../aws-kotlin-jvm-maven/serverless.yml | 9 +- .../aws-kotlin-nodejs-gradle/serverless.yml | 9 +- .../templates/aws-nodejs/serverless.yml | 9 +- .../templates/aws-provided/serverless.yml | 9 +- .../templates/aws-python/serverless.yml | 9 +- .../templates/aws-python3/serverless.yml | 9 +- .../create/templates/aws-ruby/serverless.yml | 9 +- .../templates/aws-scala-sbt/serverless.yml | 9 +- 35 files changed, 437 insertions(+), 385 deletions(-) create mode 100644 lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js create mode 100644 lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js delete mode 100644 lib/plugins/aws/package/compile/events/alb/lib/listeners.js delete mode 100644 lib/plugins/aws/package/compile/events/alb/lib/listeners.test.js diff --git a/docs/providers/aws/events/alb.md b/docs/providers/aws/events/alb.md index 001fa3d78..72f58bfce 100644 --- a/docs/providers/aws/events/alb.md +++ b/docs/providers/aws/events/alb.md @@ -18,30 +18,15 @@ The Serverless Framework makes it possible to setup the connection between Appli ## Event definition -The following code will setup an alb using a HTTPS-based listener. - ```yml functions: albEventConsumer: handler: handler.hello events: - alb: - loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 - certificateArn: arn:aws:iam::123456:server-certificate/ProdServerCert - name: alb-handler-https - listener: HTTPS:443 -``` - -The Application Load Balancer `arn` can also be referenced via `Ref`. HTTP listeners don't require the `certificateArn` property to be set. - -```yml -functions: - albEventConsumer: - handler: handler.hello - events: - - alb: - loadBalancerArn: - Ref: MyApplicationLoadBalancer - name: alb-handler-http - listener: HTTP:80 + listenerArn: arn:aws:elasticloadbalancing:us-east-1:12345:listener/app/my-load-balancer/50dc6c495c0c9188/ + priority: 1 + conditions: + host: example.com + path: /hello ``` diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index ffc517636..c663a375e 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -270,10 +270,11 @@ functions: pool: MyUserPool trigger: PreSignUp - alb: - loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 - certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert # only required when HTTPS is used - name: alb-handler-https - listener: HTTPS:443 + listenerArn: arn:aws:elasticloadbalancing:us-east-1:12345:listener/app/my-load-balancer/50dc6c495c0c9188/ + priority: 1 + conditions: + host: example.com + path: /hello layers: hello: # A Lambda layer diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index c99c5af61..95c2a41ed 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -373,8 +373,8 @@ module.exports = { getAlbTargetGroupLogicalId(functionName) { return `${this.getNormalizedFunctionName(functionName)}AlbTargetGroup`; }, - getAlbListenerLogicalId(functionName, idx) { - return `${this.getNormalizedFunctionName(functionName)}AlbListener${idx}`; + getAlbListenerRuleLogicalId(functionName, idx) { + return `${this.getNormalizedFunctionName(functionName)}AlbListenerRule${idx}`; }, // Permissions diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index a96536417..fbc19a249 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -697,10 +697,10 @@ describe('#naming()', () => { }); }); - describe('#getAlbListenerLogicalId()', () => { + describe('#getAlbListenerRuleLogicalId()', () => { it('should normalize the function name and add an index', () => { - expect(sdk.naming.getAlbListenerLogicalId('functionName', 0)) - .to.equal('FunctionNameAlbListener0'); + expect(sdk.naming.getAlbListenerRuleLogicalId('functionName', 0)) + .to.equal('FunctionNameAlbListenerRule0'); }); }); }); diff --git a/lib/plugins/aws/package/compile/events/alb/index.js b/lib/plugins/aws/package/compile/events/alb/index.js index 00cc1c54a..08970abe1 100644 --- a/lib/plugins/aws/package/compile/events/alb/index.js +++ b/lib/plugins/aws/package/compile/events/alb/index.js @@ -4,7 +4,7 @@ const BbPromise = require('bluebird'); const validate = require('./lib/validate'); const compileTargetGroups = require('./lib/targetGroups'); -const compileListeners = require('./lib/listeners'); +const compileListenerRules = require('./lib/listenerRules'); const compilePermissions = require('./lib/permissions'); class AwsCompileAlbEvents { @@ -17,22 +17,20 @@ class AwsCompileAlbEvents { this, validate, compileTargetGroups, - compileListeners, + compileListenerRules, compilePermissions ); this.hooks = { 'package:compileEvents': () => { - this.validated = this.validate(); + return BbPromise.try(() => { + this.validated = this.validate(); + if (this.validated.events.length === 0) return; - if (this.validated.events.length === 0) { - return BbPromise.resolve(); - } - - return BbPromise.bind(this) - .then(this.compileTargetGroups) - .then(this.compileListeners) - .then(this.compilePermissions); + this.compileTargetGroups(); + this.compileListenerRules(); + this.compilePermissions(); + }); }, }; } diff --git a/lib/plugins/aws/package/compile/events/alb/index.test.js b/lib/plugins/aws/package/compile/events/alb/index.test.js index ba1a8b912..c4785354e 100644 --- a/lib/plugins/aws/package/compile/events/alb/index.test.js +++ b/lib/plugins/aws/package/compile/events/alb/index.test.js @@ -21,21 +21,21 @@ describe('AwsCompileAlbEvents', () => { describe('#constructor()', () => { let compileTargetGroupsStub; - let compileListenersStub; + let compileListenerRulesStub; let compilePermissionsStub; beforeEach(() => { compileTargetGroupsStub = sinon .stub(awsCompileAlbEvents, 'compileTargetGroups').resolves(); - compileListenersStub = sinon - .stub(awsCompileAlbEvents, 'compileListeners').resolves(); + compileListenerRulesStub = sinon + .stub(awsCompileAlbEvents, 'compileListenerRules').resolves(); compilePermissionsStub = sinon .stub(awsCompileAlbEvents, 'compilePermissions').resolves(); }); afterEach(() => { awsCompileAlbEvents.compileTargetGroups.restore(); - awsCompileAlbEvents.compileListeners.restore(); + awsCompileAlbEvents.compileListenerRules.restore(); awsCompileAlbEvents.compilePermissions.restore(); }); @@ -52,22 +52,24 @@ describe('AwsCompileAlbEvents', () => { it('should run the promise chain in order', () => { const validateStub = sinon .stub(awsCompileAlbEvents, 'validate').returns({ - events: [ - { - functionName: 'first', - listener: 'HTTPS:443', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - name: 'some-alb-event-1', + events: [{ + functionName: 'first', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 1, + conditions: { + host: 'example.com', + path: '/hello', }, - ], + }], }); return awsCompileAlbEvents.hooks['package:compileEvents']().then(() => { expect(validateStub.calledOnce).to.be.equal(true); expect(compileTargetGroupsStub.calledAfter(validateStub)).to.be.equal(true); - expect(compileListenersStub.calledAfter(compileTargetGroupsStub)).to.be.equal(true); - expect(compilePermissionsStub.calledAfter(compileListenersStub)).to.be.equal(true); + expect(compileListenerRulesStub.calledAfter(compileTargetGroupsStub)).to.be.equal(true); + expect(compilePermissionsStub.calledAfter(compileListenerRulesStub)).to.be.equal(true); }); }); }); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js new file mode 100644 index 000000000..f4e9786f8 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js @@ -0,0 +1,44 @@ +'use strict'; + +module.exports = { + compileListenerRules() { + this.validated.events.forEach((event) => { + const listenerRuleLogicalId = this.provider.naming + .getAlbListenerRuleLogicalId(event.functionName, event.priority); + const targetGroupLogicalId = this.provider.naming + .getAlbTargetGroupLogicalId(event.functionName); + + const Conditions = [ + { + Field: 'path-pattern', + Values: [event.conditions.path], + }, + ]; + if (event.conditions.host) { + Conditions.push({ + Field: 'host-header', + Values: [event.conditions.host], + }); + } + + Object.assign(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { + [listenerRuleLogicalId]: { + Type: 'AWS::ElasticLoadBalancingV2::ListenerRule', + Properties: { + Actions: [ + { + Type: 'forward', + TargetGroupArn: { + Ref: targetGroupLogicalId, + }, + }, + ], + Conditions, + ListenerArn: event.listenerArn, + Priority: event.priority, + }, + }, + }); + }); + }, +}; diff --git a/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js new file mode 100644 index 000000000..4ce624774 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js @@ -0,0 +1,109 @@ +'use strict'; + +const expect = require('chai').expect; +const AwsCompileAlbEvents = require('../index'); +const Serverless = require('../../../../../../../Serverless'); +const AwsProvider = require('../../../../../provider/awsProvider'); + +describe('#compileListenerRules()', () => { + let awsCompileAlbEvents; + + beforeEach(() => { + const serverless = new Serverless(); + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.service.service = 'some-service'; + serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; + + awsCompileAlbEvents = new AwsCompileAlbEvents(serverless); + }); + + it('should create ELB listener rule resources', () => { + awsCompileAlbEvents.validated = { + events: [ + { + functionName: 'first', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 1, + conditions: { + host: 'example.com', + path: '/hello', + }, + }, + { + functionName: 'second', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 2, + conditions: { + path: '/world', + }, + }, + ], + }; + + awsCompileAlbEvents.compileListenerRules(); + + const resources = awsCompileAlbEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources.FirstAlbListenerRule1).to.deep.equal({ + Type: 'AWS::ElasticLoadBalancingV2::ListenerRule', + Properties: { + Actions: [ + { + TargetGroupArn: { + Ref: 'FirstAlbTargetGroup', + }, + Type: 'forward', + }, + ], + Conditions: [ + { + Field: 'path-pattern', + Values: [ + '/hello', + ], + }, + { + Field: 'host-header', + Values: [ + 'example.com', + ], + }, + ], + ListenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + Priority: 1, + }, + }); + expect(resources.SecondAlbListenerRule2).to.deep.equal({ + Type: 'AWS::ElasticLoadBalancingV2::ListenerRule', + Properties: { + Actions: [ + { + TargetGroupArn: { + Ref: 'SecondAlbTargetGroup', + }, + Type: 'forward', + }, + ], + Conditions: [ + { + Field: 'path-pattern', + Values: [ + '/world', + ], + }, + ], + ListenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + Priority: 2, + }, + }); + }); +}); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/listeners.js b/lib/plugins/aws/package/compile/events/alb/lib/listeners.js deleted file mode 100644 index a852af554..000000000 --- a/lib/plugins/aws/package/compile/events/alb/lib/listeners.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -const BbPromise = require('bluebird'); -const _ = require('lodash'); - -module.exports = { - compileListeners() { - this.validated.events.forEach((event, idx) => { - const targetGroupLogicalId = this.provider.naming - .getAlbTargetGroupLogicalId(event.name); - const listenerLogicalId = this.provider.naming - .getAlbListenerLogicalId(event.functionName, idx); - - const listener = event.listener; - const Protocol = listener.split(':')[0].toUpperCase(); - const Port = listener.split(':')[1]; - const LoadBalancerArn = event.loadBalancerArn; - const CertificateArn = event.certificateArn || null; - - let Certificates = []; - if (CertificateArn) { - Certificates = [ - { - CertificateArn, - }, - ]; - } - - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { - [listenerLogicalId]: { - Type: 'AWS::ElasticLoadBalancingV2::Listener', - Properties: { - DefaultActions: [ - { - TargetGroupArn: { - Ref: targetGroupLogicalId, - }, - Type: 'forward', - }, - ], - LoadBalancerArn, - Certificates, - Protocol, - Port, - }, - }, - }); - }); - - return BbPromise.resolve(); - }, -}; diff --git a/lib/plugins/aws/package/compile/events/alb/lib/listeners.test.js b/lib/plugins/aws/package/compile/events/alb/lib/listeners.test.js deleted file mode 100644 index f1796b7a6..000000000 --- a/lib/plugins/aws/package/compile/events/alb/lib/listeners.test.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const AwsCompileAlbEvents = require('../index'); -const Serverless = require('../../../../../../../Serverless'); -const AwsProvider = require('../../../../../provider/awsProvider'); - -describe('#compileListeners()', () => { - let awsCompileAlbEvents; - - beforeEach(() => { - const serverless = new Serverless(); - serverless.setProvider('aws', new AwsProvider(serverless)); - serverless.service.service = 'some-service'; - serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; - - awsCompileAlbEvents = new AwsCompileAlbEvents(serverless); - }); - - it('should create ELB listener resources', () => { - awsCompileAlbEvents.validated = { - events: [ - { - functionName: 'first', - listener: 'HTTPS:443', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - name: 'some-alb-event-1', - }, - { - functionName: 'second', - listener: 'HTTP:80', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - name: 'some-alb-event-2', - }, - ], - }; - - return awsCompileAlbEvents.compileListeners().then(() => { - const resources = awsCompileAlbEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; - - expect(resources.FirstAlbListener0).to.deep.equal({ - Type: 'AWS::ElasticLoadBalancingV2::Listener', - Properties: { - Certificates: [ - { - CertificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - }, - ], - DefaultActions: [ - { - TargetGroupArn: { - Ref: 'SomeDashalbDasheventDash1AlbTargetGroup', - }, - Type: 'forward', - }, - ], - LoadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - Port: '443', - Protocol: 'HTTPS', - }, - }); - expect(resources.SecondAlbListener1).to.deep.equal({ - Type: 'AWS::ElasticLoadBalancingV2::Listener', - Properties: { - Certificates: [], - DefaultActions: [ - { - TargetGroupArn: { - Ref: 'SomeDashalbDasheventDash2AlbTargetGroup', - }, - Type: 'forward', - }, - ], - LoadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - Port: '80', - Protocol: 'HTTP', - }, - }); - }); - }); -}); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/permissions.js b/lib/plugins/aws/package/compile/events/alb/lib/permissions.js index 46b6c2426..7ba3c44cf 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/permissions.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/permissions.js @@ -1,17 +1,16 @@ 'use strict'; -const _ = require('lodash'); -const BbPromise = require('bluebird'); - module.exports = { compilePermissions() { this.validated.events.forEach((event) => { - const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(event.functionName); + const { functionName } = event; + const lambdaLogicalId = this.provider.naming + .getLambdaLogicalId(functionName); const albPermissionLogicalId = this.provider.naming - .getLambdaAlbPermissionLogicalId(event.functionName); + .getLambdaAlbPermissionLogicalId(functionName); - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { + Object.assign(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [albPermissionLogicalId]: { Type: 'AWS::Lambda::Permission', Properties: { @@ -25,7 +24,5 @@ module.exports = { }, }); }); - - return BbPromise.resolve(); }, }; diff --git a/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js b/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js index 82f8d3692..6042590dd 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js @@ -22,53 +22,60 @@ describe('#compilePermissions()', () => { events: [ { functionName: 'first', - listener: 'HTTPS:443', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - name: 'some-alb-event-1', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 1, + conditions: { + host: 'example.com', + path: '/hello', + }, }, { functionName: 'second', - listener: 'HTTP:80', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - name: 'some-alb-event-2', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 2, + conditions: { + path: '/world', + }, }, ], }; - return awsCompileAlbEvents.compilePermissions().then(() => { - const resources = awsCompileAlbEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + awsCompileAlbEvents.compilePermissions(); - expect(resources.FirstLambdaPermissionAlb).to.deep.equal({ - Type: 'AWS::Lambda::Permission', - Properties: { - Action: 'lambda:InvokeFunction', - FunctionName: { - 'Fn::GetAtt': [ - 'FirstLambdaFunction', - 'Arn', - ], - }, - Principal: 'elasticloadbalancing.amazonaws.com', + const resources = awsCompileAlbEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources.FirstLambdaPermissionAlb).to.deep.equal({ + Type: 'AWS::Lambda::Permission', + Properties: { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'FirstLambdaFunction', + 'Arn', + ], }, - DependsOn: ['FirstLambdaFunction'], - }); - expect(resources.SecondLambdaPermissionAlb).to.deep.equal({ - Type: 'AWS::Lambda::Permission', - Properties: { - Action: 'lambda:InvokeFunction', - FunctionName: { - 'Fn::GetAtt': [ - 'SecondLambdaFunction', - 'Arn', - ], - }, - Principal: 'elasticloadbalancing.amazonaws.com', + Principal: 'elasticloadbalancing.amazonaws.com', + }, + DependsOn: ['FirstLambdaFunction'], + }); + expect(resources.SecondLambdaPermissionAlb).to.deep.equal({ + Type: 'AWS::Lambda::Permission', + Properties: { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'SecondLambdaFunction', + 'Arn', + ], }, - DependsOn: ['SecondLambdaFunction'], - }); + Principal: 'elasticloadbalancing.amazonaws.com', + }, + DependsOn: ['SecondLambdaFunction'], }); }); }); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js index 3f85da001..86f03bd50 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js @@ -1,19 +1,18 @@ 'use strict'; -const BbPromise = require('bluebird'); -const _ = require('lodash'); - module.exports = { compileTargetGroups() { this.validated.events.forEach((event) => { - const targetGroupLogicalId = this.provider.naming - .getAlbTargetGroupLogicalId(event.name); - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(event.functionName); - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaAlbPermissionLogicalId(event.functionName); + const { functionName } = event; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { + const targetGroupLogicalId = this.provider.naming + .getAlbTargetGroupLogicalId(functionName); + const lambdaLogicalId = this.provider.naming + .getLambdaLogicalId(functionName); + const lambdaPermissionLogicalId = this.provider.naming + .getLambdaAlbPermissionLogicalId(functionName); + + Object.assign(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [targetGroupLogicalId]: { Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', Properties: { @@ -25,12 +24,11 @@ module.exports = { }, }, ], + Name: `${functionName}-${this.provider.getStage()}`, }, DependsOn: [lambdaPermissionLogicalId], }, }); }); - - return BbPromise.resolve(); }, }; diff --git a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js index dd9b28237..dfba7da92 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js @@ -22,59 +22,68 @@ describe('#compileTargetGroups()', () => { events: [ { functionName: 'first', - listener: 'HTTPS:443', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - name: 'some-alb-event-1', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 1, + conditions: { + host: 'example.com', + path: '/hello', + }, }, { functionName: 'second', - listener: 'HTTP:80', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - name: 'some-alb-event-2', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 2, + conditions: { + path: '/world', + }, }, ], }; - return awsCompileAlbEvents.compileTargetGroups().then(() => { - const resources = awsCompileAlbEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + awsCompileAlbEvents.compileTargetGroups(); - expect(resources.SomeDashalbDasheventDash1AlbTargetGroup).to.deep.equal({ - Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', - Properties: { - TargetType: 'lambda', - Targets: [ - { - Id: { - 'Fn::GetAtt': [ - 'FirstLambdaFunction', - 'Arn', - ], - }, + const resources = awsCompileAlbEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources.FirstAlbTargetGroup).to.deep.equal({ + Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', + Properties: { + Name: 'first-dev', + TargetType: 'lambda', + Targets: [ + { + Id: { + 'Fn::GetAtt': [ + 'FirstLambdaFunction', + 'Arn', + ], }, - ], - }, - DependsOn: ['FirstLambdaPermissionAlb'], - }); - expect(resources.SomeDashalbDasheventDash2AlbTargetGroup).to.deep.equal({ - Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', - Properties: { - TargetType: 'lambda', - Targets: [ - { - Id: { - 'Fn::GetAtt': [ - 'SecondLambdaFunction', - 'Arn', - ], - }, + }, + ], + }, + DependsOn: ['FirstLambdaPermissionAlb'], + }); + expect(resources.SecondAlbTargetGroup).to.deep.equal({ + Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', + Properties: { + Name: 'second-dev', + TargetType: 'lambda', + Targets: [ + { + Id: { + 'Fn::GetAtt': [ + 'SecondLambdaFunction', + 'Arn', + ], }, - ], - }, - DependsOn: ['SecondLambdaPermissionAlb'], - }); + }, + ], + }, + DependsOn: ['SecondLambdaPermissionAlb'], }); }); }); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/validate.js b/lib/plugins/aws/package/compile/events/alb/lib/validate.js index e3102edb1..9bb821f0a 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/validate.js @@ -11,13 +11,17 @@ module.exports = { if (_.has(event, 'alb')) { if (_.isObject(event.alb)) { const albObj = { - name: event.alb.name, - loadBalancerArn: event.alb.loadBalancerArn, - certificateArn: event.alb.certificateArn, - listener: event.alb.listener, + listenerArn: event.alb.listenerArn, + priority: event.alb.priority, + conditions: { + path: event.alb.conditions.path, + }, // the following is data which is not defined on the event-level functionName, }; + if (event.alb.conditions.host) { + albObj.conditions.host = event.alb.conditions.host; + } events.push(albObj); } } diff --git a/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js b/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js index 8be6969f5..889bd19f1 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js @@ -23,10 +23,14 @@ describe('#validate()', () => { events: [ { alb: { - listener: 'HTTPS:443', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - name: 'some-alb-event-1', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 1, + conditions: { + host: 'example.com', + path: '/hello', + }, }, }, ], @@ -35,10 +39,13 @@ describe('#validate()', () => { events: [ { alb: { - listener: 'HTTP:80', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - name: 'some-alb-event-2', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 2, + conditions: { + path: '/world', + }, }, }, ], @@ -50,17 +57,24 @@ describe('#validate()', () => { expect(validated.events).to.deep.equal([ { functionName: 'first', - listener: 'HTTPS:443', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - name: 'some-alb-event-1', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 1, + conditions: { + host: 'example.com', + path: '/hello', + }, }, { functionName: 'second', - listener: 'HTTP:80', - loadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456:loadbalancer/app/my-load-balancer/50dc6c495c0c9188', // eslint-disable-line - certificateArn: 'arn:aws:iam::123456:server-certificate/ProdServerCert', - name: 'some-alb-event-2', + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 2, + conditions: { + path: '/world', + }, }, ]); }); diff --git a/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml b/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml index 3dd357308..b45dbccfc 100644 --- a/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml @@ -91,10 +91,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml index 993def7e6..978252814 100644 --- a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml @@ -100,10 +100,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-csharp/serverless.yml b/lib/plugins/create/templates/aws-csharp/serverless.yml index 48ce3064f..9e0b29bff 100644 --- a/lib/plugins/create/templates/aws-csharp/serverless.yml +++ b/lib/plugins/create/templates/aws-csharp/serverless.yml @@ -91,10 +91,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-fsharp/serverless.yml b/lib/plugins/create/templates/aws-fsharp/serverless.yml index 4d2757657..c185b184f 100644 --- a/lib/plugins/create/templates/aws-fsharp/serverless.yml +++ b/lib/plugins/create/templates/aws-fsharp/serverless.yml @@ -88,10 +88,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-go-dep/serverless.yml b/lib/plugins/create/templates/aws-go-dep/serverless.yml index b8ba76d50..f19abe668 100644 --- a/lib/plugins/create/templates/aws-go-dep/serverless.yml +++ b/lib/plugins/create/templates/aws-go-dep/serverless.yml @@ -99,10 +99,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-go-mod/serverless.yml b/lib/plugins/create/templates/aws-go-mod/serverless.yml index 65082379d..f46024cc4 100644 --- a/lib/plugins/create/templates/aws-go-mod/serverless.yml +++ b/lib/plugins/create/templates/aws-go-mod/serverless.yml @@ -99,10 +99,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-go/serverless.yml b/lib/plugins/create/templates/aws-go/serverless.yml index 9ee090fdb..6df5cee6c 100644 --- a/lib/plugins/create/templates/aws-go/serverless.yml +++ b/lib/plugins/create/templates/aws-go/serverless.yml @@ -99,10 +99,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml index 105257433..db6d38f11 100644 --- a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml @@ -85,10 +85,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-java-gradle/serverless.yml b/lib/plugins/create/templates/aws-java-gradle/serverless.yml index 4d9e011c2..8d0efe8e1 100644 --- a/lib/plugins/create/templates/aws-java-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-java-gradle/serverless.yml @@ -85,10 +85,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.yml b/lib/plugins/create/templates/aws-java-maven/serverless.yml index c39589916..09f619491 100644 --- a/lib/plugins/create/templates/aws-java-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-java-maven/serverless.yml @@ -85,10 +85,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml index 973f28286..157032257 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml @@ -85,10 +85,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml index 12e4b9fd4..37eb728b0 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml @@ -85,10 +85,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml index e59789ad2..e1ce95c7f 100644 --- a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml @@ -81,10 +81,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index 15f12f583..443554742 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -90,10 +90,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-provided/serverless.yml b/lib/plugins/create/templates/aws-provided/serverless.yml index 757c86e78..3411b1de9 100644 --- a/lib/plugins/create/templates/aws-provided/serverless.yml +++ b/lib/plugins/create/templates/aws-provided/serverless.yml @@ -90,10 +90,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-python/serverless.yml b/lib/plugins/create/templates/aws-python/serverless.yml index 7c9111f3f..5aef41a5d 100644 --- a/lib/plugins/create/templates/aws-python/serverless.yml +++ b/lib/plugins/create/templates/aws-python/serverless.yml @@ -90,10 +90,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-python3/serverless.yml b/lib/plugins/create/templates/aws-python3/serverless.yml index f2e656d84..bb1db67af 100644 --- a/lib/plugins/create/templates/aws-python3/serverless.yml +++ b/lib/plugins/create/templates/aws-python3/serverless.yml @@ -90,10 +90,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-ruby/serverless.yml b/lib/plugins/create/templates/aws-ruby/serverless.yml index 079186796..19a1b810b 100644 --- a/lib/plugins/create/templates/aws-ruby/serverless.yml +++ b/lib/plugins/create/templates/aws-ruby/serverless.yml @@ -90,10 +90,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml index d737b96d6..620b37323 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml +++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml @@ -87,10 +87,11 @@ functions: # pool: MyUserPool # trigger: PreSignUp # - alb: -# loadBalancerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:loadbalancer/app/my-load-balancer/50dc6c495c0c9188 -# certificateArn: arn:aws:iam::XXXXXX:server-certificate/ProdServerCert -# name: alb-handler-https -# listener: HTTPS:443 +# listenerArn: arn:aws:elasticloadbalancing:us-east-1:XXXXXX:listener/app/my-load-balancer/50dc6c495c0c9188/ +# priority: 1 +# conditions: +# host: example.com +# path: /hello # Define function environment variables here # environment: From ef4934d77ef705ddb9118a1ee73413f153cf58d7 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 3 Jun 2019 14:45:05 +0200 Subject: [PATCH 252/504] Add regression test --- .../events/apiGateway/lib/validate.test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js index 43f20c7f7..a0e692af1 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js @@ -325,6 +325,25 @@ describe('#validate()', () => { expect(() => awsCompileApigEvents.validate()).not.to.throw(Error); }); + it('should not throw when using a cognito string authorizer', () => { + awsCompileApigEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + path: '/{proxy+}', + method: 'ANY', + integration: 'lambda-proxy', + authorizer: 'arn:aws:cognito-idp:us-east-1:$XXXXX:userpool/some-user-pool', + }, + }, + ], + }, + }; + + expect(() => awsCompileApigEvents.validate()).not.to.throw(Error); + }); + it('should accept AWS_IAM as authorizer', () => { awsCompileApigEvents.serverless.service.functions = { foo: {}, From c9fec295a755e01da7c70e6c212fab7b7c1a9e09 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 15:50:12 +0200 Subject: [PATCH 253/504] Do not run integration tests for PR's --- .travis.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index dadd0b98f..5cbf9e936 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,19 +18,13 @@ stages: jobs: include: # To speed up Travis build, use one job per Platform + Node.js version combination - - name: "Lint, Unit Tests, Basic Integration Tests - Linux - Node.js v12" + - name: "Lint, Unit Tests - Linux - Node.js v12" node_js: 12 env: - SLS_IGNORE_WARNING=* - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' - # AWS_ACCESS_KEY_ID - - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= - # AWS_SECRET_ACCESS_KEY - - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= - script: - # Combine with '&&' to not continue on fail - - npm run lint-updated && npm test && npm run integration-test-run-basic - - npm run integration-test-cleanup + # Combine with '&&' to not continue on fail + script: npm run lint-updated && npm test - name: "Unit Tests - Windows - Node.js v12" os: windows node_js: 12 @@ -66,7 +60,7 @@ jobs: # AWS_SECRET_ACCESS_KEY - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= script: - - npm run integration-test-run-all + - npm run integration-test-run-basic && npm run integration-test-run-all - npm run integration-test-cleanup - stage: Deploy From d5afac17693973a3f60269700bee7bb3165ecc44 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 16:13:09 +0200 Subject: [PATCH 254/504] Ensure colors --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5cbf9e936..a7587aa35 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,7 @@ jobs: node_js: 12 env: - SLS_IGNORE_WARNING=* + - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' # AWS_ACCESS_KEY_ID - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= # AWS_SECRET_ACCESS_KEY From 1eb9deb37b295e7122b26eb0cc34408e26aa2d2c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 16:54:56 +0200 Subject: [PATCH 255/504] Fix instances checking in tests --- lib/classes/PluginManager.test.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 7a3c223fe..c95365ef1 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -682,12 +682,12 @@ describe('PluginManager', () => { it('should log a warning when trying to load unknown plugin with help flag', () => { const consoleLogStub = sinon.stub(pluginManager.serverless.cli, 'log').returns(); const servicePlugins = ['ServicePluginMock3', 'ServicePluginMock1']; - const servicePluginMock1 = new ServicePluginMock1(); pluginManager.setCliOptions({ help: true }); pluginManager.loadPlugins(servicePlugins); - expect(pluginManager.plugins).to.contain(servicePluginMock1); + expect(pluginManager.plugins + .some(plugin => plugin instanceof ServicePluginMock1)).to.equal(true); expect(consoleLogStub.calledOnce).to.equal(true); }); @@ -757,13 +757,12 @@ describe('PluginManager', () => { const servicePlugins = ['ServicePluginMock1', 'ServicePluginMock2']; pluginManager.loadAllPlugins(servicePlugins); - const servicePluginMock1 = new ServicePluginMock1(); - const servicePluginMock2 = new ServicePluginMock2(); - const enterprisePluginMock = new EnterprisePluginMock(); - - expect(pluginManager.plugins).to.contain(servicePluginMock1); - expect(pluginManager.plugins).to.contain(servicePluginMock2); - expect(pluginManager.plugins).to.contain(enterprisePluginMock); + expect(pluginManager.plugins + .some(plugin => plugin instanceof ServicePluginMock1)).to.equal(true); + expect(pluginManager.plugins + .some(plugin => plugin instanceof ServicePluginMock2)).to.equal(true); + expect(pluginManager.plugins + .some(plugin => plugin instanceof EnterprisePluginMock)).to.equal(true); // note: this test will be refactored as the Create plugin will be moved // to another directory expect(pluginManager.plugins.length).to.be.above(2); @@ -815,11 +814,10 @@ describe('PluginManager', () => { const servicePlugins = ['ServicePluginMock1', 'ServicePluginMock2']; pluginManager.loadServicePlugins(servicePlugins); - const servicePluginMock1 = new ServicePluginMock1(); - const servicePluginMock2 = new ServicePluginMock2(); - - expect(pluginManager.plugins).to.contain(servicePluginMock1); - expect(pluginManager.plugins).to.contain(servicePluginMock2); + expect(pluginManager.plugins + .some(plugin => plugin instanceof ServicePluginMock1)).to.equal(true); + expect(pluginManager.plugins + .some(plugin => plugin instanceof ServicePluginMock2)).to.equal(true); }); it('should not error if plugins = null', () => { From 54bdfdf7eb142857a6aa699fec0f8adfa6e12466 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 16:55:09 +0200 Subject: [PATCH 256/504] Update after chai upgrade --- lib/classes/PluginManager.test.js | 8 ++++---- lib/classes/YamlParser.test.js | 6 +++--- lib/plugins/aws/common/index.test.js | 10 +++++----- lib/utils/config/config.test.js | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index c95365ef1..72c3979aa 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -1588,12 +1588,12 @@ describe('PluginManager', () => { const commands = pluginManager.getCommands(); expect(commands).to.have.a.property('mycmd'); - expect(commands).to.have.a.deep.property('mycmd.commands.mysubcmd'); - expect(commands).to.have.a.deep.property('mycmd.commands.spawncmd'); + expect(commands).to.have.a.nested.property('mycmd.commands.mysubcmd'); + expect(commands).to.have.a.nested.property('mycmd.commands.spawncmd'); // Check for omitted entrypoints expect(commands).to.not.have.a.property('myep'); - expect(commands).to.not.have.a.deep.property('myep.commands.mysubep'); - expect(commands).to.not.have.a.deep.property('mycmd.commands.spawnep'); + expect(commands).to.not.have.a.nested.property('myep.commands.mysubep'); + expect(commands).to.not.have.a.nested.property('mycmd.commands.spawnep'); }); it('should return aliases', () => { diff --git a/lib/classes/YamlParser.test.js b/lib/classes/YamlParser.test.js index 71742ae86..17328fd07 100644 --- a/lib/classes/YamlParser.test.js +++ b/lib/classes/YamlParser.test.js @@ -50,7 +50,7 @@ describe('YamlParser', () => { serverless.utils.writeFileSync(path.join(tmpDirPath, 'test.yml'), testYml); return expect(serverless.yamlParser.parse(path.join(tmpDirPath, 'test.yml'))) - .to.eventually.have.deep.property('main.foo').to.equal('bar'); + .to.eventually.have.nested.property('main.foo').to.equal('bar'); }); it('should parse a .yml file with JSON-REF to JSON', () => { @@ -67,7 +67,7 @@ describe('YamlParser', () => { serverless.utils.writeFileSync(path.join(tmpDirPath, 'test.yml'), testYml); return expect(serverless.yamlParser.parse(path.join(tmpDirPath, 'test.yml'))) - .to.eventually.have.deep.property('main.foo').to.equal('bar'); + .to.eventually.have.nested.property('main.foo').to.equal('bar'); }); it('should parse a .yml file with recursive JSON-REF', () => { @@ -92,7 +92,7 @@ describe('YamlParser', () => { serverless.utils.writeFileSync(path.join(tmpDirPath, 'one.yml'), oneYml); return expect(serverless.yamlParser.parse(path.join(tmpDirPath, 'one.yml'))) - .to.eventually.have.deep.property('one.two.foo').to.equal('bar'); + .to.eventually.have.nested.property('one.two.foo').to.equal('bar'); }); }); }); diff --git a/lib/plugins/aws/common/index.test.js b/lib/plugins/aws/common/index.test.js index 3d9f9691b..087909a10 100644 --- a/lib/plugins/aws/common/index.test.js +++ b/lib/plugins/aws/common/index.test.js @@ -78,34 +78,34 @@ describe('AwsCommon', () => { describe('commands', () => { it('should be only entrypoints', () => { - expect(awsCommon.commands).to.have.deep.property('aws.type', 'entrypoint'); + expect(awsCommon.commands).to.have.nested.property('aws.type', 'entrypoint'); }); describe('aws:common:validate', () => { it('should exist', () => { expect(awsCommon.commands) - .to.have.deep.property('aws.commands.common.commands.validate'); + .to.have.nested.property('aws.commands.common.commands.validate'); }); }); describe('aws:common:cleanupTempDir', () => { it('should exist', () => { expect(awsCommon.commands) - .to.have.deep.property('aws.commands.common.commands.cleanupTempDir'); + .to.have.nested.property('aws.commands.common.commands.cleanupTempDir'); }); }); describe('aws:common:moveArtifactsToPackage', () => { it('should exist', () => { expect(awsCommon.commands) - .to.have.deep.property('aws.commands.common.commands.moveArtifactsToPackage'); + .to.have.nested.property('aws.commands.common.commands.moveArtifactsToPackage'); }); }); describe('aws:common:moveArtifactsToTemp', () => { it('should exist', () => { expect(awsCommon.commands) - .to.have.deep.property('aws.commands.common.commands.moveArtifactsToTemp'); + .to.have.nested.property('aws.commands.common.commands.moveArtifactsToTemp'); }); }); }); diff --git a/lib/utils/config/config.test.js b/lib/utils/config/config.test.js index 5ab1365b2..1b69c0175 100644 --- a/lib/utils/config/config.test.js +++ b/lib/utils/config/config.test.js @@ -31,17 +31,17 @@ describe('Config', () => { describe('When using config.getConfig', () => { it('should have userId key', () => { const conf = config.getConfig(); - expect(conf).to.have.deep.property('userId'); + expect(conf).to.have.nested.property('userId'); }); it('should have frameworkId key', () => { const conf = config.getConfig(); - expect(conf).to.have.deep.property('frameworkId'); + expect(conf).to.have.nested.property('frameworkId'); }); it('should have trackingDisabled key', () => { const conf = config.getConfig(); - expect(conf).to.have.deep.property('trackingDisabled'); + expect(conf).to.have.nested.property('trackingDisabled'); }); }); From e75a1eabb716b5f2c4eb74fdf57c84d92cac2368 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 16:57:41 +0200 Subject: [PATCH 257/504] Upgrade after chai update --- lib/classes/PluginManager.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 72c3979aa..43e3fc0cd 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -1601,7 +1601,7 @@ describe('PluginManager', () => { const commands = pluginManager.getCommands(); expect(commands).to.have.a.property('on') - .that.has.a.deep.property('commands.premise'); + .that.has.a.nested.property('commands.premise'); expect(commands).to.have.a.property('premise'); }); }); From 9f150b749c202160120f5c1addddcc997c830300 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 17:14:14 +0200 Subject: [PATCH 258/504] Upgrade chai-as-promised to v7 It comes with support for chai v4 --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6537d37c..c07ca42c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1605,9 +1605,9 @@ } }, "chai-as-promised": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-6.0.0.tgz", - "integrity": "sha1-GgKkM6byTa+sY7nJb6FoTbGqjaY=", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", "dev": true, "requires": { "check-error": "^1.0.2" diff --git a/package.json b/package.json index 42b6561b3..66d400b0d 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ }, "devDependencies": { "chai": "^4.2.0", - "chai-as-promised": "^6.0.0", + "chai-as-promised": "^7.1.1", "child-process-ext": "^2.0.0", "cli-progress-footer": "^1.1.1", "coveralls": "^3.0.3", From 304f43ff185dc84116683ac254fabb80757be2d2 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 17:30:26 +0200 Subject: [PATCH 259/504] Update after chai upgrade --- lib/classes/Service.test.js | 2 +- lib/classes/Utils.test.js | 2 +- lib/utils/fs/readFileSync.test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index 746fcb532..d51876631 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -120,7 +120,7 @@ describe('Service', () => { const serverless = new Serverless(); const noService = new Service(serverless); - return expect(noService.load()).to.eventually.resolve; + return expect(noService.load()).to.be.fulfilled; }); it('should load serverless.yml from filesystem', function () { diff --git a/lib/classes/Utils.test.js b/lib/classes/Utils.test.js index 683f3add2..1480f5e44 100644 --- a/lib/classes/Utils.test.js +++ b/lib/classes/Utils.test.js @@ -166,7 +166,7 @@ describe('Utils', () => { expect(() => { serverless.utils.readFileSync(tmpFilePath); - }).to.throw(new RegExp('YAMLException:.*invalid.yml')); + }).to.throw(/.*invalid.yml/); }); }); diff --git a/lib/utils/fs/readFileSync.test.js b/lib/utils/fs/readFileSync.test.js index 328d38028..f02a98921 100644 --- a/lib/utils/fs/readFileSync.test.js +++ b/lib/utils/fs/readFileSync.test.js @@ -40,6 +40,6 @@ describe('#readFileSync()', () => { expect(() => { readFileSync(tmpFilePath); - }).to.throw(new RegExp('YAMLException:.*invalid.yml')); + }).to.throw(/.*invalid.yml/); }); }); From 0cc153bd00a9fadfc64a9077fec23679b711a3c5 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 17:36:39 +0200 Subject: [PATCH 260/504] Upgrade sinon-chai to v3 --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index c07ca42c6..98ba286c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8410,9 +8410,9 @@ } }, "sinon-chai": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.14.0.tgz", - "integrity": "sha512-9stIF1utB0ywNHNT7RgiXbdmen8QDCRsrTjw+G9TgKt1Yexjiv8TOWZ6WHsTPz57Yky3DIswZvEqX8fpuHNDtQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.3.0.tgz", + "integrity": "sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==", "dev": true }, "sisteransi": { diff --git a/package.json b/package.json index 66d400b0d..b6114f092 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "process-utils": "^2.3.1", "proxyquire": "^1.7.10", "sinon": "^7.3.2", - "sinon-chai": "^2.9.0", + "sinon-chai": "^3.3.0", "strip-ansi": "^5.2.0" }, "dependencies": { From 25b20cf97eee6fb75011a1927f02311eae8485ef Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 17:43:05 +0200 Subject: [PATCH 261/504] Upgrade mock-require to v3 --- package-lock.json | 18 +++++------------- package.json | 2 +- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98ba286c1..143e21862 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1530,15 +1530,6 @@ } } }, - "caller-id": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-id/-/caller-id-0.1.0.tgz", - "integrity": "sha1-Wb2sCJPRLDhxQIJ5Ix+XRYNk8Hs=", - "dev": true, - "requires": { - "stack-trace": "~0.0.7" - } - }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -6690,12 +6681,13 @@ "dev": true }, "mock-require": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mock-require/-/mock-require-1.3.0.tgz", - "integrity": "sha1-gmFElS5QR2L45pJKqPY5Rl0deiQ=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz", + "integrity": "sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==", "dev": true, "requires": { - "caller-id": "^0.1.0" + "get-caller-file": "^1.0.2", + "normalize-path": "^2.1.1" } }, "module-not-found-error": { diff --git a/package.json b/package.json index b6114f092..0215c1e02 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "markdown-table": "^1.1.1", "mocha": "^6.1.4", "mocha-lcov-reporter": "^1.2.0", - "mock-require": "^1.3.0", + "mock-require": "^3.0.3", "nyc": "^14.1.1", "p-limit": "^2.2.0", "parse-github-url": "^1.0.1", From 161f4e086a785ce8cd3194a6da0f73f5704068cc Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 17:46:24 +0200 Subject: [PATCH 262/504] Upgrade proxyquire --- package-lock.json | 19 +++++++++++-------- package.json | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 143e21862..79f9de3ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7682,21 +7682,24 @@ } }, "proxyquire": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-1.8.0.tgz", - "integrity": "sha1-AtUUpb7ZhvBMuyCTrxZ0FTX3ntw=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.0.tgz", + "integrity": "sha512-kptdFArCfGRtQFv3Qwjr10lwbEV0TBJYvfqzhwucyfEXqVgmnAkyEw/S3FYzR5HI9i5QOq4rcqQjZ6AlknlCDQ==", "dev": true, "requires": { "fill-keys": "^1.0.2", "module-not-found-error": "^1.0.0", - "resolve": "~1.1.7" + "resolve": "~1.8.1" }, "dependencies": { "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } } } }, diff --git a/package.json b/package.json index 0215c1e02..df639454a 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "p-limit": "^2.2.0", "parse-github-url": "^1.0.1", "process-utils": "^2.3.1", - "proxyquire": "^1.7.10", + "proxyquire": "^2.1.0", "sinon": "^7.3.2", "sinon-chai": "^3.3.0", "strip-ansi": "^5.2.0" From 5854f4c96f87c7ee2e40f253b522417573948844 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 3 Jun 2019 17:56:45 +0200 Subject: [PATCH 263/504] Ensure needed utils --- .../compile/events/apiGateway/lib/hack/updateStage.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js index c253a3267..da5967b16 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js @@ -3,13 +3,17 @@ /* eslint-disable max-len */ /* eslint-disable no-unused-expressions */ -const expect = require('chai').expect; +const chai = require('chai'); const sinon = require('sinon'); const _ = require('lodash'); const Serverless = require('../../../../../../../../Serverless'); const AwsProvider = require('../../../../../../provider/awsProvider'); const updateStage = require('./updateStage').updateStage; +chai.use(require('sinon-chai')); + +const { expect } = chai; + describe('#updateStage()', () => { let serverless; let options; From 149be17a56258415e627ebb5b5726a0ea37e95d6 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 10:52:08 +0200 Subject: [PATCH 264/504] Add missing utils --- lib/plugins/info/info.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/info/info.test.js b/lib/plugins/info/info.test.js index 24aba7c65..7e7febac7 100644 --- a/lib/plugins/info/info.test.js +++ b/lib/plugins/info/info.test.js @@ -7,6 +7,7 @@ const sinon = require('sinon'); const expect = chai.expect; chai.use(require('chai-as-promised')); +chai.use(require('sinon-chai')); describe('Info', () => { let info; From 8a639fe7771e7cf28fb1136dc872465a7e1c0c06 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 11:33:19 +0200 Subject: [PATCH 265/504] Increase timeout for randomly failing test --- lib/plugins/aws/invokeLocal/index.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 38433d504..d4863250e 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -29,7 +29,8 @@ describe('AwsInvokeLocal', () => { let provider; let stdinStub; - beforeEach(() => { + beforeEach(function () { + this.timeout(3000); // Shifted, as occasionally timeout error occured options = { stage: 'dev', region: 'us-east-1', From 35de9c44b69eefb383def90f3ad02891385f12bd Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 11:40:57 +0200 Subject: [PATCH 266/504] Alphabetical order --- scripts/test-isolated.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-isolated.js b/scripts/test-isolated.js index 83c08cc74..d0e14dba6 100755 --- a/scripts/test-isolated.js +++ b/scripts/test-isolated.js @@ -93,8 +93,8 @@ globby(patterns).then(paths => { stdio: isMultiProcessRun ? null : 'inherit', env: { FORCE_COLOR: '1', - PATH: process.env.PATH, HOME: process.env.HOME, + PATH: process.env.PATH, USERPROFILE: process.env.USERPROFILE, }, }).then(onFinally, error => { From 39c7a79140fbf46f5966bfc1e1a0883719f6fd67 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 11:41:53 +0200 Subject: [PATCH 267/504] Ensure to expose TMPDIR to nested processes --- scripts/test-isolated.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test-isolated.js b/scripts/test-isolated.js index d0e14dba6..59049b3a5 100755 --- a/scripts/test-isolated.js +++ b/scripts/test-isolated.js @@ -95,6 +95,7 @@ globby(patterns).then(paths => { FORCE_COLOR: '1', HOME: process.env.HOME, PATH: process.env.PATH, + TMPDIR: process.env.TMPDIR, USERPROFILE: process.env.USERPROFILE, }, }).then(onFinally, error => { From fbe0697fe5afee33e605d9b9decd32b2e4f6f949 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 11:47:52 +0200 Subject: [PATCH 268/504] Ensure needed utilities --- lib/plugins/create/create.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index 315974333..5c7d7f1d6 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -1,6 +1,6 @@ 'use strict'; -const expect = require('chai').expect; +const chai = require('chai'); const fs = require('fs'); const path = require('path'); const fse = require('fs-extra'); @@ -12,6 +12,9 @@ const download = require('../../utils/downloadTemplateFromRepo'); const userStats = require('../../utils/userStats'); const { getTmpDirPath } = require('../../../tests/utils/fs'); +chai.use(require('sinon-chai')); +const { expect } = require('chai'); + describe('Create', () => { let create; let logSpy; From dff3a53f845047d38f0eedcb919da185b08cf390 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 11:50:32 +0200 Subject: [PATCH 269/504] Ensure to print child output in case of failure --- lib/classes/CLI.test.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/classes/CLI.test.js b/lib/classes/CLI.test.js index cf88a0d7e..fa8e44148 100644 --- a/lib/classes/CLI.test.js +++ b/lib/classes/CLI.test.js @@ -635,8 +635,10 @@ describe('CLI', () => { }); it('should print general --help to stdout', (done) => { - exec(`${this.serverlessExec} --help`, (err, stdout) => { + exec(`${this.serverlessExec} --help`, (err, stdout, stderr) => { if (err) { + process.stdout.write(stdout); + process.stderr.write(stderr); done(err); return; } @@ -647,8 +649,10 @@ describe('CLI', () => { }); it('should print command --help to stdout', (done) => { - exec(`${this.serverlessExec} deploy --help`, (err, stdout) => { + exec(`${this.serverlessExec} deploy --help`, (err, stdout, stderr) => { if (err) { + process.stdout.write(stdout); + process.stderr.write(stderr); done(err); return; } @@ -660,8 +664,10 @@ describe('CLI', () => { }); it('should print help --verbose to stdout', (done) => { - exec(`${this.serverlessExec} help --verbose`, (err, stdout) => { + exec(`${this.serverlessExec} help --verbose`, (err, stdout, stderr) => { if (err) { + process.stdout.write(stdout); + process.stderr.write(stderr); done(err); return; } From 2dec7d8589348845341de68a61559c7d473bfedd Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 11:52:06 +0200 Subject: [PATCH 270/504] Use destructuring --- lib/plugins/package/lib/zipService.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/package/lib/zipService.test.js b/lib/plugins/package/lib/zipService.test.js index 2c2d09d6b..04ea02630 100644 --- a/lib/plugins/package/lib/zipService.test.js +++ b/lib/plugins/package/lib/zipService.test.js @@ -19,7 +19,7 @@ const { getTmpDirPath } = require('../../../../tests/utils/fs'); // Configure chai chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); -const expect = require('chai').expect; +const { expect } = require('chai'); describe('zipService', () => { let tmpDirPath; From 91ac18677d48dd1e0a930a6d5717d7de9352551b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 11:52:29 +0200 Subject: [PATCH 271/504] Ensure needed utils --- lib/plugins/install/install.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/plugins/install/install.test.js b/lib/plugins/install/install.test.js index be6763911..78e87a706 100644 --- a/lib/plugins/install/install.test.js +++ b/lib/plugins/install/install.test.js @@ -1,6 +1,6 @@ 'use strict'; -const expect = require('chai').expect; +const chai = require('chai'); const Serverless = require('../../Serverless'); const Install = require('./install.js'); const sinon = require('sinon'); @@ -10,6 +10,9 @@ const fse = require('fs-extra'); const path = require('path'); const { getTmpDirPath } = require('../../../tests/utils/fs'); +chai.use(require('sinon-chai')); +const { expect } = require('chai'); + describe('Install', () => { let install; let serverless; From 19d404442f87995a34057b3c2cff386dd1f7cee8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 11:53:58 +0200 Subject: [PATCH 272/504] Ensure needed utils --- lib/plugins/remove/remove.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/remove/remove.test.js b/lib/plugins/remove/remove.test.js index 5d114d7a6..9006a2518 100644 --- a/lib/plugins/remove/remove.test.js +++ b/lib/plugins/remove/remove.test.js @@ -6,6 +6,7 @@ const Serverless = require('../../Serverless'); const sinon = require('sinon'); chai.use(require('chai-as-promised')); +chai.use(require('sinon-chai')); const expect = chai.expect; From 31f5812b41693ecc4ea14e6029dfb7ba35f32eb0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 12:43:27 +0200 Subject: [PATCH 273/504] Ensure distinct homedir for each test file run --- tests/mocha-reporter.js | 6 ++++-- tests/utils/fs/index.js | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 755e1fe6b..c651cc274 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -24,7 +24,7 @@ const { join } = require('path'); const os = require('os'); const Spec = require('mocha/lib/reporters/spec'); const Runner = require('mocha/lib/runner'); -const { removeSync } = require('fs-extra'); +const { mkdirsSync, removeSync } = require('fs-extra'); const { tmpDirCommonPath } = require('../tests/utils/fs'); // Ensure faster tests propagation @@ -50,6 +50,8 @@ os.homedir = () => tmpDirCommonPath; if (process.env.USERPROFILE) process.env.USERPROFILE = tmpDirCommonPath; if (process.env.HOME) process.env.HOME = tmpDirCommonPath; +mkdirsSync(tmpDirCommonPath); // Ensure homedir exists + module.exports = class ServerlessSpec extends Spec { constructor(runner) { super(runner); @@ -62,7 +64,7 @@ module.exports = class ServerlessSpec extends Spec { runner.on('suite end', suite => { if (!suite.parent || !suite.parent.root) return; // Apply just on top level suites try { - removeSync(userConfig); + removeSync(tmpDirCommonPath); } catch (error) { if (error.code !== 'ENOENT') throw error; } diff --git a/tests/utils/fs/index.js b/tests/utils/fs/index.js index 728524f08..1a2ef1f65 100644 --- a/tests/utils/fs/index.js +++ b/tests/utils/fs/index.js @@ -7,7 +7,8 @@ const crypto = require('crypto'); const YAML = require('js-yaml'); const JSZip = require('jszip'); -const tmpDirCommonPath = path.join(os.tmpdir(), 'tmpdirs-serverless'); +const tmpDirCommonPath = path.join(os.tmpdir(), 'tmpdirs-serverless', + crypto.randomBytes(2).toString('hex')); function getTmpDirPath() { return path.join(tmpDirCommonPath, crypto.randomBytes(8).toString('hex')); From f026ccaabd306c7eb612305620718de72b81e169 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 12:45:32 +0200 Subject: [PATCH 274/504] Fix lint issue --- tests/mocha-reporter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index c651cc274..85034cd4c 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -60,7 +60,6 @@ module.exports = class ServerlessSpec extends Spec { // - Enforce eventual current directory change was reverted // - Ensure to reset of eventually created user config file const startCwd = process.cwd(); - const userConfig = join(tmpDirCommonPath, '.serverlessrc'); runner.on('suite end', suite => { if (!suite.parent || !suite.parent.root) return; // Apply just on top level suites try { From f50367168c01eac3b723e16986068919fc8e7e4d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 12:45:54 +0200 Subject: [PATCH 275/504] Fix lint issue --- tests/mocha-reporter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 85034cd4c..287e3409a 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -20,7 +20,6 @@ process.on('uncaughtException', err => { throw err; }); -const { join } = require('path'); const os = require('os'); const Spec = require('mocha/lib/reporters/spec'); const Runner = require('mocha/lib/runner'); From 8145ad7c53bbb21885c251eca92e65941ae17adb Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 12:50:48 +0200 Subject: [PATCH 276/504] Remove temporary homedir after all suites --- tests/mocha-reporter.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 287e3409a..9d12ba575 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -61,11 +61,6 @@ module.exports = class ServerlessSpec extends Spec { const startCwd = process.cwd(); runner.on('suite end', suite => { if (!suite.parent || !suite.parent.root) return; // Apply just on top level suites - try { - removeSync(tmpDirCommonPath); - } catch (error) { - if (error.code !== 'ENOENT') throw error; - } if (process.cwd() !== startCwd) { runner._abort = true; // eslint-disable-line no-underscore-dangle,no-param-reassign throw new Error( @@ -75,17 +70,24 @@ module.exports = class ServerlessSpec extends Spec { } }); - if (process.version[1] < 8) return; // Async leaks detector is not reliable in Node.js v6 + runner.on('end', () => { + // Cleanup temporary homedir + try { + removeSync(tmpDirCommonPath); + } catch (error) { + if (error.code !== 'ENOENT') throw error; + } - // Async leaks detection - runner.on('end', () => + if (process.version[1] < 8) return; // Async leaks detector is not reliable in Node.js v6 + + // Async leaks detection setTimeout(() => { // If tests end with any orphaned async call then this callback will be invoked // It's a signal there's some promise chain (or in general async flow) miconfiguration throw new Error('Test ended with unfinished async jobs'); // Timeout '2' to ensure no false positives, with '1' there are observable rare scenarios // of possibly a garbage collector delaying process exit being picked up - }, 2).unref() - ); + }, 2).unref(); + }); } }; From af888c296320f1fa1484218a499ae47d36b98bc8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 12:56:54 +0200 Subject: [PATCH 277/504] Improve comment --- tests/mocha-reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 9d12ba575..1b8c7854a 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -49,7 +49,7 @@ os.homedir = () => tmpDirCommonPath; if (process.env.USERPROFILE) process.env.USERPROFILE = tmpDirCommonPath; if (process.env.HOME) process.env.HOME = tmpDirCommonPath; -mkdirsSync(tmpDirCommonPath); // Ensure homedir exists +mkdirsSync(tmpDirCommonPath); // Ensure temporary homedir exists module.exports = class ServerlessSpec extends Spec { constructor(runner) { From 746fd046f0311bbd36d9ebe47a002bd18fe39a4d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:04:04 +0200 Subject: [PATCH 278/504] Ensure windows mandatory env vars --- scripts/test-isolated.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test-isolated.js b/scripts/test-isolated.js index 59049b3a5..af2138270 100755 --- a/scripts/test-isolated.js +++ b/scripts/test-isolated.js @@ -92,6 +92,7 @@ globby(patterns).then(paths => { return spawn('npx', ['mocha', path], { stdio: isMultiProcessRun ? null : 'inherit', env: { + APPDATA: process.env.APPDATA, FORCE_COLOR: '1', HOME: process.env.HOME, PATH: process.env.PATH, From d9ff5b6dc3ed7f81119875a5afab716baeb1d5db Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:09:10 +0200 Subject: [PATCH 279/504] Improve test timeout setting --- lib/classes/Service.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index d51876631..f4c337b2c 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -735,7 +735,8 @@ describe('Service', () => { } it(`should not throw an error if http event is absent and - stage contains only alphanumeric, underscore and hyphen`, () => { + stage contains only alphanumeric, underscore and hyphen`, function () { + this.timeout(4000); // Happens to timeout, especially on Windows VM const SUtils = new Utils(); const serverlessYml = { service: 'new-service', From c5c6a9229234459bdfd6a6793b867d5ace9b4549 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:16:33 +0200 Subject: [PATCH 280/504] Skip test on windows when no admin rights --- lib/classes/Variables.test.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index dcee3c2a2..b5d84889f 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -1669,13 +1669,21 @@ module.exports = { .should.become('hello world'); }); - it('should populate symlinks', () => { + it('should populate symlinks', function () { const SUtils = new Utils(); const tmpDirPath = getTmpDirPath(); const realFilePath = path.join(tmpDirPath, 'someFile'); const symlinkPath = path.join(tmpDirPath, 'refSomeFile'); SUtils.writeFileSync(realFilePath, 'hello world'); - fse.ensureSymlinkSync(realFilePath, symlinkPath); + try { + fse.ensureSymlinkSync(realFilePath, symlinkPath); + } catch (error) { + if ((error.code === 'EPERM') && (process.platform === 'win32')) { + // On Windows creating symlinks requires certain admin OS rights + this.skip(); + } + throw error; + } serverless.config.update({ servicePath: tmpDirPath }); return serverless.variables.getValueFromFile('file(./refSomeFile)') .should.become('hello world') From 6aaac1600096d77d0b33748cf9ba28c97c67881b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:19:51 +0200 Subject: [PATCH 281/504] Increase test timeout --- lib/plugins/aws/configCredentials/awsConfigCredentials.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index 05dd68021..7e48ac02f 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -18,7 +18,8 @@ describe('AwsConfigCredentials', () => { let serverless; let tmpDirPath; - beforeEach(() => { + beforeEach(function () { + this.timeout(4000); // Test ends to timeout, especially on Windows VM tmpDirPath = getTmpDirPath(); credentialsFilePath = path.join(tmpDirPath, '.aws', 'credentials'); credentialsFileContent = '[my-profile]\n'; From 98ce05b6a89b6ba0ddcc22b2df829bf10ae7fd33 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:20:16 +0200 Subject: [PATCH 282/504] Increase test timeout --- lib/classes/Service.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index f4c337b2c..7c28d9f12 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -736,7 +736,7 @@ describe('Service', () => { it(`should not throw an error if http event is absent and stage contains only alphanumeric, underscore and hyphen`, function () { - this.timeout(4000); // Happens to timeout, especially on Windows VM + this.timeout(5000); // Happens to timeout, especially on Windows VM const SUtils = new Utils(); const serverlessYml = { service: 'new-service', From fd6ca1a33b53df6574241cd8882cafca90cfb9ae Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:24:21 +0200 Subject: [PATCH 283/504] Do not crash if tmp dir delete fails on win --- tests/mocha-reporter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 1b8c7854a..a251b9eb4 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -75,7 +75,9 @@ module.exports = class ServerlessSpec extends Spec { try { removeSync(tmpDirCommonPath); } catch (error) { - if (error.code !== 'ENOENT') throw error; + if (error.code !== 'ENOENT' || (error.code !== 'EPERM' || process.platform !== 'win32')) { + throw error; + } } if (process.version[1] < 8) return; // Async leaks detector is not reliable in Node.js v6 From f97603cf4376bac9085f30d87e4b47ca49b16e5b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:25:30 +0200 Subject: [PATCH 284/504] Turn on colors on Windows --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index a7587aa35..e8211ae74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,9 @@ jobs: - name: "Unit Tests - Windows - Node.js v12" os: windows node_js: 12 + env: + - SLS_IGNORE_WARNING=* + - FORCE_COLOR=1 # For some reason on Windows colors support is not detected before_script: # Ensure Python 2 in Windows enviroment (Ruby is already preinstalled) - | From 3573f8d7e8c28689455612fbb22d65fb7aaa3b8e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:28:13 +0200 Subject: [PATCH 285/504] Increase test timeout --- lib/plugins/aws/configCredentials/awsConfigCredentials.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index 7e48ac02f..fa56fe7e7 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -19,7 +19,7 @@ describe('AwsConfigCredentials', () => { let tmpDirPath; beforeEach(function () { - this.timeout(4000); // Test ends to timeout, especially on Windows VM + this.timeout(5000); // Test ends to timeout, especially on Windows VM tmpDirPath = getTmpDirPath(); credentialsFilePath = path.join(tmpDirPath, '.aws', 'credentials'); credentialsFileContent = '[my-profile]\n'; From ae8e14c2b96370c6bb474defabebde565899af7a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:32:21 +0200 Subject: [PATCH 286/504] Fix condition --- tests/mocha-reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index a251b9eb4..4ebd8d790 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -75,7 +75,7 @@ module.exports = class ServerlessSpec extends Spec { try { removeSync(tmpDirCommonPath); } catch (error) { - if (error.code !== 'ENOENT' || (error.code !== 'EPERM' || process.platform !== 'win32')) { + if (error.code !== 'ENOENT' && (error.code !== 'EPERM' || process.platform !== 'win32')) { throw error; } } From 8aba5e3c84c207160db0d0027e2ffc51b3606252 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:47:09 +0200 Subject: [PATCH 287/504] Ignore failed attempt to remove temporary home dir --- tests/mocha-reporter.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 4ebd8d790..1676eff6c 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -75,9 +75,7 @@ module.exports = class ServerlessSpec extends Spec { try { removeSync(tmpDirCommonPath); } catch (error) { - if (error.code !== 'ENOENT' && (error.code !== 'EPERM' || process.platform !== 'win32')) { - throw error; - } + // Safe to ignore } if (process.version[1] < 8) return; // Async leaks detector is not reliable in Node.js v6 From aa9210e85b991d370de93ef3ac2c82114e21b212 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:51:53 +0200 Subject: [PATCH 288/504] Increase timeout --- lib/classes/Service.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index 7c28d9f12..21a301613 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -736,7 +736,7 @@ describe('Service', () => { it(`should not throw an error if http event is absent and stage contains only alphanumeric, underscore and hyphen`, function () { - this.timeout(5000); // Happens to timeout, especially on Windows VM + this.timeout(10000); // Happens to timeout, especially on Windows VM const SUtils = new Utils(); const serverlessYml = { service: 'new-service', From 2086c399e9618c2a34bbef9eca75c0871fa79e43 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:54:43 +0200 Subject: [PATCH 289/504] Increase timeout --- lib/plugins/aws/deployFunction/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/deployFunction/index.test.js b/lib/plugins/aws/deployFunction/index.test.js index d456cc390..9c35f6d6c 100644 --- a/lib/plugins/aws/deployFunction/index.test.js +++ b/lib/plugins/aws/deployFunction/index.test.js @@ -21,7 +21,7 @@ describe('AwsDeployFunction', () => { let cryptoStub; beforeEach(function () { - this.timeout(3000); + this.timeout(5000); // Happens to timeout, especially in Windows VM serverless = new Serverless(); serverless.servicePath = true; serverless.service.environment = { From c587e86ccb2eb78ed82f7e875383d83a4b85acb3 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:57:19 +0200 Subject: [PATCH 290/504] Bring back reset of user config By mistake it was removed with reset of homedir --- tests/mocha-reporter.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 1676eff6c..caf8fc673 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -20,6 +20,7 @@ process.on('uncaughtException', err => { throw err; }); +const { join } = require('path'); const os = require('os'); const Spec = require('mocha/lib/reporters/spec'); const Runner = require('mocha/lib/runner'); @@ -57,10 +58,16 @@ module.exports = class ServerlessSpec extends Spec { // After test run for given file finalizes: // - Enforce eventual current directory change was reverted - // - Ensure to reset of eventually created user config file + // - Ensure to reset eventually created user config file const startCwd = process.cwd(); + const userConfig = join(tmpDirCommonPath, '.serverlessrc'); runner.on('suite end', suite => { if (!suite.parent || !suite.parent.root) return; // Apply just on top level suites + try { + removeSync(userConfig); + } catch (error) { + if (error.code !== 'ENOENT') throw error; + } if (process.cwd() !== startCwd) { runner._abort = true; // eslint-disable-line no-underscore-dangle,no-param-reassign throw new Error( From 0b801a6acad90a39b2df429e2364dadbfd9117e8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 13:59:09 +0200 Subject: [PATCH 291/504] Increase timeout --- lib/plugins/aws/invokeLocal/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index d4863250e..9c0680a02 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -30,7 +30,7 @@ describe('AwsInvokeLocal', () => { let stdinStub; beforeEach(function () { - this.timeout(3000); // Shifted, as occasionally timeout error occured + this.timeout(5000); // Shifted, as occasionally timeout error occured options = { stage: 'dev', region: 'us-east-1', From 25dec543afa616d4e23a7abcd19ce7b6ae5bab7e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 14:37:03 +0200 Subject: [PATCH 292/504] Use more accurate ensureDirSync --- tests/mocha-reporter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index caf8fc673..ddddb6acc 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -24,7 +24,7 @@ const { join } = require('path'); const os = require('os'); const Spec = require('mocha/lib/reporters/spec'); const Runner = require('mocha/lib/runner'); -const { mkdirsSync, removeSync } = require('fs-extra'); +const { ensureDirSync, removeSync } = require('fs-extra'); const { tmpDirCommonPath } = require('../tests/utils/fs'); // Ensure faster tests propagation @@ -50,7 +50,7 @@ os.homedir = () => tmpDirCommonPath; if (process.env.USERPROFILE) process.env.USERPROFILE = tmpDirCommonPath; if (process.env.HOME) process.env.HOME = tmpDirCommonPath; -mkdirsSync(tmpDirCommonPath); // Ensure temporary homedir exists +ensureDirSync(tmpDirCommonPath); // Ensure temporary homedir exists module.exports = class ServerlessSpec extends Spec { constructor(runner) { From 9ff21c89cc9cdb04e8a97fb064bdb656ad56f827 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 15:05:43 +0200 Subject: [PATCH 293/504] Ensure to handle spawn error --- lib/plugins/aws/invokeLocal/index.js | 63 +++++++++++++++++++++------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 75aef59b9..78ecac7ba 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -390,20 +390,29 @@ class AwsInvokeLocal { ].join(''); } - return new BbPromise(resolve => { + return new BbPromise((resolve, reject) => { const python = spawn(runtime.split('.')[0], ['-u', path.join(__dirname, 'invoke.py'), handlerPath, handlerName], { env: process.env }, { shell: true }); python.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); python.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - python.stdin.write(input); - python.stdin.end(); python.on('close', () => resolve()); + let isRejected = false; + python.on('error', error => { + isRejected = true; + reject(error); + }); + + process.nextTick(() => { + if (isRejected) return; // Runtime not available + python.stdin.write(input); + python.stdin.end(); + }); }); } callJavaBridge(artifactPath, className, handlerName, input) { - return new BbPromise((resolve) => fs.statAsync(artifactPath).then(() => { + return new BbPromise((resolve, reject) => fs.statAsync(artifactPath).then(() => { const java = spawn('java', [ `-DartifactPath=${artifactPath}`, `-DclassName=${className}`, @@ -419,10 +428,19 @@ class AwsInvokeLocal { java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - java.stdin.write(input); - java.stdin.end(); java.on('close', () => resolve()); - }).catch(() => { + let isRejected = false; + java.on('error', error => { + isRejected = true; + reject(error); + }); + + process.nextTick(() => { + if (isRejected) return; // Runtime not available + java.stdin.write(input); + java.stdin.end(); + }); + }, () => { throw new Error(`Artifact ${artifactPath} doesn't exists, please compile it first.`); })); } @@ -445,10 +463,9 @@ class AwsInvokeLocal { const javaBridgePath = path.join(__dirname, 'java'); const executablePath = path.join(javaBridgePath, 'target'); - return new BbPromise(resolve => fs.statAsync(executablePath) + return new BbPromise((resolve, reject) => fs.statAsync(executablePath) .then(() => this.callJavaBridge(artifactPath, className, handlerName, input)) - .then(resolve) - .catch(() => { + .then(resolve, () => { const mvn = spawn('mvn', [ 'package', '-f', @@ -459,10 +476,19 @@ class AwsInvokeLocal { .log('Building Java bridge, first invocation might take a bit longer.'); mvn.stderr.on('data', (buf) => this.serverless.cli.consoleLog(`mvn - ${buf.toString()}`)); - mvn.stdin.end(); mvn.on('close', () => this.callJavaBridge(artifactPath, className, handlerName, input) .then(resolve)); + let isRejected = false; + mvn.on('error', error => { + isRejected = true; + reject(error); + }); + + process.nextTick(() => { + if (isRejected) return; // Runtime not available + mvn.stdin.end(); + }); })); } @@ -472,15 +498,24 @@ class AwsInvokeLocal { context, }); - return new BbPromise(resolve => { + return new BbPromise((resolve, reject) => { const ruby = spawn(runtime, [path.join(__dirname, 'invoke.rb'), handlerPath, handlerName], { env: process.env }, { shell: true }); ruby.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); ruby.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - ruby.stdin.write(input); - ruby.stdin.end(); ruby.on('close', () => resolve()); + let isRejected = false; + ruby.on('error', error => { + isRejected = true; + reject(error); + }); + + process.nextTick(() => { + if (isRejected) return; // Runtime not available + ruby.stdin.write(input); + ruby.stdin.end(); + }); }); } From 77118040df1da695b8d2b1f8179d1ee435d03f6a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 15:06:12 +0200 Subject: [PATCH 294/504] Fix mock --- lib/plugins/aws/invokeLocal/index.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 9c0680a02..be4f7d1be 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -985,7 +985,9 @@ describe('AwsInvokeLocal', () => { write: writeChildStub, end: endChildStub, }, - on: (key, callback) => callback(), + on: (key, callback) => { + if (key === 'close') process.nextTick(callback); + }, }), }); From dca31748165298f0e6a2b76546c6fb5babda5d5c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 15:39:01 +0200 Subject: [PATCH 295/504] Patch mocha handling of uncaught exceptions --- tests/mocha-reporter.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index ddddb6acc..67bb5707a 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -6,20 +6,6 @@ process.on('unhandledRejection', err => { throw err; }); -// If there's an uncaught exception after rest runner wraps up -// Mocha reports it with success exit code: https://github.com/mochajs/mocha/issues/3917 -// Workaround that (otherwise we may end with green CI for failed builds): -process.on('uncaughtException', err => { - if (!process.listenerCount('exit')) { - if (process.listenerCount('uncaughtException') === 1) throw err; - return; - } - // Mocha done it's report, and registered process.exit listener which silences any further - // eventual crashes. Recover by unregistering the listener - process.removeAllListeners('exit'); - throw err; -}); - const { join } = require('path'); const os = require('os'); const Spec = require('mocha/lib/reporters/spec'); @@ -56,6 +42,24 @@ module.exports = class ServerlessSpec extends Spec { constructor(runner) { super(runner); + process.on('uncaughtException', err => { + if (!process.listenerCount('exit')) { + if (process.listenerCount('uncaughtException') === 1) { + // Mocha didn't setup listeners yet, ensure error is exposed + throw err; + } + + // Mocha ignores uncaught exceptions if they happen in conext of skipped test, expose them + if (runner.currentRunnable.isPending()) throw err; + return; + } + // If there's an uncaught exception after rest runner wraps up + // Mocha reports it with success exit code: https://github.com/mochajs/mocha/issues/3917 + // Workaround that (otherwise we may end with green CI for failed builds): + process.removeAllListeners('exit'); + throw err; + }); + // After test run for given file finalizes: // - Enforce eventual current directory change was reverted // - Ensure to reset eventually created user config file From 2dd18e9772e2f92cfd43d1bcefb3ba6079a6e1f5 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 15:49:10 +0200 Subject: [PATCH 296/504] Link mocha bug report --- tests/mocha-reporter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 67bb5707a..6a04db9e7 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -50,6 +50,7 @@ module.exports = class ServerlessSpec extends Spec { } // Mocha ignores uncaught exceptions if they happen in conext of skipped test, expose them + // https://github.com/mochajs/mocha/issues/3938 if (runner.currentRunnable.isPending()) throw err; return; } From 7d2385189725b28b704d2f54008bbb72601da407 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 15:56:26 +0200 Subject: [PATCH 297/504] Skip tests for not installed runtimes --- lib/plugins/aws/invokeLocal/index.test.js | 53 +++++++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index be4f7d1be..314c47ec1 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -8,6 +8,7 @@ const mockRequire = require('mock-require'); const EventEmitter = require('events'); const fse = require('fs-extra'); const proxyquire = require('proxyquire'); +const chalk = require('chalk'); const stripAnsi = require('strip-ansi'); const overrideEnv = require('process-utils/override-env'); const AwsProvider = require('../provider/awsProvider'); @@ -887,9 +888,10 @@ describe('AwsInvokeLocal', () => { sinon.stub(serverless.cli, 'consoleLog'); }); - afterEach(() => { + const afterEachCallback = () => { serverless.cli.consoleLog.restore(); - }); + }; + afterEach(afterEachCallback); describe('context.remainingTimeInMillis', () => { it('should become lower over time', function () { @@ -907,6 +909,20 @@ describe('AwsInvokeLocal', () => { 'withRemainingTime').then(() => { const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); expect(remainingTimes.start).to.be.above(remainingTimes.stop); + }, error => { + if (error.code === 'ENOENT' && error.path === 'python2' && !process.env.CI) { + process.stdout.write( + chalk.red('Could not test Python runtime, as it\'s not installed\n') + ); + try { + this.skip(); + } finally { + // Ensure teardown is called + // (Mocha doesn't call it -> https://github.com/mochajs/mocha/issues/3740) + afterEachCallback(); + } + } + throw error; }); }); }); @@ -927,10 +943,11 @@ describe('AwsInvokeLocal', () => { sinon.stub(serverless.cli, 'consoleLog'); }); - afterEach(() => { + const afterEachCallback = () => { serverless.cli.consoleLog.restore(); process.chdir(curdir); - }); + }; + afterEach(afterEachCallback); describe('context.remainingTimeInMillis', () => { it('should become lower over time', function () { @@ -943,6 +960,20 @@ describe('AwsInvokeLocal', () => { 'withRemainingTime').then(() => { const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); expect(remainingTimes.start).to.be.above(remainingTimes.stop); + }, error => { + if (error.code === 'ENOENT' && error.path === 'ruby' && !process.env.CI) { + process.stdout.write( + chalk.red('Could not test Ruby runtime, as it\'s not installed\n') + ); + try { + this.skip(); + } finally { + // Ensure teardown is called + // (Mocha doesn't call it -> https://github.com/mochajs/mocha/issues/3740) + afterEachCallback(); + } + } + throw error; }); }); }); @@ -959,6 +990,20 @@ describe('AwsInvokeLocal', () => { 'MyModule::MyClass.my_class_method').then(() => { const result = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); expect(result.foo).to.eq('bar'); + }, error => { + if (error.code === 'ENOENT' && error.path === 'ruby' && !process.env.CI) { + process.stdout.write( + chalk.red('Could not test Ruby runtime, as it\'s not installed\n') + ); + try { + this.skip(); + } finally { + // Ensure teardown is called + // (Mocha doesn't call it -> https://github.com/mochajs/mocha/issues/3740) + afterEachCallback(); + } + } + throw error; }); }); }); From d25730344c4057f0540ce660c069db9819b3f05b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 16:01:41 +0200 Subject: [PATCH 298/504] Improve comment --- lib/plugins/aws/invokeLocal/index.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 314c47ec1..329d68398 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -918,7 +918,7 @@ describe('AwsInvokeLocal', () => { this.skip(); } finally { // Ensure teardown is called - // (Mocha doesn't call it -> https://github.com/mochajs/mocha/issues/3740) + // (Mocha fails to do it -> https://github.com/mochajs/mocha/issues/3740) afterEachCallback(); } } @@ -969,7 +969,7 @@ describe('AwsInvokeLocal', () => { this.skip(); } finally { // Ensure teardown is called - // (Mocha doesn't call it -> https://github.com/mochajs/mocha/issues/3740) + // (Mocha fails to do -> https://github.com/mochajs/mocha/issues/3740) afterEachCallback(); } } @@ -999,7 +999,7 @@ describe('AwsInvokeLocal', () => { this.skip(); } finally { // Ensure teardown is called - // (Mocha doesn't call it -> https://github.com/mochajs/mocha/issues/3740) + // (Mocha fails to do it -> https://github.com/mochajs/mocha/issues/3740) afterEachCallback(); } } From 7955e2c3c474152d82b99e9515eced595b477ec8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 16:03:44 +0200 Subject: [PATCH 299/504] Improve timeout settings --- lib/plugins/package/package.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/package/package.test.js b/lib/plugins/package/package.test.js index 38608e0cf..6d0f7e1c4 100644 --- a/lib/plugins/package/package.test.js +++ b/lib/plugins/package/package.test.js @@ -15,7 +15,8 @@ describe('Package', () => { let options; let pkg; - beforeEach(() => { + beforeEach(function () { + this.timeout(5000); // Ocassionally timed out with default settings serverless = new Serverless(); return serverless.init().then(() => { options = { From 7245160416d868c8dfffc1dcfbc88cd1cadc0b15 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 16:06:54 +0200 Subject: [PATCH 300/504] Improve comments --- lib/plugins/aws/invokeLocal/index.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 329d68398..294aac700 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -912,7 +912,7 @@ describe('AwsInvokeLocal', () => { }, error => { if (error.code === 'ENOENT' && error.path === 'python2' && !process.env.CI) { process.stdout.write( - chalk.red('Could not test Python runtime, as it\'s not installed\n') + chalk.red('Could not test Python runtime as it\'s not installed\n') ); try { this.skip(); @@ -963,7 +963,7 @@ describe('AwsInvokeLocal', () => { }, error => { if (error.code === 'ENOENT' && error.path === 'ruby' && !process.env.CI) { process.stdout.write( - chalk.red('Could not test Ruby runtime, as it\'s not installed\n') + chalk.red('Could not test Ruby runtime as it\'s not installed\n') ); try { this.skip(); @@ -993,7 +993,7 @@ describe('AwsInvokeLocal', () => { }, error => { if (error.code === 'ENOENT' && error.path === 'ruby' && !process.env.CI) { process.stdout.write( - chalk.red('Could not test Ruby runtime, as it\'s not installed\n') + chalk.red('Could not test Ruby runtime as it\'s not installed\n') ); try { this.skip(); From c733f821f35a6c260c3cd88af8abaaa869ddb9db Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 16:45:31 +0200 Subject: [PATCH 301/504] Increase test timeout setting --- .../events/apiGateway/lib/validate.test.js | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js index a0e692af1..9fce82cd3 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js @@ -1490,40 +1490,42 @@ describe('#validate()', () => { expect(() => awsCompileApigEvents.validate()).to.throw(Error); }); - it('should show a warning message when using request / response config with HTTP-PROXY', () => { - awsCompileApigEvents.serverless.service.functions = { - first: { - events: [ - { - http: { - method: 'GET', - path: 'users/list', - integration: 'http-proxy', - request: { - uri: 'http://www.example.com', - template: { - 'template/1': '{ "stage" : "$context.stage" }', - 'template/2': '{ "httpMethod" : "$context.httpMethod" }', + it('should show a warning message when using request / response config with HTTP-PROXY', + function () { + this.timeout(5000); // Occasionally times out with default settings + awsCompileApigEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + method: 'GET', + path: 'users/list', + integration: 'http-proxy', + request: { + uri: 'http://www.example.com', + template: { + 'template/1': '{ "stage" : "$context.stage" }', + 'template/2': '{ "httpMethod" : "$context.httpMethod" }', + }, + }, + response: { + template: "$input.path('$.foo')", }, }, - response: { - template: "$input.path('$.foo')", - }, }, - }, - ], - }, - }; - // initialize so we get the log method from the CLI in place - return serverless.init().then(() => { - const logStub = sinon.stub(serverless.cli, 'log'); + ], + }, + }; + // initialize so we get the log method from the CLI in place + return serverless.init().then(() => { + const logStub = sinon.stub(serverless.cli, 'log'); - awsCompileApigEvents.validate(); + awsCompileApigEvents.validate(); - expect(logStub.calledTwice).to.be.equal(true); - expect(logStub.args[0][0].length).to.be.at.least(1); + expect(logStub.calledTwice).to.be.equal(true); + expect(logStub.args[0][0].length).to.be.at.least(1); + }); }); - }); it('should not show a warning message when using request.parameter with HTTP-PROXY', () => { awsCompileApigEvents.serverless.service.functions = { From 2bc1b2925c7aa9c2eaf7b3ddf1a627fa5fb7ff0b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 4 Jun 2019 16:49:31 +0200 Subject: [PATCH 302/504] Increase timeout --- lib/plugins/install/install.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/install/install.test.js b/lib/plugins/install/install.test.js index 78e87a706..63f07f999 100644 --- a/lib/plugins/install/install.test.js +++ b/lib/plugins/install/install.test.js @@ -21,7 +21,8 @@ describe('Install', () => { let servicePath; - beforeEach(() => { + beforeEach(function () { + this.timeout(5000); // Occassionally times out with default setting const tmpDir = getTmpDirPath(); cwd = process.cwd(); From 194a12e799e13d0741e5c893876bbfc656d23f9e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 09:40:11 +0200 Subject: [PATCH 303/504] Skip with notice functionality --- tests/mocha-reporter.js | 25 +++++++++++++++++++++++++ tests/utils/misc/index.js | 18 ++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index 6a04db9e7..e9ecf9445 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -11,7 +11,9 @@ const os = require('os'); const Spec = require('mocha/lib/reporters/spec'); const Runner = require('mocha/lib/runner'); const { ensureDirSync, removeSync } = require('fs-extra'); +const chalk = require('chalk'); const { tmpDirCommonPath } = require('../tests/utils/fs'); +const { skippedWithNotice } = require('../tests/utils/misc'); // Ensure faster tests propagation // It's to expose errors otherwise hidden by race conditions @@ -83,6 +85,29 @@ module.exports = class ServerlessSpec extends Spec { }); runner.on('end', () => { + // Output eventual skip notices + if (skippedWithNotice.length) { + const resolveTestName = test => { + const names = [test.title]; + let parent = test.parent; + while (parent) { + if (parent.title) names.push(parent.title); + parent = parent.parent; + } + return `${chalk.cyan(names.reverse().join(': '))} (in: ${chalk.grey( + test.file.slice(process.cwd().length + 1) + )})`; + }; + process.stdout.write( + ' Notice: Some tests were skipped due to following environment issues:' + + `\n\n - ${skippedWithNotice + .map( + meta => `${resolveTestName(meta.context.test)}\n\n ${chalk.red(meta.reason)}\n` + ) + .join('\n - ')}\n\n` + ); + } + // Cleanup temporary homedir try { removeSync(tmpDirCommonPath); diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 4dd68fc7d..2d928ce7b 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -4,6 +4,7 @@ const path = require('path'); const fse = require('fs-extra'); const BbPromise = require('bluebird'); const { execSync } = require('child_process'); +const chalk = require('chalk'); const { replaceTextInFile } = require('../fs'); const logger = console; @@ -102,6 +103,20 @@ function persistentRequest(...args) { }); } +const skippedWithNotice = []; + +function skipWithNotice(context, reason) { + if (process.env.CI) return; // Do not tolerate skips in CI environment + skippedWithNotice.push({ context, reason }); + process.stdout.write(chalk.yellow(`\n Skipped due to: ${chalk.red(reason)}\n\n`)); + context.skip(); +} + +function skipOnWindowsDisabledSymlinks(error, context) { + if (error.code !== 'EPERM' || process.platform !== 'win32') return; + skipWithNotice(context, 'Missing admin rights to create symlinks'); +} + module.exports = { logger, region, @@ -115,4 +130,7 @@ module.exports = { createTestService, getFunctionLogs, persistentRequest, + skippedWithNotice, + skipWithNotice, + skipOnWindowsDisabledSymlinks, }; From cde78a984f90303720e881e4b0f74531ab64c95e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 09:40:33 +0200 Subject: [PATCH 304/504] Skip with notice on lack of admin rights --- lib/utils/fs/walkDirSync.test.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/utils/fs/walkDirSync.test.js b/lib/utils/fs/walkDirSync.test.js index 27f3466cd..4780e071a 100644 --- a/lib/utils/fs/walkDirSync.test.js +++ b/lib/utils/fs/walkDirSync.test.js @@ -6,6 +6,7 @@ const writeFileSync = require('./writeFileSync'); const walkDirSync = require('./walkDirSync'); const { expect } = require('chai'); const { getTmpDirPath } = require('../../../tests/utils/fs'); +const { skipOnWindowsDisabledSymlinks } = require('../../../tests/utils/misc'); describe('#walkDirSync()', () => { it('should return an array with corresponding paths to the found files', () => { @@ -30,14 +31,19 @@ describe('#walkDirSync()', () => { expect(filePaths).to.include(tmpFilePath3); }); - it('should check noLinks option', () => { + it('should check noLinks option', function () { const tmpDirPath = getTmpDirPath(); const realFile = path.join(tmpDirPath, 'real'); writeFileSync(realFile, 'content'); const symLink = path.join(tmpDirPath, 'sym'); - fs.symlinkSync(realFile, symLink); + try { + fs.symlinkSync(realFile, symLink); + } catch (error) { + skipOnWindowsDisabledSymlinks(error, this); + throw error; + } const filePaths = walkDirSync(tmpDirPath, { noLinks: true, From a68150356cbe9c2debd31e730f1fdb4b52f43fb9 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 09:45:29 +0200 Subject: [PATCH 305/504] Support pass of after callback --- tests/utils/misc/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 2d928ce7b..d4f63eb4d 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -105,10 +105,19 @@ function persistentRequest(...args) { const skippedWithNotice = []; -function skipWithNotice(context, reason) { +function skipWithNotice(context, reason, afterCallback) { if (process.env.CI) return; // Do not tolerate skips in CI environment skippedWithNotice.push({ context, reason }); process.stdout.write(chalk.yellow(`\n Skipped due to: ${chalk.red(reason)}\n\n`)); + if (afterCallback) { + try { + // Ensure teardown is called + // (Mocha fails to do it -> https://github.com/mochajs/mocha/issues/3740) + afterCallback(); + } catch (error) { + process.stdout.write(chalk.error(`after callback crashed with: ${error.stack}\n`)); + } + } context.skip(); } From 67a67ae780b89075c7bc8456f9da4f80459ebcce Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 09:47:59 +0200 Subject: [PATCH 306/504] Validate against invalid suite injection --- tests/utils/misc/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index d4f63eb4d..9f944d997 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -106,6 +106,9 @@ function persistentRequest(...args) { const skippedWithNotice = []; function skipWithNotice(context, reason, afterCallback) { + if (!context || typeof context.skip !== 'function') { + throw new TypeError('Passed context is not a valid mocha suite'); + } if (process.env.CI) return; // Do not tolerate skips in CI environment skippedWithNotice.push({ context, reason }); process.stdout.write(chalk.yellow(`\n Skipped due to: ${chalk.red(reason)}\n\n`)); From aa5658a7dc28b13e50c73e67d2765a0358474095 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 09:52:26 +0200 Subject: [PATCH 307/504] Support afterCallback in symlink fail skip --- tests/utils/misc/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 9f944d997..4c669a8c8 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -124,9 +124,9 @@ function skipWithNotice(context, reason, afterCallback) { context.skip(); } -function skipOnWindowsDisabledSymlinks(error, context) { +function skipOnWindowsDisabledSymlinks(error, context, afterCallback) { if (error.code !== 'EPERM' || process.platform !== 'win32') return; - skipWithNotice(context, 'Missing admin rights to create symlinks'); + skipWithNotice(context, 'Missing admin rights to create symlinks', afterCallback); } module.exports = { From 0f181edd672102e1c1042d8a7a4ee5dbfe48b2c2 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 09:53:33 +0200 Subject: [PATCH 308/504] Rely on skipWithNotice --- lib/classes/Variables.test.js | 6 ++-- lib/plugins/aws/invokeLocal/index.test.js | 41 ++++------------------- 2 files changed, 9 insertions(+), 38 deletions(-) diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index b5d84889f..3bec38358 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -20,6 +20,7 @@ const slsError = require('./Error'); const Utils = require('../../lib/classes/Utils'); const Variables = require('../../lib/classes/Variables'); const { getTmpDirPath } = require('../../tests/utils/fs'); +const { skipOnWindowsDisabledSymlinks } = require('../../tests/utils/misc'); BbPromise.longStackTraces(true); @@ -1678,10 +1679,7 @@ module.exports = { try { fse.ensureSymlinkSync(realFilePath, symlinkPath); } catch (error) { - if ((error.code === 'EPERM') && (process.platform === 'win32')) { - // On Windows creating symlinks requires certain admin OS rights - this.skip(); - } + skipOnWindowsDisabledSymlinks(error, this); throw error; } serverless.config.update({ servicePath: tmpDirPath }); diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 294aac700..2512578af 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -8,13 +8,13 @@ const mockRequire = require('mock-require'); const EventEmitter = require('events'); const fse = require('fs-extra'); const proxyquire = require('proxyquire'); -const chalk = require('chalk'); const stripAnsi = require('strip-ansi'); const overrideEnv = require('process-utils/override-env'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); const CLI = require('../../../classes/CLI'); const { getTmpDirPath } = require('../../../../tests/utils/fs'); +const { skipWithNotice } = require('../../../../tests/utils/misc'); chai.use(require('chai-as-promised')); @@ -910,17 +910,8 @@ describe('AwsInvokeLocal', () => { const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); expect(remainingTimes.start).to.be.above(remainingTimes.stop); }, error => { - if (error.code === 'ENOENT' && error.path === 'python2' && !process.env.CI) { - process.stdout.write( - chalk.red('Could not test Python runtime as it\'s not installed\n') - ); - try { - this.skip(); - } finally { - // Ensure teardown is called - // (Mocha fails to do it -> https://github.com/mochajs/mocha/issues/3740) - afterEachCallback(); - } + if (error.code === 'ENOENT' && error.path === 'python2') { + skipWithNotice(this, 'Python runtime is not installed', afterEachCallback); } throw error; }); @@ -961,17 +952,8 @@ describe('AwsInvokeLocal', () => { const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); expect(remainingTimes.start).to.be.above(remainingTimes.stop); }, error => { - if (error.code === 'ENOENT' && error.path === 'ruby' && !process.env.CI) { - process.stdout.write( - chalk.red('Could not test Ruby runtime as it\'s not installed\n') - ); - try { - this.skip(); - } finally { - // Ensure teardown is called - // (Mocha fails to do -> https://github.com/mochajs/mocha/issues/3740) - afterEachCallback(); - } + if (error.code === 'ENOENT' && error.path === 'ruby') { + skipWithNotice(this, 'Ruby runtime is not installed', afterEachCallback); } throw error; }); @@ -991,17 +973,8 @@ describe('AwsInvokeLocal', () => { const result = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); expect(result.foo).to.eq('bar'); }, error => { - if (error.code === 'ENOENT' && error.path === 'ruby' && !process.env.CI) { - process.stdout.write( - chalk.red('Could not test Ruby runtime as it\'s not installed\n') - ); - try { - this.skip(); - } finally { - // Ensure teardown is called - // (Mocha fails to do it -> https://github.com/mochajs/mocha/issues/3740) - afterEachCallback(); - } + if (error.code === 'ENOENT' && error.path === 'ruby') { + skipWithNotice(this, 'Ruby runtime is not installed', afterEachCallback); } throw error; }); From 7502b0dd14608350dd3042f9409520c6a278296b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 10:03:31 +0200 Subject: [PATCH 309/504] Shift timeout --- lib/plugins/slstats/slstats.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/slstats/slstats.test.js b/lib/plugins/slstats/slstats.test.js index 3affbb65c..62c2c9ad8 100644 --- a/lib/plugins/slstats/slstats.test.js +++ b/lib/plugins/slstats/slstats.test.js @@ -11,7 +11,8 @@ describe('SlStats', () => { let slStats; let serverless; - beforeEach(() => { + beforeEach(function () { + this.timeout(5000); // Occasionally times out with default settings serverless = new Serverless(); return serverless.init().then(() => { slStats = new SlStats(serverless); From d3b7d2eac031ffe2756049e34a18184876071185 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 10:07:05 +0200 Subject: [PATCH 310/504] Unify timeout notices --- lib/classes/Service.test.js | 2 +- lib/plugins/aws/configCredentials/awsConfigCredentials.test.js | 2 +- lib/plugins/aws/deployFunction/index.test.js | 2 +- lib/plugins/aws/invokeLocal/index.test.js | 2 +- lib/plugins/install/install.test.js | 2 +- lib/plugins/package/package.test.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index 21a301613..6ac6f7d3b 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -736,7 +736,7 @@ describe('Service', () => { it(`should not throw an error if http event is absent and stage contains only alphanumeric, underscore and hyphen`, function () { - this.timeout(10000); // Happens to timeout, especially on Windows VM + this.timeout(10000); // Occasionally times out with default settings const SUtils = new Utils(); const serverlessYml = { service: 'new-service', diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index fa56fe7e7..3bc31f17c 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -19,7 +19,7 @@ describe('AwsConfigCredentials', () => { let tmpDirPath; beforeEach(function () { - this.timeout(5000); // Test ends to timeout, especially on Windows VM + this.timeout(5000); // Occasionally times out with default settings tmpDirPath = getTmpDirPath(); credentialsFilePath = path.join(tmpDirPath, '.aws', 'credentials'); credentialsFileContent = '[my-profile]\n'; diff --git a/lib/plugins/aws/deployFunction/index.test.js b/lib/plugins/aws/deployFunction/index.test.js index 9c35f6d6c..33545df02 100644 --- a/lib/plugins/aws/deployFunction/index.test.js +++ b/lib/plugins/aws/deployFunction/index.test.js @@ -21,7 +21,7 @@ describe('AwsDeployFunction', () => { let cryptoStub; beforeEach(function () { - this.timeout(5000); // Happens to timeout, especially in Windows VM + this.timeout(5000); // Occasionally times out with default settings serverless = new Serverless(); serverless.servicePath = true; serverless.service.environment = { diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 2512578af..eb2068453 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -31,7 +31,7 @@ describe('AwsInvokeLocal', () => { let stdinStub; beforeEach(function () { - this.timeout(5000); // Shifted, as occasionally timeout error occured + this.timeout(5000); // Occasionally times out with default settings options = { stage: 'dev', region: 'us-east-1', diff --git a/lib/plugins/install/install.test.js b/lib/plugins/install/install.test.js index 63f07f999..32862e60e 100644 --- a/lib/plugins/install/install.test.js +++ b/lib/plugins/install/install.test.js @@ -22,7 +22,7 @@ describe('Install', () => { let servicePath; beforeEach(function () { - this.timeout(5000); // Occassionally times out with default setting + this.timeout(5000); // Occasionally times out with default settings const tmpDir = getTmpDirPath(); cwd = process.cwd(); diff --git a/lib/plugins/package/package.test.js b/lib/plugins/package/package.test.js index 6d0f7e1c4..be992d3d7 100644 --- a/lib/plugins/package/package.test.js +++ b/lib/plugins/package/package.test.js @@ -16,7 +16,7 @@ describe('Package', () => { let pkg; beforeEach(function () { - this.timeout(5000); // Ocassionally timed out with default settings + this.timeout(5000); // Occasionally times out with default settings serverless = new Serverless(); return serverless.init().then(() => { options = { From 036d1b0527e5b447cd2e630625385275b8aee9e0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 10:23:15 +0200 Subject: [PATCH 311/504] Shift timeout setting --- lib/utils/getCommandSuggestion.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/utils/getCommandSuggestion.test.js b/lib/utils/getCommandSuggestion.test.js index 499570f6b..ea9677606 100644 --- a/lib/utils/getCommandSuggestion.test.js +++ b/lib/utils/getCommandSuggestion.test.js @@ -7,7 +7,8 @@ const Serverless = require('../../lib/Serverless'); describe('#getCommandSuggestion', () => { let serverless; - before(() => { + before(function () { + this.timeout(5000); // Occasionally times out with default settings serverless = new Serverless(); return serverless.init(); }); From 8ca00b6f9fb4562b25491d52f10238699de5f176 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 10:59:22 +0200 Subject: [PATCH 312/504] Increase default timeout Some tests tend to timeout especially when run on Windows in VM --- lib/classes/PluginManager.test.js | 8 +-- lib/classes/Service.test.js | 3 +- .../awsConfigCredentials.test.js | 3 +- lib/plugins/aws/deployFunction/index.test.js | 3 +- lib/plugins/aws/invokeLocal/index.test.js | 10 +--- .../events/apiGateway/lib/validate.test.js | 58 +++++++++---------- lib/plugins/install/install.test.js | 3 +- lib/plugins/package/package.test.js | 3 +- lib/plugins/slstats/slstats.test.js | 3 +- lib/utils/getCommandSuggestion.test.js | 3 +- package.json | 3 +- 11 files changed, 42 insertions(+), 58 deletions(-) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 43e3fc0cd..e00c4fd2b 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -722,9 +722,7 @@ describe('PluginManager', () => { mockRequire.stop('ServicePluginMock1'); }); }); - describe('#asyncPluginInit()', function () { - this.timeout(5000); - + describe('#asyncPluginInit()', () => { it('should call async init on plugins that have it', () => { const plugin1 = new ServicePluginMock1(); plugin1.asyncInit = sinon.stub().returns(BbPromise.resolve()); @@ -735,9 +733,7 @@ describe('PluginManager', () => { }); }); - describe('#loadAllPlugins()', function () { - this.timeout(5000); - + describe('#loadAllPlugins()', () => { beforeEach(function () { // eslint-disable-line prefer-arrow-callback mockRequire('ServicePluginMock1', ServicePluginMock1); mockRequire('ServicePluginMock2', ServicePluginMock2); diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index 6ac6f7d3b..d5342a097 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -123,8 +123,7 @@ describe('Service', () => { return expect(noService.load()).to.be.fulfilled; }); - it('should load serverless.yml from filesystem', function () { - this.timeout(3000); + it('should load serverless.yml from filesystem', () => { const SUtils = new Utils(); const serverlessYml = { service: 'new-service', diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index 3bc31f17c..05dd68021 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -18,8 +18,7 @@ describe('AwsConfigCredentials', () => { let serverless; let tmpDirPath; - beforeEach(function () { - this.timeout(5000); // Occasionally times out with default settings + beforeEach(() => { tmpDirPath = getTmpDirPath(); credentialsFilePath = path.join(tmpDirPath, '.aws', 'credentials'); credentialsFileContent = '[my-profile]\n'; diff --git a/lib/plugins/aws/deployFunction/index.test.js b/lib/plugins/aws/deployFunction/index.test.js index 33545df02..0066ea8a5 100644 --- a/lib/plugins/aws/deployFunction/index.test.js +++ b/lib/plugins/aws/deployFunction/index.test.js @@ -20,8 +20,7 @@ describe('AwsDeployFunction', () => { let awsDeployFunction; let cryptoStub; - beforeEach(function () { - this.timeout(5000); // Occasionally times out with default settings + beforeEach(() => { serverless = new Serverless(); serverless.servicePath = true; serverless.service.environment = { diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index eb2068453..db5817cb5 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -30,8 +30,7 @@ describe('AwsInvokeLocal', () => { let provider; let stdinStub; - beforeEach(function () { - this.timeout(5000); // Occasionally times out with default settings + beforeEach(() => { options = { stage: 'dev', region: 'us-east-1', @@ -941,8 +940,7 @@ describe('AwsInvokeLocal', () => { afterEach(afterEachCallback); describe('context.remainingTimeInMillis', () => { - it('should become lower over time', function () { - this.timeout(3000); // On Windows it tends to timeout with 2000 + it('should become lower over time', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; return awsInvokeLocal.invokeLocalRuby( @@ -961,9 +959,7 @@ describe('AwsInvokeLocal', () => { }); describe('calling a class method', () => { - it('should execute', function () { - this.timeout(3000); // On Windows it tends to timeout with 2000 - + it('should execute', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; return awsInvokeLocal.invokeLocalRuby( diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js index 9fce82cd3..a0e692af1 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js @@ -1490,42 +1490,40 @@ describe('#validate()', () => { expect(() => awsCompileApigEvents.validate()).to.throw(Error); }); - it('should show a warning message when using request / response config with HTTP-PROXY', - function () { - this.timeout(5000); // Occasionally times out with default settings - awsCompileApigEvents.serverless.service.functions = { - first: { - events: [ - { - http: { - method: 'GET', - path: 'users/list', - integration: 'http-proxy', - request: { - uri: 'http://www.example.com', - template: { - 'template/1': '{ "stage" : "$context.stage" }', - 'template/2': '{ "httpMethod" : "$context.httpMethod" }', - }, - }, - response: { - template: "$input.path('$.foo')", + it('should show a warning message when using request / response config with HTTP-PROXY', () => { + awsCompileApigEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + method: 'GET', + path: 'users/list', + integration: 'http-proxy', + request: { + uri: 'http://www.example.com', + template: { + 'template/1': '{ "stage" : "$context.stage" }', + 'template/2': '{ "httpMethod" : "$context.httpMethod" }', }, }, + response: { + template: "$input.path('$.foo')", + }, }, - ], - }, - }; - // initialize so we get the log method from the CLI in place - return serverless.init().then(() => { - const logStub = sinon.stub(serverless.cli, 'log'); + }, + ], + }, + }; + // initialize so we get the log method from the CLI in place + return serverless.init().then(() => { + const logStub = sinon.stub(serverless.cli, 'log'); - awsCompileApigEvents.validate(); + awsCompileApigEvents.validate(); - expect(logStub.calledTwice).to.be.equal(true); - expect(logStub.args[0][0].length).to.be.at.least(1); - }); + expect(logStub.calledTwice).to.be.equal(true); + expect(logStub.args[0][0].length).to.be.at.least(1); }); + }); it('should not show a warning message when using request.parameter with HTTP-PROXY', () => { awsCompileApigEvents.serverless.service.functions = { diff --git a/lib/plugins/install/install.test.js b/lib/plugins/install/install.test.js index 32862e60e..78e87a706 100644 --- a/lib/plugins/install/install.test.js +++ b/lib/plugins/install/install.test.js @@ -21,8 +21,7 @@ describe('Install', () => { let servicePath; - beforeEach(function () { - this.timeout(5000); // Occasionally times out with default settings + beforeEach(() => { const tmpDir = getTmpDirPath(); cwd = process.cwd(); diff --git a/lib/plugins/package/package.test.js b/lib/plugins/package/package.test.js index be992d3d7..38608e0cf 100644 --- a/lib/plugins/package/package.test.js +++ b/lib/plugins/package/package.test.js @@ -15,8 +15,7 @@ describe('Package', () => { let options; let pkg; - beforeEach(function () { - this.timeout(5000); // Occasionally times out with default settings + beforeEach(() => { serverless = new Serverless(); return serverless.init().then(() => { options = { diff --git a/lib/plugins/slstats/slstats.test.js b/lib/plugins/slstats/slstats.test.js index 62c2c9ad8..3affbb65c 100644 --- a/lib/plugins/slstats/slstats.test.js +++ b/lib/plugins/slstats/slstats.test.js @@ -11,8 +11,7 @@ describe('SlStats', () => { let slStats; let serverless; - beforeEach(function () { - this.timeout(5000); // Occasionally times out with default settings + beforeEach(() => { serverless = new Serverless(); return serverless.init().then(() => { slStats = new SlStats(serverless); diff --git a/lib/utils/getCommandSuggestion.test.js b/lib/utils/getCommandSuggestion.test.js index ea9677606..499570f6b 100644 --- a/lib/utils/getCommandSuggestion.test.js +++ b/lib/utils/getCommandSuggestion.test.js @@ -7,8 +7,7 @@ const Serverless = require('../../lib/Serverless'); describe('#getCommandSuggestion', () => { let serverless; - before(function () { - this.timeout(5000); // Occasionally times out with default settings + before(() => { serverless = new Serverless(); return serverless.init(); }); diff --git a/package.json b/package.json index df639454a..49437317f 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,8 @@ "postinstall": "node ./scripts/postinstall.js" }, "mocha": { - "R": "tests/mocha-reporter" + "R": "tests/mocha-reporter", + "timeout": 5000 }, "jest": { "testRegex": "(\\.|/)(tests)\\.js$", From 93a632f342d6ca63dd30f0d97a698312c139c9e7 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 5 Jun 2019 11:03:52 +0200 Subject: [PATCH 313/504] Ensure proper context --- lib/plugins/aws/invokeLocal/index.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index db5817cb5..6baa78ce8 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -940,7 +940,7 @@ describe('AwsInvokeLocal', () => { afterEach(afterEachCallback); describe('context.remainingTimeInMillis', () => { - it('should become lower over time', () => { + it('should become lower over time', function () { awsInvokeLocal.serverless.config.servicePath = __dirname; return awsInvokeLocal.invokeLocalRuby( @@ -959,7 +959,7 @@ describe('AwsInvokeLocal', () => { }); describe('calling a class method', () => { - it('should execute', () => { + it('should execute', function () { awsInvokeLocal.serverless.config.servicePath = __dirname; return awsInvokeLocal.invokeLocalRuby( From a02b31e91cb94abd01abe77c4184d6cde348764e Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 30 May 2019 15:20:22 +0200 Subject: [PATCH 314/504] Add support for S3 hosted package artifacts --- docs/providers/aws/guide/packaging.md | 35 +++++++++- lib/classes/Utils.js | 9 +++ lib/classes/Utils.test.js | 9 +++ .../aws/package/compile/functions/index.js | 53 ++++++++++++++- .../package/compile/functions/index.test.js | 64 ++++++++++++++++++- tests/utils/fs/index.js | 8 +++ 6 files changed, 171 insertions(+), 7 deletions(-) diff --git a/docs/providers/aws/guide/packaging.md b/docs/providers/aws/guide/packaging.md index 2caec7ee1..bcacd2678 100644 --- a/docs/providers/aws/guide/packaging.md +++ b/docs/providers/aws/guide/packaging.md @@ -90,7 +90,7 @@ Serverless won't zip your service if this is configured and therefore `exclude` The artifact option is especially useful in case your development environment allows you to generate a deployable artifact like Maven does for Java. -### Example +#### Service package ```yml service: my-service @@ -98,9 +98,10 @@ package: artifact: path/to/my-artifact.zip ``` -You can also use this to package functions individually. +#### Individual function packages + +You can also use this to package functions individually: -### Example ```yml service: my-service @@ -118,6 +119,34 @@ functions: method: get ``` +#### Artifacst hosted on S3 + +Artifacts can also be fetched from a remote S3 bucket. In this case you just need to provide the S3 object URL as the artifact value. This applies to both, service-wide and function-level artifact setups. + +##### Service package + +```yml +service: my-service + +package: + artifact: https://s3.amazonaws.com/some-bucket/service-artifact.zip +``` + +##### Individual function packages + +```yml +service: my-service + +package: + individually: true + +functions: + hello: + handler: com.serverless.Handler + package: + artifact: https://s3.amazonaws.com/some-bucket/function-artifact.zip +``` + ### Packaging functions separately If you want even more controls over your functions for deployment you can configure them to be packaged independently. This allows you more control for optimizing your deployment. To enable individual packaging set `individually` to true in the service or function wide packaging settings. diff --git a/lib/classes/Utils.js b/lib/classes/Utils.js index 4876f6775..c55adb24f 100644 --- a/lib/classes/Utils.js +++ b/lib/classes/Utils.js @@ -1,5 +1,7 @@ 'use strict'; +const os = require('os'); +const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const ci = require('ci-info'); @@ -31,6 +33,13 @@ class Utils { return dirExistsSync(dirPath); } + getTmpDirPath() { + const dirPath = path.join(os.tmpdir(), + 'tmpdirs-serverless', crypto.randomBytes(8).toString('hex')); + fse.ensureDirSync(dirPath); + return dirPath; + } + fileExistsSync(filePath) { return fileExistsSync(filePath); } diff --git a/lib/classes/Utils.test.js b/lib/classes/Utils.test.js index 1480f5e44..d752f70f0 100644 --- a/lib/classes/Utils.test.js +++ b/lib/classes/Utils.test.js @@ -25,6 +25,15 @@ describe('Utils', () => { utils = new Utils(serverless); }); + describe('#getTmpDirPath()', () => { + it('should create a scoped tmp directory', () => { + const dirPath = serverless.utils.getTmpDirPath(); + const stats = fse.statSync(dirPath); + expect(dirPath).to.include('tmpdirs-serverless'); + expect(stats.isDirectory()).to.equal(true); + }); + }); + describe('#dirExistsSync()', () => { describe('When reading a directory', () => { it('should detect if a directory exists', () => { diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 0ebedde2c..e2627b1f4 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -1,5 +1,6 @@ 'use strict'; +const AWS = require('aws-sdk'); const BbPromise = require('bluebird'); const crypto = require('crypto'); const fs = require('fs'); @@ -23,6 +24,7 @@ class AwsCompileFunctions { this.hooks = { 'package:compileFunctions': () => BbPromise.bind(this) + .then(this.downloadPackageArtifacts) .then(this.compileFunctions), }; } @@ -66,6 +68,46 @@ class AwsCompileFunctions { } } + downloadPackageArtifact(functionName) { + const { region } = this.options; + const S3 = new AWS.S3({ region }); + + const functionObject = this.serverless.service.getFunction(functionName); + const artifactFilePath = _.get(functionObject, 'package.artifact') || + _.get(this, 'serverless.service.package.artifact'); + + const regex = new RegExp('.*s3.amazonaws.com/(.+)/(.+)'); + const match = artifactFilePath.match(regex); + + if (match) { + return new BbPromise((resolve, reject) => { + const tmpDir = this.serverless.utils.getTmpDirPath(); + const filePath = path.join(tmpDir, match[2]); + + const readStream = S3.getObject({ + Bucket: match[1], + Key: match[2], + }).createReadStream(); + + const writeStream = fs.createWriteStream(filePath); + + readStream.on('error', (error) => reject(error)); + readStream.pipe(writeStream) + .on('error', reject) + .on('close', () => { + if (functionObject.package.artifact) { + functionObject.package.artifact = filePath; + } else { + this.serverless.service.package.artifact = filePath; + } + return resolve(filePath); + }); + }); + } + + return BbPromise.resolve(); + } + compileFunction(functionName) { const newFunction = this.cfLambdaFunctionTemplate(); const functionObject = this.serverless.service.getFunction(functionName); @@ -76,6 +118,7 @@ class AwsCompileFunctions { let artifactFilePath = functionObject.package.artifact || this.serverless.service.package.artifact; + if (!artifactFilePath || (this.serverless.service.artifact && !functionObject.package.artifact)) { let artifactFileName = serviceArtifactFileName; @@ -452,12 +495,18 @@ class AwsCompileFunctions { }); } + downloadPackageArtifacts() { + const allFunctions = this.serverless.service.getAllFunctions(); + return BbPromise.each( + allFunctions, + functionName => this.downloadPackageArtifact(functionName)); + } + compileFunctions() { const allFunctions = this.serverless.service.getAllFunctions(); return BbPromise.each( allFunctions, - functionName => this.compileFunction(functionName) - ); + functionName => this.compileFunction(functionName)); } // helper functions diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index 9d67915f5..f478397cc 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -1,19 +1,24 @@ 'use strict'; +const AWS = require('aws-sdk'); +const fs = require('fs'); const _ = require('lodash'); const path = require('path'); const chai = require('chai'); +const sinon = require('sinon'); const AwsProvider = require('../../../provider/awsProvider'); const AwsCompileFunctions = require('./index'); const Serverless = require('../../../../../Serverless'); -const { getTmpDirPath } = require('../../../../../../tests/utils/fs'); +const { getTmpDirPath, createTmpFile } = require('../../../../../../tests/utils/fs'); chai.use(require('chai-as-promised')); +chai.use(require('sinon-chai')); const expect = chai.expect; describe('AwsCompileFunctions', () => { let serverless; + let awsProvider; let awsCompileFunctions; const functionName = 'test'; const compiledFunctionName = 'TestLambdaFunction'; @@ -24,7 +29,8 @@ describe('AwsCompileFunctions', () => { region: 'us-east-1', }; serverless = new Serverless(options); - serverless.setProvider('aws', new AwsProvider(serverless, options)); + awsProvider = new AwsProvider(serverless, options); + serverless.setProvider('aws', awsProvider); serverless.cli = new serverless.classes.CLI(); awsCompileFunctions = new AwsCompileFunctions(serverless, options); awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { @@ -74,6 +80,60 @@ describe('AwsCompileFunctions', () => { expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ Blah: 'vtha' })).to.equal(false)); }); + describe('#downloadPackageArtifacts()', () => { + let requestStub; + let testFilePath; + const s3BucketName = 'test-bucket'; + const s3ArtifactName = 's3-hosted-artifact.zip'; + + beforeEach(() => { + testFilePath = createTmpFile('dummy-artifact'); + requestStub = sinon.stub(AWS, 'S3').returns({ + getObject: () => ({ + createReadStream() { + return fs.createReadStream(testFilePath); + }, + }), + }); + }); + + afterEach(() => { + AWS.S3.restore(); + }); + + it('should download the file and replace the artifact path for function packages', () => { + awsCompileFunctions.serverless.service.package.individually = true; + awsCompileFunctions.serverless.service.functions[functionName] + .package.artifact = `https://s3.amazonaws.com/${s3BucketName}/${s3ArtifactName}`; + + return expect(awsCompileFunctions.downloadPackageArtifacts()).to.be.fulfilled + .then(() => { + const artifactFileName = awsCompileFunctions.serverless.service + .functions[functionName].package.artifact.split(path.sep).pop(); + + expect(requestStub.callCount).to.equal(1); + expect(artifactFileName).to.equal(s3ArtifactName); + }); + }); + + it('should download the file and replace the artifact path for service-wide packages', () => { + awsCompileFunctions.serverless.service.package.individually = false; + awsCompileFunctions.serverless.service.functions[functionName] + .package.artifact = false; + awsCompileFunctions.serverless.service.package + .artifact = `https://s3.amazonaws.com/${s3BucketName}/${s3ArtifactName}`; + + return expect(awsCompileFunctions.downloadPackageArtifacts()).to.be.fulfilled + .then(() => { + const artifactFileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep).pop(); + + expect(requestStub.callCount).to.equal(1); + expect(artifactFileName).to.equal(s3ArtifactName); + }); + }); + }); + describe('#compileFunctions()', () => { it('should use service artifact if not individually', () => { awsCompileFunctions.serverless.service.package.individually = false; diff --git a/tests/utils/fs/index.js b/tests/utils/fs/index.js index 1a2ef1f65..e7a93f55b 100644 --- a/tests/utils/fs/index.js +++ b/tests/utils/fs/index.js @@ -3,6 +3,7 @@ const os = require('os'); const path = require('path'); const fs = require('fs'); +const fse = require('fs-extra'); const crypto = require('crypto'); const YAML = require('js-yaml'); const JSZip = require('jszip'); @@ -18,6 +19,12 @@ function getTmpFilePath(fileName) { return path.join(getTmpDirPath(), fileName); } +function createTmpFile(name) { + const filePath = getTmpFilePath(name); + fse.ensureFileSync(filePath); + return filePath; +} + function replaceTextInFile(filePath, subString, newSubString) { const fileContent = fs.readFileSync(filePath).toString(); fs.writeFileSync(filePath, fileContent.replace(subString, newSubString)); @@ -43,6 +50,7 @@ module.exports = { tmpDirCommonPath, getTmpDirPath, getTmpFilePath, + createTmpFile, replaceTextInFile, readYamlFile, writeYamlFile, From 97d62097c6bb75ffc6d38c104a710abc86151cf1 Mon Sep 17 00:00:00 2001 From: My Ho Date: Fri, 31 May 2019 14:43:39 -0700 Subject: [PATCH 315/504] remove default stage value in provider object the current default is using AWS region naming syntax, this mean other cloud provider would have to add extra code to set their default value. Instead, region default should be left to each provider. --- lib/classes/Service.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 0ec8d0606..8d05585ee 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -20,7 +20,6 @@ class Service { this.serviceObject = null; this.provider = { stage: 'dev', - region: 'us-east-1', variableSyntax: '\\${([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}', }; this.custom = {}; From c393d4b22c005f2982bdf461179cb01a9e905a94 Mon Sep 17 00:00:00 2001 From: Edmundo Rodrigues Date: Mon, 3 Jun 2019 20:12:45 -0300 Subject: [PATCH 316/504] feat(iam): use common prefix loggroup prefix to reduce size of iamRoleLambdaExecution to avoid reaching 10240 bytes limit --- .../aws/package/lib/mergeIamTemplates.js | 49 +++++----- .../aws/package/lib/mergeIamTemplates.test.js | 93 +------------------ 2 files changed, 29 insertions(+), 113 deletions(-) diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.js b/lib/plugins/aws/package/lib/mergeIamTemplates.js index cb74e820d..2698be83f 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.js @@ -83,33 +83,32 @@ module.exports = { } ); - this.serverless.service.getAllFunctions().forEach((functionName) => { - const functionObject = this.serverless.service.getFunction(functionName); + const logGroupsPrefix = this.provider.naming + .getLogGroupName(`${this.provider.serverless.service.service}-${this.provider.getStage()}`); - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement[0] - .Resource - .push({ - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + - `:log-group:${this.provider.naming.getLogGroupName(functionObject.name)}:*`, - }); + this.serverless.service.provider.compiledCloudFormationTemplate + .Resources[this.provider.naming.getRoleLogicalId()] + .Properties + .Policies[0] + .PolicyDocument + .Statement[0] + .Resource + .push({ + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${logGroupsPrefix}*:*`, + }); - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement[1] - .Resource - .push({ - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + - `:log-group:${this.provider.naming.getLogGroupName(functionObject.name)}:*:*`, - }); - }); + this.serverless.service.provider.compiledCloudFormationTemplate + .Resources[this.provider.naming.getRoleLogicalId()] + .Properties + .Policies[0] + .PolicyDocument + .Statement[1] + .Resource + .push({ + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${logGroupsPrefix}*:*:*`, + }); if (this.serverless.service.provider.iamRoleStatements) { // add custom iam role statements diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js index 43df16c77..59a41473c 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js @@ -50,7 +50,9 @@ describe('#mergeIamTemplates()', () => { it('should merge the IamRoleLambdaExecution template into the CloudFormation template', () => awsPackage.mergeIamTemplates() .then(() => { - const qualifiedFunction = awsPackage.serverless.service.getFunction(functionName).name; + const canonicalFunctionsPrefix = + `${awsPackage.serverless.service.service}-${awsPackage.provider.getStage()}`; + expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate .Resources[awsPackage.provider.naming.getRoleLogicalId()] ).to.deep.equal({ @@ -96,7 +98,7 @@ describe('#mergeIamTemplates()', () => { Resource: [ { 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + `log-group:/aws/lambda/${qualifiedFunction}:*`, + + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*`, }, ], }, @@ -108,7 +110,7 @@ describe('#mergeIamTemplates()', () => { Resource: [ { 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + `log-group:/aws/lambda/${qualifiedFunction}:*:*`, + + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*:*`, }, ], }, @@ -374,91 +376,6 @@ describe('#mergeIamTemplates()', () => { }); }); - it('should update IamRoleLambdaExecution with a logging resource for the function', () => { - const qualifiedFunction = awsPackage.serverless.service.getFunction(functionName).name; - return awsPackage.mergeIamTemplates().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement[0] - .Resource - ).to.deep.equal([ - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + `log-group:/aws/lambda/${qualifiedFunction}:*`, - }, - ]); - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement[1] - .Resource - ).to.deep.equal([ - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + `log-group:/aws/lambda/${qualifiedFunction}:*:*`, - }, - ]); - }); - }); - - it('should update IamRoleLambdaExecution with each function\'s logging resources', () => { - awsPackage.serverless.service.functions = { - func0: { - handler: 'func.function.handler', - name: 'func0', - }, - func1: { - handler: 'func.function.handler', - name: 'func1', - }, - }; - return awsPackage.mergeIamTemplates().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement[0] - .Resource - ).to.deep.equal( - [ - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + 'log-group:/aws/lambda/func0:*', - }, - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + 'log-group:/aws/lambda/func1:*', - }, - ] - ); - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement[1] - .Resource - ).to.deep.equal( - [ - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + 'log-group:/aws/lambda/func0:*:*', - }, - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + 'log-group:/aws/lambda/func1:*:*', - }, - ] - ); - }); - }); - it('should add default role if one of the functions has an ARN role', () => { awsPackage.serverless.service.functions = { func0: { From 5541f2b9eff5d06880733fe9ce9ba37d14a5467f Mon Sep 17 00:00:00 2001 From: My Ho Date: Wed, 5 Jun 2019 16:08:51 -0700 Subject: [PATCH 317/504] Fix tests --- lib/classes/Service.test.js | 361 ++++++++++++++++++------------------ 1 file changed, 180 insertions(+), 181 deletions(-) diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index d5342a097..2e8fa0af5 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -30,7 +30,6 @@ describe('Service', () => { expect(serviceInstance.serviceObject).to.be.equal(null); expect(serviceInstance.provider).to.deep.equal({ stage: 'dev', - region: 'us-east-1', variableSyntax: '\\${([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}', }); expect(serviceInstance.custom).to.deep.equal({}); @@ -268,23 +267,23 @@ describe('Service', () => { serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - expect(serviceInstance.service).to.be.equal('new-service'); - expect(serviceInstance.provider.name).to.deep.equal('aws'); - expect(serviceInstance.provider.variableSyntax).to.equal( - '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}}' - ); - expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); - expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); - expect(serviceInstance.resources.azure).to.deep.equal({}); - expect(serviceInstance.resources.google).to.deep.equal({}); - expect(serviceInstance.package.exclude.length).to.equal(1); - expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); - expect(serviceInstance.package.include.length).to.equal(1); - expect(serviceInstance.package.include[0]).to.equal('include-me'); - expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); - expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); - }); + .then(() => { + expect(serviceInstance.service).to.be.equal('new-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.provider.variableSyntax).to.equal( + '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}}' + ); + expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); + expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); + expect(serviceInstance.resources.azure).to.deep.equal({}); + expect(serviceInstance.resources.google).to.deep.equal({}); + expect(serviceInstance.package.exclude.length).to.equal(1); + expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); + expect(serviceInstance.package.include.length).to.equal(1); + expect(serviceInstance.package.include[0]).to.equal('include-me'); + expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); + expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); + }); }); it('should load serverless.js from filesystem', () => { @@ -323,23 +322,23 @@ describe('Service', () => { serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - expect(serviceInstance.service).to.be.equal('new-service'); - expect(serviceInstance.provider.name).to.deep.equal('aws'); - expect(serviceInstance.provider.variableSyntax).to.equal( - '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}}' - ); - expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); - expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); - expect(serviceInstance.resources.azure).to.deep.equal({}); - expect(serviceInstance.resources.google).to.deep.equal({}); - expect(serviceInstance.package.exclude.length).to.equal(1); - expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); - expect(serviceInstance.package.include.length).to.equal(1); - expect(serviceInstance.package.include[0]).to.equal('include-me'); - expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); - expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); - }); + .then(() => { + expect(serviceInstance.service).to.be.equal('new-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.provider.variableSyntax).to.equal( + '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}}' + ); + expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); + expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); + expect(serviceInstance.resources.azure).to.deep.equal({}); + expect(serviceInstance.resources.google).to.deep.equal({}); + expect(serviceInstance.package.exclude.length).to.equal(1); + expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); + expect(serviceInstance.package.include.length).to.equal(1); + expect(serviceInstance.package.include[0]).to.equal('include-me'); + expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); + expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); + }); }); it('should load serverless.js from filesystem', () => { @@ -378,23 +377,23 @@ describe('Service', () => { serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - expect(serviceInstance.service).to.be.equal('new-service'); - expect(serviceInstance.provider.name).to.deep.equal('aws'); - expect(serviceInstance.provider.variableSyntax).to.equal( - '\\${{([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}}' - ); - expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); - expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); - expect(serviceInstance.resources.azure).to.deep.equal({}); - expect(serviceInstance.resources.google).to.deep.equal({}); - expect(serviceInstance.package.exclude.length).to.equal(1); - expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); - expect(serviceInstance.package.include.length).to.equal(1); - expect(serviceInstance.package.include[0]).to.equal('include-me'); - expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); - expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); - }); + .then(() => { + expect(serviceInstance.service).to.be.equal('new-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.provider.variableSyntax).to.equal( + '\\${{([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}}' + ); + expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); + expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); + expect(serviceInstance.resources.azure).to.deep.equal({}); + expect(serviceInstance.resources.google).to.deep.equal({}); + expect(serviceInstance.package.exclude.length).to.equal(1); + expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); + expect(serviceInstance.package.include.length).to.equal(1); + expect(serviceInstance.package.include[0]).to.equal('include-me'); + expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); + expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); + }); }); it('should throw error if serverless.js exports invalid config', () => { @@ -451,10 +450,10 @@ describe('Service', () => { serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - // YAML should have been loaded instead of JSON - expect(serviceInstance.service).to.be.equal('YAML service'); - }); + .then(() => { + // YAML should have been loaded instead of JSON + expect(serviceInstance.service).to.be.equal('YAML service'); + }); }); it('should reject when the service name is missing', () => { @@ -491,10 +490,10 @@ describe('Service', () => { serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - expect(serviceInstance.service).to.equal('my-service'); - expect(serviceInstance.serviceObject).to.deep.equal(serverlessYaml.service); - }); + .then(() => { + expect(serviceInstance.service).to.equal('my-service'); + expect(serviceInstance.serviceObject).to.deep.equal(serverlessYaml.service); + }); }); it('should support Serverless file with a non-aws provider', () => { @@ -516,18 +515,18 @@ describe('Service', () => { serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - serviceInstance.setFunctionNames(); - const expectedFunc = { - functionA: { - name: 'customFunctionName', - events: [], - }, - }; - expect(serviceInstance.service).to.be.equal('my-service'); - expect(serviceInstance.provider.name).to.deep.equal('openwhisk'); - expect(serviceInstance.functions).to.deep.equal(expectedFunc); - }); + .then(() => { + serviceInstance.setFunctionNames(); + const expectedFunc = { + functionA: { + name: 'customFunctionName', + events: [], + }, + }; + expect(serviceInstance.service).to.be.equal('my-service'); + expect(serviceInstance.provider.name).to.deep.equal('openwhisk'); + expect(serviceInstance.functions).to.deep.equal(expectedFunc); + }); }); it('should support Serverless file with a .yaml extension', () => { @@ -549,18 +548,18 @@ describe('Service', () => { serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - serviceInstance.setFunctionNames(); - const expectedFunc = { - functionA: { - name: 'customFunctionName', - events: [], - }, - }; - expect(serviceInstance.service).to.be.equal('my-service'); - expect(serviceInstance.provider.name).to.deep.equal('aws'); - expect(serviceInstance.functions).to.deep.equal(expectedFunc); - }); + .then(() => { + serviceInstance.setFunctionNames(); + const expectedFunc = { + functionA: { + name: 'customFunctionName', + events: [], + }, + }; + expect(serviceInstance.service).to.be.equal('my-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.functions).to.deep.equal(expectedFunc); + }); }); it('should support Serverless file with a .yml extension', () => { @@ -580,18 +579,18 @@ describe('Service', () => { serviceInstance = new Service(serverless); return expect(serviceInstance.load({ stage: 'dev' })).to.eventually.be.fulfilled - .then(() => { - serviceInstance.setFunctionNames(); - const expectedFunc = { - functionA: { - name: 'my-service-dev-functionA', - events: [], - }, - }; - expect(serviceInstance.service).to.be.equal('my-service'); - expect(serviceInstance.provider.name).to.deep.equal('aws'); - expect(serviceInstance.functions).to.deep.equal(expectedFunc); - }); + .then(() => { + serviceInstance.setFunctionNames(); + const expectedFunc = { + functionA: { + name: 'my-service-dev-functionA', + events: [], + }, + }; + expect(serviceInstance.service).to.be.equal('my-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.functions).to.deep.equal(expectedFunc); + }); }); it('should reject if service property is missing', () => { @@ -679,15 +678,15 @@ describe('Service', () => { serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - // populate variables in service configuration - serverless.variables.populateService(); + .then(() => { + // populate variables in service configuration + serverless.variables.populateService(); - // validate the service configuration, now that variables are loaded - serviceInstance.validate(); + // validate the service configuration, now that variables are loaded + serviceInstance.validate(); - expect(serviceInstance.functions).to.deep.equal({}); - }); + expect(serviceInstance.functions).to.deep.equal({}); + }); }); }); @@ -716,11 +715,11 @@ describe('Service', () => { serverless.service = new Service(serverless); return expect(serverless.service.load()).to.eventually.be.fulfilled - .then(() => { - // validate the service configuration, now that variables are loaded - expect(() => serverless.service.validate()) - .to.throw('Events for "functionA" must be an array, not an string'); - }); + .then(() => { + // validate the service configuration, now that variables are loaded + expect(() => serverless.service.validate()) + .to.throw('Events for "functionA" must be an array, not an string'); + }); }); describe('stage name validation', () => { @@ -735,59 +734,59 @@ describe('Service', () => { it(`should not throw an error if http event is absent and stage contains only alphanumeric, underscore and hyphen`, function () { - this.timeout(10000); // Occasionally times out with default settings - const SUtils = new Utils(); - const serverlessYml = { - service: 'new-service', - provider: { - name: 'aws', - stage: 'xyz-101_abc-123', - }, - functions: { - first: { - events: [], + this.timeout(10000); // Occasionally times out with default settings + const SUtils = new Utils(); + const serverlessYml = { + service: 'new-service', + provider: { + name: 'aws', + stage: 'xyz-101_abc-123', }, - }, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + functions: { + first: { + events: [], + }, + }, + }; + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), + YAML.dump(serverlessYml)); - const serverless = new Serverless({ servicePath: tmpDirPath }); - return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { - expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error); + const serverless = new Serverless({ servicePath: tmpDirPath }); + return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { + expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error); + }); }); - }); it(`should not throw an error after variable population if http event is present and the populated stage contains only alphanumeric, underscore and hyphen`, () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'new-service', - provider: { - name: 'aws', - stage: '${opt:stage, "default-stage"}', - }, - functions: { - first: { - events: [ - { - http: { - path: 'foo', - method: 'GET', - }, - }, - ], + const SUtils = new Utils(); + const serverlessYml = { + service: 'new-service', + provider: { + name: 'aws', + stage: '${opt:stage, "default-stage"}', }, - }, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + functions: { + first: { + events: [ + { + http: { + path: 'foo', + method: 'GET', + }, + }, + ], + }, + }, + }; + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), + YAML.dump(serverlessYml)); - const serverless = new Serverless({ servicePath: tmpDirPath }); - return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { - expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error); + const serverless = new Serverless({ servicePath: tmpDirPath }); + return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { + expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error); + }); }); - }); it('should throw an error if http event is present and stage contains invalid chars', () => { const SUtils = new Utils(); @@ -825,38 +824,38 @@ describe('Service', () => { it(`should throw an error after variable population if http event is present and stage contains hyphen`, () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'new-service', - provider: { - name: 'aws', - stage: '${opt:stage, "default:stage"}', - }, - functions: { - first: { - events: [ - { - http: { - path: 'foo', - method: 'GET', - }, - }, - ], + const SUtils = new Utils(); + const serverlessYml = { + service: 'new-service', + provider: { + name: 'aws', + stage: '${opt:stage, "default:stage"}', }, - }, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + functions: { + first: { + events: [ + { + http: { + path: 'foo', + method: 'GET', + }, + }, + ], + }, + }, + }; + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), + YAML.dump(serverlessYml)); - const serverless = new Serverless({ servicePath: tmpDirPath }); - return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { - expect(() => serverless.service.validate()).to.throw(serverless.classes.Error, [ - 'Invalid stage name default:stage: it should contains only [-_a-zA-Z0-9]', - 'for AWS provider if http event are present', - 'according to API Gateway limitation.', - ].join(' ')); + const serverless = new Serverless({ servicePath: tmpDirPath }); + return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { + expect(() => serverless.service.validate()).to.throw(serverless.classes.Error, [ + 'Invalid stage name default:stage: it should contains only [-_a-zA-Z0-9]', + 'for AWS provider if http event are present', + 'according to API Gateway limitation.', + ].join(' ')); + }); }); - }); }); }); From 997434480a597e22eefb2b86de7a11bad095202a Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 6 Jun 2019 12:18:42 +0200 Subject: [PATCH 318/504] Remove root README generator --- README.md | 281 +-------------------- package-lock.json | 498 +------------------------------------ package.json | 5 - scripts/generate-readme.js | 73 ------ 4 files changed, 13 insertions(+), 844 deletions(-) delete mode 100644 scripts/generate-readme.js diff --git a/README.md b/README.md index 3880d8e5d..26a8cad56 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ Serverless is an MIT open-source project, actively maintained by a full-time, ve * [Examples](https://github.com/serverless/examples) * [Services](#services) * [Features](#features) -* [Plugins](#v1-plugins) -* [Example Projects](#v1-projects) +* [Plugins](https://github.com/serverless/plugins) +* [Examples](https://github.com/serverless/examples) * [Contributing](#contributing) * [Community](#community) * [Consultants](#consultants) @@ -145,283 +145,6 @@ The following are services you can instantly install and use by running `serverl * An ecosystem of serverless services and plugins. * A passionate and welcoming community! -## Plugins (V1.0) - -Use these plugins to extend or overwrite the Framework's functionality. - -[Add a plugin to this list](https://github.com/serverless/community-plugins/blob/master/plugins.json) - - -| Plugin | Author | -| :----- | :----: | -| **[Fullstack Serverless](https://github.com/MadSkills-io/fullstack-serverless)**
    A Serverless plugin to create an AWS CloudFront distribution that serves static web content from S3 and routes API traffic to API Gateway. | [MadSkills-io](http://github.com/MadSkills-io) | -| **[Go Serverless](https://github.com/thepauleh/goserverless)**
    GoFormation for Serverless. Create serverless configs with Go Structs. | [thepauleh](http://github.com/thepauleh) | -| **[Raml Serverless](https://github.com/andrewcurioso/raml-serverless)**
    Serverless plugin to work with RAML API spec documents | [andrewcurioso](http://github.com/andrewcurioso) | -| **[Serverless Alexa Plugin](https://github.com/rajington/serverless-alexa-plugin)**
    Serverless plugin to support Alexa Lambda events | [rajington](http://github.com/rajington) | -| **[Serverless Alexa Skills](https://github.com/marcy-terui/serverless-alexa-skills)**
    Manage your Alexa Skills with Serverless Framework. | [marcy-terui](http://github.com/marcy-terui) | -| **[Serverless Aliyun Function Compute](https://github.com/aliyun/serverless-aliyun-function-compute)**
    Serverless Alibaba Cloud Function Compute Plugin | [aliyun](http://github.com/aliyun) | -| **[Serverless Api Cloudfront](https://github.com/Droplr/serverless-api-cloudfront)**
    Plugin that adds CloudFront distribution in front of your API Gateway for custom domain, CDN caching and access log. | [Droplr](http://github.com/Droplr) | -| **[Serverless Api Stage](https://github.com/leftclickben/serverless-api-stage)**
    Serverless API Stage plugin, enables stage variables and logging for AWS API Gateway. | [leftclickben](http://github.com/leftclickben) | -| **[Serverless Apib Validator](https://github.com/onlicar/serverless-apib-validator)**
    Validate that an API Blueprint has full coverage over a Serverless config | [onlicar](http://github.com/onlicar) | -| **[Serverless Apig S 3](https://github.com/sdd/serverless-apig-s3)**
    Serve static front-end content from S3 via the API Gateway and deploy client bundle to S3. | [sdd](http://github.com/sdd) | -| **[Serverless Apigateway Plugin](https://github.com/GFG/serverless-apigateway-plugin)**
    Configure the AWS api gateway: Binary support, Headers and Body template mappings | [GFG](http://github.com/GFG) | -| **[Serverless Apigw Binary](https://github.com/maciejtreder/serverless-apigw-binary)**
    Plugin to enable binary support in AWS API Gateway. | [maciejtreder](http://github.com/maciejtreder) | -| **[Serverless Apigwy Binary](https://github.com/ryanmurakami/serverless-apigwy-binary)**
    Serverless plugin for configuring API Gateway to return binary responses | [ryanmurakami](http://github.com/ryanmurakami) | -| **[Serverless Appsync Plugin](https://github.com/sid88in/serverless-appsync-plugin)**
    Serverless Plugin to deploy AppSync GraphQL API | [sid88in](http://github.com/sid88in) | -| **[Serverless Attach Managed Policy](https://github.com/Nordstrom/serverless-attach-managed-policy)**
    A Serverless plugin to automatically attach an AWS Managed IAM Policy (or Policies) to all IAM Roles created by the Service. | [Nordstrom](http://github.com/Nordstrom) | -| **[Serverless Aws Alias](https://github.com/HyperBrain/serverless-aws-alias)**
    This plugin enables use of AWS aliases on Lambda functions. | [HyperBrain](http://github.com/HyperBrain) | -| **[Serverless Aws Documentation](https://github.com/9cookies/serverless-aws-documentation)**
    Serverless plugin to add documentation and models to the serverless generated API Gateway | [9cookies](http://github.com/9cookies) | -| **[Serverless Aws Nested Stacks](https://github.com/concon121/serverless-plugin-nested-stacks)**
    Yet another AWS nested stack plugin! | [concon121](http://github.com/concon121) | -| **[Serverless Aws Resource Names](https://github.com/concon121/serverless-plugin-aws-resource-names)**
    Serverless plugin to alter the default naming conventions for sls resources via a simple mapping file. | [concon121](http://github.com/concon121) | -| **[Serverless Basic Authentication](https://github.com/svdgraaf/serverless-basic-authentication)**
    Serverless Plugin for adding Basic Authentication to your api | [svdgraaf](http://github.com/svdgraaf) | -| **[Serverless Build Client](https://github.com/tgfischer/serverless-build-client)**
    Build your static website with environment variables defined in serverless.yml | [tgfischer](http://github.com/tgfischer) | -| **[Serverless Build Plugin](https://github.com/nfour/serverless-build-plugin)**
    A Node.js focused build plugin for serverless. | [nfour](http://github.com/nfour) | -| **[Serverless Cf Vars](https://gitlab.com/kabo/serverless-cf-vars)**
    Enables use of AWS pseudo functions and Fn::Sub string substitution | [kabo](http://github.com/kabo) | -| **[Serverless Cljs Plugin](https://github.com/nervous-systems/serverless-cljs-plugin)**
    Enables Clojurescript as an implementation language for Lambda handlers | [nervous-systems](http://github.com/nervous-systems) | -| **[Serverless Cloudformation Changesets](https://github.com/trek10inc/serverless-cloudformation-changesets)**
    Natively deploy to CloudFormation via Change sets, instead of directly. Allowing you to queue changes, and safely require escalated roles for final deployment. | [trek10inc](http://github.com/trek10inc) | -| **[Serverless Cloudformation Parameter Setter](https://github.com/trek10inc/serverless-cloudformation-parameter-setter)**
    Set CloudFormation parameters when deploying. | [trek10inc](http://github.com/trek10inc) | -| **[Serverless Cloudformation Resource Counter](https://github.com/drexler/serverless-cloudformation-resource-counter)**
    A plugin to count the resources generated in the AWS CloudFormation stack after deployment. | [drexler](http://github.com/drexler) | -| **[Serverless Cloudformation Sub Variables](https://github.com/santiagocardenas/serverless-cloudformation-sub-variables)**
    Serverless framework plugin for easily supporting AWS CloudFormation Sub function variables | [santiagocardenas](http://github.com/santiagocardenas) | -| **[Serverless Coffeescript](https://github.com/duanefields/serverless-coffeescript)**
    A Serverless plugin to compile your CoffeeScript into JavaScript at deployment | [duanefields](http://github.com/duanefields) | -| **[Serverless Cognito Add Custom Attributes](https://github.com/GetWala/serverless-cognito-add-custom-attributes)**
    Serverless Plugin for adding custom attributes to an existing CloudFormation-managed CognitoUserPool and CognitoUserPoolClient without losing all your users | [GetWala](http://github.com/GetWala) | -| **[Serverless Command Line Event Args](https://github.com/horike37/serverless-command-line-event-args)**
    This module is Serverless Framework plugin. Event JSON passes to your Lambda function in commandline. | [horike37](http://github.com/horike37) | -| **[Serverless Content Encoding](https://github.com/dong-dohai/serverless-content-encoding)**
    Enable Content Encoding in AWS API Gateway during deployment | [dong-dohai](http://github.com/dong-dohai) | -| **[Serverless Create Global Dynamodb Table](https://github.com/rrahul963/serverless-create-global-dynamodb-table)**
    Serverless plugin to create dynamodb global tables | [rrahul963](http://github.com/rrahul963) | -| **[Serverless Crypt](https://github.com/marcy-terui/serverless-crypt)**
    Securing the secrets on Serverless Framework by AWS KMS encryption. | [marcy-terui](http://github.com/marcy-terui) | -| **[Serverless Custom Packaging Plugin](https://github.com/hypoport/serverless-custom-packaging-plugin)**
    Plugin to package your sourcecode using a custom target path inside the zip. | [hypoport](http://github.com/hypoport) | -| **[Serverless Dependson Plugin](https://github.com/bwinant/serverless-dependson-plugin)**
    Serverless plugin that automatically generates DependsOn references for AWS Lambdas to prevent AWS RequestLimitExceeded errors. | [bwinant](http://github.com/bwinant) | -| **[Serverless Ding](https://github.com/sidgonuts/serverless-ding)**
    Serverless plugin to audibly alert user after deployment | [sidgonuts](http://github.com/sidgonuts) | -| **[Serverless Dir Config Plugin](https://github.com/economysizegeek/serverless-dir-config-plugin)**
    EXPERIMENTAL - Serverless plugin to load function and resource definitions from a directory. | [economysizegeek](http://github.com/economysizegeek) | -| **[Serverless Domain Manager](https://github.com/amplify-education/serverless-domain-manager)**
    Serverless plugin for managing custom domains with API Gateways. | [amplify-education](http://github.com/amplify-education) | -| **[Serverless Dotenv](https://github.com/Jimdo/serverless-dotenv)**
    Fetch environment variables and write it to a .env file | [Jimdo](http://github.com/Jimdo) | -| **[Serverless Dotnet](https://github.com/fruffin/serverless-dotnet)**
    A serverless plugin to run 'dotnet' commands as part of the deploy process | [fruffin](http://github.com/fruffin) | -| **[Serverless Dynalite](https://github.com/sdd/serverless-dynalite)**
    Run dynalite locally (no JVM, all JS) to simulate DynamoDB. Watch serverless.yml for table config updates. | [sdd](http://github.com/sdd) | -| **[Serverless Dynamodb Autoscaling](https://github.com/sbstjn/serverless-dynamodb-autoscaling)**
    Configure Amazon DynamoDB's native Auto Scaling for your table capacities. | [sbstjn](http://github.com/sbstjn) | -| **[Serverless Dynamodb Fixtures](https://github.com/chechu/serverless-dynamodb-fixtures)**
    Serverless Dynamodb Fixtures - Allows to load data on DynamoDB tables | [chechu](http://github.com/chechu) | -| **[Serverless Dynamodb Local](https://github.com/99xt/serverless-dynamodb-local)**
    Serverless Dynamodb Local Plugin - Allows to run dynamodb locally for serverless | [99xt](http://github.com/99xt) | -| **[Serverless Dynamodb Ttl](https://github.com/Jimdo/serverless-dynamodb-ttl)**
    Configure DynamoDB TTL in serverless.yml (until CloudFormation supports this). | [Jimdo](http://github.com/Jimdo) | -| **[Serverless Enable Api Logs](https://github.com/paulSambolin/serverless-enable-api-logs)**
    Enables Coudwatch logging for API Gateway events | [paulSambolin](http://github.com/paulSambolin) | -| **[Serverless Env Generator](https://github.com/DieProduktMacher/serverless-env-generator)**
    Manage environment variables with YAML and load them with dotenv. Supports variable encryption with KMS, multiple stages and custom profiles. | [DieProduktMacher](http://github.com/DieProduktMacher) | -| **[Serverless Ephemeral](https://github.com/Accenture/serverless-ephemeral)**
    Build and include custom stateless libraries for any language | [Accenture](http://github.com/Accenture) | -| **[Serverless Event Constant Inputs](https://github.com/dittto/serverless-event-constant-inputs)**
    Allows you to add constant inputs to events in Serverless 1.0. For more info see [constant values in Cloudwatch](https://aws.amazon.com/blogs/compute/simply-serverless-use-constant-values-in-cloudwatch-event-triggered-lambda-functions/) | [dittto](http://github.com/dittto) | -| **[Serverless Export Env](https://github.com/arabold/serverless-export-env)**
    Export environment variables into a .env file with automatic AWS CloudFormation reference resolution. | [arabold](http://github.com/arabold) | -| **[Serverless Express](https://github.com/mikestaub/serverless-express)**
    Making express app development compatible with serverless framework. | [mikestaub](http://github.com/mikestaub) | -| **[Serverless Finch](https://github.com/fernando-mc/serverless-finch)**
    A Serverless plugin to deploy static website assets to AWS S3. | [fernando-mc](http://github.com/fernando-mc) | -| **[Serverless Functions Base Path](https://github.com/kevinrambaud/serverless-functions-base-path)**
    Easily define a base path where your serverless functions are located. | [kevinrambaud](http://github.com/kevinrambaud) | -| **[Serverless Go Build](https://github.com/sean9keenan/serverless-go-build)**
    Build go source files (or public functions) using yml definition file | [sean9keenan](http://github.com/sean9keenan) | -| **[Serverless Gulp](https://github.com/rhythminme/serverless-gulp)**
    A thin task wrapper around @goserverless that allows you to automate build, test and deploy tasks using gulp | [rhythminme](http://github.com/rhythminme) | -| **[Serverless Haskell](https://github.com/seek-oss/serverless-haskell)**
    Deploying Haskell applications to AWS Lambda with Serverless | [seek-oss](http://github.com/seek-oss) | -| **[Serverless Hooks Plugin](https://github.com/uswitch/serverless-hooks-plugin)**
    Run arbitrary commands on any lifecycle event in serverless | [uswitch](http://github.com/uswitch) | -| **[Serverless Iam Roles Per Function](https://github.com/functionalone/serverless-iam-roles-per-function)**
    Serverless Plugin for easily defining IAM roles per function via the use of iamRoleStatements at the function level. | [functionalone](http://github.com/functionalone) | -| **[Serverless Ignore](https://github.com/nya1/serverless-ignore)**
    Serverless plugin to ignore files (.slsignore) | [nya1](http://github.com/nya1) | -| **[Serverless Iot Local](https://github.com/tradle/serverless-iot-local)**
    AWS Iot events with serverless-offline | [tradle](http://github.com/tradle) | -| **[Serverless Jest Plugin](https://github.com/SC5/serverless-jest-plugin)**
    A Serverless Plugin for the Serverless Framework which adds support for test-driven development using Jest | [SC5](http://github.com/SC5) | -| **[Serverless Kms Secrets](https://github.com/SC5/serverless-kms-secrets)**
    Allows to easily encrypt and decrypt secrets using KMS from the serverless CLI | [SC5](http://github.com/SC5) | -| **[Serverless Kubeless](https://github.com/serverless/serverless-kubeless)**
    Serverless plugin for deploying functions to Kubeless. | [serverless](http://github.com/serverless) | -| **[Serverless Local Dev Server](https://github.com/DieProduktMacher/serverless-local-dev-server)**
    Speeds up development of Alexa Skills, Chatbots and APIs by exposing your functions as local HTTP endpoints and mapping received events. | [DieProduktMacher](http://github.com/DieProduktMacher) | -| **[Serverless Local Environment](https://github.com/piercus/serverless-local-environment)**
    Serverless plugin to set local environment variables and remote environment variable to different values | [piercus](http://github.com/piercus) | -| **[Serverless Local Schedule](https://github.com/UnitedIncome/serverless-local-schedule)**
    Schedule AWS CloudWatch Event based invocations in local time(with DST support!) | [UnitedIncome](http://github.com/UnitedIncome) | -| **[Serverless Log Forwarding](https://github.com/amplify-education/serverless-log-forwarding)**
    Serverless plugin for forwarding CloudWatch logs to another Lambda function. | [amplify-education](http://github.com/amplify-education) | -| **[Serverless Micro](https://github.com/barstoolsports/serverless-micro)**
    Plugin to help manage multiple micro services under one main service. | [barstoolsports](http://github.com/barstoolsports) | -| **[Serverless Mocha Plugin](https://github.com/SC5/serverless-mocha-plugin)**
    A Serverless Plugin for the Serverless Framework which adds support for test-driven development using Mocha | [SC5](http://github.com/SC5) | -| **[Serverless Multi Dotnet](https://github.com/tsibelman/serverless-multi-dotnet)**
    A serverless plugin to pack C# lambdas functions that are spread to multiple CS projects. | [tsibelman](http://github.com/tsibelman) | -| **[Serverless Nested Stack](https://github.com/jagdish-176/serverless-nested-stack)**
    A plugin to Workaround for Cloudformation 200 resource limit | [jagdish-176](http://github.com/jagdish-176) | -| **[Serverless Offline](https://github.com/dherault/serverless-offline)**
    Emulate AWS λ and API Gateway locally when developing your Serverless project | [dherault](http://github.com/dherault) | -| **[Serverless Offline Direct Lambda](https://github.com/civicteam/serverless-offline-direct-lambda)**
    Allow offline direct lambda-to-lambda interactions by exposing lambdas with no API Gateway event via HTTP. | [civicteam](http://github.com/civicteam) | -| **[Serverless Offline Scheduler](https://github.com/ajmath/serverless-offline-scheduler)**
    Runs scheduled functions offline while integrating with serverless-offline | [ajmath](http://github.com/ajmath) | -| **[Serverless Offline Sns](https://github.com/mj1618/serverless-offline-sns)**
    Serverless plugin to run a local SNS server and call serverless SNS handlers with events notifications. | [mj1618](http://github.com/mj1618) | -| **[Serverless Offline Ssm](https://github.com/janders223/serverless-offline-ssm)**
    Read SSM parameters from a .env file instead of AWS | [janders223](http://github.com/janders223) | -| **[Serverless Package Common](https://github.com/onlicar/serverless-package-common)**
    Deploy microservice Python Serverless services with common code | [onlicar](http://github.com/onlicar) | -| **[Serverless Package Python Functions](https://github.com/ubaniabalogun/serverless-package-python-functions)**
    Packaging Python Lambda functions with only the dependencies/requirements they need. | [ubaniabalogun](http://github.com/ubaniabalogun) | -| **[Serverless Parameters](https://github.com/svdgraaf/serverless-parameters)**
    Add parameters to the generated cloudformation templates | [svdgraaf](http://github.com/svdgraaf) | -| **[Serverless Plugin Aws Alerts](https://github.com/ACloudGuru/serverless-plugin-aws-alerts)**
    A Serverless plugin to easily add CloudWatch alarms to functions | [ACloudGuru](http://github.com/ACloudGuru) | -| **[Serverless Plugin Aws Resolvers](https://github.com/DopplerLabs/serverless-plugin-aws-resolvers)**
    Resolves variables from ESS, RDS, or Kinesis for serverless services | [DopplerLabs](http://github.com/DopplerLabs) | -| **[Serverless Plugin Bespoken](https://github.com/bespoken/serverless-plugin-bespoken)**
    Creates a local server and a proxy so you don't have to deploy anytime you want to test your code | [bespoken](http://github.com/bespoken) | -| **[Serverless Plugin Bind Deployment Id](https://github.com/jacob-meacham/serverless-plugin-bind-deployment-id)**
    A Serverless plugin to bind the randomly generated deployment resource to your custom resources | [jacob-meacham](http://github.com/jacob-meacham) | -| **[Serverless Plugin Browserifier](https://github.com/digitalmaas/serverless-plugin-browserifier)**
    Reduce the size and speed up your Node.js based lambda's using browserify. | [digitalmaas](http://github.com/digitalmaas) | -| **[Serverless Plugin Browserify](https://github.com/doapp-ryanp/serverless-plugin-browserify)**
    Speed up your node based lambda's | [doapp-ryanp](http://github.com/doapp-ryanp) | -| **[Serverless Plugin Canary Deployments](https://github.com/davidgf/serverless-plugin-canary-deployments)**
    A Serverless plugin to implement canary deployments of Lambda functions | [davidgf](http://github.com/davidgf) | -| **[Serverless Plugin Cfauthorizer](https://github.com/SC5/serverless-plugin-cfauthorizer)**
    This plugin allows you to define your own API Gateway Authorizers as the Serverless CloudFormation resources and apply them to HTTP endpoints. | [SC5](http://github.com/SC5) | -| **[Serverless Plugin Chrome](https://github.com/adieuadieu/serverless-chrome/tree/master/packages/serverless-plugin)**
    Plugin which bundles and ensures that Headless Chrome/Chromium is running when your AWS Lambda function handler is invoked. | [adieuadieu](http://github.com/adieuadieu) | -| **[Serverless Plugin Cloudwatch Sumologic](https://github.com/ACloudGuru/serverless-plugin-cloudwatch-sumologic)**
    Plugin which auto-subscribes a log delivery lambda function to lambda log groups created by serverless | [ACloudGuru](http://github.com/ACloudGuru) | -| **[Serverless Plugin Colocate](https://github.com/aronim/serverless-plugin-colocate)**
    Serverless Plugin to keep your configuration next to your code. | [aronim](http://github.com/aronim) | -| **[Serverless Plugin Common Excludes](https://github.com/dougmoscrop/serverless-plugin-common-excludes)**
    Adds commonly excluded files to package.excludes | [dougmoscrop](http://github.com/dougmoscrop) | -| **[Serverless Plugin Custom Domain](https://github.com/dougmoscrop/serverless-plugin-custom-domain)**
    Reliably sets a BasePathMapping to an API Gateway Custom Domain | [dougmoscrop](http://github.com/dougmoscrop) | -| **[Serverless Plugin Deploy Environment](https://github.com/DopplerLabs/serverless-plugin-deploy-environment)**
    Plugin to manage deployment environment across stages | [DopplerLabs](http://github.com/DopplerLabs) | -| **[Serverless Plugin Diff](https://github.com/nicka/serverless-plugin-diff)**
    Compares your local AWS CloudFormation templates against deployed ones. | [nicka](http://github.com/nicka) | -| **[Serverless Plugin Dynamodb Autoscaling](https://github.com/medikoo/serverless-plugin-dynamodb-autoscaling)**
    Auto generate auto scaling configuration for configured DynamoDB tables | [medikoo](http://github.com/medikoo) | -| **[Serverless Plugin Elastic Beanstalk](https://github.com/rawphp/serverless-plugin-elastic-beanstalk)**
    A serverless plugin to deploy applications to AWS ElasticBeanstalk. | [rawphp](http://github.com/rawphp) | -| **[Serverless Plugin Embedded Env In Code](https://github.com/zaru/serverless-plugin-embedded-env-in-code)**
    Replace environment variables with static strings before deployment. It’s for Lambda@Edge. | [zaru](http://github.com/zaru) | -| **[Serverless Plugin Encode Env Var Objects](https://github.com/yonomi/serverless-plugin-encode-env-var-objects)**
    Serverless plugin to encode any environment variable objects. | [yonomi](http://github.com/yonomi) | -| **[Serverless Plugin External Sns Events](https://github.com/silvermine/serverless-plugin-external-sns-events)**
    Add ability for functions to use existing or external SNS topics as an event source | [silvermine](http://github.com/silvermine) | -| **[Serverless Plugin Fastdeploy](https://github.com/aronim/serverless-plugin-fastdeploy)**
    Serverless Plugin to perform fast deployments for large service packages. | [aronim](http://github.com/aronim) | -| **[Serverless Plugin Git Variables](https://github.com/jacob-meacham/serverless-plugin-git-variables)**
    A Serverless plugin to expose git variables (branch name, HEAD description, full commit hash) to your serverless services | [jacob-meacham](http://github.com/jacob-meacham) | -| **[Serverless Plugin Graphiql](https://github.com/bencooling/serverless-plugin-graphiql)**
    A Serverless plugin to run a local http server for graphiql and your graphql handler | [bencooling](http://github.com/bencooling) | -| **[Serverless Plugin Ifelse](https://github.com/anantab/serverless-plugin-ifelse)**
    A Serverless Plugin to write If Else conditions in serverless YAML file | [anantab](http://github.com/anantab) | -| **[Serverless Plugin Include Dependencies](https://github.com/dougmoscrop/serverless-plugin-include-dependencies)**
    This is a Serverless plugin that should make your deployed functions smaller. | [dougmoscrop](http://github.com/dougmoscrop) | -| **[Serverless Plugin Inject Dependencies](https://github.com/loanmarket/serverless-plugin-inject-dependencies)**
    Painlessly deploy serverless projects with only the required dependencies. | [loanmarket](http://github.com/loanmarket) | -| **[Serverless Plugin Iopipe](https://github.com/iopipe/serverless-plugin-iopipe)**
    See inside your Lambda functions with high fidelity metrics and monitoring. | [iopipe](http://github.com/iopipe) | -| **[Serverless Plugin Lambda Dead Letter](https://github.com/gmetzker/serverless-plugin-lambda-dead-letter)**
    A Serverless plugin that can configure a lambda with a dead letter queue or topic | [gmetzker](http://github.com/gmetzker) | -| **[Serverless Plugin Log Subscription](https://github.com/dougmoscrop/serverless-plugin-log-subscription)**
    Adds a CloudWatch LogSubscription for functions | [dougmoscrop](http://github.com/dougmoscrop) | -| **[Serverless Plugin Metric](https://github.com/alex20465/serverless-plugin-metric)**
    Creates dynamically AWS metric-filter resources with custom patterns | [alex20465](http://github.com/alex20465) | -| **[Serverless Plugin Multiple Responses](https://github.com/silvermine/serverless-plugin-multiple-responses)**
    Enable multiple content-types for Response template | [silvermine](http://github.com/silvermine) | -| **[Serverless Plugin Node Shim](https://github.com/jzimmek/serverless-plugin-node-shim)**
    Serverless plugin to run your functions in nodejs version (8 LTS, 9+) on aws lambda | [jzimmek](http://github.com/jzimmek) | -| **[Serverless Plugin Offline Dynamodb Stream](https://github.com/orchestrated-io/serverless-plugin-offline-dynamodb-stream)**
    Serverless Plugin for emulating dynamodb stream triggering lambda functions offline | [orchestrated-io](http://github.com/orchestrated-io) | -| **[Serverless Plugin Offline Kinesis Events](https://github.com/DopplerLabs/serverless-plugin-offline-kinesis-events)**
    Plugin that works with serverless-offline to allow offline testing of serverless functions that are triggered by Kinesis events. | [DopplerLabs](http://github.com/DopplerLabs) | -| **[Serverless Plugin Offline Kinesis](https://github.com/godu/serverless/tree/master/packages/serverless-offline-kinesis)**
    ServerlessOffline's plugin which listens Kinesis stream and invokes locally your handlers | [godu](http://github.com/godu) | -| **[Serverless Plugin Offline DynamoDBStreams](https://github.com/godu/serverless/tree/master/packages/serverless-offline-dynamodb-streams)**
    ServerlessOffline's plugin which listens DynamoDBStreams stream and invokes locally your handlers | [godu](http://github.com/godu) | -| **[Serverless Plugin Offline SQS](https://github.com/godu/serverless/tree/master/packages/serverless-offline-sqs)**
    ServerlessOffline's plugin which listens SQS queue and invokes locally your handlers | [godu](http://github.com/godu) | -| **[Serverless Plugin Optimize](https://github.com/FidelLimited/serverless-plugin-optimize)**
    Bundle with Browserify, transpile with Babel to ES5 and minify with Uglify your Serverless functions. | [FidelLimited](http://github.com/FidelLimited) | -| **[Serverless Plugin Package Dotenv File](https://github.com/ACloudGuru/serverless-plugin-package-dotenv-file)**
    A Serverless plugin to copy a .env file into the serverless package | [ACloudGuru](http://github.com/ACloudGuru) | -| **[Serverless Plugin Parent](https://github.com/aronim/serverless-plugin-parent)**
    Serverless Plugin that allows you to keep common configuration in a parent serverless.yml | [aronim](http://github.com/aronim) | -| **[Serverless Plugin Provider Groups](https://github.com/loanmarket/serverless-plugin-provider-groups)**
    A plugin to allow management of grouped settings within large serverless projects. | [loanmarket](http://github.com/loanmarket) | -| **[Serverless Plugin Reducer](https://github.com/medikoo/serverless-plugin-reducer)**
    Reduce Node.js lambda package so it contains only lambda dependencies | [medikoo](http://github.com/medikoo) | -| **[Serverless Plugin Registry](https://github.com/aronim/serverless-plugin-registry)**
    Serverless plugin to register function names with AWS SSM Parameter Store. | [aronim](http://github.com/aronim) | -| **[Serverless Plugin Scripts](https://github.com/mvila/serverless-plugin-scripts)**
    Add scripting capabilities to the Serverless Framework | [mvila](http://github.com/mvila) | -| **[Serverless Plugin Select](https://github.com/FidelLimited/serverless-plugin-select)**
    Select which functions are to be deployed based on region and stage. | [FidelLimited](http://github.com/FidelLimited) | -| **[Serverless Plugin Simulate](https://github.com/gertjvr/serverless-plugin-simulate)**
    Simulate AWS Lambda and API Gateway locally using Docker | [gertjvr](http://github.com/gertjvr) | -| **[Serverless Plugin Split Stacks](https://github.com/dougmoscrop/serverless-plugin-split-stacks)**
    Migrate certain resources to nested stacks | [dougmoscrop](http://github.com/dougmoscrop) | -| **[Serverless Plugin Stack Config](https://github.com/rawphp/serverless-plugin-stack-config)**
    A serverless plugin to manage configurations for a stack across micro-services. | [rawphp](http://github.com/rawphp) | -| **[Serverless Plugin Stack Outputs](https://github.com/svdgraaf/serverless-plugin-stack-outputs)**
    Displays stack outputs for your serverless stacks when `sls info` is ran | [svdgraaf](http://github.com/svdgraaf) | -| **[Serverless Plugin Stackstorm](https://github.com/StackStorm/serverless-plugin-stackstorm)**
    Reusable Functions from StackStorm Exchange | [StackStorm](http://github.com/StackStorm) | -| **[Serverless Plugin Stage Variables](https://github.com/svdgraaf/serverless-plugin-stage-variables)**
    Add stage variables for Serverless 1.x to ApiGateway, so you can use variables in your Lambda's | [svdgraaf](http://github.com/svdgraaf) | -| **[Serverless Plugin Subscription Filter](https://github.com/tsub/serverless-plugin-subscription-filter)**
    A serverless plugin to register AWS CloudWatchLogs subscription filter | [tsub](http://github.com/tsub) | -| **[Serverless Plugin Tracer](https://github.com/enykeev/serverless-plugin-tracer/)**
    Trace serverless hooks as they execute | [enykeev](http://github.com/enykeev) | -| **[Serverless Plugin Transpiler](https://github.com/medikoo/serverless-plugin-transpiler)**
    Transpile lambda files during packaging step | [medikoo](http://github.com/medikoo) | -| **[Serverless Plugin Typescript](https://github.com/graphcool/serverless-plugin-typescript)**
    Serverless plugin for zero-config Typescript support. | [graphcool](http://github.com/graphcool) | -| **[Serverless Plugin Vpc Eni Cleanup](https://github.com/medikoo/serverless-plugin-vpc-eni-cleanup)**
    Automatic cleanup of VPC network interfaces on stage removal | [medikoo](http://github.com/medikoo) | -| **[Serverless Plugin Warmup](https://github.com/FidelLimited/serverless-plugin-warmup)**
    Keep your lambdas warm during Winter. | [FidelLimited](http://github.com/FidelLimited) | -| **[Serverless Plugin Webpack](https://github.com/goldwasserexchange/serverless-plugin-webpack)**
    A serverless plugin to automatically bundle your functions individually with webpack | [goldwasserexchange](http://github.com/goldwasserexchange) | -| **[Serverless Plugin Write Env Vars](https://github.com/silvermine/serverless-plugin-write-env-vars)**
    Write environment variables out to a file that is compatible with dotenv | [silvermine](http://github.com/silvermine) | -| **[Serverless Prune Plugin](https://github.com/claygregory/serverless-prune-plugin)**
    Deletes old versions of functions from AWS, preserving recent and aliased versions | [claygregory](http://github.com/claygregory) | -| **[Serverless Pseudo Parameters](https://github.com/svdgraaf/serverless-pseudo-parameters)**
    Use ${AWS::AccountId} and other cloudformation pseudo parameters in your serverless.yml values | [svdgraaf](http://github.com/svdgraaf) | -| **[Serverless Puresec Cli](https://github.com/puresec/serverless-puresec-cli)**
    Serverless Plugin for magically creating IAM roles that are least privileged per function. | [puresec](http://github.com/puresec) | -| **[Serverless Python Individually](https://github.com/cfchou/serverless-python-individually)**
    A serverless framework plugin to install multiple lambda functions written in python | [cfchou](http://github.com/cfchou) | -| **[Serverless Python Requirements](https://github.com/UnitedIncome/serverless-python-requirements)**
    Serverless plugin to bundle Python packages | [UnitedIncome](http://github.com/UnitedIncome) | -| **[Serverless Reqvalidator Plugin](https://github.com/RafPe/serverless-reqvalidator-plugin)**
    Serverless plugin to add request validator to API Gateway methods | [RafPe](http://github.com/RafPe) | -| **[Serverless Resources Env](https://github.com/rurri/serverless-resources-env)**
    After Deploy, this plugin fetches cloudformation resource identifiers and sets them on AWS lambdas, and creates local .-env file | [rurri](http://github.com/rurri) | -| **[Serverless Run Function Plugin](https://github.com/lithin/serverless-run-function-plugin)**
    Run serverless function locally | [lithin](http://github.com/lithin) | -| **[Serverless Rust](https://github.com/softprops/serverless-rust)**
    Deploy Rustlang applications to AWS Lambda | [softprops](http://github.com/softprops) | -| **[Serverless S 3 Encryption](https://github.com/tradle/serverless-s3-encryption)**
    Set or remove the encryption settings on your s3 buckets | [tradle](http://github.com/tradle) | -| **[Serverless S 3 Remover](https://github.com/sinofseven/serverless-s3-remover)**
    A serverless plugin to make s3 buckets empty before deleting cloudformation stack when ```sls remove``` | [sinofseven](http://github.com/sinofseven) | -| **[Serverless S 3 Sync](https://github.com/k1LoW/serverless-s3-sync)**
    A plugin to sync local directories and S3 prefixes for Serverless Framework, | [k1LoW](http://github.com/k1LoW) | -| **[Serverless S 3 Bucket Sync](https://github.com/sbstjn/serverless-s3bucket-sync)**
    Sync a local folder with a S3 bucket after sls deploy | [sbstjn](http://github.com/sbstjn) | -| **[Serverless Sam](https://github.com/SAPessi/serverless-sam)**
    Exports an AWS SAM template for a service created with the Serverless Framework. | [SAPessi](http://github.com/SAPessi) | -| **[Serverless Scriptable Plugin](https://github.com/weixu365/serverless-scriptable-plugin)**
    Customize Serverless behavior without writing a plugin. | [weixu365](http://github.com/weixu365) | -| **[Serverless Sentry](https://github.com/arabold/serverless-sentry-plugin)**
    Automatic monitoring of memory usage, execution timeouts and forwarding of Lambda errors to Sentry (https://sentry.io). | [arabold](http://github.com/arabold) | -| **[Serverless Shell](https://github.com/UnitedIncome/serverless-shell)**
    Drop to a runtime shell with all the environment variables set that you'd have in lambda. | [UnitedIncome](http://github.com/UnitedIncome) | -| **[Serverless Sns Filter](https://github.com/MechanicalRock/serverless-sns-filter)**
    Serverless plugin to add SNS Subscription Filters to events | [MechanicalRock](http://github.com/MechanicalRock) | -| **[Serverless Spa](https://github.com/gilmarsquinelato/serverless-spa)**
    Serverless plugin to deploy your website to AWS S3 using Webpack to bundle it. | [gilmarsquinelato](http://github.com/gilmarsquinelato) | -| **[Serverless Sqs Alarms Plugin](https://github.com/sbstjn/serverless-sqs-alarms-plugin)**
    Wrapper to setup CloudWatch Alarms on SQS queue length | [sbstjn](http://github.com/sbstjn) | -| **[Serverless Sqs Fifo](https://github.com/vortarian/serverless-sqs-fifo)**
    A serverless plugin to handle creation of sqs fifo queue's in aws (stop-gap) | [vortarian](http://github.com/vortarian) | -| **[Serverless Ssm Fetch](https://github.com/gozup/serverless-ssm-fetch)**
    Sets "AWS Systems Manager Parameter Store (SSM)" parameters into functions' environment variables. | [gozup](http://github.com/gozup) | -| **[Serverless Stack Output](https://github.com/sbstjn/serverless-stack-output)**
    Store output from your AWS CloudFormation Stack in JSON/YAML/TOML files, or to pass it to a JavaScript function for further processing. | [sbstjn](http://github.com/sbstjn) | -| **[Serverless Stage Manager](https://github.com/jeremydaly/serverless-stage-manager)**
    Super simple Serverless plugin for validating stage names before deployment | [jeremydaly](http://github.com/jeremydaly) | -| **[Serverless Static](https://github.com/iliasbhal/serverless-static)**
    Easily serve files from a folder while developing on localhost with the serverless-offline plugin | [iliasbhal](http://github.com/iliasbhal) | -| **[Serverless Step Functions](https://github.com/horike37/serverless-step-functions)**
    AWS Step Functions with Serverless Framework. | [horike37](http://github.com/horike37) | -| **[Serverless Sthree Env](https://github.com/StyleTributeIT/serverless-sthree-env)**
    Serverless plugin to get config from a json formatted file in S3 and copy them to environment variable | [StyleTributeIT](http://github.com/StyleTributeIT) | -| **[Serverless Subscription Filter](https://github.com/blackevil245/serverless-subscription-filter)**
    Serverless plugin to register subscription filter for Lambda logs. Register and pipe the logs of one lambda to another to process. | [blackevil245](http://github.com/blackevil245) | -| **[Serverless Tag Api Gateway](https://github.com/gfragoso/serverless-tag-api-gateway)**
    Serverless plugin to tag API Gateway | [gfragoso](http://github.com/gfragoso) | -| **[Serverless Tag Cloud Watch Logs](https://github.com/gfragoso/serverless-tag-cloud-watch-logs)**
    Serverless plugin to tag CloudWatchLogs | [gfragoso](http://github.com/gfragoso) | -| **[Serverless Tag Sqs](https://github.com/gfragoso/serverless-tag-sqs)**
    Serverless plugin to tag SQS - Simple Queue Service | [gfragoso](http://github.com/gfragoso) | -| **[Serverless Vpc Discovery](https://github.com/amplify-education/serverless-vpc-discovery)**
    Serverless plugin for discovering VPC / Subnet / Security Group configuration by name. | [amplify-education](http://github.com/amplify-education) | -| **[Serverless Webpack](https://github.com/serverless-heaven/serverless-webpack)**
    Serverless plugin to bundle your lambdas with Webpack | [serverless-heaven](http://github.com/serverless-heaven) | -| **[Serverless Wsgi](https://github.com/logandk/serverless-wsgi)**
    Serverless plugin to deploy WSGI applications (Flask/Django/Pyramid etc.) and bundle Python packages | [logandk](http://github.com/logandk) | - - -## Example Projects (V1.0) - - -| Project Name | Author | -|:-------------|:------:| -| **[Serverless Pipeline](https://github.com/msfidelis/serverless-pipeline)**
    Simple CI/CD Pipeline to Serverless Projects on AWS using Codepipeline, Codebuild and Terraform | [msfidelis](http://github.com/msfidelis) | -| **[Jwtauthorizr](https://github.com/serverlessbuch/jwtAuthorizr)**
    Custom JWT Authorizer Lambda function for Amazon API Gateway with Bearer JWT | [serverlessbuch](http://github.com/serverlessbuch) | -| **[AWS Demo Java Spring Cloud Function Serverless](https://github.com/mbsambangi/aws-java-spring-cloud-function-demo)**
    If Java is your choice of programming language-Spring Cloud Function,Serverless Framework makes a great technology stack. It boosts developer productivity by decoupling from Vendor specific FaaS API, and deployment activities. | [mbsambangi](http://github.com/mbsambangi) | -| **[Serverless Architecture Boilerplate](https://github.com/msfidelis/serverless-architecture-boilerplate)**
    Boilerplate to organize and deploy big projects using Serverless and CloudFormation on AWS | [msfidelis](http://github.com/msfidelis) | -| **[Bablebot](https://github.com/abiglobalhealth/babelbot)**
    Lambda + API Gateway: Zero-to-chatbot in <10 lines of JS. Built-in integrations for Messenger, Telegram, Kik, Line, Twilio, Skype, and Wechat. Or roll your own! | [abiglobalhealth](http://github.com/abiglobalhealth) | -| **[Jwt Authorizr](https://github.com/serverlessbuch/jwtAuthorizr)**
    Custom JWT Authorizer Lambda function for Amazon API Gateway with Bearer JWT | [serverlessbuch](http://github.com/serverlessbuch) | -| **[Slack Signup Serverless](https://github.com/dzimine/slack-signup-serverless)**
    Serverless signup to Slack and more. Lambda with Python, StepFunctions, and Web front end. Python boilerplate included. | [dzimine](http://github.com/dzimine) | -| **[Serverless Graphql Api](https://github.com/boazdejong/serverless-graphql-api)**
    Serverless GraphQL API using Lambda and DynamoDB | [boazdejong](http://github.com/boazdejong) | -| **[Serverless Screenshot](https://github.com/svdgraaf/serverless-screenshot)**
    Serverless Screenshot Service using PhantomJS | [svdgraaf](http://github.com/svdgraaf) | -| **[Serverless Postgraphql](https://github.com/rentrop/serverless-postgraphql)**
    GraphQL endpoint for PostgreSQL using postgraphql | [rentrop](http://github.com/rentrop) | -| **[Serverless Messenger Boilerplate](https://github.com/SC5/serverless-messenger-boilerplate)**
    Serverless messenger bot boilerplate | [SC5](http://github.com/SC5) | -| **[Serverless Npm Registry](https://github.com/craftship/yith)**
    Serverless private npm registry, proxy and cache. | [craftship](http://github.com/craftship) | -| **[Serverless Pokego](https://github.com/jch254/pokego-serverless)**
    Serverless-powered API to fetch nearby Pokemon Go data | [jch254](http://github.com/jch254) | -| **[Serverless Weekly 2 Pocket App](https://github.com/s0enke/weekly2pocket)**
    Serverless-powered API for sending posts to pocket app | [s0enke](http://github.com/s0enke) | -| **[Serverless Facebook Quotebot](https://github.com/pmuens/quotebot)**
    100% Serverless Facebook messenger chatbot which will respond with inspiring quotes | [pmuens](http://github.com/pmuens) | -| **[Serverless Slack Trevorbot](https://github.com/conveyal/trevorbot)**
    Slack bot for info on where in the world is Trevor Gerhardt? | [conveyal](http://github.com/conveyal) | -| **[Serverless Garden Aid](https://github.com/garden-aid/web-bff)**
    IoT Garden Aid Backend | [garden-aid](http://github.com/garden-aid) | -| **[Serverless React Boilerplate](https://github.com/99xt/serverless-react-boilerplate)**
    A serverless react boilerplate for offline development | [99xt](http://github.com/99xt) | -| **[Serverless Delivery Framework](https://github.com/99xt/serverless-delivery-framework)**
    This is a boilerplate for version release pipeline with serverless framework | [99xt](http://github.com/99xt) | -| **[Serverless Mailgun Slack](https://github.com/Marcus-L/serverless-mailgun-slack)**
    A Serverless function for posting to a Slack Webhook in response to a Mailgun route | [Marcus-L](http://github.com/Marcus-L) | -| **[Pfs Email Serverless](https://github.com/SCPR/pfs-email-serverless)**
    This is a lambda function created by the serverless framework. It searches through members in our mongodb who have not been sent emails and sends them an email with their custom token to unlock the pledge free stream. It then marks those members off as already receiving the email. | [SCPR](http://github.com/SCPR) | -| **[Plaid Cashburndown Service](https://github.com/cplee/cashburndown-service)**
    Service for calculating cash burndown with plaid. Frontend code can be found here: https://github.com/cplee/cashburndown-site | [cplee](http://github.com/cplee) | -| **[Cordis Serverless](https://github.com/marzeelabs/cordis-serverless)**
    A serverless API for EU Cordis data | [marzeelabs](http://github.com/marzeelabs) | -| **[Serverless Newsletter Signup](https://github.com/ivanderbu2/serverless-newsletter-signup)**
    Saves user details into DynamoDB table. Required values are email, first_name and last_name. | [ivanderbu2](http://github.com/ivanderbu2) | -| **[Serverless Slack Cron](https://github.com/ivanderbu2/serverless-slack-cron)**
    Lambda function which sends messages to Slack channel in regular intervals via cron trigger. | [ivanderbu2](http://github.com/ivanderbu2) | -| **[Giphy Bot](https://github.com/tywong/lambda-workshop-2016/tree/master/giphy-bot)**
    giphy-bot for Facebook chat | [tywong](http://github.com/tywong) | -| **[Jwt Lambda Python](https://github.com/mikaelmork/jwt-auth.serverless)**
    Minimal proof-of-concept implementation of JWT with Serverless / AWS Lambda | [mikaelmork](http://github.com/mikaelmork) | -| **[Sls Access Counter](https://github.com/takahashim/sls-access-counter)**
    Site visitor counter | [takahashim](http://github.com/takahashim) | -| **[Sls Form Mail](https://github.com/takahashim/sls-form-mail)**
    Send SNS email from form data | [takahashim](http://github.com/takahashim) | -| **[Serverless Python Sample](https://github.com/bennybauer/serverless-python-sample)**
    A simple serverless python sample with REST API endpoints and dependencies | [bennybauer](http://github.com/bennybauer) | -| **[Serverless Msg Gateway](https://github.com/yonahforst/msg-gateway)**
    A messaging aggregator for kik, skype, twilio, telegram, & messenger. Send and receive messages in a standard format. | [yonahforst](http://github.com/yonahforst) | -| **[Serverless AWS Rekognition Finpics](https://github.com/rgfindl/finpics)**
    Use AWS Rekognition to provide a faces search of finpics.com | [rgfindl](http://github.com/rgfindl) | -| **[Serverless Slack Emojibot](https://github.com/markhobson/emojibot)**
    Serverless slack bot for emoji | [markhobson](http://github.com/markhobson) | -| **[Keboola Developer Portal](https://github.com/keboola/developer-portal)**
    Keboola developer portal built with Serverless | [keboola](http://github.com/keboola) | -| **[Serverless Cloudwatch Rds Custom Metrics](https://github.com/AndrewFarley/serverless-cloudwatch-rds-custom-metrics)**
    A NodeJS-based MySQL RDS Data Collection script to push Custom Metrics to Cloudwatch with Serverless | [AndrewFarley](http://github.com/AndrewFarley) | -| **[Jrestless Examples](https://github.com/bbilger/jrestless-examples)**
    [JRestless](https://github.com/bbilger/jrestless) (Java / JAX-RS) examples for [API Gateway Functions](https://github.com/bbilger/jrestless-examples/tree/master/aws/gateway) ([plain JAX-RS](https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-showcase), [Spring](https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-spring), [binary data requests/responses](https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-binary), [custom authorizers](https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-security-custom-authorizer) and [Cognito User Pool authorizers](https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-security-cognito-authorizer)), [SNS Functions](https://github.com/bbilger/jrestless-examples/blob/master/aws/sns/aws-sns-usage-example) (asynchronous communication between functions) and [Service Functions](https://github.com/bbilger/jrestless-examples/blob/master/aws/service/aws-service-usage-example) (synchronous HTTP-like communication between functions - transparent through Feign) | [bbilger](http://github.com/bbilger) | -| **[Sc 5 Serverless Boilerplate](https://github.com/SC5/sc5-serverless-boilerplate)**
    A boilerplate that contains setup for test-driven development | [SC5](http://github.com/SC5) | -| **[Serverless Blog To Podcast](https://github.com/SC5/serverless-blog-to-podcast)**
    Service that reads RSS feed and converts the entries to a podcast feed and audio files using Amazon Polly | [SC5](http://github.com/SC5) | -| **[Offset Trump](https://github.com/FLGMwt/offset-trump)**
    Single page app using Serverless (C# runtime) and S3 site hosting. Pledge to do a good thing for the next four years to offset the potential negative effects of the US Presidency | [FLGMwt](http://github.com/FLGMwt) | -| **[Serverless Url Shortener](https://github.com/aletheia/serverless-url-shortener)**
    A simple url-shortener, using Serverless framework | [aletheia](http://github.com/aletheia) | -| **[Serverless Html Pdf](https://github.com/calvintychan/serverless-html-pdf)**
    Service that convert HTML to PDF using PhantomJS's rasterize example. | [calvintychan](http://github.com/calvintychan) | -| **[Serverless Examples Cached Rds Ws](https://github.com/mugglmenzel/serverless-examples-cached-rds-ws)**
    A serverless framework example project that uses API Gateway, ElastiCache, and RDS PostgreSQL. | [mugglmenzel](http://github.com/mugglmenzel) | -| **[Bittman](https://github.com/rhlsthrm/bittman)**
    A serverless project that follows a stock trading algorithm and uses scheduled functions to save data to DynamoDB and send emails through Mailgun. | [rhlsthrm](http://github.com/rhlsthrm) | -| **[Adoptable Pet Bot](https://github.com/lynnaloo/adoptable-pet-bot)**
    Tweets adoptable pets using Serverless (Node.js) and AWS Lambda | [lynnaloo](http://github.com/lynnaloo) | -| **[Owntracks Serverless](https://github.com/dschep/owntracks-serverless)**
    A serverless implementation of the OwnTracks HTTP backend | [dschep](http://github.com/dschep) | -| **[Serverless Modern Koa](https://github.com/barczaG/serverless-modern-koa)**
    Serverless modern koa starter kit | [barczaG](http://github.com/barczaG) | -| **[Serverless React JS Universal Rendering Boilerplate](https://github.com/TylorShin/react-universal-in-serverless)**
    ReactJS web app Starter kit does universal (isomorphic) rendering with Serverless | [TylorShin](http://github.com/TylorShin) | -| **[Open Bot](https://github.com/open-bot/open-bot)**
    An unoptionated Github bot driven by a configuration file in the repository | [open-bot](http://github.com/open-bot) | -| **[Aws Ses Serverless Example](https://github.com/lakshmantgld/aws-ses-serverless-example)**
    AWS SES example in NodeJS using lambda | [lakshmantgld](http://github.com/lakshmantgld) | -| **[AWS API Gateway Serverless Project Written In Go](https://github.com/yunspace/serverless-golang)**
    A serverless project that contains an API Gateway endpoint powered by a Lambda function written in golang and built using [eawsy/aws-lambda-go-shim](https://github.com/eawsy/aws-lambda-go-shim). | [yunspace](http://github.com/yunspace) | -| **[Video Preview And Analysis Service](https://github.com/laardee/video-preview-and-analysis-service)**
    An event-driven service that generates labels using Amazon Rekognition and creates preview GIF animation from a video file. | [laardee](http://github.com/laardee) | -| **[Serverless ES 6 7 CRUD API](https://github.com/AnomalyInnovations/serverless-stack-demo-api)**
    [Serverless Stack](http://serverless-stack.com) examples of backend CRUD APIs (DynamoDB + Lambda + API Gateway + Cognito User Pool authorizer) for [React.js single-page app](http://demo.serverless-stack.com) | [AnomalyInnovations](http://github.com/AnomalyInnovations) | -| **[SQS Worker With AWS Lambda And Cloud Watch Alarms](https://github.com/sbstjn/sqs-worker-serverless)**
    Process messages stored in SQS with an [auto-scaled AWS Lambda worker](https://sbstjn.com/serverless-sqs-worker-with-aws-lambda.html) function. | [sbstjn](http://github.com/sbstjn) | -| **[Serverless Image Manager](https://github.com/TylorShin/lambda-image-manager)**
    image upload / download with resizing. Used API gateway's binary support & serverless | [TylorShin](http://github.com/TylorShin) | -| **[AWS Lambda Power Tuning Powered By Step Functions](https://github.com/alexcasalboni/aws-lambda-power-tuning)**
    Build a [Step Functions](https://aws.amazon.com/step-functions/) state machine to optimize your AWS Lambda Function memory/power configuration. | [alexcasalboni](http://github.com/alexcasalboni) | -| **[Amazon Kinesis Streams Fan Out Via Kinesis Analytics](https://github.com/alexcasalboni/kinesis-streams-fan-out-kinesis-analytics)**
    Use [Amazon Kinesis Analytics](https://aws.amazon.com/kinesis/analytics/) to fan-out your Kinesis Streams and avoid read throttling. | [alexcasalboni](http://github.com/alexcasalboni) | -| **[Grants Api Serverless](https://github.com/comicrelief/grants-api-serverless)**
    ES6 API to consume data from an external API, ingest into Elasticsearch and return a queryable endpoint on top of Elasticsearch | [comicrelief](http://github.com/comicrelief) | -| **[Honey Lambda](https://github.com/0x4D31/honeyLambda)**
    a simple, serverless application designed to create and monitor URL {honey}tokens, on top of AWS Lambda and Amazon API Gateway | [0x4D31](http://github.com/0x4D31) | -| **[Faultline](https://github.com/faultline/faultline)**
    Error tracking tool on AWS managed services. | [faultline](http://github.com/faultline) | -| **[Stack Overflow Monitor](https://github.com/picsoung/stackoverflowmonitor)**
    Monitor Stack Overflow questions and post them in a Slack channel | [picsoung](http://github.com/picsoung) | -| **[Serverless Analytics](https://github.com/sbstjn/serverless-analytics)**
    Write your own Google Analytics clone and track website visitors serverless with API Gateway, Kinesis, Lambda, and DynamoDB. | [sbstjn](http://github.com/sbstjn) | -| **[React Stripe Serverless Ecommerce](https://github.com/patrick-michelberger/serverless-shop)**
    Serverless E-Commerce App with AWS Lambda, Stripe and React | [patrick-michelberger](http://github.com/patrick-michelberger) | -| **[Serverless Medium Text To Speech](https://github.com/RafalWilinski/serverless-medium-text-to-speech)**
    Serverless-based, text-to-speech service for Medium articles | [RafalWilinski](http://github.com/RafalWilinski) | -| **[Serverless Java Dynamo DB Implementation Example](https://github.com/igorbakman/java-lambda-dynamodb)**
    example for java programmers that want to work with AWS-Lambda and DynamoDB | [igorbakman](http://github.com/igorbakman) | -| **[AWS Cognito Custom User Pool Example](https://github.com/bsdkurt/aws-node-custom-user-pool)**
    Example CloudFormation custom resource backed by a lambda using Cognito User Pools | [bsdkurt](http://github.com/bsdkurt) | -| **[Serverless Lambda Protobuf Responses](https://github.com/theburningmonk/lambda-protobuf-demo)**
    Demo using API Gateway and Lambda with Protocol Buffer | [theburningmonk](http://github.com/theburningmonk) | -| **[Serverless Telegram Bot](https://github.com/jonatasbaldin/serverless-telegram-bot)**
    This example demonstrates how to setup an echo Telegram Bot using the Serverless Framework ⚡🤖 | [jonatasbaldin](http://github.com/jonatasbaldin) | -| **[Serverless Line Bot](https://github.com/jiyeonseo/azure-line-bot-example)**
    Echo Line bot using Azure Function with Node.js | [jiyeonseo](http://github.com/jiyeonseo) | -| **[Serverless Dashboard For Atom Editor](https://github.com/horike37/serverless-dashboard-for-atom)**
    Atom editor package which allows you to deploy and visualize your serverless services with Serverless Framework on your editor. | [horike37](http://github.com/horike37) | -| **[Serverless Lambda Vpc Nat Redis](https://github.com/ittus/aws-lambda-vpc-nat-examples)**
    Demo using API Gateway and Lambda with VPC and NAT to access Internet and AWS Resource | [ittus](http://github.com/ittus) | -| **[Serverless Gitlab CI](https://github.com/bvincent1/serverless-gitlab-ci)**
    Simple Gitlab CI template for automatic testing and deployments | [bvincent1](http://github.com/bvincent1) | -| **[Serverless Ffmpeg](https://github.com/kvaggelakos/serverless-ffmpeg)**
    Bucket event driven FFMPEG using serverless. Input bucket => Serverless ffmpeg => Output bucket. | [kvaggelakos](http://github.com/kvaggelakos) | -| **[Serverless SSH Command](https://github.com/upgle/serverless-openwhisk-ssh)**
    Example of executing ssh command with OpenWhisk | [upgle](http://github.com/upgle) | -| **[Realtime WW 2 Alexa Skill](https://github.com/ceilfors/realtime-ww2-alexa)**
    An alexa skill project that's using Alexa SDK. Can also be used for a working example of serverless-webpack (with use of async/await via babel). | [ceilfors](http://github.com/ceilfors) | -| **[Serverless Kakao Bot](https://github.com/JisuPark/serverless-kakao-bot)**
    Easy development for Kakaotalk Bot with Serverless | [JisuPark](http://github.com/JisuPark) | -| **[Serverless Q A Example](https://github.com/jacksoncharles/serverless-qa-template-api)**
    Inspired by the AWS example forum. A multitenancy Q&A template for surveys, forums and more | [jacksoncharles](http://github.com/jacksoncharles) | -| **[Personal Access Tokens Cron Check](https://github.com/madtrick/cfpat-audit)**
    Audit for leaked PAT in your Contentful organization. How to use serverless as cronjobs to keep your Personal Access Tokens secure | [madtrick](http://github.com/madtrick) | -| **[Serverless Sns Api](https://github.com/eddielisc/serverless-sns-api)**
    Build a SNS service on AWS, support backend API for SNS by device, by group and by user | [eddielisc](http://github.com/eddielisc) | -| **[Daily Instance Backups With AMI Rotation](https://github.com/AndrewFarley/AWSAutomatedDailyInstanceAMISnapshots)**
    A simple Python application which scans through your entire AWS account for tagged instances, makes daily AMIs of them, and rotates their backups automatically | [AndrewFarley](http://github.com/AndrewFarley) | -| **[Serverless Instagram Crawler](https://github.com/kimcoder/serverless-instagram-crawler)**
    Instagram hashtag Crawler with Lambda & DynamoDB. | [kimcoder](http://github.com/kimcoder) | -| **[Serving Binary Files](https://github.com/thomastoye/serverless-binary-files-xlsx)**
    Small example showing how to serve binary files using Serverless on AWS with the serverless-apigw-binary plugin, using generated Excel files as an example | [thomastoye](http://github.com/thomastoye) | -| **[Serverless CloudWatch Proxy](https://github.com/abbasdgr8/cloudwatch-proxy)**
    Logging adapter that consumes log streams from AWS CloudWatch, streams them to other log destinations | [abbasdgr8](http://github.com/abbasdgr8) | - - ## Contributing We love our contributors! Please read our [Contributing Document](CONTRIBUTING.md) to learn how you can start working on the Framework yourself. diff --git a/package-lock.json b/package-lock.json index 79f9de3ca..532105c06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -820,15 +820,6 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" }, - "ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -842,12 +833,6 @@ "color-convert": "^1.9.0" } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true - }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -990,12 +975,6 @@ "es-abstract": "^1.7.0" } }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true - }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -1049,12 +1028,6 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, - "autolinker": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.15.3.tgz", - "integrity": "sha1-NCQX2PLzRhsUzwkIjV7fh5HcmDI=", - "dev": true - }, "aws-sdk": { "version": "2.454.0", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.454.0.tgz", @@ -1830,12 +1803,6 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, - "coffee-script": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", - "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", - "dev": true - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -2264,12 +2231,6 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "deepmerge": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz", - "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==", - "dev": true - }, "default-require-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", @@ -2323,12 +2284,6 @@ "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", "dev": true }, - "diacritics-map": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz", - "integrity": "sha1-bfwP+dAQAKLt8oZTccrDFulJd68=", - "dev": true - }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -3043,57 +2998,6 @@ } } }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - }, - "dependencies": { - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "expect": { "version": "24.8.0", "resolved": "https://registry.npmjs.org/expect/-/expect-24.8.0.tgz", @@ -3577,8 +3481,7 @@ "version": "2.1.1", "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3602,15 +3505,13 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3627,22 +3528,19 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3773,8 +3671,7 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3788,7 +3685,6 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3805,7 +3701,6 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3814,15 +3709,13 @@ "version": "0.0.8", "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3843,7 +3736,6 @@ "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3932,8 +3824,7 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3947,7 +3838,6 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4043,8 +3933,7 @@ "version": "5.1.2", "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -4086,7 +3975,6 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4108,7 +3996,6 @@ "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4157,15 +4044,13 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true, - "optional": true + "dev": true } } }, @@ -4337,36 +4222,6 @@ "lodash": "^4.17.5" } }, - "gray-matter": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-2.1.1.tgz", - "integrity": "sha1-MELZrewqHe1qdwep7SOA+KF6Qw4=", - "dev": true, - "requires": { - "ansi-red": "^0.1.1", - "coffee-script": "^1.12.4", - "extend-shallow": "^2.0.1", - "js-yaml": "^3.8.1", - "toml": "^2.3.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - } - } - }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -4520,25 +4375,6 @@ "whatwg-encoding": "^1.0.1" } }, - "http-basic": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-2.5.1.tgz", - "integrity": "sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s=", - "dev": true, - "requires": { - "caseless": "~0.11.0", - "concat-stream": "^1.4.6", - "http-response-object": "^1.0.0" - }, - "dependencies": { - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", - "dev": true - } - } - }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -4551,12 +4387,6 @@ "toidentifier": "1.0.0" } }, - "http-response-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-1.1.0.tgz", - "integrity": "sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM=", - "dev": true - }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -4832,12 +4662,6 @@ "is-path-inside": "^1.0.0" } }, - "is-local-path": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-local-path/-/is-local-path-0.1.6.tgz", - "integrity": "sha1-gV0USxTVac7L6tTVaTCX8Aqb9sU=", - "dev": true - }, "is-my-ip-valid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", @@ -5962,15 +5786,6 @@ "package-json": "^4.0.0" } }, - "lazy-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", - "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", - "dev": true, - "requires": { - "set-getter": "^0.1.0" - } - }, "lazystream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", @@ -6024,53 +5839,6 @@ "immediate": "~3.0.5" } }, - "list-item": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/list-item/-/list-item-1.1.1.tgz", - "integrity": "sha1-DGXQDih8tmPMs8s4Sad+iewmilY=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "extend-shallow": "^2.0.1", - "is-number": "^2.1.0", - "repeat-string": "^1.5.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -6279,128 +6047,6 @@ "object-visit": "^1.0.0" } }, - "markdown-link": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/markdown-link/-/markdown-link-0.1.1.tgz", - "integrity": "sha1-MsXGUZmmRXMWMi0eQinRNAfIx88=", - "dev": true - }, - "markdown-magic": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/markdown-magic/-/markdown-magic-0.1.25.tgz", - "integrity": "sha512-NBVMv2IPdKaRIXcL8qmLkfq9O17tkByTr8sRkJ4l76tkp401hxCUA0r9mkhtnGJRevCqZ2KoHrIf9WYQUn8ztA==", - "dev": true, - "requires": { - "commander": "^2.9.0", - "deepmerge": "^1.3.0", - "find-up": "^2.1.0", - "fs-extra": "^1.0.0", - "globby": "^6.1.0", - "is-local-path": "^0.1.6", - "markdown-toc": "^1.0.2", - "sync-request": "^3.0.1" - }, - "dependencies": { - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "markdown-table": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", - "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", - "dev": true - }, - "markdown-toc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/markdown-toc/-/markdown-toc-1.2.0.tgz", - "integrity": "sha512-eOsq7EGd3asV0oBfmyqngeEIhrbkc7XVP63OwcJBIhH2EpG2PzFcbZdhy1jutXSlRBBVMNXHvMtSr5LAxSUvUg==", - "dev": true, - "requires": { - "concat-stream": "^1.5.2", - "diacritics-map": "^0.1.0", - "gray-matter": "^2.1.0", - "lazy-cache": "^2.0.2", - "list-item": "^1.1.1", - "markdown-link": "^0.1.1", - "minimist": "^1.2.0", - "mixin-deep": "^1.1.3", - "object.pick": "^1.2.0", - "remarkable": "^1.7.1", - "repeat-string": "^1.6.1", - "strip-color": "^0.1.0" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -7419,12 +7065,6 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" }, - "parse-github-url": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", - "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", - "dev": true - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -7643,15 +7283,6 @@ "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "dev": true }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "requires": { - "asap": "~2.0.3" - } - }, "promise-queue": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", @@ -7744,25 +7375,6 @@ "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -7938,28 +7550,6 @@ "es6-error": "^4.0.1" } }, - "remarkable": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.1.tgz", - "integrity": "sha1-qspJchALZqZCpjoQIcpLrBvjv/Y=", - "dev": true, - "requires": { - "argparse": "~0.1.15", - "autolinker": "~0.15.0" - }, - "dependencies": { - "argparse": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", - "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", - "dev": true, - "requires": { - "underscore": "~1.7.0", - "underscore.string": "~2.4.0" - } - } - } - }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -8309,15 +7899,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "set-getter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", - "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", - "dev": true, - "requires": { - "to-object-path": "^0.3.0" - } - }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", @@ -8897,12 +8478,6 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, - "strip-color": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/strip-color/-/strip-color-0.1.0.tgz", - "integrity": "sha1-EG9l09PmotlAHKwOsM6LinArT3s=", - "dev": true - }, "strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", @@ -8960,17 +8535,6 @@ "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", "dev": true }, - "sync-request": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-3.0.1.tgz", - "integrity": "sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M=", - "dev": true, - "requires": { - "concat-stream": "^1.4.7", - "http-response-object": "^1.0.1", - "then-request": "^2.0.1" - } - }, "table": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", @@ -9134,28 +8698,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "then-request": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", - "integrity": "sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE=", - "dev": true, - "requires": { - "caseless": "~0.11.0", - "concat-stream": "^1.4.7", - "http-basic": "^2.5.1", - "http-response-object": "^1.1.0", - "promise": "^7.1.1", - "qs": "^6.1.0" - }, - "dependencies": { - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", - "dev": true - } - } - }, "throat": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", @@ -9251,12 +8793,6 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, - "toml": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz", - "integrity": "sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==", - "dev": true - }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -9391,18 +8927,6 @@ "through": "^2.3.8" } }, - "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", - "dev": true - }, - "underscore.string": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", - "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", - "dev": true - }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", diff --git a/package.json b/package.json index 49437317f..a35219337 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "coverage": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm test", "lint": "eslint . --cache", "lint-updated": "pipe-git-updated --ext=js -- eslint --cache", - "docs": "node scripts/generate-readme.js", "integration-test-run-package": "jest --maxWorkers=5 integration-package", "integration-test-run-basic": "jest --maxWorkers=5 integration-basic", "integration-test-run-all": "jest --maxWorkers=5 integration-all", @@ -91,15 +90,11 @@ "git-list-updated": "^1.1.2", "jest-circus": "^24.8.0", "jest-cli": "^24.5.0", - "markdown-link": "^0.1.1", - "markdown-magic": "^0.1.25", - "markdown-table": "^1.1.1", "mocha": "^6.1.4", "mocha-lcov-reporter": "^1.2.0", "mock-require": "^3.0.3", "nyc": "^14.1.1", "p-limit": "^2.2.0", - "parse-github-url": "^1.0.1", "process-utils": "^2.3.1", "proxyquire": "^2.1.0", "sinon": "^7.3.2", diff --git a/scripts/generate-readme.js b/scripts/generate-readme.js deleted file mode 100644 index 9c9454a2c..000000000 --- a/scripts/generate-readme.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -/** - * adds content to the repos README.md file - */ -const path = require('path'); -const _ = require('lodash'); -const mdTable = require('markdown-table'); -const parseGithubURL = require('parse-github-url'); -const mdLink = require('markdown-link'); -const markdownMagic = require('markdown-magic'); -const remoteRequest = require('markdown-magic/lib/utils/remoteRequest'); - -function getExamplesList() { - const examplesUrl = 'https://raw.githubusercontent.com/serverless/examples/master/community-examples.json'; - const remoteContent = remoteRequest(examplesUrl); - - return JSON.parse(remoteContent); -} - -function getPluginsList() { - const pluginUrl = 'https://raw.githubusercontent.com/serverless/plugins/master/plugins.json'; - const remoteContent = remoteRequest(pluginUrl); - - return JSON.parse(remoteContent).sort((a, b) => // eslint-disable-line - a.name < b.name ? -1 : 1 - ); -} - -function getTableRow(name, description, url) { - const { owner } = parseGithubURL(url); - const profileURL = `http://github.com/${owner}`; - - return [ - `**${mdLink(_.startCase(name), url)}**
    ${description}`, - `${mdLink(owner, profileURL)}`, - ]; -} - -function getReadmeTable(rowsData, columns) { - const mdTableData = [columns]; - - rowsData.forEach(({ name, description, githubUrl }) => { - const tableRow = getTableRow(name, description, githubUrl); - mdTableData.push(tableRow); - }); - - return mdTable(mdTableData, { - align: ['l', 'c'], - pad: false, - }); -} - -const config = { - transforms: { - GENERATE_SERVERLESS_EXAMPLES_TABLE(content, options) { // eslint-disable-line - const examplesList = getExamplesList(); - - return getReadmeTable(examplesList, ['Project Name', 'Author']); - }, - GENERATE_SERVERLESS_PLUGIN_TABLE(content, options) { // eslint-disable-line - const pluginsList = getPluginsList(); - - return getReadmeTable(pluginsList, ['Plugin', 'Author']); - }, - }, -}; - -const markdownPath = path.join(__dirname, '..', 'README.md'); -// const markdownPath = path.join(__dirname, '..', 'test/fixtures/test.md') -markdownMagic(markdownPath, config, () => { - console.log(`${markdownPath} updated!`); // eslint-disable-line -}); From e2522597910efe848a3290b8ce42056eb2d774c0 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 6 Jun 2019 12:27:19 +0200 Subject: [PATCH 319/504] Fix linting issues --- lib/classes/Service.test.js | 146 ++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index 2e8fa0af5..60b0a9f29 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -733,60 +733,60 @@ describe('Service', () => { } it(`should not throw an error if http event is absent and - stage contains only alphanumeric, underscore and hyphen`, function () { - this.timeout(10000); // Occasionally times out with default settings - const SUtils = new Utils(); - const serverlessYml = { - service: 'new-service', - provider: { - name: 'aws', - stage: 'xyz-101_abc-123', + stage contains only alphanumeric, underscore and hyphen`, function () { + this.timeout(10000); // Occasionally times out with default settings + const SUtils = new Utils(); + const serverlessYml = { + service: 'new-service', + provider: { + name: 'aws', + stage: 'xyz-101_abc-123', + }, + functions: { + first: { + events: [], }, - functions: { - first: { - events: [], - }, - }, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + }, + }; + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), + YAML.dump(serverlessYml)); - const serverless = new Serverless({ servicePath: tmpDirPath }); - return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { - expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error); - }); + const serverless = new Serverless({ servicePath: tmpDirPath }); + return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { + expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error); }); + }); it(`should not throw an error after variable population if http event is present and - the populated stage contains only alphanumeric, underscore and hyphen`, () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'new-service', - provider: { - name: 'aws', - stage: '${opt:stage, "default-stage"}', - }, - functions: { - first: { - events: [ - { - http: { - path: 'foo', - method: 'GET', - }, + the populated stage contains only alphanumeric, underscore and hyphen`, () => { + const SUtils = new Utils(); + const serverlessYml = { + service: 'new-service', + provider: { + name: 'aws', + stage: '${opt:stage, "default-stage"}', + }, + functions: { + first: { + events: [ + { + http: { + path: 'foo', + method: 'GET', }, - ], - }, + }, + ], }, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + }, + }; + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), + YAML.dump(serverlessYml)); - const serverless = new Serverless({ servicePath: tmpDirPath }); - return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { - expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error); - }); + const serverless = new Serverless({ servicePath: tmpDirPath }); + return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { + expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error); }); + }); it('should throw an error if http event is present and stage contains invalid chars', () => { const SUtils = new Utils(); @@ -824,38 +824,38 @@ describe('Service', () => { it(`should throw an error after variable population if http event is present and stage contains hyphen`, () => { - const SUtils = new Utils(); - const serverlessYml = { - service: 'new-service', - provider: { - name: 'aws', - stage: '${opt:stage, "default:stage"}', - }, - functions: { - first: { - events: [ - { - http: { - path: 'foo', - method: 'GET', - }, + const SUtils = new Utils(); + const serverlessYml = { + service: 'new-service', + provider: { + name: 'aws', + stage: '${opt:stage, "default:stage"}', + }, + functions: { + first: { + events: [ + { + http: { + path: 'foo', + method: 'GET', }, - ], - }, + }, + ], }, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + }, + }; + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), + YAML.dump(serverlessYml)); - const serverless = new Serverless({ servicePath: tmpDirPath }); - return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { - expect(() => serverless.service.validate()).to.throw(serverless.classes.Error, [ - 'Invalid stage name default:stage: it should contains only [-_a-zA-Z0-9]', - 'for AWS provider if http event are present', - 'according to API Gateway limitation.', - ].join(' ')); - }); + const serverless = new Serverless({ servicePath: tmpDirPath }); + return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { + expect(() => serverless.service.validate()).to.throw(serverless.classes.Error, [ + 'Invalid stage name default:stage: it should contains only [-_a-zA-Z0-9]', + 'for AWS provider if http event are present', + 'according to API Gateway limitation.', + ].join(' ')); }); + }); }); }); From 83b8e05e1d5d025a09c20bdeaafa1347b5d4d136 Mon Sep 17 00:00:00 2001 From: My Ho Date: Thu, 6 Jun 2019 11:41:22 -0700 Subject: [PATCH 320/504] lint: add script to run lint fix provide a npm script for contributor to quickly fix their linting issues locally. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a35219337..57be13d58 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "test-isolated": "node scripts/test-isolated.js", "coverage": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm test", "lint": "eslint . --cache", + "lint:fix": "npm run lint -- --fix", "lint-updated": "pipe-git-updated --ext=js -- eslint --cache", "integration-test-run-package": "jest --maxWorkers=5 integration-package", "integration-test-run-basic": "jest --maxWorkers=5 integration-basic", From 779d47a891f63d900969acd86d005ee2a2a94b67 Mon Sep 17 00:00:00 2001 From: My Ho Date: Thu, 6 Jun 2019 11:55:51 -0700 Subject: [PATCH 321/504] updating contributing guide on how to fix linting issues --- CONTRIBUTING.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f71d94c5..6ce9e1452 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,19 @@ You can do that by replying to [issues on Github](https://github.com/serverless/ # Code Style -We aim for clean, consistent code style. We're using ESlint to check for codestyle issues using the Airbnb preset (you can run `npm run lint` to lint your code). +We aim for clean, consistent code style. We're using ESlint to check for codestyle issues using the Airbnb preset. + +## Verifying linting style + +``` +npm run lint +``` + +## Fixing lint issues + +``` +npm run lint:fix +``` To help reduce the effort of creating contributions with this style, an [.editorconfig file](http://editorconfig.org/) is provided that your editor may use to override any conflicting global defaults and automate a subset of the style settings. From f878e6ed768df22f1415cc654786d6799200d647 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Fri, 7 Jun 2019 11:29:25 +0200 Subject: [PATCH 322/504] Add pretest script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 57be13d58..65e50b049 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "integration-test-run-basic": "jest --maxWorkers=5 integration-basic", "integration-test-run-all": "jest --maxWorkers=5 integration-all", "integration-test-cleanup": "node tests/utils/aws-cleanup.js", - "postinstall": "node ./scripts/postinstall.js" + "postinstall": "node ./scripts/postinstall.js", + "pretest": "npm run lint" }, "mocha": { "R": "tests/mocha-reporter", From ad7d6f5f735d6bc56bfef30aee95ba967706ca28 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Fri, 7 Jun 2019 11:30:21 +0200 Subject: [PATCH 323/504] Remove pretest script --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 65e50b049..57be13d58 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,7 @@ "integration-test-run-basic": "jest --maxWorkers=5 integration-basic", "integration-test-run-all": "jest --maxWorkers=5 integration-all", "integration-test-cleanup": "node tests/utils/aws-cleanup.js", - "postinstall": "node ./scripts/postinstall.js", - "pretest": "npm run lint" + "postinstall": "node ./scripts/postinstall.js" }, "mocha": { "R": "tests/mocha-reporter", From 96dc83fc01012940d5f5df0c2b2775c90b957d70 Mon Sep 17 00:00:00 2001 From: NomadBlacky Date: Sat, 8 Jun 2019 23:10:19 +0900 Subject: [PATCH 324/504] Update to Scala 2.13.0 --- lib/plugins/create/templates/aws-scala-sbt/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/create/templates/aws-scala-sbt/build.sbt b/lib/plugins/create/templates/aws-scala-sbt/build.sbt index 399dd42bb..0412a0915 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/build.sbt +++ b/lib/plugins/create/templates/aws-scala-sbt/build.sbt @@ -4,7 +4,7 @@ import sbtrelease.Version name := "hello" resolvers += Resolver.sonatypeRepo("public") -scalaVersion := "2.12.8" +scalaVersion := "2.13.0" releaseNextVersion := { ver => Version(ver).map(_.bumpMinor.string).getOrElse("Error") } From 393f57288d03f9dd3b208598e20ea57549b7989d Mon Sep 17 00:00:00 2001 From: NomadBlacky Date: Sat, 8 Jun 2019 23:19:58 +0900 Subject: [PATCH 325/504] Migrate codebase to Scala 2.13.0 --- .../src/main/scala/hello/ApiGatewayResponse.scala | 2 +- .../aws-scala-sbt/src/main/scala/hello/Handler.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/ApiGatewayResponse.scala b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/ApiGatewayResponse.scala index 9a436e5d2..e0b121bbe 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/ApiGatewayResponse.scala +++ b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/ApiGatewayResponse.scala @@ -3,4 +3,4 @@ package hello import scala.beans.BeanProperty case class ApiGatewayResponse(@BeanProperty statusCode: Integer, @BeanProperty body: String, - @BeanProperty headers: java.util.Map[String, Object], @BeanProperty base64Encoded: Boolean = false) + @BeanProperty headers: java.util.Map[String, String], @BeanProperty base64Encoded: Boolean = false) diff --git a/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala index 7f0cae1d0..aeb4b3e52 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala +++ b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala @@ -3,7 +3,7 @@ package hello import com.amazonaws.services.lambda.runtime.{Context, RequestHandler} import org.apache.logging.log4j.{LogManager, Logger} -import scala.collection.JavaConverters +import scala.jdk.CollectionConverters._ class Handler extends RequestHandler[Request, Response] { @@ -20,7 +20,7 @@ class ApiGatewayHandler extends RequestHandler[Request, ApiGatewayResponse] { def handleRequest(input: Request, context: Context): ApiGatewayResponse = { val headers = Map("x-custom-response-header" -> "my custom response header value") ApiGatewayResponse(200, "Go Serverless v1.0! Your function executed successfully!", - JavaConverters.mapAsJavaMap[String, Object](headers), + headers.asJava, true) } } From 0daa26a92ca712b6771cd194a540556e26d6e953 Mon Sep 17 00:00:00 2001 From: NomadBlacky Date: Sat, 8 Jun 2019 23:52:19 +0900 Subject: [PATCH 326/504] Fix artifact path in aws-scala-sbt --- lib/plugins/create/templates/aws-scala-sbt/serverless.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml index 620b37323..f7fbacd3d 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml +++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml @@ -51,7 +51,7 @@ provider: # Make sure to run "sbt assembly" to create a jar file # with all your dependencies and put that jar file name here. package: - artifact: target/scala-2.12/hello.jar + artifact: target/scala-2.13/hello.jar functions: hello: From 06c81870573f5b406efb9f653fb1b94328233998 Mon Sep 17 00:00:00 2001 From: Oskar Date: Sun, 9 Jun 2019 21:49:36 +0200 Subject: [PATCH 327/504] Update docs | dont use provider.tags with shared API Gateway --- docs/providers/aws/events/apigateway.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index b9717bc7c..1499502d6 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -1279,6 +1279,8 @@ functions: You can use this method to share your API Gateway across services in same region. Read about this limitation [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html). +**Note:** We've noticed you can't use provider.tags together with `Fn::ImportValue` for `restApiId` and `restApiRootResourceId`. Doing so won't resolve the imported value, and therefore returns an error. + ### Manually Configuring shared API Gateway From 2b813033706a2f37edec952b1c331f628353c6b7 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 5 Jun 2019 17:41:28 -0400 Subject: [PATCH 328/504] `--config` option. closes #5589 closes #4485 closes #4473 closes #4124 closes #5822 --- lib/Serverless.js | 6 ++- lib/classes/CLI.js | 11 +++- lib/classes/PluginManager.js | 2 +- lib/classes/Service.js | 62 +++++------------------ lib/classes/Service.test.js | 22 ++++++++ lib/classes/Utils.js | 6 ++- lib/plugins/print/print.js | 2 +- lib/plugins/print/print.test.js | 1 + lib/utils/getServerlessConfigFile.js | 21 +++++--- lib/utils/getServerlessConfigFile.test.js | 23 ++++++--- 10 files changed, 87 insertions(+), 69 deletions(-) diff --git a/lib/Serverless.js b/lib/Serverless.js index 697c51065..d373cf3d5 100644 --- a/lib/Serverless.js +++ b/lib/Serverless.js @@ -30,8 +30,12 @@ class Serverless { this.variables = new Variables(this); this.pluginManager = new PluginManager(this); + // create a new CLI instance + const cli = new CLI(this); + // get an array of commands and options that should be processed + const processedInput = cli.processInput(); // use the servicePath from the options or try to find it in the CWD - configObject.servicePath = configObject.servicePath || this.utils.findServicePath(); + configObject.servicePath = configObject.servicePath || this.utils.findServicePath(processedInput.options.config); this.config = new Config(this, configObject); diff --git a/lib/classes/CLI.js b/lib/classes/CLI.js index 4b6b58a72..c73a3bc07 100644 --- a/lib/classes/CLI.js +++ b/lib/classes/CLI.js @@ -152,7 +152,16 @@ class CLI { displayCommandOptions(commandObject) { const dotsLength = 40; - _.forEach(commandObject.options, (optionsObject, option) => { + + const commandOptions = commandObject.configDependent ? + Object.assign({}, commandObject.options, { + config: { + usage: 'Path to serverless config file', + shortcut: 'c', + }, + }) : commandObject.options; + + _.forEach(commandOptions, (optionsObject, option) => { let optionsDots = _.repeat('.', dotsLength - option.length); const optionsUsage = optionsObject.usage; diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index daf7260e3..f1e425aca 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -45,7 +45,7 @@ class PluginManager { loadConfigFile() { return serverlessConfigFileUtils - .getServerlessConfigFile(this.serverless.config.servicePath) + .getServerlessConfigFile(this.serverless.processedInput.options.config, this.serverless.config.servicePath) .then((serverlessConfigFile) => { this.serverlessConfigFile = serverlessConfigFile; return; diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 0ec8d0606..f20d72e78 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -5,6 +5,7 @@ const path = require('path'); const _ = require('lodash'); const BbPromise = require('bluebird'); const semver = require('semver'); +const serverlessConfigFileUtils = require('../utils/getServerlessConfigFile'); const validAPIGatewayStageNamePattern = /^[-_a-zA-Z0-9]+$/; @@ -46,54 +47,19 @@ class Service { return BbPromise.resolve(); } - // List of supported service filename variants. - // The order defines the precedence. - const serviceFilenames = [ - 'serverless.yaml', - 'serverless.yml', - 'serverless.json', - 'serverless.js', - ]; - - const serviceFilePaths = _.map(serviceFilenames, filename => path.join(servicePath, filename)); - const serviceFileIndex = _.findIndex(serviceFilePaths, - filename => this.serverless.utils.fileExistsSync(filename) - ); - - // Set the filename if found, otherwise set the preferred variant. - const serviceFilePath = serviceFileIndex !== -1 ? - serviceFilePaths[serviceFileIndex] : - _.first(serviceFilePaths); - const serviceFilename = serviceFileIndex !== -1 ? - serviceFilenames[serviceFileIndex] : - _.first(serviceFilenames); - - if (serviceFilename === 'serverless.js') { - return BbPromise.try(() => { - // use require to load serverless.js file - // eslint-disable-next-line global-require - const configExport = require(serviceFilePath); - // In case of a promise result, first resolve it. - return configExport; - }).then(config => { - if (!_.isPlainObject(config)) { - throw new Error('serverless.js must export plain object'); - } - - return that.loadServiceFileParam(serviceFilename, config); - }); - } - - return that.serverless.yamlParser - .parse(serviceFilePath) - .then((serverlessFileParam) => - that.loadServiceFileParam(serviceFilename, serverlessFileParam) - ); + return BbPromise.all([ + serverlessConfigFileUtils.getServerlessConfigFilePath( + this.serverless.processedInput.options.config, this.serverless.config.servicePath), + serverlessConfigFileUtils.getServerlessConfigFile( + this.serverless.processedInput.options.config, this.serverless.config.servicePath), + ]).then(that.loadServiceFileParam.bind(that)); } - loadServiceFileParam(serviceFilename, serverlessFileParam) { + loadServiceFileParam([serviceFilename, serverlessFileParam]) { const that = this; + that.serviceFilename = serviceFilename.split(path.sep).pop() + const serverlessFile = serverlessFileParam; // basic service level validation const version = this.serverless.utils.getVersion(); @@ -101,18 +67,18 @@ class Service { if (ymlVersion && !semver.satisfies(version, ymlVersion)) { const errorMessage = [ `The Serverless version (${version}) does not satisfy the`, - ` "frameworkVersion" (${ymlVersion}) in ${serviceFilename}`, + ` "frameworkVersion" (${ymlVersion}) in ${this.serviceFilename}`, ].join(''); throw new ServerlessError(errorMessage); } if (!serverlessFile.service) { - throw new ServerlessError(`"service" property is missing in ${serviceFilename}`); + throw new ServerlessError(`"service" property is missing in ${this.serviceFilename}`); } if (_.isObject(serverlessFile.service) && !serverlessFile.service.name) { - throw new ServerlessError(`"service" is missing the "name" property in ${serviceFilename}`); // eslint-disable-line max-len + throw new ServerlessError(`"service" is missing the "name" property in ${this.serviceFilename}`); // eslint-disable-line max-len } if (!serverlessFile.provider) { - throw new ServerlessError(`"provider" property is missing in ${serviceFilename}`); + throw new ServerlessError(`"provider" property is missing in ${this.serviceFilename}`); } // ####################################################################### diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index d5342a097..fd2acdfc8 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -17,6 +17,7 @@ const expect = require('chai').expect; describe('Service', () => { describe('#constructor()', () => { const serverless = new Serverless(); + serverless.processedInput = {options: {}}; it('should attach serverless instance', () => { const serviceInstance = new Service(serverless); @@ -118,6 +119,7 @@ describe('Service', () => { it('should resolve if no servicePath is found', () => { const serverless = new Serverless(); + serverless.processedInput = {options: {}}; const noService = new Service(serverless); return expect(noService.load()).to.be.fulfilled; @@ -156,6 +158,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless(); + serverless.processedInput = {options: {}}; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -210,6 +213,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless(); + serverless.processedInput = {options: {}}; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -264,6 +268,7 @@ describe('Service', () => { JSON.stringify(serverlessJSON)); const serverless = new Serverless(); + serverless.processedInput = {options: {}}; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -319,6 +324,7 @@ describe('Service', () => { `module.exports = ${JSON.stringify(serverlessJSON)};`); const serverless = new Serverless(); + serverless.processedInput = {options: {}}; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -374,6 +380,7 @@ describe('Service', () => { `module.exports = new Promise(resolve => { resolve(${JSON.stringify(serverlessJSON)}) });`); const serverless = new Serverless(); + serverless.processedInput = {options: {}}; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -404,6 +411,7 @@ describe('Service', () => { 'module.exports = function config() {};'); const serverless = new Serverless(); + serverless.processedInput = {options: {}}; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -447,6 +455,7 @@ describe('Service', () => { YAML.dump(serverlessJSON)); const serverless = new Serverless(); + serverless.processedInput = {options: {}}; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -468,6 +477,7 @@ describe('Service', () => { YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be @@ -488,6 +498,7 @@ describe('Service', () => { YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled @@ -513,6 +524,7 @@ describe('Service', () => { YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled @@ -546,6 +558,7 @@ describe('Service', () => { YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled @@ -577,6 +590,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; serviceInstance = new Service(serverless); return expect(serviceInstance.load({ stage: 'dev' })).to.eventually.be.fulfilled @@ -604,6 +618,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be @@ -620,6 +635,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be @@ -638,6 +654,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; const getVersion = sinon.stub(serverless.utils, 'getVersion'); getVersion.returns('1.0.2'); serviceInstance = new Service(serverless); @@ -658,6 +675,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; const getVersion = sinon.stub(serverless.utils, 'getVersion'); getVersion.returns('1.2.2'); serviceInstance = new Service(serverless); @@ -675,6 +693,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; serverless.variables.service = serverless.service; serviceInstance = new Service(serverless); @@ -713,6 +732,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; serverless.service = new Service(serverless); return expect(serverless.service.load()).to.eventually.be.fulfilled @@ -753,6 +773,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); + serverless.processedInput = {options: {}}; return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error); }); @@ -1007,6 +1028,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless(); + serverless.processedInput = {options: {}}; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); diff --git a/lib/classes/Utils.js b/lib/classes/Utils.js index c55adb24f..eecf80f31 100644 --- a/lib/classes/Utils.js +++ b/lib/classes/Utils.js @@ -106,10 +106,12 @@ class Utils { return Math.random().toString(36).substr(2, length); } - findServicePath() { + findServicePath(customPath) { let servicePath = null; - if (fileExistsSync(path.join(process.cwd(), 'serverless.yml'))) { + if (customPath && fileExistsSync(path.join(process.cwd(), customPath))) { + servicePath = process.cwd(); + } else if (fileExistsSync(path.join(process.cwd(), 'serverless.yml'))) { servicePath = process.cwd(); } else if (fileExistsSync(path.join(process.cwd(), 'serverless.yaml'))) { servicePath = process.cwd(); diff --git a/lib/plugins/print/print.js b/lib/plugins/print/print.js index aea70be26..4ae525219 100644 --- a/lib/plugins/print/print.js +++ b/lib/plugins/print/print.js @@ -96,7 +96,7 @@ class Print { // the codebase. Avoiding that, this method must read the serverless.yml file itself, adorn it // as the Service class would and then populate it, reversing the adornments thereafter in // preparation for printing the service for the user. - return getServerlessConfigFile(this.serverless.config.servicePath) + return getServerlessConfigFile(this.serverless.processedInput.options.config, this.serverless.config.servicePath) .then((svc) => { const service = svc; this.adorn(service); diff --git a/lib/plugins/print/print.test.js b/lib/plugins/print/print.test.js index 5787492db..3e6e8e7e6 100644 --- a/lib/plugins/print/print.test.js +++ b/lib/plugins/print/print.test.js @@ -30,6 +30,7 @@ describe('Print', () => { region: 'us-east-1', }; serverless.cli = new CLI(serverless); + serverless.processedInput = { options: {} }; print = new PrintPlugin(serverless); print.serverless.cli = { consoleLog: sinon.spy(), diff --git a/lib/utils/getServerlessConfigFile.js b/lib/utils/getServerlessConfigFile.js index c0f07a6b2..d34f581a3 100644 --- a/lib/utils/getServerlessConfigFile.js +++ b/lib/utils/getServerlessConfigFile.js @@ -6,8 +6,14 @@ const path = require('path'); const fileExists = require('./fs/fileExists'); const readFile = require('./fs/readFile'); -const getServerlessConfigFilePath = (srvcPath) => { +const getServerlessConfigFilePath = (configFileName, srvcPath) => { const servicePath = srvcPath || process.cwd(); + + if (configFileName) { + const customPath = path.join(servicePath, configFileName); + return fileExists(customPath).then(exists => (exists ? customPath : null)); + } + const jsonPath = path.join(servicePath, 'serverless.json'); const ymlPath = path.join(servicePath, 'serverless.yml'); const yamlPath = path.join(servicePath, 'serverless.yaml'); @@ -19,12 +25,12 @@ const getServerlessConfigFilePath = (srvcPath) => { yaml: fileExists(yamlPath), js: fileExists(jsPath), }).then(exists => { - if (exists.json) { - return jsonPath; + if (exists.yaml) { + return yamlPath; } else if (exists.yml) { return ymlPath; - } else if (exists.yaml) { - return yamlPath; + } else if (exists.json) { + return jsonPath; } else if (exists.js) { return jsPath; } @@ -46,7 +52,7 @@ const handleJsConfigFile = (jsConfigFile) => BbPromise.try(() => { throw new Error('serverless.js must export plain object'); }); -const getServerlessConfigFile = _.memoize(srvcPath => getServerlessConfigFilePath(srvcPath) +const getServerlessConfigFile = _.memoize((configFileName, srvcPath) => getServerlessConfigFilePath(configFileName, srvcPath) .then(configFilePath => { if (configFilePath !== null) { const isJSConfigFile = _.last(_.split(configFilePath, '.')) === 'js'; @@ -59,7 +65,6 @@ const getServerlessConfigFile = _.memoize(srvcPath => getServerlessConfigFilePat } return ''; - }) -); + }), (configFileName, srvcPath) => srvcPath); module.exports = { getServerlessConfigFile, getServerlessConfigFilePath }; diff --git a/lib/utils/getServerlessConfigFile.test.js b/lib/utils/getServerlessConfigFile.test.js index 6351828b9..def084b01 100644 --- a/lib/utils/getServerlessConfigFile.test.js +++ b/lib/utils/getServerlessConfigFile.test.js @@ -23,7 +23,7 @@ describe('#getServerlessConfigFile()', () => { const randomFilePath = path.join(tmpDirPath, 'not-a-serverless-file'); writeFileSync(randomFilePath, 'some content'); - return expect(getServerlessConfigFile(tmpDirPath)).to.be.fulfilled.then((result) => { + return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then((result) => { expect(result).to.equal(''); }); }); @@ -32,7 +32,7 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.yml'); writeFileSync(serverlessFilePath, 'service: my-yml-service'); - return expect(getServerlessConfigFile(tmpDirPath)).to.be.fulfilled.then((result) => { + return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then((result) => { expect(result).to.deep.equal({ service: 'my-yml-service' }); }); }); @@ -41,7 +41,16 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.yaml'); writeFileSync(serverlessFilePath, 'service: my-yaml-service'); - return expect(getServerlessConfigFile(tmpDirPath)).to.be.fulfilled.then((result) => { + return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then((result) => { + expect(result).to.deep.equal({ service: 'my-yaml-service' }); + }); + }); + + it('should return the file content if a foobar.yaml file is specified & found', () => { + const serverlessFilePath = path.join(tmpDirPath, 'foobar.yaml'); + + writeFileSync(serverlessFilePath, 'service: my-yaml-service'); + return expect(getServerlessConfigFile('foobar.yaml', tmpDirPath)).to.be.fulfilled.then((result) => { expect(result).to.deep.equal({ service: 'my-yaml-service' }); }); }); @@ -50,7 +59,7 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.json'); writeFileSync(serverlessFilePath, '{ "service": "my-json-service" }'); - return expect(getServerlessConfigFile(tmpDirPath)).to.be.fulfilled.then((result) => { + return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then((result) => { expect(result).to.deep.equal({ service: 'my-json-service' }); }); }); @@ -62,7 +71,7 @@ describe('#getServerlessConfigFile()', () => { 'module.exports = {"service": "my-json-service"};' ); - return expect(getServerlessConfigFile(tmpDirPath)).to.be.fulfilled.then( + return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then( (result) => { expect(result).to.deep.equal({ service: 'my-json-service' }); } @@ -76,7 +85,7 @@ describe('#getServerlessConfigFile()', () => { 'module.exports = new Promise(resolve => { resolve({"service": "my-json-service"}); });' ); - return expect(getServerlessConfigFile(tmpDirPath)).to.be.fulfilled.then( + return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then( (result) => { expect(result).to.deep.equal({ service: 'my-json-service' }); } @@ -90,7 +99,7 @@ describe('#getServerlessConfigFile()', () => { 'module.exports = function config() {};' ); - return expect(getServerlessConfigFile(tmpDirPath)) + return expect(getServerlessConfigFile(undefined, tmpDirPath)) .to.be.rejectedWith('serverless.js must export plain object'); }); From 66f1606910928a3c9ddd703ac1452202873695f4 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 6 Jun 2019 09:10:03 -0400 Subject: [PATCH 329/504] copy docs from #5589 --- docs/providers/aws/cli-reference/deploy.md | 1 + docs/providers/aws/guide/deploying.md | 2 +- docs/providers/azure/cli-reference/deploy.md | 1 + docs/providers/azure/guide/deploying.md | 2 ++ docs/providers/azure/guide/intro.md | 4 ++-- docs/providers/cloudflare/cli-reference/deploy.md | 1 + docs/providers/cloudflare/guide/deploying.md | 4 +++- docs/providers/fn/cli-reference/deploy.md | 1 + docs/providers/fn/guide/deploying.md | 2 ++ docs/providers/fn/guide/intro.md | 4 ++-- docs/providers/google/cli-reference/deploy.md | 3 +++ docs/providers/google/guide/deploying.md | 2 ++ docs/providers/google/guide/intro.md | 4 ++-- docs/providers/kubeless/cli-reference/deploy.md | 1 + docs/providers/kubeless/guide/deploying.md | 2 ++ docs/providers/kubeless/guide/intro.md | 4 ++-- docs/providers/openwhisk/cli-reference/deploy.md | 1 + docs/providers/openwhisk/guide/deploying.md | 2 ++ docs/providers/openwhisk/guide/intro.md | 4 ++-- docs/providers/spotinst/cli-reference/deploy.md | 1 + docs/providers/spotinst/guide/intro.md | 4 ++-- 21 files changed, 36 insertions(+), 14 deletions(-) diff --git a/docs/providers/aws/cli-reference/deploy.md b/docs/providers/aws/cli-reference/deploy.md index 305a5d1cb..058f6ed4c 100644 --- a/docs/providers/aws/cli-reference/deploy.md +++ b/docs/providers/aws/cli-reference/deploy.md @@ -19,6 +19,7 @@ serverless deploy ``` ## Options +- `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--stage` or `-s` The stage in your service that you want to deploy to. - `--region` or `-r` The region in that stage that you want to deploy to. - `--package` or `-p` path to a pre-packaged directory and skip packaging step. diff --git a/docs/providers/aws/guide/deploying.md b/docs/providers/aws/guide/deploying.md index 8fd9423cb..30a4d5191 100644 --- a/docs/providers/aws/guide/deploying.md +++ b/docs/providers/aws/guide/deploying.md @@ -24,7 +24,7 @@ serverless deploy Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to Amazon Web Services. -**Note:** You can always enforce a deployment using the `--force` option. +**Note:** You can always enforce a deployment using the `--force` option, or specify a different configuration file name with the the `--config` option. ### How It Works diff --git a/docs/providers/azure/cli-reference/deploy.md b/docs/providers/azure/cli-reference/deploy.md index bf53ce438..3c2266462 100644 --- a/docs/providers/azure/cli-reference/deploy.md +++ b/docs/providers/azure/cli-reference/deploy.md @@ -23,6 +23,7 @@ serverless deploy ``` ## Options +- `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--noDeploy` or `-n` Skips the deployment steps and leaves artifacts in the `.serverless` directory - `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. - `--function` or `-f` Invoke `deploy function` (see above). Convenience shortcut. diff --git a/docs/providers/azure/guide/deploying.md b/docs/providers/azure/guide/deploying.md index a2a979054..0189364cf 100644 --- a/docs/providers/azure/guide/deploying.md +++ b/docs/providers/azure/guide/deploying.md @@ -28,6 +28,8 @@ Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to Azure Functions. +**Note:** You can specify a different configuration file name with the the `--config` option. + ### How It Works The Serverless Framework translates all syntax in `serverless.yml` to an Azure diff --git a/docs/providers/azure/guide/intro.md b/docs/providers/azure/guide/intro.md index f33a846c7..685157c13 100644 --- a/docs/providers/azure/guide/intro.md +++ b/docs/providers/azure/guide/intro.md @@ -63,7 +63,7 @@ needed for that event and configure your functions to listen to it. A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the -Resources your Functions use, all in one file entitled `serverless.yml` (or +Resources your Functions use, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml @@ -91,7 +91,7 @@ functions: # Your "Functions" ``` When you deploy with the Framework by running `serverless deploy`, everything in -`serverless.yml` is deployed at once. +`serverless.yml` (or the file specified with the `--config` option) is deployed at once. ### Plugins diff --git a/docs/providers/cloudflare/cli-reference/deploy.md b/docs/providers/cloudflare/cli-reference/deploy.md index 6e9c3e5fb..46d185bef 100644 --- a/docs/providers/cloudflare/cli-reference/deploy.md +++ b/docs/providers/cloudflare/cli-reference/deploy.md @@ -42,6 +42,7 @@ serverless deploy This is the simplest deployment usage possible. With this command, Serverless will deploy your service to Cloudflare. ## Options +- `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--verbose` or `-v`: Shows all stack events during deployment, and display any Stack Output. - `--function` or `-f`: Invokes `deploy function` (see above). Convenience shortcut diff --git a/docs/providers/cloudflare/guide/deploying.md b/docs/providers/cloudflare/guide/deploying.md index 20c229ee0..f0e528c6d 100644 --- a/docs/providers/cloudflare/guide/deploying.md +++ b/docs/providers/cloudflare/guide/deploying.md @@ -45,7 +45,9 @@ serverless deploy ``` Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to your Cloudflare Worker. If you've made changes to any of your routes since last deploying, the Serverless Framework will update them on the server for you. - + +**Note:** You can specify a different configuration file name with the the `--config` option. + ### How It Works The Serverless Framework reads in `serverless.yml` and uses it to provision your Functions. diff --git a/docs/providers/fn/cli-reference/deploy.md b/docs/providers/fn/cli-reference/deploy.md index 1e1f632c6..43efd9c3a 100644 --- a/docs/providers/fn/cli-reference/deploy.md +++ b/docs/providers/fn/cli-reference/deploy.md @@ -23,6 +23,7 @@ serverless deploy This is the simplest deployment usage possible. With this command Serverless will deploy your service to the configured Fn server. ## Options +- `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. - `--function` or `-f` Invoke `deploy function` (see above). Convenience shortcut diff --git a/docs/providers/fn/guide/deploying.md b/docs/providers/fn/guide/deploying.md index 40463c854..45ba71290 100644 --- a/docs/providers/fn/guide/deploying.md +++ b/docs/providers/fn/guide/deploying.md @@ -24,6 +24,8 @@ serverless deploy Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to your Fn cluster. +**Note:** You can specify a different configuration file name with the the `--config` option. + ### How It Works The Serverless Framework translates all syntax in `serverless.yml` to [Fn](https://github.com/fnproject/fn) calls to provision your Functions. diff --git a/docs/providers/fn/guide/intro.md b/docs/providers/fn/guide/intro.md index a18a032c5..b9581a716 100644 --- a/docs/providers/fn/guide/intro.md +++ b/docs/providers/fn/guide/intro.md @@ -42,7 +42,7 @@ Anything that triggers an Fn Event to execute is regarded by the Framework as an ### Services -A **Service** is the Serverless Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the Events that trigger them, all in one file entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Serverless Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the Events that trigger them, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml # serverless.yml @@ -59,4 +59,4 @@ functions: # Your "Functions" path: /hello ``` -When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` is deployed at once. +When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` (or the file specified with the `--config` option) is deployed at once. diff --git a/docs/providers/google/cli-reference/deploy.md b/docs/providers/google/cli-reference/deploy.md index 0eea151a7..7cc741b44 100644 --- a/docs/providers/google/cli-reference/deploy.md +++ b/docs/providers/google/cli-reference/deploy.md @@ -18,6 +18,9 @@ The `serverless deploy` command deploys your entire service via the Google Cloud serverless deploy ``` +## Options +- `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. + ## Artifacts After the `serverless deploy` command runs all created deployment artifacts are placed in the `.serverless` folder of the service. diff --git a/docs/providers/google/guide/deploying.md b/docs/providers/google/guide/deploying.md index c7faeda32..5542a4acf 100644 --- a/docs/providers/google/guide/deploying.md +++ b/docs/providers/google/guide/deploying.md @@ -24,6 +24,8 @@ serverless deploy Use this method when you have updated your Function, Events or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to the Google Cloud. +**Note:** You can specify a different configuration file name with the the `--config` option. + ### How It Works The Serverless Framework translates all syntax in `serverless.yml` to a Google Deployment Manager configuration template. diff --git a/docs/providers/google/guide/intro.md b/docs/providers/google/guide/intro.md index 6f03ea684..d044ae36a 100644 --- a/docs/providers/google/guide/intro.md +++ b/docs/providers/google/guide/intro.md @@ -46,7 +46,7 @@ When you define an event for your Google Cloud Function in the Serverless Framew ### Services -A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml # serverless.yml @@ -59,7 +59,7 @@ functions: # Your "Functions" - http: create ``` -When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` is deployed at once. +When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` (or the file specified with the `--config` option) is deployed at once. ### Plugins diff --git a/docs/providers/kubeless/cli-reference/deploy.md b/docs/providers/kubeless/cli-reference/deploy.md index dbfa1232d..b4898ffc4 100644 --- a/docs/providers/kubeless/cli-reference/deploy.md +++ b/docs/providers/kubeless/cli-reference/deploy.md @@ -23,6 +23,7 @@ serverless deploy This is the simplest deployment usage possible. With this command Serverless will deploy your service to the default Kubernetes cluster in your kubeconfig file. ## Options +- `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--noDeploy` or `-n` Skips the deployment steps and leaves artifacts in the `.serverless` directory. - `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. - `--package` or `-p` The path of a previously packaged deployment to get deployed (skips packaging step). diff --git a/docs/providers/kubeless/guide/deploying.md b/docs/providers/kubeless/guide/deploying.md index cdb4883d9..7780ad2b6 100644 --- a/docs/providers/kubeless/guide/deploying.md +++ b/docs/providers/kubeless/guide/deploying.md @@ -24,6 +24,8 @@ serverless deploy -v Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to your Kubernetes cluster. +**Note:** You can specify a different configuration file name with the the `--config` option. + ### How It Works The Serverless Framework translates all syntax in `serverless.yml` to [the Function object API](https://github.com/kubeless/kubeless/blob/master/pkg/spec/spec.go) calls to provision your Functions and Events. diff --git a/docs/providers/kubeless/guide/intro.md b/docs/providers/kubeless/guide/intro.md index 4a2fdd533..c056a2f6a 100644 --- a/docs/providers/kubeless/guide/intro.md +++ b/docs/providers/kubeless/guide/intro.md @@ -42,7 +42,7 @@ Anything that triggers an Kubeless Event to execute is regarded by the Framework ### Services -A **Service** is the Serverless Framework's unit of organization (not to be confused with [Kubernetes Services](https://kubernetes.io/docs/concepts/services-networking/service/). You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the Events that trigger them, all in one file entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Serverless Framework's unit of organization (not to be confused with [Kubernetes Services](https://kubernetes.io/docs/concepts/services-networking/service/). You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the Events that trigger them, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml # serverless.yml @@ -57,4 +57,4 @@ functions: # Your "Functions" path: /hello ``` -When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` is deployed at once. +When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` (or the file specified with the `--config` option) is deployed at once. diff --git a/docs/providers/openwhisk/cli-reference/deploy.md b/docs/providers/openwhisk/cli-reference/deploy.md index 6adb35ee9..52be99c56 100644 --- a/docs/providers/openwhisk/cli-reference/deploy.md +++ b/docs/providers/openwhisk/cli-reference/deploy.md @@ -19,6 +19,7 @@ serverless deploy ``` ## Options +- `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--noDeploy` or `-n` Skips the deployment steps and leaves artifacts in the `.serverless` directory - `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. - `--function` or `-f` Invoke `deploy function` (see above). Convenience shortcut - cannot be used with `--package`. diff --git a/docs/providers/openwhisk/guide/deploying.md b/docs/providers/openwhisk/guide/deploying.md index 3236e3c88..d403c679e 100644 --- a/docs/providers/openwhisk/guide/deploying.md +++ b/docs/providers/openwhisk/guide/deploying.md @@ -24,6 +24,8 @@ serverless deploy Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to Apache OpenWhisk. +**Note:** You can specify a different configuration file name with the the `--config` option. + ### How It Works The Serverless Framework translates all syntax in `serverless.yml` to [platform API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/openwhisk/openwhisk/master/core/controller/src/main/resources/whiskswagger.json) calls to provision your Actions, Triggers, Rules and APIs. diff --git a/docs/providers/openwhisk/guide/intro.md b/docs/providers/openwhisk/guide/intro.md index 06738f35b..c37b6e3f6 100644 --- a/docs/providers/openwhisk/guide/intro.md +++ b/docs/providers/openwhisk/guide/intro.md @@ -47,7 +47,7 @@ When you define an event for your Apache OpenWhisk Action in the Serverless Fram ### Services -A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml # serverless.yml @@ -62,7 +62,7 @@ functions: # Your "Functions" events: - http: delete /users/delete ``` -When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` is deployed at once. +When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` (or the file specified with the `--config` option) is deployed at once. ### Plugins diff --git a/docs/providers/spotinst/cli-reference/deploy.md b/docs/providers/spotinst/cli-reference/deploy.md index b5b909220..3f5f98d36 100755 --- a/docs/providers/spotinst/cli-reference/deploy.md +++ b/docs/providers/spotinst/cli-reference/deploy.md @@ -19,5 +19,6 @@ serverless deploy -v ``` ## Options +- `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--package` or `-p` path to a pre-packaged directory and skip packaging step. - `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. diff --git a/docs/providers/spotinst/guide/intro.md b/docs/providers/spotinst/guide/intro.md index c5b13e15f..7c8b748b2 100644 --- a/docs/providers/spotinst/guide/intro.md +++ b/docs/providers/spotinst/guide/intro.md @@ -73,7 +73,7 @@ module.exports.main = function main (event, context, callback) { ### Services -A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml @@ -98,7 +98,7 @@ functions: # key: value ``` -When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` is deployed at once. +When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` (or the file specified with the `--config` option) is deployed at once. ### Plugins From a1af588ccd6e0681a8ba5ed79189e2e85b08554c Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 6 Jun 2019 15:27:44 -0400 Subject: [PATCH 330/504] improve memoization key for getServerlessConfigFile --- lib/utils/getServerlessConfigFile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/getServerlessConfigFile.js b/lib/utils/getServerlessConfigFile.js index d34f581a3..607c92005 100644 --- a/lib/utils/getServerlessConfigFile.js +++ b/lib/utils/getServerlessConfigFile.js @@ -65,6 +65,6 @@ const getServerlessConfigFile = _.memoize((configFileName, srvcPath) => getServe } return ''; - }), (configFileName, srvcPath) => srvcPath); + }), (configFileName, srvcPath) => `${configFileName} - ${srvcPath}`); module.exports = { getServerlessConfigFile, getServerlessConfigFilePath }; From 94bf5a43821127d0429993ebbcef55e2ac0931c7 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Thu, 6 Jun 2019 15:45:41 -0400 Subject: [PATCH 331/504] delint --- lib/Serverless.js | 3 +- lib/classes/CLI.js | 10 +++--- lib/classes/PluginManager.js | 3 +- lib/classes/Service.js | 2 +- lib/classes/Service.test.js | 44 +++++++++++------------ lib/plugins/print/print.js | 3 +- lib/utils/getServerlessConfigFile.js | 3 +- lib/utils/getServerlessConfigFile.test.js | 7 ++-- 8 files changed, 40 insertions(+), 35 deletions(-) diff --git a/lib/Serverless.js b/lib/Serverless.js index d373cf3d5..43e0c00fb 100644 --- a/lib/Serverless.js +++ b/lib/Serverless.js @@ -35,7 +35,8 @@ class Serverless { // get an array of commands and options that should be processed const processedInput = cli.processInput(); // use the servicePath from the options or try to find it in the CWD - configObject.servicePath = configObject.servicePath || this.utils.findServicePath(processedInput.options.config); + configObject.servicePath = configObject.servicePath || + this.utils.findServicePath(processedInput.options.config); this.config = new Config(this, configObject); diff --git a/lib/classes/CLI.js b/lib/classes/CLI.js index c73a3bc07..53c53f699 100644 --- a/lib/classes/CLI.js +++ b/lib/classes/CLI.js @@ -155,11 +155,11 @@ class CLI { const commandOptions = commandObject.configDependent ? Object.assign({}, commandObject.options, { - config: { - usage: 'Path to serverless config file', - shortcut: 'c', - }, - }) : commandObject.options; + config: { + usage: 'Path to serverless config file', + shortcut: 'c', + }, + }) : commandObject.options; _.forEach(commandOptions, (optionsObject, option) => { let optionsDots = _.repeat('.', dotsLength - option.length); diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index f1e425aca..cb987fda6 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -45,7 +45,8 @@ class PluginManager { loadConfigFile() { return serverlessConfigFileUtils - .getServerlessConfigFile(this.serverless.processedInput.options.config, this.serverless.config.servicePath) + .getServerlessConfigFile( + this.serverless.processedInput.options.config, this.serverless.config.servicePath) .then((serverlessConfigFile) => { this.serverlessConfigFile = serverlessConfigFile; return; diff --git a/lib/classes/Service.js b/lib/classes/Service.js index f20d72e78..7227b6976 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -58,7 +58,7 @@ class Service { loadServiceFileParam([serviceFilename, serverlessFileParam]) { const that = this; - that.serviceFilename = serviceFilename.split(path.sep).pop() + that.serviceFilename = serviceFilename.split(path.sep).pop(); const serverlessFile = serverlessFileParam; // basic service level validation diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index fd2acdfc8..d53c23b4d 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -17,7 +17,7 @@ const expect = require('chai').expect; describe('Service', () => { describe('#constructor()', () => { const serverless = new Serverless(); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; it('should attach serverless instance', () => { const serviceInstance = new Service(serverless); @@ -119,7 +119,7 @@ describe('Service', () => { it('should resolve if no servicePath is found', () => { const serverless = new Serverless(); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; const noService = new Service(serverless); return expect(noService.load()).to.be.fulfilled; @@ -158,7 +158,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless(); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -213,7 +213,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless(); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -268,7 +268,7 @@ describe('Service', () => { JSON.stringify(serverlessJSON)); const serverless = new Serverless(); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -324,7 +324,7 @@ describe('Service', () => { `module.exports = ${JSON.stringify(serverlessJSON)};`); const serverless = new Serverless(); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -380,7 +380,7 @@ describe('Service', () => { `module.exports = new Promise(resolve => { resolve(${JSON.stringify(serverlessJSON)}) });`); const serverless = new Serverless(); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -411,7 +411,7 @@ describe('Service', () => { 'module.exports = function config() {};'); const serverless = new Serverless(); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -455,7 +455,7 @@ describe('Service', () => { YAML.dump(serverlessJSON)); const serverless = new Serverless(); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); @@ -477,7 +477,7 @@ describe('Service', () => { YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be @@ -498,7 +498,7 @@ describe('Service', () => { YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled @@ -524,7 +524,7 @@ describe('Service', () => { YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled @@ -558,7 +558,7 @@ describe('Service', () => { YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be.fulfilled @@ -590,7 +590,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); return expect(serviceInstance.load({ stage: 'dev' })).to.eventually.be.fulfilled @@ -618,7 +618,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be @@ -635,7 +635,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); return expect(serviceInstance.load()).to.eventually.be @@ -654,7 +654,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; const getVersion = sinon.stub(serverless.utils, 'getVersion'); getVersion.returns('1.0.2'); serviceInstance = new Service(serverless); @@ -675,7 +675,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; const getVersion = sinon.stub(serverless.utils, 'getVersion'); getVersion.returns('1.2.2'); serviceInstance = new Service(serverless); @@ -693,7 +693,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serverless.variables.service = serverless.service; serviceInstance = new Service(serverless); @@ -732,7 +732,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serverless.service = new Service(serverless); return expect(serverless.service.load()).to.eventually.be.fulfilled @@ -773,7 +773,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error); }); @@ -1028,7 +1028,7 @@ describe('Service', () => { YAML.dump(serverlessYml)); const serverless = new Serverless(); - serverless.processedInput = {options: {}}; + serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); diff --git a/lib/plugins/print/print.js b/lib/plugins/print/print.js index 4ae525219..460fb9862 100644 --- a/lib/plugins/print/print.js +++ b/lib/plugins/print/print.js @@ -96,7 +96,8 @@ class Print { // the codebase. Avoiding that, this method must read the serverless.yml file itself, adorn it // as the Service class would and then populate it, reversing the adornments thereafter in // preparation for printing the service for the user. - return getServerlessConfigFile(this.serverless.processedInput.options.config, this.serverless.config.servicePath) + return getServerlessConfigFile( + this.serverless.processedInput.options.config, this.serverless.config.servicePath) .then((svc) => { const service = svc; this.adorn(service); diff --git a/lib/utils/getServerlessConfigFile.js b/lib/utils/getServerlessConfigFile.js index 607c92005..2654dbf81 100644 --- a/lib/utils/getServerlessConfigFile.js +++ b/lib/utils/getServerlessConfigFile.js @@ -52,7 +52,8 @@ const handleJsConfigFile = (jsConfigFile) => BbPromise.try(() => { throw new Error('serverless.js must export plain object'); }); -const getServerlessConfigFile = _.memoize((configFileName, srvcPath) => getServerlessConfigFilePath(configFileName, srvcPath) +const getServerlessConfigFile = _.memoize((configFileName, srvcPath) => + getServerlessConfigFilePath(configFileName, srvcPath) .then(configFilePath => { if (configFilePath !== null) { const isJSConfigFile = _.last(_.split(configFilePath, '.')) === 'js'; diff --git a/lib/utils/getServerlessConfigFile.test.js b/lib/utils/getServerlessConfigFile.test.js index def084b01..1ee3c9add 100644 --- a/lib/utils/getServerlessConfigFile.test.js +++ b/lib/utils/getServerlessConfigFile.test.js @@ -50,9 +50,10 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'foobar.yaml'); writeFileSync(serverlessFilePath, 'service: my-yaml-service'); - return expect(getServerlessConfigFile('foobar.yaml', tmpDirPath)).to.be.fulfilled.then((result) => { - expect(result).to.deep.equal({ service: 'my-yaml-service' }); - }); + return expect(getServerlessConfigFile('foobar.yaml', tmpDirPath)).to.be.fulfilled.then( + (result) => { + expect(result).to.deep.equal({ service: 'my-yaml-service' }); + }); }); it('should return the file content if a serverless.json file is found', () => { From 37d0c71793558b5560006b7b68afbb2d5dc7ba39 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 7 Jun 2019 09:24:45 -0400 Subject: [PATCH 332/504] fix use of getServerlessConfigFilePath in packaging --- lib/plugins/package/lib/packageService.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/plugins/package/lib/packageService.js b/lib/plugins/package/lib/packageService.js index 2ba7159e2..1342bdf6f 100644 --- a/lib/plugins/package/lib/packageService.js +++ b/lib/plugins/package/lib/packageService.js @@ -24,7 +24,8 @@ module.exports = { getExcludes(exclude, excludeLayers) { return serverlessConfigFileUtils - .getServerlessConfigFilePath(this.serverless.config.servicePath) + .getServerlessConfigFilePath( + this.serverless.processedInput.options.config, this.serverless.config.servicePath) .then(configFilePath => { const packageExcludes = this.serverless.service.package.exclude || []; // add local service plugins Path From 560bbb0d07f71b5c9310e3847bee22f1150dd341 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 7 Jun 2019 12:49:16 -0400 Subject: [PATCH 333/504] fix unit tests for packageService --- lib/plugins/package/lib/packageService.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/package/lib/packageService.test.js b/lib/plugins/package/lib/packageService.test.js index cda987239..d5c9ac1ca 100644 --- a/lib/plugins/package/lib/packageService.test.js +++ b/lib/plugins/package/lib/packageService.test.js @@ -20,6 +20,7 @@ describe('#packageService()', () => { beforeEach(() => { serverless = new Serverless(); + serverless.processedInput = { options: {} }; packagePlugin = new Package(serverless, {}); packagePlugin.serverless.cli = new serverless.classes.CLI(); packagePlugin.serverless.service.functions = { From 79bb5acc48937ce61c43303a595c7aeeb4a3210b Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 10 Jun 2019 08:57:37 -0400 Subject: [PATCH 334/504] Throw an error if sls config specifed doesnt exist --- lib/classes/Utils.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/classes/Utils.js b/lib/classes/Utils.js index eecf80f31..793e46e43 100644 --- a/lib/classes/Utils.js +++ b/lib/classes/Utils.js @@ -109,8 +109,12 @@ class Utils { findServicePath(customPath) { let servicePath = null; - if (customPath && fileExistsSync(path.join(process.cwd(), customPath))) { - servicePath = process.cwd(); + if (customPath) { + if (fileExistsSync(path.join(process.cwd(), customPath))) { + servicePath = process.cwd(); + } else { + throw new Error(`Config file ${customPath} not found`); + } } else if (fileExistsSync(path.join(process.cwd(), 'serverless.yml'))) { servicePath = process.cwd(); } else if (fileExistsSync(path.join(process.cwd(), 'serverless.yaml'))) { From 846cdabecedafb2d519d33ef63a6de1307676067 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 10 Jun 2019 09:32:51 -0400 Subject: [PATCH 335/504] use minimist directly to parse the --config option early rather than instantiating CLI --- lib/Serverless.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/Serverless.js b/lib/Serverless.js index 43e0c00fb..ca3ffd90b 100644 --- a/lib/Serverless.js +++ b/lib/Serverless.js @@ -4,6 +4,7 @@ const path = require('path'); const BbPromise = require('bluebird'); const os = require('os'); const updateNotifier = require('update-notifier'); +const minimist = require('minimist'); const pkg = require('../package.json'); const CLI = require('./classes/CLI'); const Config = require('./classes/Config'); @@ -30,13 +31,9 @@ class Serverless { this.variables = new Variables(this); this.pluginManager = new PluginManager(this); - // create a new CLI instance - const cli = new CLI(this); - // get an array of commands and options that should be processed - const processedInput = cli.processInput(); // use the servicePath from the options or try to find it in the CWD configObject.servicePath = configObject.servicePath || - this.utils.findServicePath(processedInput.options.config); + this.utils.findServicePath(minimist(process.argv.slice(2)).config); this.config = new Config(this, configObject); From 970a1cc3f8647d46c3c02429141c774c01a03d53 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 10 Jun 2019 10:49:14 -0400 Subject: [PATCH 336/504] refactor getServerlessConfigFile to simply accept a serverless object --- lib/classes/PluginManager.js | 3 +- lib/classes/Service.js | 6 +-- lib/plugins/package/lib/packageService.js | 3 +- lib/plugins/print/print.js | 3 +- lib/utils/autocomplete.js | 2 +- lib/utils/getServerlessConfigFile.js | 16 ++++---- lib/utils/getServerlessConfigFile.test.js | 45 ++++++++++++++++++----- 7 files changed, 51 insertions(+), 27 deletions(-) diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index cb987fda6..1e6c063b1 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -45,8 +45,7 @@ class PluginManager { loadConfigFile() { return serverlessConfigFileUtils - .getServerlessConfigFile( - this.serverless.processedInput.options.config, this.serverless.config.servicePath) + .getServerlessConfigFile(this.serverless) .then((serverlessConfigFile) => { this.serverlessConfigFile = serverlessConfigFile; return; diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 7227b6976..dcae1baee 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -48,10 +48,8 @@ class Service { } return BbPromise.all([ - serverlessConfigFileUtils.getServerlessConfigFilePath( - this.serverless.processedInput.options.config, this.serverless.config.servicePath), - serverlessConfigFileUtils.getServerlessConfigFile( - this.serverless.processedInput.options.config, this.serverless.config.servicePath), + serverlessConfigFileUtils.getServerlessConfigFilePath(this.serverless), + serverlessConfigFileUtils.getServerlessConfigFile(this.serverless), ]).then(that.loadServiceFileParam.bind(that)); } diff --git a/lib/plugins/package/lib/packageService.js b/lib/plugins/package/lib/packageService.js index 1342bdf6f..ab6d27a82 100644 --- a/lib/plugins/package/lib/packageService.js +++ b/lib/plugins/package/lib/packageService.js @@ -24,8 +24,7 @@ module.exports = { getExcludes(exclude, excludeLayers) { return serverlessConfigFileUtils - .getServerlessConfigFilePath( - this.serverless.processedInput.options.config, this.serverless.config.servicePath) + .getServerlessConfigFilePath(this.serverless) .then(configFilePath => { const packageExcludes = this.serverless.service.package.exclude || []; // add local service plugins Path diff --git a/lib/plugins/print/print.js b/lib/plugins/print/print.js index 460fb9862..d0dcffb32 100644 --- a/lib/plugins/print/print.js +++ b/lib/plugins/print/print.js @@ -96,8 +96,7 @@ class Print { // the codebase. Avoiding that, this method must read the serverless.yml file itself, adorn it // as the Service class would and then populate it, reversing the adornments thereafter in // preparation for printing the service for the user. - return getServerlessConfigFile( - this.serverless.processedInput.options.config, this.serverless.config.servicePath) + return getServerlessConfigFile(this.serverless) .then((svc) => { const service = svc; this.adorn(service); diff --git a/lib/utils/autocomplete.js b/lib/utils/autocomplete.js index 84cec0b94..5381da6d0 100644 --- a/lib/utils/autocomplete.js +++ b/lib/utils/autocomplete.js @@ -41,7 +41,7 @@ const cacheFileValid = (serverlessConfigFile, validationHash) => { const autocomplete = () => { const servicePath = process.cwd(); - return getServerlessConfigFile(servicePath) + return getServerlessConfigFile({ processedInput: { options: {} }, config: { servicePath } }) .then((serverlessConfigFile) => getCacheFile(servicePath) .then((cacheFile) => { if (!cacheFile || !cacheFileValid(serverlessConfigFile, cacheFile.validationHash)) { diff --git a/lib/utils/getServerlessConfigFile.js b/lib/utils/getServerlessConfigFile.js index 2654dbf81..c185b1d1d 100644 --- a/lib/utils/getServerlessConfigFile.js +++ b/lib/utils/getServerlessConfigFile.js @@ -6,11 +6,11 @@ const path = require('path'); const fileExists = require('./fs/fileExists'); const readFile = require('./fs/readFile'); -const getServerlessConfigFilePath = (configFileName, srvcPath) => { - const servicePath = srvcPath || process.cwd(); +const getServerlessConfigFilePath = (serverless) => { + const servicePath = serverless.config.servicePath || process.cwd(); - if (configFileName) { - const customPath = path.join(servicePath, configFileName); + if (serverless.processedInput.options.config) { + const customPath = path.join(servicePath, serverless.processedInput.options.config); return fileExists(customPath).then(exists => (exists ? customPath : null)); } @@ -52,8 +52,8 @@ const handleJsConfigFile = (jsConfigFile) => BbPromise.try(() => { throw new Error('serverless.js must export plain object'); }); -const getServerlessConfigFile = _.memoize((configFileName, srvcPath) => - getServerlessConfigFilePath(configFileName, srvcPath) +const getServerlessConfigFile = _.memoize((serverless) => + getServerlessConfigFilePath(serverless) .then(configFilePath => { if (configFilePath !== null) { const isJSConfigFile = _.last(_.split(configFilePath, '.')) === 'js'; @@ -66,6 +66,8 @@ const getServerlessConfigFile = _.memoize((configFileName, srvcPath) => } return ''; - }), (configFileName, srvcPath) => `${configFileName} - ${srvcPath}`); + }), + (serverless) => `${ + serverless.processedInput.options.config} - ${serverless.config.servicePath}`); module.exports = { getServerlessConfigFile, getServerlessConfigFilePath }; diff --git a/lib/utils/getServerlessConfigFile.test.js b/lib/utils/getServerlessConfigFile.test.js index 1ee3c9add..87a84aab4 100644 --- a/lib/utils/getServerlessConfigFile.test.js +++ b/lib/utils/getServerlessConfigFile.test.js @@ -23,7 +23,10 @@ describe('#getServerlessConfigFile()', () => { const randomFilePath = path.join(tmpDirPath, 'not-a-serverless-file'); writeFileSync(randomFilePath, 'some content'); - return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then((result) => { + return expect(getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + })).to.be.fulfilled.then((result) => { expect(result).to.equal(''); }); }); @@ -32,7 +35,10 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.yml'); writeFileSync(serverlessFilePath, 'service: my-yml-service'); - return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then((result) => { + return expect(getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + })).to.be.fulfilled.then((result) => { expect(result).to.deep.equal({ service: 'my-yml-service' }); }); }); @@ -41,7 +47,10 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.yaml'); writeFileSync(serverlessFilePath, 'service: my-yaml-service'); - return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then((result) => { + return expect(getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + })).to.be.fulfilled.then((result) => { expect(result).to.deep.equal({ service: 'my-yaml-service' }); }); }); @@ -50,7 +59,10 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'foobar.yaml'); writeFileSync(serverlessFilePath, 'service: my-yaml-service'); - return expect(getServerlessConfigFile('foobar.yaml', tmpDirPath)).to.be.fulfilled.then( + return expect(getServerlessConfigFile({ + processedInput: { options: { config: 'foobar.yaml' } }, + config: { servicePath: tmpDirPath }, + })).to.be.fulfilled.then( (result) => { expect(result).to.deep.equal({ service: 'my-yaml-service' }); }); @@ -60,7 +72,10 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.json'); writeFileSync(serverlessFilePath, '{ "service": "my-json-service" }'); - return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then((result) => { + return expect(getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + })).to.be.fulfilled.then((result) => { expect(result).to.deep.equal({ service: 'my-json-service' }); }); }); @@ -72,7 +87,10 @@ describe('#getServerlessConfigFile()', () => { 'module.exports = {"service": "my-json-service"};' ); - return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then( + return expect(getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + })).to.be.fulfilled.then( (result) => { expect(result).to.deep.equal({ service: 'my-json-service' }); } @@ -86,7 +104,10 @@ describe('#getServerlessConfigFile()', () => { 'module.exports = new Promise(resolve => { resolve({"service": "my-json-service"}); });' ); - return expect(getServerlessConfigFile(undefined, tmpDirPath)).to.be.fulfilled.then( + return expect(getServerlessConfigFile( + { processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + })).to.be.fulfilled.then( (result) => { expect(result).to.deep.equal({ service: 'my-json-service' }); } @@ -100,7 +121,10 @@ describe('#getServerlessConfigFile()', () => { 'module.exports = function config() {};' ); - return expect(getServerlessConfigFile(undefined, tmpDirPath)) + return expect(getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + })) .to.be.rejectedWith('serverless.js must export plain object'); }); @@ -110,7 +134,10 @@ describe('#getServerlessConfigFile()', () => { writeFileSync(serverlessFilePath, 'service: my-yml-service'); const cwd = process.cwd(); process.chdir(tmpDirPath); - return expect(getServerlessConfigFile()).to.be.fulfilled + return expect(getServerlessConfigFile({ + processedInput: { options: {} }, + config: {}, + })).to.be.fulfilled .then((result) => { process.chdir(cwd); expect(result).to.deep.equal({ service: 'my-yml-service' }); From 0c94737bec12b466e254bab6ec7b836e2aef8657 Mon Sep 17 00:00:00 2001 From: Miles Rausch Date: Mon, 10 Jun 2019 16:28:12 -0500 Subject: [PATCH 337/504] Fix formatting issue with Markdown link --- docs/providers/cloudflare/guide/intro.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/cloudflare/guide/intro.md b/docs/providers/cloudflare/guide/intro.md index 428cef53d..d38aa516d 100644 --- a/docs/providers/cloudflare/guide/intro.md +++ b/docs/providers/cloudflare/guide/intro.md @@ -21,7 +21,7 @@ The Serverless Framework is different than other application frameworks because: # Serverless.yml -The `serverless.yml` file is what molds the Worker(s) of your project. Using the [Serverless Cloudflare Workers plugin](https://github.com/cloudflare/serverless-cloudflare-workers, a `serverless.yml` will look like: +The `serverless.yml` file is what molds the Worker(s) of your project. Using the [Serverless Cloudflare Workers plugin](https://github.com/cloudflare/serverless-cloudflare-workers), a `serverless.yml` will look like: ```yml # serverless.yml @@ -196,4 +196,4 @@ plugins: You can add our `serverless-cloudflare-workers` plugin to your project by running `npm install --save serverless-cloudflare-workers`. -* *`workers.dev` domains are not currently supported using Serverless, but you can track our progress on [this Github issue](https://github.com/cloudflare/serverless-cloudflare-workers/issues/36).* \ No newline at end of file +* *`workers.dev` domains are not currently supported using Serverless, but you can track our progress on [this Github issue](https://github.com/cloudflare/serverless-cloudflare-workers/issues/36).* From d7a9d2f877f6f3ebc091a33dbd9770cba7faf604 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 11 Jun 2019 08:48:58 -0400 Subject: [PATCH 338/504] Don't change signature of loadServiceFileParam --- lib/classes/Service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/classes/Service.js b/lib/classes/Service.js index dcae1baee..307da65bf 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -50,10 +50,10 @@ class Service { return BbPromise.all([ serverlessConfigFileUtils.getServerlessConfigFilePath(this.serverless), serverlessConfigFileUtils.getServerlessConfigFile(this.serverless), - ]).then(that.loadServiceFileParam.bind(that)); + ]).then((args) => that.loadServiceFileParam(...args)); } - loadServiceFileParam([serviceFilename, serverlessFileParam]) { + loadServiceFileParam(serviceFilename, serverlessFileParam) { const that = this; that.serviceFilename = serviceFilename.split(path.sep).pop(); From 2db59d6cabc342c4c839ec496383c460ee144799 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Tue, 11 Jun 2019 08:52:00 -0400 Subject: [PATCH 339/504] use path.basename --- lib/classes/Service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 307da65bf..74bbd6158 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -56,7 +56,7 @@ class Service { loadServiceFileParam(serviceFilename, serverlessFileParam) { const that = this; - that.serviceFilename = serviceFilename.split(path.sep).pop(); + that.serviceFilename = path.basename(serviceFilename); const serverlessFile = serverlessFileParam; // basic service level validation From c5f4f1ab3f33f4e4078af30034358a9240cb07a4 Mon Sep 17 00:00:00 2001 From: "Ben Scholzen (DASPRiD)" Date: Tue, 11 Jun 2019 18:03:05 +0200 Subject: [PATCH 340/504] Reference custom ApiGateway for models and request validators if configured --- .../package/compile/events/apiGateway/lib/method/index.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js index 0d12adb4d..0f2269ee6 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js @@ -84,9 +84,7 @@ module.exports = { [modelLogicalId]: { Type: 'AWS::ApiGateway::Model', Properties: { - RestApiId: { - Ref: this.provider.naming.getRestApiLogicalId(), - }, + RestApiId: this.provider.getApiGatewayRestApiId(), ContentType: contentType, Schema: schema, }, @@ -94,9 +92,7 @@ module.exports = { [validatorLogicalId]: { Type: 'AWS::ApiGateway::RequestValidator', Properties: { - RestApiId: { - Ref: this.provider.naming.getRestApiLogicalId(), - }, + RestApiId: this.provider.getApiGatewayRestApiId(), ValidateRequestBody: true, ValidateRequestParameters: true, }, From 00de26831fb580fe622f734996398e81c2e2e3cf Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 11 Jun 2019 20:51:22 +0200 Subject: [PATCH 341/504] Bump dependencies --- package.json | 54 ++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 57be13d58..5792b2fc7 100644 --- a/package.json +++ b/package.json @@ -81,18 +81,18 @@ "chai-as-promised": "^7.1.1", "child-process-ext": "^2.0.0", "cli-progress-footer": "^1.1.1", - "coveralls": "^3.0.3", - "eslint": "^3.3.1", + "coveralls": "^3.0.4", + "eslint": "^3.19.0", "eslint-config-airbnb": "^10.0.1", - "eslint-config-airbnb-base": "^5.0.2", - "eslint-plugin-import": "^1.13.0", - "eslint-plugin-jsx-a11y": "^2.1.0", - "eslint-plugin-react": "^6.1.1", + "eslint-config-airbnb-base": "^5.0.3", + "eslint-plugin-import": "^1.16.0", + "eslint-plugin-jsx-a11y": "^2.2.3", + "eslint-plugin-react": "^6.10.3", "git-list-updated": "^1.1.2", "jest-circus": "^24.8.0", - "jest-cli": "^24.5.0", + "jest-cli": "^24.8.0", "mocha": "^6.1.4", - "mocha-lcov-reporter": "^1.2.0", + "mocha-lcov-reporter": "^1.3.0", "mock-require": "^3.0.3", "nyc": "^14.1.1", "p-limit": "^2.2.0", @@ -104,45 +104,45 @@ }, "dependencies": { "@serverless/enterprise-plugin": "^1.0.3", - "archiver": "^1.1.0", + "archiver": "^1.3.0", "async": "^1.5.2", - "aws-sdk": "^2.430.0", - "bluebird": "^3.5.0", + "aws-sdk": "^2.473.0", + "bluebird": "^3.5.5", "cachedir": "^2.2.0", - "chalk": "^2.0.0", - "ci-info": "^1.1.1", - "download": "^5.0.2", + "chalk": "^2.4.2", + "ci-info": "^1.6.0", + "download": "^5.0.3", "fast-levenshtein": "^2.0.6", - "filesize": "^3.3.0", + "filesize": "^3.6.1", "fs-extra": "^0.26.7", "get-stdin": "^5.0.1", "globby": "^6.1.0", - "graceful-fs": "^4.1.11", + "graceful-fs": "^4.1.15", "https-proxy-agent": "^2.2.1", "is-docker": "^1.1.0", - "js-yaml": "^3.13.0", + "js-yaml": "^3.13.1", "json-cycle": "^1.3.0", - "json-refs": "^2.1.5", + "json-refs": "^2.1.7", "jszip": "^3.2.1", "jwt-decode": "^2.2.0", - "lodash": "^4.13.1", + "lodash": "^4.17.11", "minimist": "^1.2.0", "mkdirp": "^0.5.1", - "moment": "^2.13.0", + "moment": "^2.24.0", "nanomatch": "^1.2.13", - "node-fetch": "^1.6.0", - "object-hash": "^1.2.0", - "promise-queue": "^2.2.3", + "node-fetch": "^1.7.3", + "object-hash": "^1.3.1", + "promise-queue": "^2.2.5", "raven": "^1.2.1", - "rc": "^1.1.6", + "rc": "^1.2.8", "replaceall": "^0.1.6", "semver": "^5.7.0", "semver-regex": "^1.0.0", "tabtab": "^2.2.2", "untildify": "^3.0.3", - "update-notifier": "^2.2.0", - "uuid": "^2.0.2", - "write-file-atomic": "^2.1.0", + "update-notifier": "^2.5.0", + "uuid": "^2.0.3", + "write-file-atomic": "^2.4.3", "yaml-ast-parser": "0.0.34" } } From c633553671bd674137bdae8cd7be089dfe923c0c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 11 Jun 2019 20:54:03 +0200 Subject: [PATCH 342/504] Update package-lock.json --- package-lock.json | 891 +++++++++++++++++++--------------------------- 1 file changed, 372 insertions(+), 519 deletions(-) diff --git a/package-lock.json b/package-lock.json index 532105c06..e4dfa5fb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,17 +24,17 @@ } }, "@babel/core": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.4.tgz", - "integrity": "sha512-lQgGX3FPRgbz2SKmhMtYgJvVzGZrmjaF4apZ2bLwofAKiSjxU0drPh4S/VasyYXwaTs+A1gvQ45BN8SQJzHsQQ==", + "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.4", + "@babel/parser": "^7.4.5", "@babel/template": "^7.4.4", - "@babel/traverse": "^7.4.4", + "@babel/traverse": "^7.4.5", "@babel/types": "^7.4.4", "convert-source-map": "^1.1.0", "debug": "^4.1.0", @@ -53,6 +53,18 @@ "requires": { "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "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 } } }, @@ -67,6 +79,14 @@ "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-function-name": { @@ -135,9 +155,9 @@ } }, "@babel/parser": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz", - "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==", + "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-syntax-object-rest-spread": { @@ -161,16 +181,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", @@ -191,6 +211,12 @@ "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.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, @@ -334,12 +360,6 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "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 } } }, @@ -359,12 +379,6 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "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 } } }, @@ -420,12 +434,6 @@ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "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 - }, "write-file-atomic": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", @@ -451,9 +459,9 @@ } }, "@serverless/enterprise-plugin": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.3.tgz", - "integrity": "sha512-qvKaidc3ixuZ1XAa/bzYeKDzuO2jJSEz4KR4syr3+L060wXHJh4i/E7JZxAODQJbFq4gjE+zCUZ1ut6uZvs6ag==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.6.tgz", + "integrity": "sha512-5NMOIP1OW3E7C+jG2per/hmImRd3eqM+nYrlbaR+VBCC4wHSLtBYkZYpCvFnuX96yNwDlhlrO3DA93dA9UiShg==", "requires": { "@serverless/event-mocks": "^1.1.1", "@serverless/platform-sdk": "^1.0.0", @@ -482,14 +490,6 @@ "universalify": "^0.1.0" } }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", @@ -542,14 +542,6 @@ "dev": true, "requires": { "type-detect": "4.0.8" - }, - "dependencies": { - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - } } }, "@sinonjs/formatio": { @@ -580,9 +572,9 @@ "dev": true }, "@types/babel__core": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.1.tgz", - "integrity": "sha512-+hjBtgcFPYyCTo0A15+nxrCVJL7aC6Acg87TXd5OW3QhHswdrOLoles+ldL2Uk8q++7yIfl4tURtztccdeeyOw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", + "integrity": "sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -612,9 +604,9 @@ } }, "@types/babel__traverse": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.6.tgz", - "integrity": "sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.7.tgz", + "integrity": "sha512-CeBpmX1J8kWLcDEnI3Cl2Eo6RfbGvzUctA+CjZUhOKDFbLfcr7fc4usEqLNWetrlJd7RhAkyYe2czXop4fICpw==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -646,9 +638,9 @@ } }, "@types/lodash": { - "version": "4.14.132", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.132.tgz", - "integrity": "sha512-RNUU1rrh85NgUJcjOOr96YXr+RHwInGbaQCZmlitqOaCKXffj8bh+Zxwuq5rjDy5OgzFldDVoqk4pyLEDiwxIw==" + "version": "4.14.134", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.134.tgz", + "integrity": "sha512-2/O0khFUCFeDlbi7sZ7ZFRCcT812fAeOLm7Ev4KbwASkZ575TDrDcY7YyaoHdTOzKcNbfiwLYZqPmoC4wadrsw==" }, "@types/stack-utils": { "version": "1.0.1", @@ -725,9 +717,9 @@ "dev": true }, "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "requires": { "es6-promisify": "^5.0.0" } @@ -966,13 +958,13 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "array.prototype.find": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz", - "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.0.tgz", + "integrity": "sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.13.0" } }, "asn1": { @@ -1029,9 +1021,9 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "aws-sdk": { - "version": "2.454.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.454.0.tgz", - "integrity": "sha512-1vB9DNIwh+mqKD2IZspYTQapCD6f5VnMT5V2VPlXJ1CNcUdFSU8FFyxKmYApNs+S3re1h3fhWDjpwTreS+XLRQ==", + "version": "2.473.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.473.0.tgz", + "integrity": "sha512-1Qr16lOcz4ANzl/oPQRR+fxchfvUx4PVQhUNnDU3FH9OBfU3Xj+Vh6bGYFbreFQgqIqXUTEuJR5pC44uK70YfA==", "requires": { "buffer": "4.9.1", "events": "1.1.1", @@ -1242,9 +1234,9 @@ } }, "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==" }, "body-parser": { "version": "1.19.0", @@ -1261,21 +1253,6 @@ "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==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "boxen": { @@ -1668,6 +1645,11 @@ } } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -1930,9 +1912,9 @@ } }, "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==" }, "cookie-signature": { "version": "1.0.6", @@ -1964,9 +1946,9 @@ } }, "coveralls": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.3.tgz", - "integrity": "sha512-viNfeGlda2zJr8Gj1zqXpDMRjw9uM54p7wzZdvLRyOgnAfCe974Dq4veZkjJdxQXbmdppu6flEajFYseHYaUhg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.4.tgz", + "integrity": "sha512-eyqUWA/7RT0JagiL0tThVhjbIjoiEUyWCjtUJoOPcWoeofP5WK/jb2OJYoBFrR6DvplR+AxOyuBqk4JHkk5ykA==", "dev": true, "requires": { "growl": "~> 1.10.0", @@ -2112,11 +2094,11 @@ } }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, "decamelize": { @@ -2460,9 +2442,9 @@ } }, "es6-promise": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", - "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==" + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, "es6-promisify": { "version": "5.0.0", @@ -2496,14 +2478,14 @@ } }, "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", "dev": true, "requires": { "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.1" } }, @@ -2517,6 +2499,27 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, + "escodegen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "dev": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + } + } + }, "escope": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", @@ -2591,15 +2594,6 @@ "supports-color": "^2.0.0" } }, - "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" - } - }, "inquirer": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", @@ -2621,12 +2615,6 @@ "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 - }, "run-async": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", @@ -2677,23 +2665,6 @@ "debug": "^2.2.0", "object-assign": "^4.0.1", "resolve": "^1.1.6" - }, - "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 - } } }, "eslint-plugin-import": { @@ -2720,15 +2691,6 @@ "pkg-up": "^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" - } - }, "doctrine": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.3.0.tgz", @@ -2738,12 +2700,6 @@ "esutils": "^2.0.2", "isarray": "^1.0.0" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true } } }, @@ -2900,15 +2856,6 @@ "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==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -2947,6 +2894,12 @@ } } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -2989,12 +2942,6 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true } } }, @@ -3047,26 +2994,6 @@ "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" - }, - "dependencies": { - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, - "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" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "extend": { @@ -3270,21 +3197,6 @@ "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==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "find-cache-dir": { @@ -3350,13 +3262,6 @@ "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", "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==" - } } }, "flat-cache": { @@ -3452,6 +3357,16 @@ "klaw": "^1.0.0", "path-is-absolute": "^1.0.0", "rimraf": "^2.2.8" + }, + "dependencies": { + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "requires": { + "graceful-fs": "^4.1.6" + } + } } }, "fs.realpath": { @@ -3472,28 +3387,25 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "bundled": true, "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": false, - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "bundled": true, + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "resolved": false, - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3503,15 +3415,15 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "bundled": true, + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": false, - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3519,40 +3431,37 @@ }, "chownr": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "bundled": true, "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "bundled": true, + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": false, - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "bundled": true, + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "bundled": true, + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "bundled": true, "dev": true, "optional": true }, "debug": { "version": "4.1.1", - "resolved": false, - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3561,29 +3470,25 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": false, - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "bundled": true, "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "bundled": true, "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": false, - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bundled": true, "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": false, - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3592,15 +3497,13 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "bundled": true, "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": false, - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3616,8 +3519,7 @@ }, "glob": { "version": "7.1.3", - "resolved": false, - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3631,15 +3533,13 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "bundled": true, "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": false, - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3648,8 +3548,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": false, - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3658,8 +3557,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3669,53 +3567,51 @@ }, "inherits": { "version": "2.0.3", - "resolved": false, - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "bundled": true, + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", - "resolved": false, - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "bundled": true, "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "bundled": true, "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": false, - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "resolved": false, - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "bundled": true, + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", - "resolved": false, - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3723,8 +3619,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": false, - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3733,24 +3628,22 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": false, - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.1.1", - "resolved": false, - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "bundled": true, "dev": true, "optional": true }, "needle": { "version": "2.3.0", - "resolved": false, - "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3761,8 +3654,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": false, - "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3780,8 +3672,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3791,15 +3682,13 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": false, - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "bundled": true, "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": false, - "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3809,8 +3698,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": false, - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3822,44 +3710,40 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "bundled": true, + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": false, - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "bundled": true, "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": false, - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "bundled": true, "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "bundled": true, "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": false, - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3869,22 +3753,19 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "bundled": true, "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "bundled": true, "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "resolved": false, - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3896,8 +3777,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": false, - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "bundled": true, "dev": true, "optional": true } @@ -3905,8 +3785,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3921,8 +3800,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": false, - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3931,50 +3809,45 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": false, - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "bundled": true, + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": false, - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "bundled": true, "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": false, - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "bundled": true, "dev": true, "optional": true }, "semver": { "version": "5.7.0", - "resolved": false, - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "bundled": true, "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "bundled": true, "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "bundled": true, "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3983,8 +3856,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -3993,24 +3865,22 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": false, - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "bundled": true, "dev": true, "optional": true }, "tar": { "version": "4.4.8", - "resolved": false, - "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -4025,15 +3895,13 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "bundled": true, "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": false, - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -4042,15 +3910,15 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "bundled": true, + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", - "resolved": false, - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true + "bundled": true, + "dev": true, + "optional": true } } }, @@ -4244,14 +4112,6 @@ "optimist": "^0.6.1", "source-map": "^0.6.1", "uglify-js": "^3.1.4" - }, - "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 - } } }, "har-schema": { @@ -4335,6 +4195,11 @@ "kind-of": "^4.0.0" }, "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", @@ -4405,6 +4270,21 @@ "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==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "iconv-lite": { @@ -4584,9 +4464,9 @@ "dev": true }, "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" }, "is-callable": { "version": "1.1.4", @@ -4699,6 +4579,11 @@ "kind-of": "^3.0.2" }, "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -4867,9 +4752,9 @@ }, "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==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", "dev": true } } @@ -4944,24 +4829,24 @@ "semver": "^5.6.0" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "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.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.4.tgz", - "integrity": "sha512-QCHGyZEK0bfi9GR215QSm+NJwFKEShbtc7tfbUdLAEzn3kKhLDDZqvljn8rPZM9v8CEOhzL1nlYoO4r1ryl67w==", + "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" @@ -5452,12 +5337,6 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "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 } } }, @@ -5587,37 +5466,11 @@ "xml-name-validator": "^3.0.0" }, "dependencies": { - "escodegen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", - "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", - "dev": true, - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "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, - "optional": true } } }, @@ -5700,9 +5553,9 @@ "integrity": "sha512-3MWTH77OHLf3muMknZJS4GnDhGPMITyF9D84hpRQrjt1Hk3pBtTiyZcqodHUDSaDq8VDy9YyIbanRI+3RoW3FA==" }, "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { "graceful-fs": "^4.1.6" } @@ -5959,9 +5812,9 @@ } }, "lolex": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.0.1.tgz", - "integrity": "sha512-UHuOBZ5jjsKuzbB/gRNNW8Vg8f00Emgskdq2kvZxgBJCS0aqquAuXai/SkWORlKeZEiNQWZjFZOqIUcH9LqKCw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.1.0.tgz", + "integrity": "sha512-BYxIEXiVq5lGIXeVHnsFzqa1TxN5acnKnPCdlZSpzm8viNEOhiigupA4vTQ9HEFQ6nLTQ9wQOgBknJgzUYQ9Aw==", "dev": true }, "loose-envify": { @@ -6091,14 +5944,6 @@ "dev": true, "requires": { "source-map": "^0.6.1" - }, - "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 - } } }, "merge-stream": { @@ -6234,6 +6079,15 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "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" + } + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -6269,6 +6123,12 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "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 + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -6348,9 +6208,9 @@ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "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==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "mute-stream": { "version": "0.0.6", @@ -6358,9 +6218,9 @@ "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=" }, "nan": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true, "optional": true }, @@ -6423,15 +6283,15 @@ "dev": true }, "nise": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", - "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.0.tgz", + "integrity": "sha512-Z3sfYEkLFzFmL8KY6xnSJLRxwQwYBjOXi/24lb62ZnZiGA0JUzGGTI6TBIgfCSMIDl9Jlu8SRmHNACLTemDHww==", "dev": true, "requires": { "@sinonjs/formatio": "^3.1.0", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", - "lolex": "^2.3.2", + "lolex": "^4.1.0", "path-to-regexp": "^1.7.0" }, "dependencies": { @@ -6441,12 +6301,6 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "lolex": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", - "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", - "dev": true - }, "path-to-regexp": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", @@ -6716,9 +6570,9 @@ } }, "yargs-parser": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.0.tgz", - "integrity": "sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -6764,6 +6618,11 @@ "kind-of": "^3.0.2" } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -7289,9 +7148,9 @@ "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=" }, "prompts": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.4.tgz", - "integrity": "sha512-HTzM3UWp/99A0gk51gAegwo1QRYA7xjcZufMNe33rCclFszUYAuHe1fIN/3ZmiHeGPkUsNaRyQm1hHOfM0PKxA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.1.0.tgz", + "integrity": "sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==", "dev": true, "requires": { "kleur": "^3.0.2", @@ -7340,9 +7199,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==", "dev": true }, "pump": { @@ -7392,6 +7251,11 @@ "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=" + }, "uuid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", @@ -7657,9 +7521,9 @@ } }, "resolve": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", - "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", + "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -7716,9 +7580,9 @@ } }, "rsvp": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.4.tgz", - "integrity": "sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==", + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", "dev": true }, "run-async": { @@ -7865,20 +7729,10 @@ "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==", - "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=" - } - } + "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==" } } }, @@ -8023,14 +7877,6 @@ "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", @@ -8065,6 +7911,11 @@ } } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -8103,10 +7954,10 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" }, - "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", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -8141,6 +7992,12 @@ "kind-of": "^3.2.0" }, "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -8153,9 +8010,9 @@ } }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.5.2", @@ -8176,13 +8033,6 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } } }, "source-map-url": { @@ -8263,9 +8113,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", - "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -8343,6 +8193,11 @@ } } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -8519,6 +8374,21 @@ "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==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "supports-color": { @@ -8641,21 +8511,6 @@ "mkdirp": "^0.5.1", "npmlog": "^2.0.3", "object-assign": "^4.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" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } } }, "tar-stream": { @@ -8757,6 +8612,11 @@ "kind-of": "^3.0.2" }, "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -8892,9 +8752,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "uglify-js": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.12.tgz", - "integrity": "sha512-KeQesOpPiZNgVwJj8Ge3P4JYbQHUdZzpx6Fahy6eKAYRSV4zhVmLXoC+JtOeYxcHCHTve8RG1ZGdTvpeOUM26Q==", + "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": { @@ -8908,13 +8768,6 @@ "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", "dev": true, "optional": 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, - "optional": true } } }, @@ -9314,9 +9167,9 @@ } }, "write-file-atomic": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", - "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", + "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==", "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", From 6416b0a7e70f7acfe5b31a8bb99f2927632eb956 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 11 Jun 2019 20:54:43 +0200 Subject: [PATCH 343/504] Whitespace --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f29cd3a7..b256ad76b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # 1.44.1 (2019-05-28) + - [Fix enterprise plugin lookup in global yarn installs](https://github.com/serverless/serverless/pull/6183) ## Meta From f9ee71280f635750e98dc7a71973f9c7080228a0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 11 Jun 2019 20:56:51 +0200 Subject: [PATCH 344/504] Release v1.45.0 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b256ad76b..1d08e116e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +# 1.45.0 (2019-06-12) + +- [Fix and improve ESlint config](https://github.com/serverless/serverless/pull/6188) +- [Tests: Fix mocha config](https://github.com/serverless/serverless/pull/6187) +- [Thorough integration testing](https://github.com/serverless/serverless/pull/6148) +- [Tests: Isolation improvements](https://github.com/serverless/serverless/pull/6186) +- [Add support for Websocket Logs](https://github.com/serverless/serverless/pull/6088) +- [Cleanup and improve Travis CI configuration](https://github.com/serverless/serverless/pull/6178) +- [Tests: Fix stub configuration](https://github.com/serverless/serverless/pull/6205) +- [Tests: Upgrade Sinon](https://github.com/serverless/serverless/pull/6206) +- [Add Application Load Balancer event source](https://github.com/serverless/serverless/pull/6073) +- [Do not run integration tests for PR's](https://github.com/serverless/serverless/pull/6207) +- [Adding a validation to validation.js script](https://github.com/serverless/serverless/pull/6192) +- [Tests: Upgrade dependencies, improve isolation and experience on Windows](https://github.com/serverless/serverless/pull/6208) +- [Add support for S3 hosted package artifacts](https://github.com/serverless/serverless/pull/6196) +- [Remove root README generator](https://github.com/serverless/serverless/pull/6215) +- [Myho/npm lint fix](https://github.com/serverless/serverless/pull/6217) +- [Use common prefix for log groups permissions at Lambdas' execution roles](https://github.com/serverless/serverless/pull/6212) +- [Update Scala version to 2.13.0 for aws-scala-sbt template](https://github.com/serverless/serverless/pull/6222) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.1...v1.45.0) + + # 1.44.1 (2019-05-28) - [Fix enterprise plugin lookup in global yarn installs](https://github.com/serverless/serverless/pull/6183) diff --git a/package-lock.json b/package-lock.json index e4dfa5fb3..cd27db017 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.44.1", + "version": "1.45.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5792b2fc7..879eb991f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.44.1", + "version": "1.45.0", "engines": { "node": ">=6.0" }, From 39eeaf19d6734d80b3dd01bc6e1d0c090dc44e7b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 11 Jun 2019 22:11:06 +0200 Subject: [PATCH 345/504] Add missing item to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d08e116e..4f38b8c87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 1.45.0 (2019-06-12) +- [Add `--config` option](https://github.com/serverless/serverless/pull/6216) - [Fix and improve ESlint config](https://github.com/serverless/serverless/pull/6188) - [Tests: Fix mocha config](https://github.com/serverless/serverless/pull/6187) - [Thorough integration testing](https://github.com/serverless/serverless/pull/6148) From 585a75ed811299d63525a940c11ea4c882379a74 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 10:56:41 +0200 Subject: [PATCH 346/504] Ensure release tags are build --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e8211ae74..cab0cf2d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,9 @@ git: depth: 1 # No need for commits history branches: - only: master # Do not build PR branches + only: + - master # Do not build PR branches + - /^v\d+\.\d+\.\d+$/ # Ensure to build release tags env: SLS_IGNORE_WARNING=* # Default env From dabf1327c18b21175360774758c707a51522f971 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 11:14:00 +0200 Subject: [PATCH 347/504] Ensure latest version of Node.js --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index cab0cf2d9..e2b068cef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,6 +70,7 @@ jobs: - npm run integration-test-cleanup - stage: Deploy + node_js: 12 script: skip deploy: provider: npm From 93ac3b2dc5f15df03fa222c1c578b1823dfdf500 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 13:00:41 +0200 Subject: [PATCH 348/504] Ensure to use proper property name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 879eb991f..7e682f05b 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "postinstall": "node ./scripts/postinstall.js" }, "mocha": { - "R": "tests/mocha-reporter", + "reporter": "tests/mocha-reporter", "timeout": 5000 }, "jest": { From 5f60426b6bcd9ef47dec7906a3d2f30a0733f1fd Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Wed, 12 Jun 2019 08:39:34 -0400 Subject: [PATCH 349/504] PLAT-1091 - message in `npm i` output about the `serverless` qucikstart command --- scripts/postinstall.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 823bed854..31ff381ef 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -1,12 +1,28 @@ 'use strict'; const path = require('path'); +const chalk = require('chalk') /* eslint-disable no-console */ const Serverless = require('../lib/Serverless'); const execSync = require('child_process').execSync; +const truthyStr = (val) => val && !['0', 'false', 'f', 'n', 'no'].includes(val.toLowerCase()) +const { CI, ADBLOCK, SILENT } = process.env +if (!truthyStr(CI) && !truthyStr(ADBLOCK) && !truthyStr(SILENT)) { + console.log( + chalk.yellow(`\ + +------------------------------------------------------------------+ + | | + | Serverless Framework successfully installed! | + | To start your first project, run “serverless” in a new folder. | + | | + +------------------------------------------------------------------+ +`) + ) +} + try { const serverless = new Serverless(); From 490e714ccd924a85eb32a82768e409587864ffad Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 14:53:18 +0200 Subject: [PATCH 350/504] Obtain path to statements once --- .../aws/package/lib/mergeIamTemplates.js | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.js b/lib/plugins/aws/package/lib/mergeIamTemplates.js index 2698be83f..cfa1893e1 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.js @@ -86,24 +86,21 @@ module.exports = { const logGroupsPrefix = this.provider.naming .getLogGroupName(`${this.provider.serverless.service.service}-${this.provider.getStage()}`); - this.serverless.service.provider.compiledCloudFormationTemplate + const policyDocumentStatements = this.serverless.service.provider.compiledCloudFormationTemplate .Resources[this.provider.naming.getRoleLogicalId()] .Properties .Policies[0] .PolicyDocument - .Statement[0] + .Statement; + + policyDocumentStatements[0] .Resource .push({ 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + `:log-group:${logGroupsPrefix}*:*`, }); - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement[1] + policyDocumentStatements[1] .Resource .push({ 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + @@ -117,12 +114,8 @@ module.exports = { .Properties .Policies[0] .PolicyDocument - .Statement = this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement.concat(this.serverless.service.provider.iamRoleStatements); + .Statement = policyDocumentStatements + .concat(this.serverless.service.provider.iamRoleStatements); } if (this.serverless.service.provider.iamManagedPolicies) { From d417a0fc4ef8ba699358668248d30d47ec249acd Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 14:58:24 +0200 Subject: [PATCH 351/504] Seclude canonicalFunctionNamePrefix var --- lib/plugins/aws/package/lib/mergeIamTemplates.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.js b/lib/plugins/aws/package/lib/mergeIamTemplates.js index cfa1893e1..7b35403e0 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.js @@ -83,8 +83,10 @@ module.exports = { } ); + const canonicalFunctionNamePrefix = + `${this.provider.serverless.service.service}-${this.provider.getStage()}`; const logGroupsPrefix = this.provider.naming - .getLogGroupName(`${this.provider.serverless.service.service}-${this.provider.getStage()}`); + .getLogGroupName(canonicalFunctionNamePrefix); const policyDocumentStatements = this.serverless.service.provider.compiledCloudFormationTemplate .Resources[this.provider.naming.getRoleLogicalId()] From 6796ccf866d8ac58a2b86514ecfdc96d8a21c6e5 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 15:38:16 +0200 Subject: [PATCH 352/504] Ensure tests reflect more closely a real world --- .../aws/package/lib/mergeIamTemplates.test.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js index 59a41473c..7d0bee446 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js @@ -9,12 +9,15 @@ const AwsPackage = require('../index'); describe('#mergeIamTemplates()', () => { let awsPackage; let serverless; + const serviceName = 'new-service'; const functionName = 'test'; + const stage = 'dev'; + const resolvedFunctionName = `${serviceName}-${stage}-${functionName}`; beforeEach(() => { serverless = new Serverless(); const options = { - stage: 'dev', + stage, region: 'us-east-1', }; serverless.setProvider('aws', new AwsProvider(serverless, options)); @@ -23,14 +26,14 @@ describe('#mergeIamTemplates()', () => { awsPackage.serverless.service.provider.compiledCloudFormationTemplate = { Resources: {}, }; - awsPackage.serverless.service.service = 'new-service'; + awsPackage.serverless.service.service = serviceName; awsPackage.serverless.service.functions = { [functionName]: { - name: 'test', artifact: 'test.zip', handler: 'handler.hello', }, }; + serverless.service.setFunctionNames(); // Ensure to resolve function names }); it('should not merge if there are no functions', () => { @@ -291,7 +294,7 @@ describe('#mergeIamTemplates()', () => { { Type: 'AWS::Logs::LogGroup', Properties: { - LogGroupName: awsPackage.provider.naming.getLogGroupName(functionName), + LogGroupName: awsPackage.provider.naming.getLogGroupName(resolvedFunctionName), }, } ); @@ -310,7 +313,7 @@ describe('#mergeIamTemplates()', () => { { Type: 'AWS::Logs::LogGroup', Properties: { - LogGroupName: awsPackage.provider.naming.getLogGroupName(functionName), + LogGroupName: awsPackage.provider.naming.getLogGroupName(resolvedFunctionName), RetentionInDays: 5, }, } From 426393b2b3d8120235923d55b223107a10ad7822 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 15:48:27 +0200 Subject: [PATCH 353/504] Configure test exposing issue with custom function name Related to #6236 --- .../aws/package/lib/mergeIamTemplates.test.js | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js index 7d0bee446..c659ab00f 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js @@ -139,6 +139,114 @@ describe('#mergeIamTemplates()', () => { }) ); + + it('should ensure IAM policies for custom named functions', () => { + const customFunctionName = 'foo-bar'; + awsPackage.serverless.service.functions = { + [functionName]: { + name: customFunctionName, + artifact: 'test.zip', + handler: 'handler.hello', + }, + }; + serverless.service.setFunctionNames(); // Ensure to resolve function names + + return awsPackage.mergeIamTemplates() + .then(() => { + const canonicalFunctionsPrefix = + `${awsPackage.serverless.service.service}-${awsPackage.provider.getStage()}`; + + expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate + .Resources[awsPackage.provider.naming.getRoleLogicalId()] + ).to.deep.equal({ + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { + Service: [ + 'lambda.amazonaws.com', + ], + }, + Action: [ + 'sts:AssumeRole', + ], + }, + ], + }, + Path: '/', + Policies: [ + { + PolicyName: { + 'Fn::Join': [ + '-', + [ + awsPackage.provider.getStage(), + awsPackage.serverless.service.service, + 'lambda', + ], + ], + }, + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: [ + 'logs:CreateLogStream', + ], + Resource: [ + { + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*`, + }, + { + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + `log-group:/aws/lambda/${customFunctionName}:*`, + }, + ], + }, + { + Effect: 'Allow', + Action: [ + 'logs:PutLogEvents', + ], + Resource: [ + { + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*:*`, + }, + { + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + `log-group:/aws/lambda/${customFunctionName}:*:*`, + }, + ], + }, + ], + }, + }, + ], + RoleName: { + 'Fn::Join': [ + '-', + [ + awsPackage.serverless.service.service, + awsPackage.provider.getStage(), + { + Ref: 'AWS::Region', + }, + 'lambdaRole', + ], + ], + }, + }, + }); + }); + }); + it('should add custom IAM policy statements', () => { awsPackage.serverless.service.provider.iamRoleStatements = [ { From 025d58579e20cb68f16e516f781f51479416482b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 15:55:01 +0200 Subject: [PATCH 354/504] Fix setup of IAM policies for functions with custom names --- .../aws/package/lib/mergeIamTemplates.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.js b/lib/plugins/aws/package/lib/mergeIamTemplates.js index 7b35403e0..fddea5482 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.js @@ -109,6 +109,30 @@ module.exports = { `:log-group:${logGroupsPrefix}*:*:*`, }); + this.serverless.service.getAllFunctions().forEach((functionName) => { + const { name: resolvedFunctionName } = this.serverless.service.getFunction(functionName); + if (!resolvedFunctionName || resolvedFunctionName.startsWith(canonicalFunctionNamePrefix)) { + return; + } + + const customFunctionNamelogGroupsPrefix = + this.provider.naming.getLogGroupName(resolvedFunctionName); + + policyDocumentStatements[0] + .Resource + .push({ + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${customFunctionNamelogGroupsPrefix}:*`, + }); + + policyDocumentStatements[1] + .Resource + .push({ + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${customFunctionNamelogGroupsPrefix}:*:*`, + }); + }); + if (this.serverless.service.provider.iamRoleStatements) { // add custom iam role statements this.serverless.service.provider.compiledCloudFormationTemplate From 093881b6a582392a1d318535597b4fb6e3d07d36 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 16:00:29 +0200 Subject: [PATCH 355/504] Add code comments --- lib/plugins/aws/package/lib/mergeIamTemplates.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.js b/lib/plugins/aws/package/lib/mergeIamTemplates.js index fddea5482..030751af7 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.js @@ -95,6 +95,7 @@ module.exports = { .PolicyDocument .Statement; + // Ensure general polices for functions with default name resolution policyDocumentStatements[0] .Resource .push({ @@ -109,6 +110,7 @@ module.exports = { `:log-group:${logGroupsPrefix}*:*:*`, }); + // Ensure policies for functions with custom name resolution this.serverless.service.getAllFunctions().forEach((functionName) => { const { name: resolvedFunctionName } = this.serverless.service.getFunction(functionName); if (!resolvedFunctionName || resolvedFunctionName.startsWith(canonicalFunctionNamePrefix)) { From 9ae66384007743190c758646e472bfdb3d3f10cd Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 16:10:17 +0200 Subject: [PATCH 356/504] Update package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cd27db017..fb510dbd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7521,9 +7521,9 @@ } }, "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", "dev": true, "requires": { "path-parse": "^1.0.6" From 710bfa699a19839c89c1ed5fe9cb951cf43adb09 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 16:10:40 +0200 Subject: [PATCH 357/504] Release v1.45.1 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f38b8c87..b23756d6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 1.45.1 (2019-06-12) + +- [Fix IAM policies setup for functions with custom name](https://github.com/serverless/serverless/pull/6240) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.45.0...v1.45.1) + + # 1.45.0 (2019-06-12) - [Add `--config` option](https://github.com/serverless/serverless/pull/6216) diff --git a/package-lock.json b/package-lock.json index fb510dbd7..0e7462712 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.45.0", + "version": "1.45.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 879eb991f..9fd83e12f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.45.0", + "version": "1.45.1", "engines": { "node": ">=6.0" }, From 1d00c0440490723a37e558810a3cdecbe718642f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 12 Jun 2019 16:21:58 +0200 Subject: [PATCH 358/504] Update changelog for release --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b23756d6b..7bf612b16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 1.45.1 (2019-06-12) - [Fix IAM policies setup for functions with custom name](https://github.com/serverless/serverless/pull/6240) +- [Fix Travis CI deploy config](https://github.com/serverless/serverless/pull/6234) ## Meta - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.45.0...v1.45.1) From 5d127d0f319d94aab585b4c2a90c157312c114f8 Mon Sep 17 00:00:00 2001 From: Alex DeBrie Date: Wed, 12 Jun 2019 12:08:06 -0500 Subject: [PATCH 359/504] Fix duplicate packaging issue --- lib/plugins/package/lib/packageService.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/plugins/package/lib/packageService.js b/lib/plugins/package/lib/packageService.js index ab6d27a82..62a9cde97 100644 --- a/lib/plugins/package/lib/packageService.js +++ b/lib/plugins/package/lib/packageService.js @@ -225,10 +225,16 @@ module.exports = { params.include.forEach((pattern) => { patterns.push(pattern); }); + + // This strips "./" from the beginning of an includes pattern to avoid + // duplication of paths. See https://github.com/serverless/serverless/issues/5748 + const strippedIncludes = params.include.map((pattern) => { + return pattern.replace(/^.\//, ''); + }); // NOTE: please keep this order of concatenating the include params // rather than doing it the other way round! // see https://github.com/serverless/serverless/pull/5825 for more information - return globby(['**'].concat(params.include), { + return globby(['**'].concat(strippedIncludes), { cwd: path.join(this.serverless.config.servicePath, prefix || ''), dot: true, silent: true, From 5282cab7085a7bc2ae53eeb98a8d823857f70ecd Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 13 Jun 2019 15:34:47 +0200 Subject: [PATCH 360/504] Fix timeout response template --- .../aws/package/compile/events/apiGateway/lib/validate.js | 2 +- .../aws/package/compile/events/apiGateway/lib/validate.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js index 2b6ced95f..5bfc634e6 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -30,7 +30,7 @@ const DEFAULT_STATUS_CODES = { pattern: '[\\s\\S]*\\[502\\][\\s\\S]*', }, 504: { - pattern: '([\\s\\S]*\\[504\\][\\s\\S]*)|(^[Task timed out].*)', + pattern: '([\\s\\S]*\\[504\\][\\s\\S]*)|(.*Task timed out after \\d+\\.\\d+ seconds$)', }, }; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js index a0e692af1..e66f709b7 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js @@ -1891,7 +1891,7 @@ describe('#validate()', () => { pattern: '[\\s\\S]*\\[502\\][\\s\\S]*', }, 504: { - pattern: '([\\s\\S]*\\[504\\][\\s\\S]*)|(^[Task timed out].*)', + pattern: '([\\s\\S]*\\[504\\][\\s\\S]*)|(.*Task timed out after \\d+\\.\\d+ seconds$)', }, }); }); From ce126418ebf2f52e5adce1728ed30c3430503808 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 13 Jun 2019 17:58:02 +0200 Subject: [PATCH 361/504] Allow console.info calls in tests It's to report on long going tasks or valuable debug information --- tests/.eslintrc.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js index 162340ca5..9b891dbe3 100644 --- a/tests/.eslintrc.js +++ b/tests/.eslintrc.js @@ -1,5 +1,9 @@ module.exports = { "parserOptions": { "ecmaVersion": 2017, + }, + "rules": { + // console.info allowed to report on long going tasks or valuable debug information + "no-console": ["error", { allow: ["info"] }] } }; From ab7c1997714a6f537ee28ed12ac63d7911bb5e82 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 13 Jun 2019 17:58:19 +0200 Subject: [PATCH 362/504] Log child process output in case of fail --- tests/utils/misc/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 4c669a8c8..0bf7f29aa 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -3,9 +3,9 @@ const path = require('path'); const fse = require('fs-extra'); const BbPromise = require('bluebird'); -const { execSync } = require('child_process'); const chalk = require('chalk'); const { replaceTextInFile } = require('../fs'); +const { execSync } = require('../child-process'); const logger = console; From 695496bea847af9cb7d4436e0a871d05ba2ccb9e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 13:11:03 +0200 Subject: [PATCH 363/504] Config alphabetical order --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 05497a91f..9d937f761 100644 --- a/package.json +++ b/package.json @@ -69,11 +69,11 @@ "timeout": 5000 }, "jest": { - "testRegex": "(\\.|/)(tests)\\.js$", - "testEnvironment": "node", "setupFilesAfterEnv": [ "/tests/setup-tests.js" ], + "testEnvironment": "node", + "testRegex": "(\\.|/)(tests)\\.js$", "testRunner": "jest-circus/runner" }, "devDependencies": { From d9f266e66757498b189ea043dd547dc903ab100a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 13:13:26 +0200 Subject: [PATCH 364/504] Ensure output from tests is not silenced with Jest Workaround for Jest bug: https://github.com/facebook/jest/issues/5281 --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9d937f761..e05a38b30 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,8 @@ ], "testEnvironment": "node", "testRegex": "(\\.|/)(tests)\\.js$", - "testRunner": "jest-circus/runner" + "testRunner": "jest-circus/runner", + "useStderr": true }, "devDependencies": { "chai": "^4.2.0", From 033debcf4039e0ab05e398726d654b2f7aca5c59 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 13:14:17 +0200 Subject: [PATCH 365/504] Ensure some progress logs in integration tests --- tests/integration-all/api-gateway/tests.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/integration-all/api-gateway/tests.js b/tests/integration-all/api-gateway/tests.js index bdd1d5578..5454a3990 100644 --- a/tests/integration-all/api-gateway/tests.js +++ b/tests/integration-all/api-gateway/tests.js @@ -24,9 +24,11 @@ describe('AWS - API Gateway Integration Test', () => { beforeAll(() => { tmpDirPath = getTmpDirPath(); + console.info(`Temporary path: ${tmpDirPath}`); serverlessFilePath = path.join(tmpDirPath, 'serverless.yml'); serviceName = createTestService('aws-nodejs', tmpDirPath, path.join(__dirname, 'service')); StackName = `${serviceName}-${stage}`; + console.info(`Deploying "${StackName}" service...`); deployService(); // create an external REST API const externalRestApiName = `${stage}-${serviceName}-ext-api`; @@ -37,6 +39,8 @@ describe('AWS - API Gateway Integration Test', () => { }) .then((resources) => { restApiRootResourceId = resources[0].id; + console.info('Created external rest API ' + + `(id: ${restApiId}, root resource id: ${restApiRootResourceId})`); }); }); @@ -47,8 +51,11 @@ describe('AWS - API Gateway Integration Test', () => { delete serverless.provider.apiGateway.restApiRootResourceId; writeYamlFile(serverlessFilePath, serverless); // NOTE: deploying once again to get the stack into the original state + console.info('Redeploying service...'); deployService(); + console.info('Removing service...'); removeService(); + console.info('Deleting external rest API...'); return deleteRestApi(restApiId); }); From 138bbc3265a4c23b550e428246050c59c7cd547e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 13:21:15 +0200 Subject: [PATCH 366/504] Improve user mesage --- .../events/apiGateway/lib/hack/updateStage.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js index 0aa14e06b..d08165613 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js @@ -38,13 +38,12 @@ module.exports = { } const errorMessage = [ - 'Rest API could not be resolved. ', - 'This might be casued by a custom API Gateway setup. ', - 'With you current setup stage specific configurations such as ', - '`tracing`, `logs` and `tags` are not supported', - '', - 'Please update your configuration or open up an issue if you feel ', - 'that there\'s a way to support your setup.', + 'Rest API id could not be resolved.\n', + 'This might be caused by a custom API Gateway configuration.\n\n', + 'In given setup stage specific options such as ', + '`tracing`, `logs` and `tags` are not supported.\n\n', + 'Please update your configuration (or open up an issue if you feel ', + 'that there\'s a way to support your setup).', ].join(''); throw new Error(errorMessage); From be1cd8074896a802ff66e1526ac0814f5d69b92b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 15:10:43 +0200 Subject: [PATCH 367/504] Improve test service setup --- tests/integration-all/api-gateway/tests.js | 2 +- tests/utils/misc/index.js | 26 +++++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/tests/integration-all/api-gateway/tests.js b/tests/integration-all/api-gateway/tests.js index bdd1d5578..cc43e22eb 100644 --- a/tests/integration-all/api-gateway/tests.js +++ b/tests/integration-all/api-gateway/tests.js @@ -25,7 +25,7 @@ describe('AWS - API Gateway Integration Test', () => { beforeAll(() => { tmpDirPath = getTmpDirPath(); serverlessFilePath = path.join(tmpDirPath, 'serverless.yml'); - serviceName = createTestService('aws-nodejs', tmpDirPath, path.join(__dirname, 'service')); + serviceName = createTestService(tmpDirPath, { templateDir: path.join(__dirname, 'service') }); StackName = `${serviceName}-${stage}`; deployService(); // create an external REST API diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 4c669a8c8..51e2f2bba 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -5,7 +5,7 @@ const fse = require('fs-extra'); const BbPromise = require('bluebird'); const { execSync } = require('child_process'); const chalk = require('chalk'); -const { replaceTextInFile } = require('../fs'); +const { readYamlFile, writeYamlFile } = require('../fs'); const logger = console; @@ -47,20 +47,30 @@ function replaceEnv(values) { return originals; } -function createTestService(templateName, tmpDir, testServiceDir) { +function createTestService(tmpDir, options = { + // Either templateName or templateDir have to be provided + templateName: null, // Template name to use (e.g. 'aws-nodejs') + templateDir: null, // Path to prepared service template +}) { const serviceName = getServiceName(); fse.mkdirsSync(tmpDir); process.chdir(tmpDir); - // create a new Serverless service - execSync(`${serverlessExec} create --template ${templateName}`); - - if (testServiceDir) { - fse.copySync(testServiceDir, tmpDir, { clobber: true, preserveTimestamps: true }); + if (options.templateName) { + // create a new Serverless service + execSync(`${serverlessExec} create --template ${options.templateName}`); + } else if (options.templateDir) { + fse.copySync(options.templateDir, tmpDir, { clobber: true, preserveTimestamps: true }); + } else { + throw new Error("Either 'templateName' or 'templateDir' options have to be provided"); } - replaceTextInFile('serverless.yml', templateName, serviceName); + // Ensure unique service name + const serverlessFilePath = path.join(tmpDir, 'serverless.yml'); + const serverlessConfig = readYamlFile(serverlessFilePath); + serverlessConfig.service = serviceName; + writeYamlFile(serverlessFilePath, serverlessConfig); process.env.TOPIC_1 = `${serviceName}-1`; process.env.TOPIC_2 = `${serviceName}-1`; From 61370c3262856e354e8da5041db8246a090e1a5f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 15:14:00 +0200 Subject: [PATCH 368/504] Return whole service config not just service name --- tests/integration-all/api-gateway/tests.js | 4 +++- tests/utils/misc/index.js | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/integration-all/api-gateway/tests.js b/tests/integration-all/api-gateway/tests.js index cc43e22eb..a584ec939 100644 --- a/tests/integration-all/api-gateway/tests.js +++ b/tests/integration-all/api-gateway/tests.js @@ -25,7 +25,9 @@ describe('AWS - API Gateway Integration Test', () => { beforeAll(() => { tmpDirPath = getTmpDirPath(); serverlessFilePath = path.join(tmpDirPath, 'serverless.yml'); - serviceName = createTestService(tmpDirPath, { templateDir: path.join(__dirname, 'service') }); + const serverlessConfig = + createTestService(tmpDirPath, { templateDir: path.join(__dirname, 'service') }); + serviceName = serverlessConfig.service; StackName = `${serviceName}-${stage}`; deployService(); // create an external REST API diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 51e2f2bba..0e4318756 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -79,8 +79,7 @@ function createTestService(tmpDir, options = { process.env.COGNITO_USER_POOL_1 = `${serviceName}-1`; process.env.COGNITO_USER_POOL_2 = `${serviceName}-2`; - // return the name of the CloudFormation stack - return serviceName; + return serverlessConfig; } function getFunctionLogs(functionName) { From 43a1c6d937b6f64bfed76da61512e0c4db865c37 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 15:14:56 +0200 Subject: [PATCH 369/504] Fix variable name case --- tests/integration-all/api-gateway/tests.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration-all/api-gateway/tests.js b/tests/integration-all/api-gateway/tests.js index a584ec939..e9053660e 100644 --- a/tests/integration-all/api-gateway/tests.js +++ b/tests/integration-all/api-gateway/tests.js @@ -15,7 +15,7 @@ const CF = new AWS.CloudFormation({ region }); describe('AWS - API Gateway Integration Test', () => { let serviceName; let endpoint; - let StackName; + let stackName; let tmpDirPath; let serverlessFilePath; let restApiId; @@ -28,7 +28,7 @@ describe('AWS - API Gateway Integration Test', () => { const serverlessConfig = createTestService(tmpDirPath, { templateDir: path.join(__dirname, 'service') }); serviceName = serverlessConfig.service; - StackName = `${serviceName}-${stage}`; + stackName = `${serviceName}-${stage}`; deployService(); // create an external REST API const externalRestApiName = `${stage}-${serviceName}-ext-api`; @@ -55,7 +55,7 @@ describe('AWS - API Gateway Integration Test', () => { }); beforeEach(() => { - return CF.describeStacks({ StackName }).promise() + return CF.describeStacks({ StackName: stackName }).promise() .then((result) => _.find(result.Stacks[0].Outputs, { OutputKey: 'ServiceEndpoint' }).OutputValue) .then((endpointOutput) => { From 794d63d953fef9810083320cd012d1ec661f5dec Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 15:28:36 +0200 Subject: [PATCH 370/504] Ensure unique API key name --- .eslintrc.js | 1 + tests/integration-all/api-gateway/tests.js | 8 ++++++-- tests/utils/misc/index.js | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index b95dee02b..f73e9b60f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,6 +7,7 @@ module.exports = { "func-names": "off", "global-require": "off", // Interfers with optional and eventual circular references "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/scripts/**", "**/tests/**"]}], + "no-param-reassign": "off", "no-use-before-define": "off", "react/require-extension": "off", // Forced by airbnb, not applicable (also deprecated) "strict": ["error", "safe"], // airbnb implies we're transpiling with babel, we're not diff --git a/tests/integration-all/api-gateway/tests.js b/tests/integration-all/api-gateway/tests.js index e9053660e..3b6586650 100644 --- a/tests/integration-all/api-gateway/tests.js +++ b/tests/integration-all/api-gateway/tests.js @@ -25,8 +25,12 @@ describe('AWS - API Gateway Integration Test', () => { beforeAll(() => { tmpDirPath = getTmpDirPath(); serverlessFilePath = path.join(tmpDirPath, 'serverless.yml'); - const serverlessConfig = - createTestService(tmpDirPath, { templateDir: path.join(__dirname, 'service') }); + const serverlessConfig = createTestService(tmpDirPath, { + templateDir: path.join(__dirname, 'service'), + serverlessConfigHook: + // Ensure unique API key name for each test (to avoid collision among concurrent CI runs) + config => (config.provider.apiKeys[0].name = `${config.service}-api-key-1`), + }); serviceName = serverlessConfig.service; stackName = `${serviceName}-${stage}`; deployService(); diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 0e4318756..84ed30e5f 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -51,6 +51,7 @@ function createTestService(tmpDir, options = { // Either templateName or templateDir have to be provided templateName: null, // Template name to use (e.g. 'aws-nodejs') templateDir: null, // Path to prepared service template + serverlessConfigHook: null, // Eventual hook to furhter customize serverless config }) { const serviceName = getServiceName(); @@ -70,6 +71,7 @@ function createTestService(tmpDir, options = { const serverlessFilePath = path.join(tmpDir, 'serverless.yml'); const serverlessConfig = readYamlFile(serverlessFilePath); serverlessConfig.service = serviceName; + if (options.serverlessConfigHook) options.serverlessConfigHook(serverlessConfig); writeYamlFile(serverlessFilePath, serverlessConfig); process.env.TOPIC_1 = `${serviceName}-1`; From edb4f0081509ada346d16982b0f9192a7f26a8a4 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 15:51:13 +0200 Subject: [PATCH 371/504] Ensure unique API key to avoid collision among concurrent runs --- tests/integration-all/api-gateway/tests.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/integration-all/api-gateway/tests.js b/tests/integration-all/api-gateway/tests.js index 3b6586650..357472c38 100644 --- a/tests/integration-all/api-gateway/tests.js +++ b/tests/integration-all/api-gateway/tests.js @@ -20,6 +20,7 @@ describe('AWS - API Gateway Integration Test', () => { let serverlessFilePath; let restApiId; let restApiRootResourceId; + let apiKey; const stage = 'dev'; beforeAll(() => { @@ -28,8 +29,11 @@ describe('AWS - API Gateway Integration Test', () => { const serverlessConfig = createTestService(tmpDirPath, { templateDir: path.join(__dirname, 'service'), serverlessConfigHook: - // Ensure unique API key name for each test (to avoid collision among concurrent CI runs) - config => (config.provider.apiKeys[0].name = `${config.service}-api-key-1`), + // Ensure unique API key for each test (to avoid collision among concurrent CI runs) + config => { + apiKey = `${config.service}-api-key-1`; + config.provider.apiKeys[0] = { name: apiKey, value: apiKey }; + }, }); serviceName = serverlessConfig.service; stackName = `${serviceName}-${stage}`; @@ -196,8 +200,6 @@ describe('AWS - API Gateway Integration Test', () => { }); it('should succeed if correct API key is given', () => { - const apiKey = '0p3ns3s4m3-0p3ns3s4m3-0p3ns3s4m3'; - return fetch(testEndpoint, { headers: { 'X-API-Key': apiKey } }) .then(response => response.json()) .then((json) => { From f19b042bef14f7a6d637c02f4446f68c6109e0d1 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 15:53:37 +0200 Subject: [PATCH 372/504] Improve code comments --- tests/utils/misc/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 84ed30e5f..f61513fe8 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -49,9 +49,9 @@ function replaceEnv(values) { function createTestService(tmpDir, options = { // Either templateName or templateDir have to be provided - templateName: null, // Template name to use (e.g. 'aws-nodejs') - templateDir: null, // Path to prepared service template - serverlessConfigHook: null, // Eventual hook to furhter customize serverless config + templateName: null, // Generic template to use (e.g. 'aws-nodejs') + templateDir: null, // Path to custom pre-prepared service template + serverlessConfigHook: null, // Eventual hook that allows serverless config customization }) { const serviceName = getServiceName(); From 73197597085a147ecf246512787ce18f534beb77 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 15:54:24 +0200 Subject: [PATCH 373/504] Improve comment placement --- tests/utils/misc/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index f61513fe8..1a7c843f7 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -67,9 +67,9 @@ function createTestService(tmpDir, options = { throw new Error("Either 'templateName' or 'templateDir' options have to be provided"); } - // Ensure unique service name const serverlessFilePath = path.join(tmpDir, 'serverless.yml'); const serverlessConfig = readYamlFile(serverlessFilePath); + // Ensure unique service name serverlessConfig.service = serviceName; if (options.serverlessConfigHook) options.serverlessConfigHook(serverlessConfig); writeYamlFile(serverlessFilePath, serverlessConfig); From b5b2b55273b1a51f5d979af061ebb85cda5eb593 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 16:02:46 +0200 Subject: [PATCH 374/504] Mark fields that need to be unique per each run --- tests/integration-all/api-gateway/service/serverless.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration-all/api-gateway/service/serverless.yml b/tests/integration-all/api-gateway/service/serverless.yml index 75bdd3fd2..a714152a5 100644 --- a/tests/integration-all/api-gateway/service/serverless.yml +++ b/tests/integration-all/api-gateway/service/serverless.yml @@ -1,12 +1,12 @@ -service: aws-nodejs # NOTE: update this with your service name +service: CHANGE_TO_UNIQUE_PER_RUN provider: name: aws runtime: nodejs10.x versionFunctions: false apiKeys: - - name: api-key-1 - value: 0p3ns3s4m3-0p3ns3s4m3-0p3ns3s4m3 + - name: CHANGE_TO_UNIQUE_PER_RUN + value: CHANGE_TO_UNIQUE_PER_RUN functions: # core functions From 977f176a856af4e4858498c66a9886bd479af2c4 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 14 Jun 2019 16:24:40 +0200 Subject: [PATCH 375/504] Improve comment wording --- tests/utils/misc/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 1a7c843f7..cb5dfaa85 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -51,7 +51,7 @@ function createTestService(tmpDir, options = { // Either templateName or templateDir have to be provided templateName: null, // Generic template to use (e.g. 'aws-nodejs') templateDir: null, // Path to custom pre-prepared service template - serverlessConfigHook: null, // Eventual hook that allows serverless config customization + serverlessConfigHook: null, // Eventual hook that allows to customize serverless config }) { const serviceName = getServiceName(); From 14044edaae1e89b11fdedc935a60474cd0166547 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 14 Jun 2019 17:06:30 -0700 Subject: [PATCH 376/504] fix: Slim down Azure Functions template --- .../create/templates/azure-nodejs/.funcignore | 7 ------ .../azure-nodejs/.vscode/extensions.json | 5 ---- .../azure-nodejs/.vscode/launch.json | 12 ---------- .../azure-nodejs/.vscode/settings.json | 7 ------ .../templates/azure-nodejs/.vscode/tasks.json | 23 ------------------- .../{goodbye/index.js => goodbye.js} | 0 .../azure-nodejs/goodbye/function.json | 19 --------------- .../templates/azure-nodejs/goodbye/sample.dat | 3 --- .../azure-nodejs/{hello/index.js => hello.js} | 0 .../azure-nodejs/hello/function.json | 19 --------------- .../templates/azure-nodejs/hello/sample.dat | 3 --- .../create/templates/azure-nodejs/host.json | 5 ++-- .../azure-nodejs/local.settings.json | 8 ------- .../templates/azure-nodejs/proxies.json | 5 ---- .../templates/azure-nodejs/serverless.yml | 4 ++-- 15 files changed, 4 insertions(+), 116 deletions(-) delete mode 100644 lib/plugins/create/templates/azure-nodejs/.funcignore delete mode 100644 lib/plugins/create/templates/azure-nodejs/.vscode/extensions.json delete mode 100644 lib/plugins/create/templates/azure-nodejs/.vscode/launch.json delete mode 100644 lib/plugins/create/templates/azure-nodejs/.vscode/settings.json delete mode 100644 lib/plugins/create/templates/azure-nodejs/.vscode/tasks.json rename lib/plugins/create/templates/azure-nodejs/{goodbye/index.js => goodbye.js} (100%) delete mode 100644 lib/plugins/create/templates/azure-nodejs/goodbye/function.json delete mode 100644 lib/plugins/create/templates/azure-nodejs/goodbye/sample.dat rename lib/plugins/create/templates/azure-nodejs/{hello/index.js => hello.js} (100%) delete mode 100644 lib/plugins/create/templates/azure-nodejs/hello/function.json delete mode 100644 lib/plugins/create/templates/azure-nodejs/hello/sample.dat delete mode 100644 lib/plugins/create/templates/azure-nodejs/local.settings.json delete mode 100644 lib/plugins/create/templates/azure-nodejs/proxies.json diff --git a/lib/plugins/create/templates/azure-nodejs/.funcignore b/lib/plugins/create/templates/azure-nodejs/.funcignore deleted file mode 100644 index 517922249..000000000 --- a/lib/plugins/create/templates/azure-nodejs/.funcignore +++ /dev/null @@ -1,7 +0,0 @@ -*.js.map -*.ts -.git* -.vscode -local.settings.json -test -tsconfig.json \ No newline at end of file diff --git a/lib/plugins/create/templates/azure-nodejs/.vscode/extensions.json b/lib/plugins/create/templates/azure-nodejs/.vscode/extensions.json deleted file mode 100644 index 26786f933..000000000 --- a/lib/plugins/create/templates/azure-nodejs/.vscode/extensions.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "recommendations": [ - "ms-azuretools.vscode-azurefunctions" - ] -} diff --git a/lib/plugins/create/templates/azure-nodejs/.vscode/launch.json b/lib/plugins/create/templates/azure-nodejs/.vscode/launch.json deleted file mode 100644 index 9306c8ade..000000000 --- a/lib/plugins/create/templates/azure-nodejs/.vscode/launch.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Attach to Node Functions", - "type": "node", - "request": "attach", - "port": 9229, - "preLaunchTask": "func: host start" - } - ] -} diff --git a/lib/plugins/create/templates/azure-nodejs/.vscode/settings.json b/lib/plugins/create/templates/azure-nodejs/.vscode/settings.json deleted file mode 100644 index c5fd41834..000000000 --- a/lib/plugins/create/templates/azure-nodejs/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "azureFunctions.deploySubpath": ".", - "azureFunctions.projectLanguage": "JavaScript", - "azureFunctions.projectRuntime": "~2", - "debug.internalConsoleOptions": "neverOpen", - "azureFunctions.preDeployTask": "npm prune" -} diff --git a/lib/plugins/create/templates/azure-nodejs/.vscode/tasks.json b/lib/plugins/create/templates/azure-nodejs/.vscode/tasks.json deleted file mode 100644 index 6a7a88b5c..000000000 --- a/lib/plugins/create/templates/azure-nodejs/.vscode/tasks.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "type": "func", - "command": "host start", - "problemMatcher": "$func-watch", - "isBackground": true, - "dependsOn": "npm install" - }, - { - "type": "shell", - "label": "npm install", - "command": "npm install" - }, - { - "type": "shell", - "label": "npm prune", - "command": "npm prune --production", - "problemMatcher": [] - } - ] -} diff --git a/lib/plugins/create/templates/azure-nodejs/goodbye/index.js b/lib/plugins/create/templates/azure-nodejs/goodbye.js similarity index 100% rename from lib/plugins/create/templates/azure-nodejs/goodbye/index.js rename to lib/plugins/create/templates/azure-nodejs/goodbye.js diff --git a/lib/plugins/create/templates/azure-nodejs/goodbye/function.json b/lib/plugins/create/templates/azure-nodejs/goodbye/function.json deleted file mode 100644 index 7eb1f8f2d..000000000 --- a/lib/plugins/create/templates/azure-nodejs/goodbye/function.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" - } - ] -} diff --git a/lib/plugins/create/templates/azure-nodejs/goodbye/sample.dat b/lib/plugins/create/templates/azure-nodejs/goodbye/sample.dat deleted file mode 100644 index 26aac46f1..000000000 --- a/lib/plugins/create/templates/azure-nodejs/goodbye/sample.dat +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "Azure" -} \ No newline at end of file diff --git a/lib/plugins/create/templates/azure-nodejs/hello/index.js b/lib/plugins/create/templates/azure-nodejs/hello.js similarity index 100% rename from lib/plugins/create/templates/azure-nodejs/hello/index.js rename to lib/plugins/create/templates/azure-nodejs/hello.js diff --git a/lib/plugins/create/templates/azure-nodejs/hello/function.json b/lib/plugins/create/templates/azure-nodejs/hello/function.json deleted file mode 100644 index 7eb1f8f2d..000000000 --- a/lib/plugins/create/templates/azure-nodejs/hello/function.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" - } - ] -} diff --git a/lib/plugins/create/templates/azure-nodejs/hello/sample.dat b/lib/plugins/create/templates/azure-nodejs/hello/sample.dat deleted file mode 100644 index 26aac46f1..000000000 --- a/lib/plugins/create/templates/azure-nodejs/hello/sample.dat +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "Azure" -} \ No newline at end of file diff --git a/lib/plugins/create/templates/azure-nodejs/host.json b/lib/plugins/create/templates/azure-nodejs/host.json index 4952c4375..d2059a46b 100644 --- a/lib/plugins/create/templates/azure-nodejs/host.json +++ b/lib/plugins/create/templates/azure-nodejs/host.json @@ -1,4 +1,3 @@ { - "version": "2.0" - } - \ No newline at end of file + "version": "2.0" +} diff --git a/lib/plugins/create/templates/azure-nodejs/local.settings.json b/lib/plugins/create/templates/azure-nodejs/local.settings.json deleted file mode 100644 index dbf98ec52..000000000 --- a/lib/plugins/create/templates/azure-nodejs/local.settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "", - "FUNCTIONS_WORKER_RUNTIME": "node" - } - } - \ No newline at end of file diff --git a/lib/plugins/create/templates/azure-nodejs/proxies.json b/lib/plugins/create/templates/azure-nodejs/proxies.json deleted file mode 100644 index f02a6018b..000000000 --- a/lib/plugins/create/templates/azure-nodejs/proxies.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/proxies", - "proxies": {} - } - \ No newline at end of file diff --git a/lib/plugins/create/templates/azure-nodejs/serverless.yml b/lib/plugins/create/templates/azure-nodejs/serverless.yml index 3e49bd916..054455f33 100644 --- a/lib/plugins/create/templates/azure-nodejs/serverless.yml +++ b/lib/plugins/create/templates/azure-nodejs/serverless.yml @@ -37,7 +37,7 @@ package: functions: hello: - handler: hello/index.handler + handler: hello.handler events: - http: true x-azure-settings: @@ -47,7 +47,7 @@ functions: direction: out name: res goodbye: - handler: goodbye/index.handler + handler: goodbye.handler events: - http: true x-azure-settings: From 5038c78f3461d6b6b0d83bdd470760e705737225 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 14 Jun 2019 17:12:25 -0700 Subject: [PATCH 377/504] Fix tests --- lib/plugins/create/create.test.js | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index 5c7d7f1d6..a44673991 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -567,30 +567,10 @@ describe('Create', () => { expect(dirContent).to.include('package.json'); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('.gitignore'); - expect(dirContent).to.include('.funcignore'); expect(dirContent).to.include('host.json'); - expect(dirContent).to.include('local.settings.json'); - expect(dirContent).to.include('proxies.json'); expect(dirContent).to.include('README.md'); - // VS Code directory for local development - expect(dirContent).to.include('.vscode'); - const vsCodeDirContent = fs.readdirSync(`${tmpDir}/.vscode`); - expect(vsCodeDirContent).to.include('extensions.json'); - expect(vsCodeDirContent).to.include('launch.json'); - expect(vsCodeDirContent).to.include('settings.json'); - expect(vsCodeDirContent).to.include('tasks.json'); - // Directory containing first function handler and bindings - expect(dirContent).to.include('hello'); - const helloFunctionDirContent = fs.readdirSync(`${tmpDir}/hello`); - expect(helloFunctionDirContent).to.include('function.json'); - expect(helloFunctionDirContent).to.include('index.js'); - expect(helloFunctionDirContent).to.include('sample.dat'); - // Directory containing second function handler and bindings - expect(dirContent).to.include('goodbye'); - const goodbyeFunctionDirContent = fs.readdirSync(`${tmpDir}/goodbye`); - expect(goodbyeFunctionDirContent).to.include('function.json'); - expect(goodbyeFunctionDirContent).to.include('index.js'); - expect(goodbyeFunctionDirContent).to.include('sample.dat'); + expect(dirContent).to.include("hello.js"); + expect(dirContent).to.include("goodbye.js"); }); }); From c40710412c0e6056e19fca56b3bf0d2419a0f830 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 14 Jun 2019 23:08:37 -0700 Subject: [PATCH 378/504] Fix lint --- lib/plugins/create/create.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index a44673991..9d61db345 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -569,8 +569,8 @@ describe('Create', () => { expect(dirContent).to.include('.gitignore'); expect(dirContent).to.include('host.json'); expect(dirContent).to.include('README.md'); - expect(dirContent).to.include("hello.js"); - expect(dirContent).to.include("goodbye.js"); + expect(dirContent).to.include('hello.js'); + expect(dirContent).to.include('goodbye.js'); }); }); From c0b51bc12f609c851ae25c99fa591ce76f823554 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 17 Jun 2019 13:01:02 +0200 Subject: [PATCH 379/504] Address PR review comments / add tests --- lib/plugins/package/lib/packageService.js | 2 +- .../package/lib/packageService.test.js | 27 ++++++++++++++++++- tests/utils/fs/index.js | 7 +++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/plugins/package/lib/packageService.js b/lib/plugins/package/lib/packageService.js index 62a9cde97..f54c7548d 100644 --- a/lib/plugins/package/lib/packageService.js +++ b/lib/plugins/package/lib/packageService.js @@ -229,7 +229,7 @@ module.exports = { // This strips "./" from the beginning of an includes pattern to avoid // duplication of paths. See https://github.com/serverless/serverless/issues/5748 const strippedIncludes = params.include.map((pattern) => { - return pattern.replace(/^.\//, ''); + return path.normalize(pattern); }); // NOTE: please keep this order of concatenating the include params // rather than doing it the other way round! diff --git a/lib/plugins/package/lib/packageService.test.js b/lib/plugins/package/lib/packageService.test.js index d5c9ac1ca..a6e032106 100644 --- a/lib/plugins/package/lib/packageService.test.js +++ b/lib/plugins/package/lib/packageService.test.js @@ -3,16 +3,18 @@ const _ = require('lodash'); const BbPromise = require('bluebird'); const path = require('path'); +const fse = require('fs-extra'); const chai = require('chai'); const sinon = require('sinon'); const Package = require('../package'); const Serverless = require('../../../Serverless'); const serverlessConfigFileUtils = require('../../../../lib/utils/getServerlessConfigFile'); +const { createTmpDir } = require('../../../../tests/utils/fs'); // Configure chai chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); -const expect = require('chai').expect; +const { expect } = require('chai'); describe('#packageService()', () => { let serverless; @@ -177,6 +179,28 @@ describe('#packageService()', () => { }); }); + describe('#resolveFilePathsFromPatterns()', () => { + let tmpDirPath; + + beforeEach(() => { + tmpDirPath = createTmpDir(); + const filePath = path.join(tmpDirPath, 'bin', 'file'); + fse.ensureFileSync(filePath); + }); + + it('should foo', () => { + packagePlugin.serverless.config.servicePath = tmpDirPath; + + const params = { + exclude: ['./**'], + include: ['./bin/**'], + }; + + return expect(packagePlugin.resolveFilePathsFromPatterns(params)).to.be.fulfilled + .then((res) => expect(res).to.deep.equal(['bin/file'])); + }); + }); + describe('#packageService()', () => { it('should package all functions', () => { serverless.service.package.individually = false; @@ -516,6 +540,7 @@ describe('#packageService()', () => { ])); }); }); + describe('#packageLayer()', () => { const exclude = ['test-exclude']; const include = ['test-include']; diff --git a/tests/utils/fs/index.js b/tests/utils/fs/index.js index e7a93f55b..e1d1b42b9 100644 --- a/tests/utils/fs/index.js +++ b/tests/utils/fs/index.js @@ -19,6 +19,12 @@ function getTmpFilePath(fileName) { return path.join(getTmpDirPath(), fileName); } +function createTmpDir() { + const dirPath = getTmpDirPath(); + fse.ensureDirSync(dirPath); + return dirPath; +} + function createTmpFile(name) { const filePath = getTmpFilePath(name); fse.ensureFileSync(filePath); @@ -50,6 +56,7 @@ module.exports = { tmpDirCommonPath, getTmpDirPath, getTmpFilePath, + createTmpDir, createTmpFile, replaceTextInFile, readYamlFile, From a4f851db571e9532be6da7f7fc888c87eed38d6d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 17 Jun 2019 13:59:58 +0200 Subject: [PATCH 380/504] Integration tests that covers integration lambda timeout --- tests/integration-all/api-gateway/service/core.js | 11 +++++++++++ .../api-gateway/service/serverless.yml | 8 ++++++++ tests/integration-all/api-gateway/tests.js | 6 ++++++ 3 files changed, 25 insertions(+) diff --git a/tests/integration-all/api-gateway/service/core.js b/tests/integration-all/api-gateway/service/core.js index 326d614cb..aa19d4084 100644 --- a/tests/integration-all/api-gateway/service/core.js +++ b/tests/integration-all/api-gateway/service/core.js @@ -43,9 +43,20 @@ async function apiKeys(event) { }; } +async function timeout(event) { + return new Promise(resolve => setTimeout(() => resolve({ + statusCode: 200, + body: JSON.stringify({ + message: 'Should not happen (timeout expected)', + event, + }), + }), 2000)); +} + module.exports = { minimal, cors, customAuthorizers, apiKeys, + timeout, }; diff --git a/tests/integration-all/api-gateway/service/serverless.yml b/tests/integration-all/api-gateway/service/serverless.yml index 75bdd3fd2..68be27ed3 100644 --- a/tests/integration-all/api-gateway/service/serverless.yml +++ b/tests/integration-all/api-gateway/service/serverless.yml @@ -57,6 +57,14 @@ functions: path: api-keys method: GET private: true + timeout: + handler: core.timeout + timeout: 1 + events: + - http: + method: GET + integration: lambda + path: integration-lambda-timeout # helper functions authorizer: handler: helper.auth diff --git a/tests/integration-all/api-gateway/tests.js b/tests/integration-all/api-gateway/tests.js index bdd1d5578..89adb9a73 100644 --- a/tests/integration-all/api-gateway/tests.js +++ b/tests/integration-all/api-gateway/tests.js @@ -264,4 +264,10 @@ describe('AWS - API Gateway Integration Test', () => { .then((json) => expect(json.message).to.equal('Hello from API Gateway! - (minimal)')); }); }); + + describe('Integration Lambda Timeout', () => { + it('should result with 504 status code', + () => fetch(`${endpoint}/integration-lambda-timeout`) + .then(response => expect(response.status).to.equal(504))); + }); }); From f596448f700f9f06bc55826ae6c52a72e54efa8a Mon Sep 17 00:00:00 2001 From: Christoph Gysin Date: Tue, 18 Jun 2019 16:14:46 +0300 Subject: [PATCH 381/504] Extract setting up routes with and without authorizer definition --- .../events/websockets/lib/authorizers.test.js | 201 ++++++++++-------- 1 file changed, 113 insertions(+), 88 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js index 1925ae6ce..9fe3b8308 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js @@ -8,102 +8,127 @@ const AwsProvider = require('../../../../../provider/awsProvider'); describe('#compileAuthorizers()', () => { let awsCompileWebsocketsEvents; - beforeEach(() => { - const serverless = new Serverless(); - serverless.setProvider('aws', new AwsProvider(serverless)); - serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; + describe('for routes with authorizer definition', () => { + beforeEach(() => { + const serverless = new Serverless(); + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; - awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); + awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); + + awsCompileWebsocketsEvents.websocketsApiLogicalId + = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'] }, + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth'], + }, + }, + ], + }; + }); + + it('should create an authorizer resource', () => { + return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.deep.equal({ + AuthWebsocketsAuthorizer: { + Type: 'AWS::ApiGatewayV2::Authorizer', + Properties: { + ApiId: { + Ref: 'WebsocketsApi', + }, + Name: 'auth', + AuthorizerType: 'REQUEST', + AuthorizerUri: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':apigateway:', + { + Ref: 'AWS::Region', + }, + ':lambda:path/2015-03-31/functions/', + { + 'Fn::GetAtt': [ + 'AuthLambdaFunction', + 'Arn', + ], + }, + '/invocations', + ], + ], + }, + IdentitySource: ['route.request.header.Auth'], + }, + }, + }); + }); + }); - awsCompileWebsocketsEvents.websocketsApiLogicalId - = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); }); - it('should create an authorizer resource for routes with authorizer definition', () => { - awsCompileWebsocketsEvents.validated = { - events: [ - { - functionName: 'First', - route: '$connect', - authorizer: { - name: 'auth', - uri: { - 'Fn::Join': ['', - [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':apigateway:', - { Ref: 'AWS::Region' }, - ':lambda:path/2015-03-31/functions/', - { 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'] }, - '/invocations', - ], - ], - }, - identitySource: ['route.request.header.Auth'], - }, - }, - ], - }; + describe('for routes without authorizer definition', () => { + beforeEach(() => { + const serverless = new Serverless(); + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; - return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); - expect(resources).to.deep.equal({ - AuthWebsocketsAuthorizer: { - Type: 'AWS::ApiGatewayV2::Authorizer', - Properties: { - ApiId: { - Ref: 'WebsocketsApi', - }, - Name: 'auth', - AuthorizerType: 'REQUEST', - AuthorizerUri: { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition', - }, - ':apigateway:', - { - Ref: 'AWS::Region', - }, - ':lambda:path/2015-03-31/functions/', - { - 'Fn::GetAtt': [ - 'AuthLambdaFunction', - 'Arn', - ], - }, - '/invocations', - ], - ], - }, - IdentitySource: ['route.request.header.Auth'], + awsCompileWebsocketsEvents.websocketsApiLogicalId + = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', }, - }, + ], + }; + }); + + it('should NOT create an authorizer resource for routes with not authorizer definition', () => { + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', + }, + ], + }; + + return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.deep.equal({}); }); }); }); - - it('should NOT create an authorizer resource for routes with not authorizer definition', () => { - awsCompileWebsocketsEvents.validated = { - events: [ - { - functionName: 'First', - route: '$connect', - }, - ], - }; - - return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; - - expect(resources).to.deep.equal({}); - }); - }); }); From 2cfd47565308255c2d7d082611a370b4d8c3ad99 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 18 Jun 2019 15:15:11 +0200 Subject: [PATCH 382/504] Run full lint on branches and tags (lint-updated crashes on tags and non-master branches as master branch then is not retrieved by Travis) --- .travis.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e2b068cef..8cb78599e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,15 @@ jobs: - SLS_IGNORE_WARNING=* - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' # Combine with '&&' to not continue on fail - script: npm run lint-updated && npm test + script: + - | + if [ $TRAVIS_PULL_REQUEST = false ] + then + npm run lint + else + npm run lint-updated + fi && + npm test - name: "Unit Tests - Windows - Node.js v12" os: windows node_js: 12 From 214e09cb56367b29bb82387b5544ea5d059e637e Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 18 Jun 2019 15:18:10 +0200 Subject: [PATCH 383/504] Move duplicate file detection to package zipping code --- lib/plugins/package/lib/packageService.js | 7 +--- .../package/lib/packageService.test.js | 22 ------------ lib/plugins/package/lib/zipService.js | 36 ++++++++++--------- lib/plugins/package/lib/zipService.test.js | 24 +++++++++++++ 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/lib/plugins/package/lib/packageService.js b/lib/plugins/package/lib/packageService.js index f54c7548d..22792f78a 100644 --- a/lib/plugins/package/lib/packageService.js +++ b/lib/plugins/package/lib/packageService.js @@ -226,15 +226,10 @@ module.exports = { patterns.push(pattern); }); - // This strips "./" from the beginning of an includes pattern to avoid - // duplication of paths. See https://github.com/serverless/serverless/issues/5748 - const strippedIncludes = params.include.map((pattern) => { - return path.normalize(pattern); - }); // NOTE: please keep this order of concatenating the include params // rather than doing it the other way round! // see https://github.com/serverless/serverless/pull/5825 for more information - return globby(['**'].concat(strippedIncludes), { + return globby(['**'].concat(params.include), { cwd: path.join(this.serverless.config.servicePath, prefix || ''), dot: true, silent: true, diff --git a/lib/plugins/package/lib/packageService.test.js b/lib/plugins/package/lib/packageService.test.js index a6e032106..68a50c330 100644 --- a/lib/plugins/package/lib/packageService.test.js +++ b/lib/plugins/package/lib/packageService.test.js @@ -179,28 +179,6 @@ describe('#packageService()', () => { }); }); - describe('#resolveFilePathsFromPatterns()', () => { - let tmpDirPath; - - beforeEach(() => { - tmpDirPath = createTmpDir(); - const filePath = path.join(tmpDirPath, 'bin', 'file'); - fse.ensureFileSync(filePath); - }); - - it('should foo', () => { - packagePlugin.serverless.config.servicePath = tmpDirPath; - - const params = { - exclude: ['./**'], - include: ['./bin/**'], - }; - - return expect(packagePlugin.resolveFilePathsFromPatterns(params)).to.be.fulfilled - .then((res) => expect(res).to.deep.equal(['bin/file'])); - }); - }); - describe('#packageService()', () => { it('should package all functions', () => { serverless.service.package.individually = false; diff --git a/lib/plugins/package/lib/zipService.js b/lib/plugins/package/lib/zipService.js index 9b7137c27..2d53538bc 100644 --- a/lib/plugins/package/lib/zipService.js +++ b/lib/plugins/package/lib/zipService.js @@ -82,27 +82,29 @@ module.exports = { output.on('error', (err) => reject(err)); zip.on('error', (err) => reject(err)); - output.on('open', () => { zip.pipe(output); - BbPromise.all(files.map(this.getFileContentAndStat.bind(this))).then((contents) => { - _.forEach(_.sortBy(contents, ['filePath']), (file) => { - const name = file.filePath.slice(prefix ? `${prefix}${path.sep}`.length : 0); - let mode = file.stat.mode; - if (filesToChmodPlusX && _.includes(filesToChmodPlusX, name) - && file.stat.mode % 2 === 0) { - mode += 1; - } - zip.append(file.data, { - name, - mode, - date: new Date(0), // necessary to get the same hash when zipping the same content - }); - }); + const normalizedFiles = _.uniq(files.map(file => path.normalize(file))); - zip.finalize(); - }).catch(reject); + return BbPromise.all(normalizedFiles.map(this.getFileContentAndStat.bind(this))) + .then((contents) => { + _.forEach(_.sortBy(contents, ['filePath']), (file) => { + const name = file.filePath.slice(prefix ? `${prefix}${path.sep}`.length : 0); + let mode = file.stat.mode; + if (filesToChmodPlusX && _.includes(filesToChmodPlusX, name) + && file.stat.mode % 2 === 0) { + mode += 1; + } + zip.append(file.data, { + name, + mode, + date: new Date(0), // necessary to get the same hash when zipping the same content + }); + }); + + zip.finalize(); + }).catch(reject); }); }); }, diff --git a/lib/plugins/package/lib/zipService.test.js b/lib/plugins/package/lib/zipService.test.js index 04ea02630..82823f69d 100644 --- a/lib/plugins/package/lib/zipService.test.js +++ b/lib/plugins/package/lib/zipService.test.js @@ -1036,6 +1036,30 @@ describe('zipService', () => { }); }); + it('should include files only once', () => { + params.zipFileName = getTestArtifactFileName('include-outside-working-dir'); + serverless.config.servicePath = path.join(serverless.config.servicePath, 'lib'); + params.exclude = [ + './**', + ]; + params.include = [ + '.././bin/**', + ]; + + return expect(packagePlugin.zip(params)).to.eventually.be + .equal(path.join(serverless.config.servicePath, '.serverless', params.zipFileName)) + .then(artifact => { + const data = fs.readFileSync(artifact); + return expect(zip.loadAsync(data)).to.be.fulfilled; + }).then(unzippedData => { + const unzippedFileData = unzippedData.files; + expect(Object.keys(unzippedFileData).sort()).to.deep.equal([ + 'bin/binary-444', + 'bin/binary-777', + ]); + }); + }); + it('should throw an error if no files are matched', () => { params.exclude = ['**/**']; params.include = []; From d00e700bcf4ea5b470e69dc02451666bb4d2a12f Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 18 Jun 2019 15:20:08 +0200 Subject: [PATCH 384/504] Remove unused code --- lib/plugins/package/lib/packageService.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/plugins/package/lib/packageService.test.js b/lib/plugins/package/lib/packageService.test.js index 68a50c330..f03b5e8c3 100644 --- a/lib/plugins/package/lib/packageService.test.js +++ b/lib/plugins/package/lib/packageService.test.js @@ -3,13 +3,11 @@ const _ = require('lodash'); const BbPromise = require('bluebird'); const path = require('path'); -const fse = require('fs-extra'); const chai = require('chai'); const sinon = require('sinon'); const Package = require('../package'); const Serverless = require('../../../Serverless'); const serverlessConfigFileUtils = require('../../../../lib/utils/getServerlessConfigFile'); -const { createTmpDir } = require('../../../../tests/utils/fs'); // Configure chai chai.use(require('chai-as-promised')); From 152b4c460a7dc6b6db914d8bcd4f1a61d8a22d3d Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Mon, 17 Jun 2019 11:59:48 +0200 Subject: [PATCH 385/504] Fix #6017 Allow to load plugin from path The list of plugins can now contain relative paths. For example: ```yaml plugins: # will load myfirstplugin/plugin.js or myfirstplugin/plugin/index.js - 'myfirstplugin/plugin' # will load mysecondplugin/plugin.js or mysecondplugin/plugin/index.js - 'mysecondplugin/plugin' ``` --- docs/providers/aws/guide/plugins.md | 15 +++++++++++---- lib/classes/PluginManager.js | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/providers/aws/guide/plugins.md b/docs/providers/aws/guide/plugins.md index fc52aafb2..c8fb95e48 100644 --- a/docs/providers/aws/guide/plugins.md +++ b/docs/providers/aws/guide/plugins.md @@ -63,9 +63,7 @@ custom: ## Service local plugin -If you are working on a plugin or have a plugin that is just designed for one project they can be loaded from the local folder. Local plugins can be added in the `plugins` array in `serverless.yml`. - -By default local plugins can be added to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. +If you are working on a plugin or have a plugin that is just designed for one project, it can be loaded from the local `.serverless_plugins` folder at the root of your service. Local plugins can be added in the `plugins` array in `serverless.yml`. ```yml plugins: - custom-serverless-plugin @@ -78,10 +76,19 @@ plugins: modules: - custom-serverless-plugin ``` -The `custom-serverless-plugin` will be loaded from the `custom_serverless_plugins` directory at the root of your service. If the `localPath` is not provided or empty `.serverless_plugins` directory will be taken as the `localPath`. +The `custom-serverless-plugin` will be loaded from the `custom_serverless_plugins` directory at the root of your service. If the `localPath` is not provided or empty, the `.serverless_plugins` directory will be used. The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `localPath` folder (`.serverless_plugins` by default). +If you want to load a plugin from a specific directory without affecting other plugins, you can also specify a path relative to the root of your service: +```yaml +plugins: + # This plugin will be loaded from the `.serverless_plugins/` or `node_modules/` directories + - custom-serverless-plugin + # This plugin will be loaded from the `sub/directory/` directory + - sub/directory/another-custom-plugin +``` + ### Load Order Keep in mind that the order you define your plugins matters. When Serverless loads all the core plugins and then the custom plugins in the order you've defined them. diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 1e6c063b1..716740984 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -151,6 +151,7 @@ class PluginManager { if (pluginsObject.localPath) { module.paths.unshift(pluginsObject.localPath); } + module.paths.unshift(this.serverless.config.servicePath); this.loadPlugins(pluginsObject.modules .filter(name => name !== '@serverless/enterprise-plugin')); } From c6f64e50e1ebb086150d189b751a191e19098c03 Mon Sep 17 00:00:00 2001 From: Jonathan Wilbur Date: Tue, 18 Jun 2019 17:58:45 -0400 Subject: [PATCH 386/504] Fix #6267 --- lib/plugins/aws/invokeLocal/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 78ecac7ba..4aa51c986 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -548,7 +548,7 @@ class AwsInvokeLocal { errorResult = { errorMessage: err.message, errorType: err.constructor.name, - stackTrace: err.stack.split('\n'), + stackTrace: err.stack ? err.stack.split('\n') : [], }; } else { errorResult = { From 142a9d8304edc59b5f17de90979dddfd31c977c5 Mon Sep 17 00:00:00 2001 From: Christoph Gysin Date: Mon, 17 Jun 2019 14:45:13 +0300 Subject: [PATCH 387/504] Support external ApiGateway websocket API fixes #6270 --- docs/providers/aws/events/apigateway.md | 28 +++++++++++++++++-- docs/providers/aws/guide/serverless.yml.md | 1 + .../compile/events/websockets/lib/api.js | 7 +++++ .../compile/events/websockets/lib/api.test.js | 12 ++++++++ .../events/websockets/lib/authorizers.js | 4 +-- .../events/websockets/lib/authorizers.test.js | 14 ++++++++++ .../events/websockets/lib/deployment.js | 6 ++-- .../events/websockets/lib/integrations.js | 4 +-- .../events/websockets/lib/permissions.js | 9 ++++-- .../compile/events/websockets/lib/routes.js | 4 +-- .../compile/events/websockets/lib/stage.js | 4 +-- lib/plugins/aws/provider/awsProvider.js | 12 ++++++++ 12 files changed, 85 insertions(+), 20 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 1499502d6..41bc88ccd 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -1103,6 +1103,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx # REST API resource ID. Default is generated by the framework restApiRootResourceId: xxxxxxxxxx # Root resource, represent as / path + websocketApiId: xxxxxxxxxx # Websocket API resource ID. Default is generated by the framewok description: Some Description # optional - description of deployment history functions: @@ -1119,6 +1120,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + websocketApiId: xxxxxxxxxx description: Some Description functions: @@ -1136,6 +1138,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + websocketApiId: xxxxxxxxxx description: Some Description functions: @@ -1155,6 +1158,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + websocketApiId: xxxxxxxxxx description: Some Description restApiResources: /posts: xxxxxxxxxx @@ -1170,6 +1174,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + websocketApiId: xxxxxxxxxx description: Some Description restApiResources: /posts: xxxxxxxxxx @@ -1188,6 +1193,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx # restApiRootResourceId: xxxxxxxxxx # Optional + websocketApiId: xxxxxxxxxx description: Some Description restApiResources: /posts: xxxxxxxxxx @@ -1213,7 +1219,7 @@ functions: ### Easiest and CI/CD friendly example of using shared API Gateway and API Resources. -You can define your API Gateway resource in its own service and export the `restApiId` and `restApiRootResourceId` using cloudformation cross-stack references. +You can define your API Gateway resource in its own service and export the `restApiId`, `restApiRootResourceId` and `websocketApiId` using cloudformation cross-stack references. ```yml service: my-api @@ -1231,6 +1237,13 @@ resources: Properties: Name: MyApiGW + MyWebsocketApi: + Type: AWS::ApiGatewayV2::Api + Properties: + Name: MyWebsocketApi + ProtocolType: WEBSOCKET + RouteSelectionExpression: '$request.body.action' + Outputs: apiGatewayRestApiId: Value: @@ -1245,9 +1258,16 @@ resources: - RootResourceId Export: Name: MyApiGateway-rootResourceId + + websocketApiId: + Value: + Ref: MyWebsocketApi + Export: + Name: MyApiGateway-websocketApiId + ``` -This creates API gateway and then exports the `restApiId` and `rootResourceId` values using cloudformation cross stack output. +This creates API gateway and then exports the `restApiId`, `rootResourceId` and `websocketApiId` values using cloudformation cross stack output. We will import this and reference in future services. ```yml @@ -1259,6 +1279,8 @@ provider: 'Fn::ImportValue': MyApiGateway-restApiId restApiRootResourceId: 'Fn::ImportValue': MyApiGateway-rootResourceId + websocketApiId: + 'Fn::ImportValue': MyApiGateway-websocketApiId functions: service-a-functions @@ -1272,6 +1294,8 @@ provider: 'Fn::ImportValue': MyApiGateway-restApiId restApiRootResourceId: 'Fn::ImportValue': MyApiGateway-rootResourceId + websocketApiId: + 'Fn::ImportValue': MyApiGateway-websocketApiId functions: service-b-functions diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index c663a375e..8456f3402 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -60,6 +60,7 @@ provider: restApiResources: # List of existing resources that were created in the REST API. This is required or the stack will be conflicted '/users': xxxxxxxxxx '/users/create': xxxxxxxxxx + websocketApiId: # Websocket API resource ID. Default is generated by the framewok apiKeySourceType: HEADER # Source of API key for usage plan. HEADER or AUTHORIZER. minimumCompressionSize: 1024 # Compress response when larger than specified size in bytes (must be between 0 and 10485760) description: Some Description # Optional description for the API Gateway stage deployment diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/api.js b/lib/plugins/aws/package/compile/events/websockets/lib/api.js index f52067a6a..50212050c 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/api.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/api.js @@ -5,6 +5,13 @@ const BbPromise = require('bluebird'); module.exports = { compileApi() { + const apiGateway = this.serverless.service.provider.apiGateway || {}; + + // immediately return if we're using an external websocket API id + if (apiGateway.websocketApiId) { + return BbPromise.resolve(); + } + this.websocketsApiLogicalId = this.provider.naming.getWebsocketsApiLogicalId(); const RouteSelectionExpression = this.serverless.service.provider diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js index a3ed46155..fbe7242f0 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js @@ -50,6 +50,18 @@ describe('#compileApi()', () => { }); })); + it('should ignore API resource creation if there is predefined websocketApi config', () => { + awsCompileWebsocketsEvents.serverless.service.provider.apiGateway = { + websocketApiId: '5ezys3sght', + }; + return awsCompileWebsocketsEvents.compileApi().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.not.have.property('WebsocketsApi'); + }); + }); + it('should add the websockets policy', () => awsCompileWebsocketsEvents .compileApi().then(() => { const resources = awsCompileWebsocketsEvents.serverless.service.provider diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js index 31062df82..48f413080 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js @@ -16,9 +16,7 @@ module.exports = { [websocketsAuthorizerLogicalId]: { Type: 'AWS::ApiGatewayV2::Authorizer', Properties: { - ApiId: { - Ref: this.websocketsApiLogicalId, - }, + ApiId: this.provider.getApiGatewayWebsocketApiId(), Name: event.authorizer.name, AuthorizerType: 'REQUEST', AuthorizerUri: event.authorizer.uri, diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js index 9fe3b8308..5728ae0b6 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js @@ -90,6 +90,20 @@ describe('#compileAuthorizers()', () => { }); }); + it('should use existing Api if there is predefined websocketApi config', () => { + awsCompileWebsocketsEvents.serverless.service.provider.apiGateway = { + websocketApiId: '5ezys3sght', + }; + + return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources.AuthWebsocketsAuthorizer.Properties).to.contain({ + ApiId: '5ezys3sght', + }); + }); + }); }); describe('for routes without authorizer definition', () => { diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/deployment.js b/lib/plugins/aws/package/compile/events/websockets/lib/deployment.js index 341d42870..144c5d8a1 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/deployment.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/deployment.js @@ -18,9 +18,7 @@ module.exports = { Type: 'AWS::ApiGatewayV2::Deployment', DependsOn: routeLogicalIds, Properties: { - ApiId: { - Ref: this.websocketsApiLogicalId, - }, + ApiId: this.provider.getApiGatewayWebsocketApiId(), Description: this.serverless.service.provider .websocketsDescription || 'Serverless Websockets', }, @@ -34,7 +32,7 @@ module.exports = { 'Fn::Join': ['', [ 'wss://', - { Ref: this.provider.naming.getWebsocketsApiLogicalId() }, + this.provider.getApiGatewayWebsocketApiId(), '.execute-api.', { Ref: 'AWS::Region' }, '.', diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/integrations.js b/lib/plugins/aws/package/compile/events/websockets/lib/integrations.js index c892ee302..1dd44c520 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/integrations.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/integrations.js @@ -14,9 +14,7 @@ module.exports = { [websocketsIntegrationLogicalId]: { Type: 'AWS::ApiGatewayV2::Integration', Properties: { - ApiId: { - Ref: this.websocketsApiLogicalId, - }, + ApiId: this.provider.getApiGatewayWebsocketApiId(), IntegrationType: 'AWS_PROXY', IntegrationUri: { 'Fn::Join': ['', diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js index 4b42f42f8..08833474f 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js @@ -6,6 +6,7 @@ const BbPromise = require('bluebird'); module.exports = { compilePermissions() { this.validated.events.forEach(event => { + const websocketApiId = this.provider.getApiGatewayWebsocketApiId(); const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(event.functionName); const websocketsPermissionLogicalId = this.provider.naming @@ -14,7 +15,9 @@ module.exports = { _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [websocketsPermissionLogicalId]: { Type: 'AWS::Lambda::Permission', - DependsOn: [this.websocketsApiLogicalId, lambdaLogicalId], + DependsOn: (websocketApiId.Ref !== undefined) + ? [websocketApiId.Ref, lambdaLogicalId] + : [lambdaLogicalId], Properties: { FunctionName: { 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], @@ -32,7 +35,9 @@ module.exports = { const authorizerPermissionTemplate = { [websocketsAuthorizerPermissionLogicalId]: { Type: 'AWS::Lambda::Permission', - DependsOn: [this.websocketsApiLogicalId], + DependsOn: (websocketApiId.Ref !== undefined) + ? [websocketApiId.Ref] + : [], Properties: { Action: 'lambda:InvokeFunction', Principal: 'apigateway.amazonaws.com', diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/routes.js b/lib/plugins/aws/package/compile/events/websockets/lib/routes.js index ef3aa7a86..2817e624b 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/routes.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/routes.js @@ -16,9 +16,7 @@ module.exports = { [websocketsRouteLogicalId]: { Type: 'AWS::ApiGatewayV2::Route', Properties: { - ApiId: { - Ref: this.websocketsApiLogicalId, - }, + ApiId: this.provider.getApiGatewayWebsocketApiId(), RouteKey: event.route, AuthorizationType: 'NONE', Target: { diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/stage.js b/lib/plugins/aws/package/compile/events/websockets/lib/stage.js index ae2472565..2ee8ad2f0 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/stage.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/stage.js @@ -23,9 +23,7 @@ module.exports = { const stageResource = { Type: 'AWS::ApiGatewayV2::Stage', Properties: { - ApiId: { - Ref: this.websocketsApiLogicalId, - }, + ApiId: this.provider.getApiGatewayWebsocketApiId(), DeploymentId: { Ref: this.websocketsDeploymentLogicalId, }, diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index bcc309d7a..f58ed2a62 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -544,6 +544,18 @@ class AwsProvider { })); } + /** + * Get API Gateway websocket API ID from serverless config + */ + getApiGatewayWebsocketApiId() { + if (this.serverless.service.provider.apiGateway + && this.serverless.service.provider.apiGateway.websocketApiId) { + return this.serverless.service.provider.apiGateway.websocketApiId; + } + + return { Ref: this.naming.getWebsocketsApiLogicalId() }; + } + getStackResources(next, resourcesParam) { let resources = resourcesParam; const params = { From b96404763b21be6a6b1ded2cec11f2f1492a5d9a Mon Sep 17 00:00:00 2001 From: Jonathan Wilbur Date: Wed, 19 Jun 2019 09:12:44 -0400 Subject: [PATCH 388/504] Make stackTrace undefined if err.stack undefined --- lib/plugins/aws/invokeLocal/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 4aa51c986..582f3aaf6 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -548,7 +548,7 @@ class AwsInvokeLocal { errorResult = { errorMessage: err.message, errorType: err.constructor.name, - stackTrace: err.stack ? err.stack.split('\n') : [], + stackTrace: err.stack && err.stack.split('\n'), }; } else { errorResult = { From c67dec15f7d159ac8a0f91f0783b344a857f1443 Mon Sep 17 00:00:00 2001 From: floydnoel Date: Wed, 19 Jun 2019 11:06:15 -0600 Subject: [PATCH 389/504] fix typo --- docs/providers/aws/guide/packaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/guide/packaging.md b/docs/providers/aws/guide/packaging.md index bcacd2678..cbe3b6fd0 100644 --- a/docs/providers/aws/guide/packaging.md +++ b/docs/providers/aws/guide/packaging.md @@ -119,7 +119,7 @@ functions: method: get ``` -#### Artifacst hosted on S3 +#### Artifacts hosted on S3 Artifacts can also be fetched from a remote S3 bucket. In this case you just need to provide the S3 object URL as the artifact value. This applies to both, service-wide and function-level artifact setups. From 1019939969765622d4c37eabcba1c8ad42393e99 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Thu, 20 Jun 2019 11:53:35 +0200 Subject: [PATCH 390/504] #6017 Plugin relative paths should start with `./` Following the discussion in #6261 it should be possible to load plugin via relative paths, as long as the path starts with `./`. For example: ```yaml plugins: # will load myfirstplugin/plugin.js or myfirstplugin/plugin/index.js - './myfirstplugin/plugin' # will load mysecondplugin/plugin.js or mysecondplugin/plugin/index.js - './mysecondplugin/plugin' ``` --- docs/providers/aws/guide/plugins.md | 2 +- lib/classes/PluginManager.js | 5 +++-- lib/classes/PluginManager.test.js | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/providers/aws/guide/plugins.md b/docs/providers/aws/guide/plugins.md index c8fb95e48..c8414137f 100644 --- a/docs/providers/aws/guide/plugins.md +++ b/docs/providers/aws/guide/plugins.md @@ -86,7 +86,7 @@ plugins: # This plugin will be loaded from the `.serverless_plugins/` or `node_modules/` directories - custom-serverless-plugin # This plugin will be loaded from the `sub/directory/` directory - - sub/directory/another-custom-plugin + - ./sub/directory/another-custom-plugin ``` ### Load Order diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 716740984..328fc27c7 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -100,7 +100,9 @@ class PluginManager { loadPlugins(plugins) { plugins.forEach((plugin) => { try { - const Plugin = require(plugin); // eslint-disable-line global-require + const servicePath = this.serverless.config.servicePath; + const pluginPath = plugin.startsWith('./') ? path.join(servicePath, plugin) : plugin; + const Plugin = require(pluginPath); // eslint-disable-line global-require this.addPlugin(Plugin); } catch (error) { @@ -151,7 +153,6 @@ class PluginManager { if (pluginsObject.localPath) { module.paths.unshift(pluginsObject.localPath); } - module.paths.unshift(this.serverless.config.servicePath); this.loadPlugins(pluginsObject.modules .filter(name => name !== '@serverless/enterprise-plugin')); } diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index e00c4fd2b..bd7a0e56c 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -803,11 +803,13 @@ describe('PluginManager', () => { describe('#loadServicePlugins()', () => { beforeEach(function () { // eslint-disable-line prefer-arrow-callback mockRequire('ServicePluginMock1', ServicePluginMock1); - mockRequire('ServicePluginMock2', ServicePluginMock2); + // Plugins loaded via a relative path should be required relative to the service path + const servicePath = pluginManager.serverless.config.servicePath; + mockRequire(`${servicePath}/RelativePath/ServicePluginMock2`, ServicePluginMock2); }); it('should load the service plugins', () => { - const servicePlugins = ['ServicePluginMock1', 'ServicePluginMock2']; + const servicePlugins = ['ServicePluginMock1', './RelativePath/ServicePluginMock2']; pluginManager.loadServicePlugins(servicePlugins); expect(pluginManager.plugins From 88325c72f5ee051839bee556f7776d5e718ce92a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 20 Jun 2019 15:05:51 +0200 Subject: [PATCH 391/504] Remove package-lock.json from repository Currently it doesn't serve any purpose, while introducing some problems --- .gitignore | 1 + RELEASE_CHECKLIST.md | 5 +- package-lock.json | 9361 ------------------------------------------ package.json | 1 - 4 files changed, 3 insertions(+), 9365 deletions(-) delete mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index d62ad120c..71b581d34 100755 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.log npm-debug.log npm-shrinkwrap.json +/package-lock.json # Runtime data pids diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index 606b0c43c..e4200a133 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -20,15 +20,14 @@ milestone if still open ## Prepare Package - [ ] Install the latest `npm` version or Docker container with latest `node` and `npm` (Ensure to work with an `npm` version which is distributed with latest `node` version) -- [ ] Remove the `node_modules` folder and the `package-lock.json` file and run `npm install` (Removing both ensures that `package-lock.json` is updated with the latest versions of dependencies) - [ ] Update `CHANGELOG.md` with the content from your clipboard - [ ] Make sure all files that need to be pushed are included in `package.json -> files` -- [ ] Commit your changes (make sure that `package.json`, `package-lock.json` and `CHANGELOG.md` are updated) +- [ ] Commit your changes (make sure that `package.json` and `CHANGELOG.md` are updated) - [ ] Push your branch and open up a new PR - [ ] Await approval and merge the PR into `master` - [ ] Go back to the branch you want to release from (e.g. `master`) and pull the changes from GitHub - [ ] Make sure there are no local changes to your repository (or reset with `git reset --hard HEAD`) -- [ ] Check `package.json` and `package-lock.json` version config to make sure it fits what we want to release +- [ ] Check `package.json` version config to make sure it fits what we want to release ## Releasing diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 0e7462712..000000000 --- a/package-lock.json +++ /dev/null @@ -1,9361 +0,0 @@ -{ - "name": "serverless", - "version": "1.45.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "2-thenable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/2-thenable/-/2-thenable-1.0.0.tgz", - "integrity": "sha512-HqiDzaLDFCXkcCO/SwoyhRwqYtINFHF7t9BDRq4x90TOKNAJpiqUt9X5lQ08bwxYzc067HUywDjGySpebHcUpw==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.47" - } - }, - "@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" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "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 - } - } - }, - "@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-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-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-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/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" - }, - "dependencies": { - "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 - } - } - }, - "@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-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/template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", - "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.4.4", - "@babel/types": "^7.4.4" - } - }, - "@babel/traverse": { - "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.5", - "@babel/types": "^7.4.4", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.11" - }, - "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" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", - "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - }, - "@cnakazawa/watch": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", - "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", - "dev": true, - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - } - }, - "@jest/console": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", - "integrity": "sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==", - "dev": true, - "requires": { - "@jest/source-map": "^24.3.0", - "chalk": "^2.0.1", - "slash": "^2.0.0" - }, - "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } - } - }, - "@jest/core": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.8.0.tgz", - "integrity": "sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A==", - "dev": true, - "requires": { - "@jest/console": "^24.7.1", - "@jest/reporters": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/transform": "^24.8.0", - "@jest/types": "^24.8.0", - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "graceful-fs": "^4.1.15", - "jest-changed-files": "^24.8.0", - "jest-config": "^24.8.0", - "jest-haste-map": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-regex-util": "^24.3.0", - "jest-resolve-dependencies": "^24.8.0", - "jest-runner": "^24.8.0", - "jest-runtime": "^24.8.0", - "jest-snapshot": "^24.8.0", - "jest-util": "^24.8.0", - "jest-validate": "^24.8.0", - "jest-watcher": "^24.8.0", - "micromatch": "^3.1.10", - "p-each-series": "^1.0.0", - "pirates": "^4.0.1", - "realpath-native": "^1.1.0", - "rimraf": "^2.5.4", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - } - } - }, - "@jest/environment": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.8.0.tgz", - "integrity": "sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw==", - "dev": true, - "requires": { - "@jest/fake-timers": "^24.8.0", - "@jest/transform": "^24.8.0", - "@jest/types": "^24.8.0", - "jest-mock": "^24.8.0" - } - }, - "@jest/fake-timers": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.8.0.tgz", - "integrity": "sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw==", - "dev": true, - "requires": { - "@jest/types": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-mock": "^24.8.0" - } - }, - "@jest/reporters": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.8.0.tgz", - "integrity": "sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw==", - "dev": true, - "requires": { - "@jest/environment": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/transform": "^24.8.0", - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "istanbul-lib-coverage": "^2.0.2", - "istanbul-lib-instrument": "^3.0.1", - "istanbul-lib-report": "^2.0.4", - "istanbul-lib-source-maps": "^3.0.1", - "istanbul-reports": "^2.1.1", - "jest-haste-map": "^24.8.0", - "jest-resolve": "^24.8.0", - "jest-runtime": "^24.8.0", - "jest-util": "^24.8.0", - "jest-worker": "^24.6.0", - "node-notifier": "^5.2.1", - "slash": "^2.0.0", - "source-map": "^0.6.0", - "string-length": "^2.0.0" - }, - "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } - } - }, - "@jest/source-map": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.3.0.tgz", - "integrity": "sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.1.15", - "source-map": "^0.6.0" - }, - "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - } - } - }, - "@jest/test-result": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.8.0.tgz", - "integrity": "sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng==", - "dev": true, - "requires": { - "@jest/console": "^24.7.1", - "@jest/types": "^24.8.0", - "@types/istanbul-lib-coverage": "^2.0.0" - } - }, - "@jest/test-sequencer": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz", - "integrity": "sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg==", - "dev": true, - "requires": { - "@jest/test-result": "^24.8.0", - "jest-haste-map": "^24.8.0", - "jest-runner": "^24.8.0", - "jest-runtime": "^24.8.0" - } - }, - "@jest/transform": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.8.0.tgz", - "integrity": "sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^24.8.0", - "babel-plugin-istanbul": "^5.1.0", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.15", - "jest-haste-map": "^24.8.0", - "jest-regex-util": "^24.3.0", - "jest-util": "^24.8.0", - "micromatch": "^3.1.10", - "realpath-native": "^1.1.0", - "slash": "^2.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "2.4.1" - }, - "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "write-file-atomic": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", - "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - } - } - }, - "@jest/types": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.8.0.tgz", - "integrity": "sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^12.0.9" - } - }, - "@serverless/enterprise-plugin": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@serverless/enterprise-plugin/-/enterprise-plugin-1.0.6.tgz", - "integrity": "sha512-5NMOIP1OW3E7C+jG2per/hmImRd3eqM+nYrlbaR+VBCC4wHSLtBYkZYpCvFnuX96yNwDlhlrO3DA93dA9UiShg==", - "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==", - "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==" - } - } - }, - "@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==", - "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==", - "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": { - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - } - } - }, - "@sinonjs/commons": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", - "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/formatio": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", - "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", - "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.0.2", - "array-from": "^2.1.1", - "lodash": "^4.17.11" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", - "integrity": "sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz", - "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", - "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.7.tgz", - "integrity": "sha512-CeBpmX1J8kWLcDEnI3Cl2Eo6RfbGvzUctA+CjZUhOKDFbLfcr7fc4usEqLNWetrlJd7RhAkyYe2czXop4fICpw==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", - "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", - "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "@types/lodash": { - "version": "4.14.134", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.134.tgz", - "integrity": "sha512-2/O0khFUCFeDlbi7sZ7ZFRCcT812fAeOLm7Ev4KbwASkZ575TDrDcY7YyaoHdTOzKcNbfiwLYZqPmoC4wadrsw==" - }, - "@types/stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", - "dev": true - }, - "@types/yargs": { - "version": "12.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz", - "integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==", - "dev": true - }, - "abab": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", - "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==", - "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", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "acorn-globals": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", - "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", - "dev": true - } - } - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "acorn-walk": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", - "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", - "dev": true - }, - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - } - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "ansi": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", - "integrity": "sha1-DELU+xcWDVqa8eSEus4cZpIsGyE=" - }, - "ansi-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", - "requires": { - "string-width": "^2.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=" - }, - "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=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "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==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "append-transform": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", - "dev": true, - "requires": { - "default-require-extensions": "^2.0.0" - } - }, - "archiver": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", - "integrity": "sha1-TyGU1tj5nfP1MeaIHxTxXVX6ryI=", - "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==", - "requires": { - "lodash": "^4.17.11" - } - } - } - }, - "archiver-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", - "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=", - "requires": { - "glob": "^7.0.0", - "graceful-fs": "^4.1.0", - "lazystream": "^1.0.0", - "lodash": "^4.8.0", - "normalize-path": "^2.0.0", - "readable-stream": "^2.0.0" - } - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "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=" - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" - }, - "array.prototype.find": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.0.tgz", - "integrity": "sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.13.0" - } - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" - }, - "aws-sdk": { - "version": "2.473.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.473.0.tgz", - "integrity": "sha512-1Qr16lOcz4ANzl/oPQRR+fxchfvUx4PVQhUNnDU3FH9OBfU3Xj+Vh6bGYFbreFQgqIqXUTEuJR5pC44uK70YfA==", - "requires": { - "buffer": "4.9.1", - "events": "1.1.1", - "ieee754": "1.1.8", - "jmespath": "0.15.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "uuid": "3.3.2", - "xml2js": "0.4.19" - }, - "dependencies": { - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "ieee754": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", - "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - } - } - }, - "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 - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "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" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.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 - } - } - }, - "babel-jest": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.8.0.tgz", - "integrity": "sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw==", - "dev": true, - "requires": { - "@jest/transform": "^24.8.0", - "@jest/types": "^24.8.0", - "@types/babel__core": "^7.1.0", - "babel-plugin-istanbul": "^5.1.0", - "babel-preset-jest": "^24.6.0", - "chalk": "^2.4.2", - "slash": "^2.0.0" - }, - "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } - } - }, - "babel-plugin-istanbul": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.4.tgz", - "integrity": "sha512-dySz4VJMH+dpndj0wjJ8JPs/7i1TdSPb1nRrn56/92pKOF9VKC1FMFJmMXjzlGGusnCAqujP6PBCiKq0sVA+YQ==", - "dev": true, - "requires": { - "find-up": "^3.0.0", - "istanbul-lib-instrument": "^3.3.0", - "test-exclude": "^5.2.3" - }, - "dependencies": { - "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" - } - } - } - }, - "babel-plugin-jest-hoist": { - "version": "24.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz", - "integrity": "sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==", - "dev": true, - "requires": { - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-jest": { - "version": "24.6.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz", - "integrity": "sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==", - "dev": true, - "requires": { - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "babel-plugin-jest-hoist": "^24.6.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "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" - } - }, - "boxen": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", - "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.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=" - }, - "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=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - } - } - }, - "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", - "dev": true - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "bser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", - "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "cachedir": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.2.0.tgz", - "integrity": "sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ==" - }, - "caching-transform": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", - "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", - "dev": true, - "requires": { - "hasha": "^3.0.0", - "make-dir": "^2.0.0", - "package-hash": "^3.0.0", - "write-file-atomic": "^2.4.2" - }, - "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } - } - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dev": true, - "requires": { - "rsvp": "^4.8.4" - } - }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "caw": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", - "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", - "requires": { - "get-proxy": "^2.0.0", - "isurl": "^1.0.0-alpha5", - "tunnel-agent": "^0.6.0", - "url-to-options": "^1.0.1" - } - }, - "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - } - }, - "chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", - "dev": true, - "requires": { - "check-error": "^1.0.2" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "child-process-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/child-process-ext/-/child-process-ext-2.0.0.tgz", - "integrity": "sha512-yTIjjYfLcZ8/gje3+O//GW57REjRaYx+I+7SEHnkzMJSrSESFGPStDG01hWpYWgdhcC5oTTP8Vstp/rXM3ddCg==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.5", - "es5-ext": "^0.10.46", - "split2": "^3.1", - "stream-promise": "^3.1" - }, - "dependencies": { - "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" - } - } - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" - }, - "cli-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", - "dev": true, - "requires": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.5" - } - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "cli-progress-footer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cli-progress-footer/-/cli-progress-footer-1.1.1.tgz", - "integrity": "sha512-J0uW2u06pWI0tMKCbcCiMOZd8TbWj4EpuYgPo4Jiwih/FfGbd4dbLcJieO0Ior1pY1HBrnmCuHFk6GB9azE4pg==", - "dev": true, - "requires": { - "cli-color": "^1.4", - "d": "1", - "es5-ext": "^0.10.47", - "process-utils": "^2.0.1", - "timers-ext": "^0.1.7" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" - }, - "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" - }, - "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 - }, - "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": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "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" - } - } - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", - "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", - "requires": { - "graceful-readlink": ">= 1.0.0" - } - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "compress-commons": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", - "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", - "requires": { - "buffer-crc32": "^0.2.1", - "crc32-stream": "^2.0.0", - "normalize-path": "^2.0.0", - "readable-stream": "^2.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "config-chain": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "configstore": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", - "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", - "requires": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "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==" - }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" - }, - "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==", - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "coveralls": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.4.tgz", - "integrity": "sha512-eyqUWA/7RT0JagiL0tThVhjbIjoiEUyWCjtUJoOPcWoeofP5WK/jb2OJYoBFrR6DvplR+AxOyuBqk4JHkk5ykA==", - "dev": true, - "requires": { - "growl": "~> 1.10.0", - "js-yaml": "^3.11.0", - "lcov-parse": "^0.0.10", - "log-driver": "^1.2.7", - "minimist": "^1.2.0", - "request": "^2.86.0" - } - }, - "cp-file": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", - "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "make-dir": "^2.0.0", - "nested-error-stacks": "^2.0.0", - "pify": "^4.0.1", - "safe-buffer": "^5.0.1" - }, - "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } - } - }, - "crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "requires": { - "buffer": "^5.1.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=", - "requires": { - "crc": "^3.4.4", - "readable-stream": "^2.0.0" - } - }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" - }, - "cssom": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", - "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", - "dev": true - }, - "cssstyle": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz", - "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "^0.10.9" - } - }, - "damerau-levenshtein": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", - "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "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" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" - }, - "decompress": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz", - "integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=", - "requires": { - "decompress-tar": "^4.0.0", - "decompress-tarbz2": "^4.0.0", - "decompress-targz": "^4.0.0", - "decompress-unzip": "^4.0.1", - "graceful-fs": "^4.1.10", - "make-dir": "^1.0.0", - "pify": "^2.3.0", - "strip-dirs": "^2.0.0" - } - }, - "decompress-tar": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", - "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", - "requires": { - "file-type": "^5.2.0", - "is-stream": "^1.1.0", - "tar-stream": "^1.5.2" - } - }, - "decompress-tarbz2": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", - "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", - "requires": { - "decompress-tar": "^4.1.0", - "file-type": "^6.1.0", - "is-stream": "^1.1.0", - "seek-bzip": "^1.0.5", - "unbzip2-stream": "^1.0.9" - }, - "dependencies": { - "file-type": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", - "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==" - } - } - }, - "decompress-targz": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", - "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", - "requires": { - "decompress-tar": "^4.1.1", - "file-type": "^5.2.0", - "is-stream": "^1.1.0" - } - }, - "decompress-unzip": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", - "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", - "requires": { - "file-type": "^3.8.0", - "get-stream": "^2.2.0", - "pify": "^2.3.0", - "yauzl": "^2.4.2" - }, - "dependencies": { - "file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" - }, - "get-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", - "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", - "requires": { - "object-assign": "^4.0.1", - "pinkie-promise": "^2.0.0" - } - } - } - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "default-require-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", - "dev": true, - "requires": { - "strip-bom": "^3.0.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "diff-sequences": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz", - "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==", - "dev": true - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - } - }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "requires": { - "is-obj": "^1.0.0" - } - }, - "download": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/download/-/download-5.0.3.tgz", - "integrity": "sha1-Y1N/l3+ZJmow64oqL70fILgAD3o=", - "requires": { - "caw": "^2.0.0", - "decompress": "^4.0.0", - "filenamify": "^2.0.0", - "get-stream": "^3.0.0", - "got": "^6.3.0", - "mkdirp": "^0.5.1", - "pify": "^2.3.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "~0.4.13" - } - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.0", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es5-ext": { - "version": "0.10.50", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", - "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "^1.0.0" - } - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "requires": { - "es6-promise": "^4.0.3" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "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=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "escodegen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", - "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", - "dev": true, - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - } - } - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", - "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", - "dev": true, - "requires": { - "babel-code-frame": "^6.16.0", - "chalk": "^1.1.3", - "concat-stream": "^1.5.2", - "debug": "^2.1.1", - "doctrine": "^2.0.0", - "escope": "^3.6.0", - "espree": "^3.4.0", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "glob": "^7.0.3", - "globals": "^9.14.0", - "ignore": "^3.2.0", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.7.5", - "strip-bom": "^3.0.0", - "strip-json-comments": "~2.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.0.0" - }, - "dependencies": { - "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" - } - }, - "inquirer": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true, - "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true, - "requires": { - "once": "^1.3.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.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 - } - } - }, - "eslint-config-airbnb": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-10.0.1.tgz", - "integrity": "sha1-pHAQhkbWxF4fY5oD8R1QShqkrtw=", - "dev": true, - "requires": { - "eslint-config-airbnb-base": "^5.0.2" - } - }, - "eslint-config-airbnb-base": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-5.0.3.tgz", - "integrity": "sha1-lxSsNews1/qw1E0Uip+R2ylEB00=", - "dev": true - }, - "eslint-import-resolver-node": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz", - "integrity": "sha1-Wt2BBujJKNssuiMrzZ76hG49oWw=", - "dev": true, - "requires": { - "debug": "^2.2.0", - "object-assign": "^4.0.1", - "resolve": "^1.1.6" - } - }, - "eslint-plugin-import": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-1.16.0.tgz", - "integrity": "sha1-svoH68xTUE0PKkR3WC7Iv/GHG58=", - "dev": true, - "requires": { - "builtin-modules": "^1.1.1", - "contains-path": "^0.1.0", - "debug": "^2.2.0", - "doctrine": "1.3.x", - "es6-map": "^0.1.3", - "es6-set": "^0.1.4", - "eslint-import-resolver-node": "^0.2.0", - "has": "^1.0.1", - "lodash.cond": "^4.3.0", - "lodash.endswith": "^4.0.1", - "lodash.find": "^4.3.0", - "lodash.findindex": "^4.3.0", - "minimatch": "^3.0.3", - "object-assign": "^4.0.1", - "pkg-dir": "^1.0.0", - "pkg-up": "^1.0.0" - }, - "dependencies": { - "doctrine": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.3.0.tgz", - "integrity": "sha1-E+dWgrVVGEJCdvfBc3g0Vu+RPSY=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - } - } - }, - "eslint-plugin-jsx-a11y": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-2.2.3.tgz", - "integrity": "sha1-TjXLcbin23AqxBXIBuuOjZ6mxl0=", - "dev": true, - "requires": { - "damerau-levenshtein": "^1.0.0", - "jsx-ast-utils": "^1.0.0", - "object-assign": "^4.0.1" - } - }, - "eslint-plugin-react": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz", - "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", - "dev": true, - "requires": { - "array.prototype.find": "^2.0.1", - "doctrine": "^1.2.2", - "has": "^1.0.1", - "jsx-ast-utils": "^1.3.4", - "object.assign": "^4.0.4" - }, - "dependencies": { - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - } - } - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "essentials": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/essentials/-/essentials-1.0.0.tgz", - "integrity": "sha512-Xq511rzvYFFDnUDFXqfA6LxyYAlp3BM2qCm9+ml3Xcs28RPmJczKUluYiGYUUEu4yTwU5jsm0TSzUxUxZxf3Zw==", - "dev": true - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" - }, - "exec-sh": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", - "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==", - "dev": true - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.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" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "expect": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-24.8.0.tgz", - "integrity": "sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA==", - "dev": true, - "requires": { - "@jest/types": "^24.8.0", - "ansi-styles": "^3.2.0", - "jest-get-type": "^24.8.0", - "jest-matcher-utils": "^24.8.0", - "jest-message-util": "^24.8.0", - "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==", - "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" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "external-editor": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", - "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", - "requires": { - "extend": "^3.0.0", - "spawn-sync": "^1.0.15", - "tmp": "^0.0.29" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "fb-watchman": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", - "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", - "dev": true, - "requires": { - "bser": "^2.0.0" - } - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "requires": { - "pend": "~1.2.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=" - }, - "filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=" - }, - "filenamify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", - "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", - "requires": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.0", - "trim-repeated": "^1.0.0" - } - }, - "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==" - }, - "fill-keys": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, - "requires": { - "is-object": "~1.0.1", - "merge-descriptors": "~1.0.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - } - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "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" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "dependencies": { - "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" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - } - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "requires": { - "is-buffer": "~2.0.3" - } - }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "foreground-child": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", - "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", - "dev": true, - "requires": { - "cross-spawn": "^4", - "signal-exit": "^3.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - } - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "requires": { - "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=" - }, - "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=", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - }, - "dependencies": { - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "requires": { - "graceful-fs": "^4.1.6" - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "optional": true, - "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" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^4.1.0", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "gauge": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz", - "integrity": "sha1-6c7FSD09TuDvRLYKfZnkk14TbZM=", - "requires": { - "ansi": "^0.3.0", - "has-unicode": "^2.0.0", - "lodash.pad": "^4.1.0", - "lodash.padend": "^4.1.0", - "lodash.padstart": "^4.1.0" - } - }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dev": true, - "requires": { - "is-property": "^1.0.2" - } - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "^1.0.0" - } - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, - "get-proxy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", - "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", - "requires": { - "npm-conf": "^1.1.0" - } - }, - "get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "git-list-updated": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/git-list-updated/-/git-list-updated-1.1.2.tgz", - "integrity": "sha512-PGmbGbjttcyTNUIK1Ecef9nUvlz7tb+zOI7/hkrHgl1G9V2bRCF78XE1ddWNdshXVklVEqdXRM5l/iJvLIjk0Q==", - "dev": true, - "requires": { - "2-thenable": "1", - "child-process-ext": "2", - "es5-ext": "^0.10.46", - "essentials": "1", - "minimist": "^1.2" - } - }, - "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" - } - }, - "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", - "requires": { - "ini": "^1.3.4" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" - }, - "graphlib": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.7.tgz", - "integrity": "sha512-TyI9jIy2J4j0qgPmOOrHTCtpPqJGN/aurBwc6ZT+bRii+di1I+Wv3obRhVrmBEXet+qkMaEX67dXrwsd3QQM6w==", - "requires": { - "lodash": "^4.17.5" - } - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true - }, - "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", - "dev": true, - "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "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" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "requires": { - "has-symbol-support-x": "^1.4.1" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hasha": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", - "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", - "dev": true, - "requires": { - "is-stream": "^1.0.1" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "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==", - "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", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", - "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==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" - }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "dev": true, - "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - }, - "dependencies": { - "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" - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "inquirer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", - "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", - "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" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "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" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.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=" - } - } - }, - "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.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 - }, - "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==" - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-docker": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-1.1.0.tgz", - "integrity": "sha1-8EN01O7lMQ6ajhE78UlUEeRhdqE=" - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "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" - } - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-installed-globally": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", - "requires": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" - } - }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true - }, - "is-my-json-valid": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", - "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==", - "dev": true, - "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" - } - }, - "is-natural-number": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", - "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=" - }, - "is-npm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" - }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "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==" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "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=", - "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=", - "dev": true - }, - "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-hook": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", - "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", - "dev": true, - "requires": { - "append-transform": "^1.0.0" - } - }, - "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 - } - } - }, - "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": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "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" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "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==", - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, - "jest-changed-files": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.8.0.tgz", - "integrity": "sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug==", - "dev": true, - "requires": { - "@jest/types": "^24.8.0", - "execa": "^1.0.0", - "throat": "^4.0.0" - }, - "dependencies": { - "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" - } - }, - "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" - } - } - } - }, - "jest-circus": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-24.8.0.tgz", - "integrity": "sha512-2QASG3QuDdk0SMP2O73D8u3/lc/A/E2G7q23v5WhbUR+hCGzWZXwRMKif18f11dSLfL1wcrMbwE4IorvV0DRVw==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^24.8.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^24.8.0", - "jest-matcher-utils": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-snapshot": "^24.8.0", - "jest-util": "^24.8.0", - "pretty-format": "^24.8.0", - "stack-utils": "^1.0.1", - "throat": "^4.0.0" - } - }, - "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" - }, - "dependencies": { - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - } - } - }, - "jest-config": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.8.0.tgz", - "integrity": "sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^24.8.0", - "@jest/types": "^24.8.0", - "babel-jest": "^24.8.0", - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^24.8.0", - "jest-environment-node": "^24.8.0", - "jest-get-type": "^24.8.0", - "jest-jasmine2": "^24.8.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.8.0", - "jest-util": "^24.8.0", - "jest-validate": "^24.8.0", - "micromatch": "^3.1.10", - "pretty-format": "^24.8.0", - "realpath-native": "^1.1.0" - } - }, - "jest-diff": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.8.0.tgz", - "integrity": "sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff-sequences": "^24.3.0", - "jest-get-type": "^24.8.0", - "pretty-format": "^24.8.0" - } - }, - "jest-docblock": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.3.0.tgz", - "integrity": "sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==", - "dev": true, - "requires": { - "detect-newline": "^2.1.0" - } - }, - "jest-each": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.8.0.tgz", - "integrity": "sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA==", - "dev": true, - "requires": { - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.8.0", - "jest-util": "^24.8.0", - "pretty-format": "^24.8.0" - } - }, - "jest-environment-jsdom": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz", - "integrity": "sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ==", - "dev": true, - "requires": { - "@jest/environment": "^24.8.0", - "@jest/fake-timers": "^24.8.0", - "@jest/types": "^24.8.0", - "jest-mock": "^24.8.0", - "jest-util": "^24.8.0", - "jsdom": "^11.5.1" - } - }, - "jest-environment-node": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.8.0.tgz", - "integrity": "sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q==", - "dev": true, - "requires": { - "@jest/environment": "^24.8.0", - "@jest/fake-timers": "^24.8.0", - "@jest/types": "^24.8.0", - "jest-mock": "^24.8.0", - "jest-util": "^24.8.0" - } - }, - "jest-get-type": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.8.0.tgz", - "integrity": "sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ==", - "dev": true - }, - "jest-haste-map": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.8.0.tgz", - "integrity": "sha512-ZBPRGHdPt1rHajWelXdqygIDpJx8u3xOoLyUBWRW28r3tagrgoepPrzAozW7kW9HrQfhvmiv1tncsxqHJO1onQ==", - "dev": true, - "requires": { - "@jest/types": "^24.8.0", - "anymatch": "^2.0.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.2.7", - "graceful-fs": "^4.1.15", - "invariant": "^2.2.4", - "jest-serializer": "^24.4.0", - "jest-util": "^24.8.0", - "jest-worker": "^24.6.0", - "micromatch": "^3.1.10", - "sane": "^4.0.3", - "walker": "^1.0.7" - } - }, - "jest-jasmine2": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz", - "integrity": "sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^24.8.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^24.8.0", - "jest-matcher-utils": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-runtime": "^24.8.0", - "jest-snapshot": "^24.8.0", - "jest-util": "^24.8.0", - "pretty-format": "^24.8.0", - "throat": "^4.0.0" - } - }, - "jest-leak-detector": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz", - "integrity": "sha512-cG0yRSK8A831LN8lIHxI3AblB40uhv0z+SsQdW3GoMMVcK+sJwrIIyax5tu3eHHNJ8Fu6IMDpnLda2jhn2pD/g==", - "dev": true, - "requires": { - "pretty-format": "^24.8.0" - } - }, - "jest-matcher-utils": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz", - "integrity": "sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-diff": "^24.8.0", - "jest-get-type": "^24.8.0", - "pretty-format": "^24.8.0" - } - }, - "jest-message-util": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.8.0.tgz", - "integrity": "sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "@types/stack-utils": "^1.0.1", - "chalk": "^2.0.1", - "micromatch": "^3.1.10", - "slash": "^2.0.0", - "stack-utils": "^1.0.1" - }, - "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } - } - }, - "jest-mock": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.8.0.tgz", - "integrity": "sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A==", - "dev": true, - "requires": { - "@jest/types": "^24.8.0" - } - }, - "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", - "dev": true - }, - "jest-regex-util": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", - "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", - "dev": true - }, - "jest-resolve": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.8.0.tgz", - "integrity": "sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==", - "dev": true, - "requires": { - "@jest/types": "^24.8.0", - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "jest-pnp-resolver": "^1.2.1", - "realpath-native": "^1.1.0" - } - }, - "jest-resolve-dependencies": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz", - "integrity": "sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw==", - "dev": true, - "requires": { - "@jest/types": "^24.8.0", - "jest-regex-util": "^24.3.0", - "jest-snapshot": "^24.8.0" - } - }, - "jest-runner": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.8.0.tgz", - "integrity": "sha512-utFqC5BaA3JmznbissSs95X1ZF+d+4WuOWwpM9+Ak356YtMhHE/GXUondZdcyAAOTBEsRGAgH/0TwLzfI9h7ow==", - "dev": true, - "requires": { - "@jest/console": "^24.7.1", - "@jest/environment": "^24.8.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "chalk": "^2.4.2", - "exit": "^0.1.2", - "graceful-fs": "^4.1.15", - "jest-config": "^24.8.0", - "jest-docblock": "^24.3.0", - "jest-haste-map": "^24.8.0", - "jest-jasmine2": "^24.8.0", - "jest-leak-detector": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-resolve": "^24.8.0", - "jest-runtime": "^24.8.0", - "jest-util": "^24.8.0", - "jest-worker": "^24.6.0", - "source-map-support": "^0.5.6", - "throat": "^4.0.0" - } - }, - "jest-runtime": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.8.0.tgz", - "integrity": "sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA==", - "dev": true, - "requires": { - "@jest/console": "^24.7.1", - "@jest/environment": "^24.8.0", - "@jest/source-map": "^24.3.0", - "@jest/transform": "^24.8.0", - "@jest/types": "^24.8.0", - "@types/yargs": "^12.0.2", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.1.15", - "jest-config": "^24.8.0", - "jest-haste-map": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-mock": "^24.8.0", - "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.8.0", - "jest-snapshot": "^24.8.0", - "jest-util": "^24.8.0", - "jest-validate": "^24.8.0", - "realpath-native": "^1.1.0", - "slash": "^2.0.0", - "strip-bom": "^3.0.0", - "yargs": "^12.0.2" - }, - "dependencies": { - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } - } - }, - "jest-serializer": { - "version": "24.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.4.0.tgz", - "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==", - "dev": true - }, - "jest-snapshot": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.8.0.tgz", - "integrity": "sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^24.8.0", - "chalk": "^2.0.1", - "expect": "^24.8.0", - "jest-diff": "^24.8.0", - "jest-matcher-utils": "^24.8.0", - "jest-message-util": "^24.8.0", - "jest-resolve": "^24.8.0", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^24.8.0", - "semver": "^5.5.0" - } - }, - "jest-util": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.8.0.tgz", - "integrity": "sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA==", - "dev": true, - "requires": { - "@jest/console": "^24.7.1", - "@jest/fake-timers": "^24.8.0", - "@jest/source-map": "^24.3.0", - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "callsites": "^3.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.15", - "is-ci": "^2.0.0", - "mkdirp": "^0.5.1", - "slash": "^2.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - } - } - }, - "jest-validate": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.8.0.tgz", - "integrity": "sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA==", - "dev": true, - "requires": { - "@jest/types": "^24.8.0", - "camelcase": "^5.0.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.8.0", - "leven": "^2.1.0", - "pretty-format": "^24.8.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } - } - }, - "jest-watcher": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.8.0.tgz", - "integrity": "sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw==", - "dev": true, - "requires": { - "@jest/test-result": "^24.8.0", - "@jest/types": "^24.8.0", - "@types/yargs": "^12.0.9", - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "jest-util": "^24.8.0", - "string-length": "^2.0.0" - }, - "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - } - } - }, - "jest-worker": { - "version": "24.6.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.6.0.tgz", - "integrity": "sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==", - "dev": true, - "requires": { - "merge-stream": "^1.0.1", - "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" - } - } - } - }, - "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - } - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-cycle": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/json-cycle/-/json-cycle-1.3.0.tgz", - "integrity": "sha512-FD/SedD78LCdSvJaOUQAXseT8oQBb5z6IVYaQaCrVUlu9zOAr1BDdKyVYQaSD/GDsAMrXpKcOyBD4LIl8nfjHw==" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-refs": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/json-refs/-/json-refs-2.1.7.tgz", - "integrity": "sha1-uesB/in16j6Sh48VrqEK04taz4k=", - "requires": { - "commander": "^2.9.0", - "graphlib": "^2.1.1", - "js-yaml": "^3.8.3", - "native-promise-only": "^0.8.1", - "path-loader": "^1.0.2", - "slash": "^1.0.0", - "uri-js": "^3.0.2" - }, - "dependencies": { - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" - } - } - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "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=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "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" - } - }, - "jsonata": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/jsonata/-/jsonata-1.6.4.tgz", - "integrity": "sha512-3MWTH77OHLf3muMknZJS4GnDhGPMITyF9D84hpRQrjt1Hk3pBtTiyZcqodHUDSaDq8VDy9YyIbanRI+3RoW3FA==" - }, - "jsonfile": { - "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=", - "dev": true - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "jsx-ast-utils": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", - "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", - "dev": true - }, - "jszip": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.1.tgz", - "integrity": "sha512-iCMBbo4eE5rb1VCpm5qXOAaUiRKRUKiItn8ah2YQQx9qymmSAY98eyQfioChEYcVQLh0zxJ3wS4A0mh90AVPvw==", - "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" - } - }, - "just-extend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", - "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", - "dev": true - }, - "jwt-decode": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", - "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "latest-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", - "requires": { - "package-json": "^4.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" - } - }, - "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" - } - }, - "lcov-parse": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", - "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", - "dev": true - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "requires": { - "immediate": "~3.0.5" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "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" - }, - "dependencies": { - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "lodash.cond": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", - "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", - "dev": true - }, - "lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" - }, - "lodash.endswith": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.endswith/-/lodash.endswith-4.2.1.tgz", - "integrity": "sha1-/tWawXOO0+I27dcGTsRWRIs3vAk=", - "dev": true - }, - "lodash.find": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", - "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=", - "dev": true - }, - "lodash.findindex": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.findindex/-/lodash.findindex-4.6.0.tgz", - "integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=", - "dev": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, - "lodash.pad": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", - "integrity": "sha1-QzCUmoM6fI2iLMIPaibE1Z3runA=" - }, - "lodash.padend": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", - "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=" - }, - "lodash.padstart": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", - "integrity": "sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=" - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - }, - "lolex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.1.0.tgz", - "integrity": "sha512-BYxIEXiVq5lGIXeVHnsFzqa1TxN5acnKnPCdlZSpzm8viNEOhiigupA4vTQ9HEFQ6nLTQ9wQOgBknJgzUYQ9Aw==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "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" - } - }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "dev": true, - "requires": { - "es5-ext": "~0.10.2" - } - }, - "lsmod": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", - "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - } - } - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "requires": { - "object-visit": "^1.0.0" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "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" - } - }, - "memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - } - }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "requires": { - "mime-db": "1.40.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "mocha": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", - "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", - "dev": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.2.2", - "yargs-parser": "13.0.0", - "yargs-unparser": "1.5.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "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" - } - }, - "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-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "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" - } - }, - "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 - }, - "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 - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" - } - }, - "yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "mocha-lcov-reporter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", - "integrity": "sha1-Rpve9PivyaEWBW8HnfYYLQr7A4Q=", - "dev": true - }, - "mock-require": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz", - "integrity": "sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==", - "dev": true, - "requires": { - "get-caller-file": "^1.0.2", - "normalize-path": "^2.1.1" - } - }, - "module-not-found-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "mute-stream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", - "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=" - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "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==" - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "nested-error-stacks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", - "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "nise": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.0.tgz", - "integrity": "sha512-Z3sfYEkLFzFmL8KY6xnSJLRxwQwYBjOXi/24lb62ZnZiGA0JUzGGTI6TBIgfCSMIDl9Jlu8SRmHNACLTemDHww==", - "dev": true, - "requires": { - "@sinonjs/formatio": "^3.1.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^4.1.0", - "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "dev": true, - "requires": { - "isarray": "0.0.1" - } - } - } - }, - "node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU=", - "requires": { - "minimatch": "^3.0.2" - } - }, - "node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-notifier": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", - "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-conf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", - "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", - "requires": { - "config-chain": "^1.1.11", - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - } - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - } - }, - "npmlog": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-2.0.4.tgz", - "integrity": "sha1-mLUlMPJRTKkNCexbIsiEZyI3VpI=", - "requires": { - "ansi": "~0.3.1", - "are-we-there-yet": "~1.1.2", - "gauge": "~1.2.5" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "nwsapi": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", - "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==", - "dev": true - }, - "nyc": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", - "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "caching-transform": "^3.0.2", - "convert-source-map": "^1.6.0", - "cp-file": "^6.2.0", - "find-cache-dir": "^2.1.0", - "find-up": "^3.0.0", - "foreground-child": "^1.5.6", - "glob": "^7.1.3", - "istanbul-lib-coverage": "^2.0.5", - "istanbul-lib-hook": "^2.0.7", - "istanbul-lib-instrument": "^3.3.0", - "istanbul-lib-report": "^2.0.8", - "istanbul-lib-source-maps": "^3.0.6", - "istanbul-reports": "^2.2.4", - "js-yaml": "^3.13.1", - "make-dir": "^2.1.0", - "merge-source-map": "^1.1.0", - "resolve-from": "^4.0.0", - "rimraf": "^2.6.3", - "signal-exit": "^3.0.2", - "spawn-wrap": "^1.4.2", - "test-exclude": "^5.2.3", - "uuid": "^3.3.2", - "yargs": "^13.2.2", - "yargs-parser": "^13.0.0" - }, - "dependencies": { - "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": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.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-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "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 - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "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==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "yargs": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", - "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.0" - } - }, - "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "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 - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-hash": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", - "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "requires": { - "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", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "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=", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" - }, - "opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "requires": { - "is-wsl": "^1.1.0" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "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" - }, - "dependencies": { - "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" - } - }, - "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" - } - } - } - }, - "os-shim": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-each-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", - "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", - "dev": true, - "requires": { - "p-reduce": "^1.0.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "p-try": "^2.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==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", - "dev": true - }, - "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==", - "dev": true - }, - "package-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", - "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^3.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - } - }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", - "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - } - }, - "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" - }, - "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" - } - }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "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==" - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-loader": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/path-loader/-/path-loader-1.0.10.tgz", - "integrity": "sha512-CMP0v6S6z8PHeJ6NFVyVJm6WyJjIwFvyz2b0n2/4bKdS/0uZa/9sKUlYZzubrn3zuDRU0zIuEDX9DZYQ2ZI8TA==", - "requires": { - "native-promise-only": "^0.8.1", - "superagent": "^3.8.3" - } - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "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=" - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "dev": true - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "dev": true, - "requires": { - "find-up": "^1.0.0" - } - }, - "pkg-up": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz", - "integrity": "sha1-Pgj7RhUlxEIWJKM7n35tCvWwWiY=", - "dev": true, - "requires": { - "find-up": "^1.0.0" - } - }, - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, - "pretty-format": { - "version": "24.8.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.8.0.tgz", - "integrity": "sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==", - "dev": true, - "requires": { - "@jest/types": "^24.8.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - }, - "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 - } - } - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "process-utils": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/process-utils/-/process-utils-2.3.1.tgz", - "integrity": "sha512-ZTNuK06C/RMIh85Yk9OFTq8h7WG4mXK3NJkqnHm63QH5by82KNU7MvEMPzYsrlYbvcJflUsor63uLB7mSx30Yg==", - "dev": true, - "requires": { - "type": "^1.0.1" - } - }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "promise-queue": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", - "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=" - }, - "prompts": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.1.0.tgz", - "integrity": "sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==", - "dev": true, - "requires": { - "kleur": "^3.0.2", - "sisteransi": "^1.0.0" - } - }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" - }, - "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==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" - } - }, - "proxyquire": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.0.tgz", - "integrity": "sha512-kptdFArCfGRtQFv3Qwjr10lwbEV0TBJYvfqzhwucyfEXqVgmnAkyEw/S3FYzR5HI9i5QOq4rcqQjZ6AlknlCDQ==", - "dev": true, - "requires": { - "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.0", - "resolve": "~1.8.1" - }, - "dependencies": { - "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", - "dev": true, - "requires": { - "path-parse": "^1.0.5" - } - } - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "psl": { - "version": "1.1.32", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", - "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==", - "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" - } - }, - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, - "ramda": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", - "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" - }, - "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==" - }, - "raven": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", - "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", - "requires": { - "cookie": "0.3.1", - "json-stringify-safe": "5.0.1", - "lsmod": "1.0.0", - "stack-trace": "0.0.9", - "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=" - }, - "uuid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", - "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" - } - } - }, - "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==", - "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", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", - "dev": true, - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" - }, - "dependencies": { - "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" - } - } - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "mute-stream": "0.0.5" - }, - "dependencies": { - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - } - } - }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "dev": true, - "requires": { - "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" - } - }, - "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==" - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^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", - "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", - "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "requires": { - "rc": "^1.0.1" - } - }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", - "dev": true, - "requires": { - "es6-error": "^4.0.1" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "replaceall": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/replaceall/-/replaceall-0.1.6.tgz", - "integrity": "sha1-gdgax663LX9cSUKt8ml6MiBojY4=" - }, - "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" - }, - "dependencies": { - "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 - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true - } - } - }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", - "dev": true, - "requires": { - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, - "resolve": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", - "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "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": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "requires": { - "is-promise": "^2.1.0" - } - }, - "rx": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" - }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "dependencies": { - "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" - } - }, - "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" - } - } - } - }, - "sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" - }, - "seek-bzip": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", - "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", - "requires": { - "commander": "~2.8.1" - } - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" - }, - "semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "requires": { - "semver": "^5.0.3" - } - }, - "semver-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz", - "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=" - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "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": { - "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==" - } - } - }, - "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==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - } - } - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "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 - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "sinon": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", - "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.1", - "diff": "^3.5.0", - "lolex": "^4.0.1", - "nise": "^1.4.10", - "supports-color": "^5.5.0" - } - }, - "sinon-chai": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.3.0.tgz", - "integrity": "sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==", - "dev": true - }, - "sisteransi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", - "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "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==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" - }, - "spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", - "requires": { - "concat-stream": "^1.4.7", - "os-shim": "^0.1.2" - } - }, - "spawn-wrap": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.2.tgz", - "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", - "dev": true, - "requires": { - "foreground-child": "^1.5.6", - "mkdirp": "^0.5.0", - "os-homedir": "^1.0.1", - "rimraf": "^2.6.2", - "signal-exit": "^3.0.2", - "which": "^1.3.0" - } - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "split2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.1.1.tgz", - "integrity": "sha512-emNzr1s7ruq4N+1993yht631/JH+jaj0NYBosuKmLcq+JkGQ9MmTw1RB1fGaTCzUuseRIClrlSLHRNYGwWQ58Q==", - "dev": true, - "requires": { - "readable-stream": "^3.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-trace": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, - "stream-promise": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/stream-promise/-/stream-promise-3.2.0.tgz", - "integrity": "sha512-P+7muTGs2C8yRcgJw/PPt61q7O517tDHiwYEzMWo1GSBCcZedUMT/clz7vUNsSxFphIlJ6QUL4GexQKlfJoVtA==", - "dev": true, - "requires": { - "2-thenable": "^1.0.0", - "es5-ext": "^0.10.49", - "is-stream": "^1.1.0" - } - }, - "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", - "dev": true, - "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.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 - }, - "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" - } - } - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "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" - }, - "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 - } - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", - "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", - "requires": { - "is-natural-number": "^4.0.1" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, - "superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "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==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-tree": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", - "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", - "dev": true - }, - "table": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "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" - } - }, - "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": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.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 - }, - "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" - } - } - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.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 - } - } - }, - "tabtab": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tabtab/-/tabtab-2.2.2.tgz", - "integrity": "sha1-egR/FDsBC0y9MfhX6ClhUSy/ThQ=", - "requires": { - "debug": "^2.2.0", - "inquirer": "^1.0.2", - "lodash.difference": "^4.5.0", - "lodash.uniq": "^4.5.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "npmlog": "^2.0.3", - "object-assign": "^4.1.0" - } - }, - "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - } - }, - "term-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", - "requires": { - "execa": "^0.7.0" - } - }, - "test-exclude": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", - "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", - "dev": true, - "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^2.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" - }, - "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dev": true, - "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, - "tmp": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", - "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", - "requires": { - "os-tmpdir": "~1.0.1" - } - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "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==" - }, - "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==", - "dev": true, - "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=", - "dev": true - } - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - } - } - }, - "trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", - "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "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", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "uglify-js": { - "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": { - "commander": "~2.20.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true, - "optional": true - } - } - }, - "unbzip2-stream": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz", - "integrity": "sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==", - "requires": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } - } - }, - "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "requires": { - "crypto-random-string": "^1.0.0" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "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=" - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - } - } - }, - "untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==" - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" - }, - "update-notifier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", - "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", - "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "uri-js": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", - "integrity": "sha1-+QuFhQf4HepNz7s8TD2/orVX+qo=", - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" - }, - "url": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "requires": { - "prepend-http": "^1.0.1" - } - }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" - }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "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=" - }, - "uuid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "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=" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", - "dev": true, - "requires": { - "browser-process-hrtime": "^0.1.2" - } - }, - "walkdir": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", - "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=" - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "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==" - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "widest-line": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", - "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", - "requires": { - "string-width": "^2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "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=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "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==", - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xdg-basedir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" - } - }, - "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "yaml-ast-parser": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.34.tgz", - "integrity": "sha1-0A88+ddztyQUCa6SpnQNHbGfSeY=" - }, - "yamljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", - "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", - "requires": { - "argparse": "^1.0.7", - "glob": "^7.0.5" - } - }, - "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" - }, - "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 - }, - "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" - } - }, - "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 - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "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-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" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } - } - }, - "yargs-unparser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", - "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", - "dev": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.11", - "yargs": "^12.0.5" - } - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "zip-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", - "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=", - "requires": { - "archiver-utils": "^1.3.0", - "compress-commons": "^1.2.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0" - } - } - } -} diff --git a/package.json b/package.json index e05a38b30..6aa0f1b22 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "scripts/preuninstall.js", "scripts/pre-release.js", "package.json", - "package-lock.json", "README.md", "LICENSE.txt", "CHANGELOG.md" From 474ba4eb00b18c5587459e7e3ff6e55679983189 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 20 Jun 2019 15:07:43 +0200 Subject: [PATCH 392/504] Remove shrinkwrap scripts Originally it was intended to lock dependencies for CLI still it's not confirmed whether it works that way and after all we do not want to lock it to not prevent updates of @serverless/enterprise-plugin --- scripts/shrinkwrap | 10 ---------- scripts/update-dep-shrinkwrap | 9 --------- 2 files changed, 19 deletions(-) delete mode 100755 scripts/shrinkwrap delete mode 100755 scripts/update-dep-shrinkwrap diff --git a/scripts/shrinkwrap b/scripts/shrinkwrap deleted file mode 100755 index bef5c337b..000000000 --- a/scripts/shrinkwrap +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# NOTE this script should only be executed from the root directory (not within "scripts") - -rm -rf node_modules -rm -f npm-shrinkwrap.json -npm install -npm prune --production -npm shrinkwrap -rm -f package-lock.json diff --git a/scripts/update-dep-shrinkwrap b/scripts/update-dep-shrinkwrap deleted file mode 100755 index 997dec345..000000000 --- a/scripts/update-dep-shrinkwrap +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -e - -rm -fr node_modules - -docker pull node:latest -docker-compose run serverless-node npm install -docker-compose run serverless-node npm shrinkwrap From f76f549e04bbe35889d14e57d42f23c4cbab2758 Mon Sep 17 00:00:00 2001 From: joetravis Date: Thu, 20 Jun 2019 10:05:15 -0700 Subject: [PATCH 393/504] Use naming to get stackName --- .../compile/events/apiGateway/lib/hack/disassociateUsagePlan.js | 2 +- .../events/apiGateway/lib/hack/disassociateUsagePlan.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.js index 3930e1528..c7c1d3f99 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.js @@ -9,7 +9,7 @@ module.exports = { if (apiKeys && apiKeys.length) { this.serverless.cli.log('Removing usage plan association...'); - const stackName = `${this.serverless.service.service}-${this.provider.getStage()}`; + const stackName = `${this.provider.naming.getStackName()}`; return BbPromise.all([ this.provider.request('CloudFormation', 'describeStackResource', diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.test.js index c0dfbd7fc..9631e788a 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.test.js @@ -69,7 +69,7 @@ describe('#disassociateUsagePlan()', () => { 'CloudFormation', 'describeStackResource', { - StackName: `${serverless.service.service}-${awsProvider.getStage()}`, + StackName: `${awsProvider.naming.getStackName()}`, LogicalResourceId: 'ApiGatewayRestApi', } )).to.be.equal(true); From 385f9f855e0eb837b6252648dcad8863ec19339d Mon Sep 17 00:00:00 2001 From: Gareth McCumskey Date: Fri, 21 Jun 2019 08:42:28 +0200 Subject: [PATCH 394/504] Added correction based on community feedback --- docs/providers/cloudflare/guide/quick-start.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/cloudflare/guide/quick-start.md b/docs/providers/cloudflare/guide/quick-start.md index 93577b68e..964e6173f 100644 --- a/docs/providers/cloudflare/guide/quick-start.md +++ b/docs/providers/cloudflare/guide/quick-start.md @@ -19,7 +19,7 @@ This guide is a walk through of using the Serverless Plugin to deploy Cloudflare *Note:`workers.dev` domains are not currently supported using Serverless, but you can track our progress on [this Github issue](https://github.com/cloudflare/serverless-cloudflare-workers/issues/36).* ## Pre-requisites -Node.js `v6.5.0` or later. +Node.js `v10.X` or later. Serverless CLI `v1.31.0` or later. You can run `npm install -g serverless` to install it. you also need our `serverless-cloudflare-workers` plugin. You can install it in your project with `npm install --save serverless-cloudflare-workers`. ## Create a new service From 0242995dc1c7ca61bff0ae6887190ac17144efaf Mon Sep 17 00:00:00 2001 From: Hazlank <401840614@qq.com> Date: Fri, 21 Jun 2019 18:05:45 +0800 Subject: [PATCH 395/504] Remove README redundant link --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 26a8cad56..b1341bb62 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ Serverless is an MIT open-source project, actively maintained by a full-time, ve * [Services](#services) * [Features](#features) * [Plugins](https://github.com/serverless/plugins) -* [Examples](https://github.com/serverless/examples) * [Contributing](#contributing) * [Community](#community) * [Consultants](#consultants) From a8bff62abdd89fa2d37f50f9b56564708896a537 Mon Sep 17 00:00:00 2001 From: Jonathan Schellack Date: Sat, 22 Jun 2019 18:47:37 -0500 Subject: [PATCH 396/504] Fix typo in link to ALB docs "events" was misspelled as "evetns" in the url to alb.md --- docs/providers/aws/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/README.md b/docs/providers/aws/README.md index a194ca32b..f6e3f7129 100644 --- a/docs/providers/aws/README.md +++ b/docs/providers/aws/README.md @@ -93,7 +93,7 @@ If you have any questions, [search the forums](https://forum.serverless.com?utm_
  • Schedule
  • SNS
  • SQS
  • -
  • ALB
  • +
  • ALB
  • Alexa Skill
  • Alexa Smart Home
  • IoT
  • From bc0e05fbbd770879078aac981899987c7c114f2b Mon Sep 17 00:00:00 2001 From: Edward Goubely Date: Thu, 20 Jun 2019 15:30:38 +0200 Subject: [PATCH 397/504] Add ip, method, header and query conditions to ALB events --- .../compile/events/alb/lib/listenerRules.js | 41 +++++- .../events/alb/lib/listenerRules.test.js | 6 +- .../compile/events/alb/lib/validate.js | 68 ++++++++- .../compile/events/alb/lib/validate.test.js | 137 +++++++++++++++++- 4 files changed, 241 insertions(+), 11 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js index f4e9786f8..462a5522c 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js @@ -11,16 +11,51 @@ module.exports = { const Conditions = [ { Field: 'path-pattern', - Values: [event.conditions.path], + Values: event.conditions.path, }, ]; if (event.conditions.host) { Conditions.push({ Field: 'host-header', - Values: [event.conditions.host], + Values: event.conditions.host, + }); + } + if (event.conditions.method) { + Conditions.push({ + Field: 'http-request-method', + HttpRequestMethodConfig: { + Values: event.conditions.method, + }, + }); + } + if (event.conditions.header) { + Conditions.push({ + Field: 'http-header', + HttpHeaderConfig: { + HttpHeaderName: event.conditions.header.name, + Values: event.conditions.header.values, + }, + }); + } + if (event.conditions.query) { + Conditions.push({ + Field: 'query-string', + QueryStringConfig: { + Values: Object.keys(event.conditions.query).map(key => ({ + Key: key, + Value: event.conditions.query[key], + })), + }, + }); + } + if (event.conditions.ip) { + Conditions.push({ + Field: 'source-ip', + SourceIpConfig: { + Values: event.conditions.ip, + }, }); } - Object.assign(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [listenerRuleLogicalId]: { Type: 'AWS::ElasticLoadBalancingV2::ListenerRule', diff --git a/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js index 4ce624774..d9d4f22b4 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js @@ -27,8 +27,8 @@ describe('#compileListenerRules()', () => { + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { - host: 'example.com', - path: '/hello', + host: ['example.com'], + path: ['/hello'], }, }, { @@ -38,7 +38,7 @@ describe('#compileListenerRules()', () => { + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 2, conditions: { - path: '/world', + path: ['/world'], }, }, ], diff --git a/lib/plugins/aws/package/compile/events/alb/lib/validate.js b/lib/plugins/aws/package/compile/events/alb/lib/validate.js index 9bb821f0a..76ecd8ffe 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/validate.js @@ -2,6 +2,10 @@ const _ = require('lodash'); +// eslint-disable-next-line max-len +const CIDR_IPV6_PATTERN = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))$/; +const CIDR_IPV4_PATTERN = /^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$/; + module.exports = { validate() { const events = []; @@ -14,13 +18,26 @@ module.exports = { listenerArn: event.alb.listenerArn, priority: event.alb.priority, conditions: { - path: event.alb.conditions.path, + // concat usage allows the user to provide value as a string or an array + path: _.concat(event.alb.conditions.path), }, // the following is data which is not defined on the event-level functionName, }; if (event.alb.conditions.host) { - albObj.conditions.host = event.alb.conditions.host; + albObj.conditions.host = _.concat(event.alb.conditions.host); + } + if (event.alb.conditions.method) { + albObj.conditions.method = _.concat(event.alb.conditions.method); + } + if (event.alb.conditions.header) { + albObj.conditions.header = this.validateHeaderCondition(event, functionName); + } + if (event.alb.conditions.query) { + albObj.conditions.query = this.validateQueryCondition(event, functionName); + } + if (event.alb.conditions.ip) { + albObj.conditions.ip = this.validateIpCondition(event, functionName); } events.push(albObj); } @@ -32,4 +49,51 @@ module.exports = { events, }; }, + + validateHeaderCondition(event, functionName) { + const messageTitle = `Invalid ALB event "header" condition in function "${functionName}".`; + if (!_.isObject(event.alb.conditions.header) + || !event.alb.conditions.header.name + || !event.alb.conditions.header.values) { + const errorMessage = [ + messageTitle, + ' You must provide an object with "name" and "values" properties.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + } + if (!_.isArray(event.alb.conditions.header.values)) { + const errorMessage = [ + messageTitle, + ' Property "values" must be an array.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + } + return event.alb.conditions.header; + }, + + validateQueryCondition(event, functionName) { + if (!_.isObject(event.alb.conditions.query)) { + const errorMessage = [ + `Invalid ALB event "query" condition in function "${functionName}".`, + ' You must provide an object.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + } + return event.alb.conditions.query; + }, + + validateIpCondition(event, functionName) { + const cidrBlocks = _.concat(event.alb.conditions.ip); + const allValuesAreCidr = cidrBlocks + .every(cidr => CIDR_IPV4_PATTERN.test(cidr) || CIDR_IPV6_PATTERN.test(cidr)); + + if (!allValuesAreCidr) { + const errorMessage = [ + `Invalid ALB event "ip" condition in function "${functionName}".`, + ' You must provide values in a valid IPv4 or IPv6 CIDR format.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + } + return cidrBlocks; + }, }; diff --git a/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js b/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js index 889bd19f1..103831fc1 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js @@ -30,6 +30,8 @@ describe('#validate()', () => { conditions: { host: 'example.com', path: '/hello', + method: 'GET', + ip: ['192.168.0.1/1', 'fe80:0000:0000:0000:0204:61ff:fe9d:f156/3'], }, }, }, @@ -45,6 +47,10 @@ describe('#validate()', () => { priority: 2, conditions: { path: '/world', + method: ['POST', 'GET'], + query: { + foo: 'bar', + }, }, }, }, @@ -62,8 +68,10 @@ describe('#validate()', () => { + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { - host: 'example.com', - path: '/hello', + host: ['example.com'], + path: ['/hello'], + method: ['GET'], + ip: ['192.168.0.1/1', 'fe80:0000:0000:0000:0204:61ff:fe9d:f156/3'], }, }, { @@ -73,9 +81,132 @@ describe('#validate()', () => { + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 2, conditions: { - path: '/world', + path: ['/world'], + method: ['POST', 'GET'], + query: { + foo: 'bar', + }, }, }, ]); }); + + it('should throw when given an invalid query condition', () => { + awsCompileAlbEvents.serverless.service.functions = { + first: { + events: [ + { + alb: { + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 1, + conditions: { + path: '/hello', + query: 'ss', + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileAlbEvents.validate()).to.throw(Error); + }); + + it('should throw when given an invalid ip condition', () => { + awsCompileAlbEvents.serverless.service.functions = { + first: { + events: [ + { + alb: { + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 1, + conditions: { + path: '/hello', + ip: '1.1.1.1', + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileAlbEvents.validate()).to.throw(Error); + }); + + it('should throw when given an invalid header condition', () => { + awsCompileAlbEvents.serverless.service.functions = { + first: { + events: [ + { + alb: { + listenerArn: 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', + priority: 1, + conditions: { + path: '/hello', + header: ['foo'], + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileAlbEvents.validate()).to.throw(Error); + }); + + describe('#validateIpCondition()', () => { + it('should throw if ip is not a valid ipv6 or ipv4 cidr block', () => { + const event = { alb: { conditions: { ip: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156/' } } }; + expect(() => awsCompileAlbEvents.validateIpCondition(event, '')).to.throw(Error); + }); + + it('should return the value as array if it is a valid ipv6 cidr block', () => { + const event = { alb: { conditions: { ip: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156/127' } } }; + expect(awsCompileAlbEvents.validateIpCondition(event, '')) + .to.deep.equal(['fe80:0000:0000:0000:0204:61ff:fe9d:f156/127']); + }); + + it('should return the value as array if it is a valid ipv4 cidr block', () => { + const event = { alb: { conditions: { ip: '192.168.0.1/21' } } }; + expect(awsCompileAlbEvents.validateIpCondition(event, '')) + .to.deep.equal(['192.168.0.1/21']); + }); + }); + + describe('#validateQueryCondition()', () => { + it('should throw if query is not an object', () => { + const event = { alb: { conditions: { query: 'foo' } } }; + expect(() => awsCompileAlbEvents.validateQueryCondition(event, '')).to.throw(Error); + }); + + it('should return the value if it is an object', () => { + const event = { alb: { conditions: { query: { foo: 'bar' } } } }; + expect(awsCompileAlbEvents.validateQueryCondition(event, '')) + .to.deep.equal({ foo: 'bar' }); + }); + }); + + describe('#validateHeaderCondition()', () => { + it('should throw if header does not have the required properties', () => { + const event = { alb: { conditions: { header: { name: 'foo', value: 'bar' } } } }; + expect(() => awsCompileAlbEvents.validateHeaderCondition(event, '')).to.throw(Error); + }); + + it('should throw if header.values is not an array', () => { + const event = { alb: { conditions: { header: { name: 'foo', values: 'bar' } } } }; + expect(() => awsCompileAlbEvents.validateHeaderCondition(event, '')).to.throw(Error); + }); + + it('should return the value if it is valid', () => { + const event = { alb: { conditions: { header: { name: 'foo', values: ['bar'] } } } }; + expect(awsCompileAlbEvents.validateHeaderCondition(event, '')) + .to.deep.equal({ name: 'foo', values: ['bar'] }); + }); + }); }); From 6eeba11b26508331931f397f621b272d1324ffcd Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 25 Jun 2019 13:06:43 +0200 Subject: [PATCH 398/504] Update documentation --- docs/providers/aws/events/alb.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/providers/aws/events/alb.md b/docs/providers/aws/events/alb.md index 72f58bfce..a367d17aa 100644 --- a/docs/providers/aws/events/alb.md +++ b/docs/providers/aws/events/alb.md @@ -27,6 +27,35 @@ functions: listenerArn: arn:aws:elasticloadbalancing:us-east-1:12345:listener/app/my-load-balancer/50dc6c495c0c9188/ priority: 1 conditions: - host: example.com path: /hello ``` + +## Using different conditions + +```yml +functions: + albEventConsumer: + handler: handler.hello + events: + - alb: + listenerArn: arn:aws:elasticloadbalancing:us-east-1:12345:listener/app/my-load-balancer/50dc6c495c0c9188/ + priority: 1 + conditions: + host: example.com + path: /hello + method: + - POST + - PATCH + host: + - example.com + - example2.com + header: + name: foo + values: + - bar + query: + bar: true + ip: + - fe80:0000:0000:0000:0204:61ff:fe9d:f156/6 + - 192.168.0.1/0 +``` From 0afe7ab03be67c0fe7896badd94b635a2b176c88 Mon Sep 17 00:00:00 2001 From: Adam Fanello Date: Tue, 25 Jun 2019 15:31:19 -0700 Subject: [PATCH 399/504] Add Onica as a Consultant --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b1341bb62..e119df3eb 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ These consultants use the Serverless Framework and can help you build your serve * [OneSpeed](https://onespeed.io/) * [Seraro](http://www.seraro.com/) - Who also runs Atlanta Serverless Meetup (https://www.meetup.com/Atlanta-CABI-Camp-Cloud-AI-Blockchain-IOT) and Delhi Serverless Meetup (https://www.meetup.com/Delhi-NCR-Serverless-Architecture-Meetup/) * [superluminar](https://superluminar.io) - runs serverlessdays Hamburg and Serverless Meetup Hamburg +* [Onica](https://www.onica.com/aws-cloud-native-developers/) - AWS Premier Consulting Partner for Cloud Native Development and host of [eleven regional Meetup groups](https://www.onica.com/events/). ---- ## Licensing From ecc06ca36f5147e2cbd34e80091dce0170c4c3df Mon Sep 17 00:00:00 2001 From: Gareth McCumskey Date: Wed, 26 Jun 2019 05:50:31 +0200 Subject: [PATCH 400/504] Corrected typo in example on documentation page --- docs/providers/aws/events/apigateway.md | 81 +++++++++++++------------ 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 1499502d6..2b9224fba 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -12,45 +12,46 @@ layout: Doc # API Gateway -- [API Gateway](#api-gateway) - - [Lambda Proxy Integration](#lambda-proxy-integration) - - [Simple HTTP Endpoint](#simple-http-endpoint) - - [Example "LAMBDA-PROXY" event (default)](#example-lambda-proxy-event-default) - - [HTTP Endpoint with Extended Options](#http-endpoint-with-extended-options) - - [Enabling CORS](#enabling-cors) - - [HTTP Endpoints with `AWS_IAM` Authorizers](#http-endpoints-with-aws_iam-authorizers) - - [HTTP Endpoints with Custom Authorizers](#http-endpoints-with-custom-authorizers) - - [Catching Exceptions In Your Lambda Function](#catching-exceptions-in-your-lambda-function) - - [Setting API keys for your Rest API](#setting-api-keys-for-your-rest-api) - - [Configuring endpoint types](#configuring-endpoint-types) - - [Request Parameters](#request-parameters) - - [Request Schema Validation](#request-schema-validation) - - [Setting source of API key for metering requests](#setting-source-of-api-key-for-metering-requests) - - [Lambda Integration](#lambda-integration) - - [Example "LAMBDA" event (before customization)](#example-lambda-event-before-customization) - - [Request templates](#request-templates) - - [Default Request Templates](#default-request-templates) - - [Custom Request Templates](#custom-request-templates) - - [Pass Through Behavior](#pass-through-behavior) - - [Responses](#responses) - - [Custom Response Headers](#custom-response-headers) - - [Custom Response Templates](#custom-response-templates) - - [Status Codes](#status-codes) - - [Available Status Codes](#available-status-codes) - - [Using Status Codes](#using-status-codes) - - [Custom Status Codes](#custom-status-codes) - - [Setting an HTTP Proxy on API Gateway](#setting-an-http-proxy-on-api-gateway) - - [Share API Gateway and API Resources](#share-api-gateway-and-api-resources) - - [Easiest and CI/CD friendly example of using shared API Gateway and API Resources.](#easiest-and-cicd-friendly-example-of-using-shared-api-gateway-and-api-resources) - - [Manually Configuring shared API Gateway](#manually-configuring-shared-api-gateway) - - [Note while using authorizers with shared API Gateway](#note-while-using-authorizers-with-shared-api-gateway) - - [Share Authorizer](#share-authorizer) - - [Resource Policy](#resource-policy) - - [Compression](#compression) - - [Binary Media Types](#binary-media-types) - - [AWS X-Ray Tracing](#aws-x-ray-tracing) - - [Tags / Stack Tags](#tags--stack-tags) - - [Logs](#logs) +- [API Gateway](#API-Gateway) + - [Lambda Proxy Integration](#Lambda-Proxy-Integration) + - [Simple HTTP Endpoint](#Simple-HTTP-Endpoint) + - [Example "LAMBDA-PROXY" event (default)](#Example-%22LAMBDA-PROXY%22-event-default) + - [HTTP Endpoint with Extended Options](#HTTP-Endpoint-with-Extended-Options) + - [Enabling CORS](#Enabling-CORS) + - [HTTP Endpoints with `AWS_IAM` Authorizers](#HTTP-Endpoints-with-AWSIAM-Authorizers) + - [HTTP Endpoints with Custom Authorizers](#HTTP-Endpoints-with-Custom-Authorizers) + - [Using asynchronous integration](#Using-asynchronous-integration) + - [Catching Exceptions In Your Lambda Function](#Catching-Exceptions-In-Your-Lambda-Function) + - [Setting API keys for your Rest API](#Setting-API-keys-for-your-Rest-API) + - [Configuring endpoint types](#Configuring-endpoint-types) + - [Request Parameters](#Request-Parameters) + - [Request Schema Validators](#Request-Schema-Validators) + - [Setting source of API key for metering requests](#Setting-source-of-API-key-for-metering-requests) + - [Lambda Integration](#Lambda-Integration) + - [Example "LAMBDA" event (before customization)](#Example-%22LAMBDA%22-event-before-customization) + - [Request templates](#Request-templates) + - [Default Request Templates](#Default-Request-Templates) + - [Custom Request Templates](#Custom-Request-Templates) + - [Pass Through Behavior](#Pass-Through-Behavior) + - [Responses](#Responses) + - [Custom Response Headers](#Custom-Response-Headers) + - [Custom Response Templates](#Custom-Response-Templates) + - [Status Codes](#Status-Codes) + - [Available Status Codes](#Available-Status-Codes) + - [Using Status Codes](#Using-Status-Codes) + - [Custom Status Codes](#Custom-Status-Codes) + - [Setting an HTTP Proxy on API Gateway](#Setting-an-HTTP-Proxy-on-API-Gateway) + - [Share API Gateway and API Resources](#Share-API-Gateway-and-API-Resources) + - [Easiest and CI/CD friendly example of using shared API Gateway and API Resources.](#Easiest-and-CICD-friendly-example-of-using-shared-API-Gateway-and-API-Resources) + - [Manually Configuring shared API Gateway](#Manually-Configuring-shared-API-Gateway) + - [Note while using authorizers with shared API Gateway](#Note-while-using-authorizers-with-shared-API-Gateway) + - [Share Authorizer](#Share-Authorizer) + - [Resource Policy](#Resource-Policy) + - [Compression](#Compression) + - [Binary Media Types](#Binary-Media-Types) + - [AWS X-Ray Tracing](#AWS-X-Ray-Tracing) + - [Tags / Stack Tags](#Tags--Stack-Tags) + - [Logs](#Logs) _Are you looking for tutorials on using API Gateway? Check out the following resources:_ @@ -1157,7 +1158,7 @@ provider: restApiRootResourceId: xxxxxxxxxx description: Some Description restApiResources: - /posts: xxxxxxxxxx + posts: xxxxxxxxxx functions: ... From 34e7db89cfd34cbb2cae7ac5f7ad6250b7d26c5f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 26 Jun 2019 09:50:59 +0200 Subject: [PATCH 401/504] Bump dependencies --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6aa0f1b22..091bf476c 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "eslint-plugin-import": "^1.16.0", "eslint-plugin-jsx-a11y": "^2.2.3", "eslint-plugin-react": "^6.10.3", - "git-list-updated": "^1.1.2", + "git-list-updated": "^1.2.0", "jest-circus": "^24.8.0", "jest-cli": "^24.8.0", "mocha": "^6.1.4", @@ -103,10 +103,10 @@ "strip-ansi": "^5.2.0" }, "dependencies": { - "@serverless/enterprise-plugin": "^1.0.3", + "@serverless/enterprise-plugin": "^1.0.7", "archiver": "^1.3.0", "async": "^1.5.2", - "aws-sdk": "^2.473.0", + "aws-sdk": "^2.482.0", "bluebird": "^3.5.5", "cachedir": "^2.2.0", "chalk": "^2.4.2", From a380b91b0fcff464c4798ecf8fb741a695119729 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 26 Jun 2019 09:55:37 +0200 Subject: [PATCH 402/504] Release v1.46.0 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf612b16..ab82f978c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +# 1.46.0 (2019-06-26) + +- [Fix formatting issue with Markdown link](https://github.com/serverless/serverless/pull/6228) +- [Update docs | dont use provider.tags with shared API Gateway](https://github.com/serverless/serverless/pull/6225) +- [Fix: Update azure template](https://github.com/serverless/serverless/pull/6258) +- [Improve user message](https://github.com/serverless/serverless/pull/6254) +- [Reference custom ApiGateway for models and request validators if conf…](https://github.com/serverless/serverless/pull/6231) +- [Ensure integration tests do not fail when run concurrently](https://github.com/serverless/serverless/pull/6256) +- [Improve integration test experience](https://github.com/serverless/serverless/pull/6253) +- [Fix lambda integration timeout response template](https://github.com/serverless/serverless/pull/6255) +- [Fix duplicate packaging issue](https://github.com/serverless/serverless/pull/6244) +- [Fix Travis configuration for branch/tag runs](https://github.com/serverless/serverless/pull/6265) +- [fixed a typo 🖊](https://github.com/serverless/serverless/pull/6275) +- [Fix #6267](https://github.com/serverless/serverless/pull/6268) +- [#6017 Allow to load plugin from path](https://github.com/serverless/serverless/pull/6261) +- [Added correction based on community feedback](https://github.com/serverless/serverless/pull/6286) +- [Remove package-lock.json and shrinkwrap scripts](https://github.com/serverless/serverless/pull/6280) +- [Remove README redundant link](https://github.com/serverless/serverless/pull/6288) +- [Remove default stage value in provider object](https://github.com/serverless/serverless/pull/6200) +- [Use naming to get stackName](https://github.com/serverless/serverless/pull/6285) +- [Fix typo in link to ALB docs](https://github.com/serverless/serverless/pull/6292) +- [Add ip, method, header and query conditions to ALB events](https://github.com/serverless/serverless/pull/6293) +- [Feature/support external websocket api](https://github.com/serverless/serverless/pull/6272) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.45.1...v1.46.0) + # 1.45.1 (2019-06-12) - [Fix IAM policies setup for functions with custom name](https://github.com/serverless/serverless/pull/6240) diff --git a/package.json b/package.json index 091bf476c..e1ae79e3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.45.1", + "version": "1.46.0", "engines": { "node": ">=6.0" }, From e4db50087fb47c214f3e57ba69f8ad3076fd6341 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Wed, 26 Jun 2019 10:48:41 +0200 Subject: [PATCH 403/504] Update and fix docs --- docs/providers/aws/events/apigateway.md | 107 ++++++++++++++---------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 2b9224fba..37f806765 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -12,46 +12,45 @@ layout: Doc # API Gateway -- [API Gateway](#API-Gateway) - - [Lambda Proxy Integration](#Lambda-Proxy-Integration) - - [Simple HTTP Endpoint](#Simple-HTTP-Endpoint) - - [Example "LAMBDA-PROXY" event (default)](#Example-%22LAMBDA-PROXY%22-event-default) - - [HTTP Endpoint with Extended Options](#HTTP-Endpoint-with-Extended-Options) - - [Enabling CORS](#Enabling-CORS) - - [HTTP Endpoints with `AWS_IAM` Authorizers](#HTTP-Endpoints-with-AWSIAM-Authorizers) - - [HTTP Endpoints with Custom Authorizers](#HTTP-Endpoints-with-Custom-Authorizers) - - [Using asynchronous integration](#Using-asynchronous-integration) - - [Catching Exceptions In Your Lambda Function](#Catching-Exceptions-In-Your-Lambda-Function) - - [Setting API keys for your Rest API](#Setting-API-keys-for-your-Rest-API) - - [Configuring endpoint types](#Configuring-endpoint-types) - - [Request Parameters](#Request-Parameters) - - [Request Schema Validators](#Request-Schema-Validators) - - [Setting source of API key for metering requests](#Setting-source-of-API-key-for-metering-requests) - - [Lambda Integration](#Lambda-Integration) - - [Example "LAMBDA" event (before customization)](#Example-%22LAMBDA%22-event-before-customization) - - [Request templates](#Request-templates) - - [Default Request Templates](#Default-Request-Templates) - - [Custom Request Templates](#Custom-Request-Templates) - - [Pass Through Behavior](#Pass-Through-Behavior) - - [Responses](#Responses) - - [Custom Response Headers](#Custom-Response-Headers) - - [Custom Response Templates](#Custom-Response-Templates) - - [Status Codes](#Status-Codes) - - [Available Status Codes](#Available-Status-Codes) - - [Using Status Codes](#Using-Status-Codes) - - [Custom Status Codes](#Custom-Status-Codes) - - [Setting an HTTP Proxy on API Gateway](#Setting-an-HTTP-Proxy-on-API-Gateway) - - [Share API Gateway and API Resources](#Share-API-Gateway-and-API-Resources) - - [Easiest and CI/CD friendly example of using shared API Gateway and API Resources.](#Easiest-and-CICD-friendly-example-of-using-shared-API-Gateway-and-API-Resources) - - [Manually Configuring shared API Gateway](#Manually-Configuring-shared-API-Gateway) - - [Note while using authorizers with shared API Gateway](#Note-while-using-authorizers-with-shared-API-Gateway) - - [Share Authorizer](#Share-Authorizer) - - [Resource Policy](#Resource-Policy) - - [Compression](#Compression) - - [Binary Media Types](#Binary-Media-Types) - - [AWS X-Ray Tracing](#AWS-X-Ray-Tracing) - - [Tags / Stack Tags](#Tags--Stack-Tags) - - [Logs](#Logs) +- [API Gateway](#api-gateway) + - [Lambda Proxy Integration](#lambda-proxy-integration) + - [Simple HTTP Endpoint](#simple-http-endpoint) + - [Example "LAMBDA-PROXY" event (default)](#example-lambda-proxy-event-default) + - [HTTP Endpoint with Extended Options](#http-endpoint-with-extended-options) + - [Enabling CORS](#enabling-cors) + - [HTTP Endpoints with `AWS_IAM` Authorizers](#http-endpoints-with-aws_iam-authorizers) + - [HTTP Endpoints with Custom Authorizers](#http-endpoints-with-custom-authorizers) + - [Catching Exceptions In Your Lambda Function](#catching-exceptions-in-your-lambda-function) + - [Setting API keys for your Rest API](#setting-api-keys-for-your-rest-api) + - [Configuring endpoint types](#configuring-endpoint-types) + - [Request Parameters](#request-parameters) + - [Request Schema Validation](#request-schema-validation) + - [Setting source of API key for metering requests](#setting-source-of-api-key-for-metering-requests) + - [Lambda Integration](#lambda-integration) + - [Example "LAMBDA" event (before customization)](#example-lambda-event-before-customization) + - [Request templates](#request-templates) + - [Default Request Templates](#default-request-templates) + - [Custom Request Templates](#custom-request-templates) + - [Pass Through Behavior](#pass-through-behavior) + - [Responses](#responses) + - [Custom Response Headers](#custom-response-headers) + - [Custom Response Templates](#custom-response-templates) + - [Status Codes](#status-codes) + - [Available Status Codes](#available-status-codes) + - [Using Status Codes](#using-status-codes) + - [Custom Status Codes](#custom-status-codes) + - [Setting an HTTP Proxy on API Gateway](#setting-an-http-proxy-on-api-gateway) + - [Share API Gateway and API Resources](#share-api-gateway-and-api-resources) + - [Easiest and CI/CD friendly example of using shared API Gateway and API Resources.](#easiest-and-cicd-friendly-example-of-using-shared-api-gateway-and-api-resources) + - [Manually Configuring shared API Gateway](#manually-configuring-shared-api-gateway) + - [Note while using authorizers with shared API Gateway](#note-while-using-authorizers-with-shared-api-gateway) + - [Share Authorizer](#share-authorizer) + - [Resource Policy](#resource-policy) + - [Compression](#compression) + - [Binary Media Types](#binary-media-types) + - [AWS X-Ray Tracing](#aws-x-ray-tracing) + - [Tags / Stack Tags](#tags--stack-tags) + - [Logs](#logs) _Are you looking for tutorials on using API Gateway? Check out the following resources:_ @@ -1104,6 +1103,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx # REST API resource ID. Default is generated by the framework restApiRootResourceId: xxxxxxxxxx # Root resource, represent as / path + websocketApiId: xxxxxxxxxx # Websocket API resource ID. Default is generated by the framewok description: Some Description # optional - description of deployment history functions: @@ -1120,6 +1120,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + websocketApiId: xxxxxxxxxx description: Some Description functions: @@ -1137,6 +1138,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + websocketApiId: xxxxxxxxxx description: Some Description functions: @@ -1156,6 +1158,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + websocketApiId: xxxxxxxxxx description: Some Description restApiResources: posts: xxxxxxxxxx @@ -1171,6 +1174,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx + websocketApiId: xxxxxxxxxx description: Some Description restApiResources: /posts: xxxxxxxxxx @@ -1189,6 +1193,7 @@ provider: apiGateway: restApiId: xxxxxxxxxx # restApiRootResourceId: xxxxxxxxxx # Optional + websocketApiId: xxxxxxxxxx description: Some Description restApiResources: /posts: xxxxxxxxxx @@ -1214,7 +1219,7 @@ functions: ### Easiest and CI/CD friendly example of using shared API Gateway and API Resources. -You can define your API Gateway resource in its own service and export the `restApiId` and `restApiRootResourceId` using cloudformation cross-stack references. +You can define your API Gateway resource in its own service and export the `restApiId`, `restApiRootResourceId` and `websocketApiId` using cloudformation cross-stack references. ```yml service: my-api @@ -1232,6 +1237,13 @@ resources: Properties: Name: MyApiGW + MyWebsocketApi: + Type: AWS::ApiGatewayV2::Api + Properties: + Name: MyWebsocketApi + ProtocolType: WEBSOCKET + RouteSelectionExpression: '$request.body.action' + Outputs: apiGatewayRestApiId: Value: @@ -1246,9 +1258,16 @@ resources: - RootResourceId Export: Name: MyApiGateway-rootResourceId + + websocketApiId: + Value: + Ref: MyWebsocketApi + Export: + Name: MyApiGateway-websocketApiId + ``` -This creates API gateway and then exports the `restApiId` and `rootResourceId` values using cloudformation cross stack output. +This creates API gateway and then exports the `restApiId`, `rootResourceId` and `websocketApiId` values using cloudformation cross stack output. We will import this and reference in future services. ```yml @@ -1260,6 +1279,8 @@ provider: 'Fn::ImportValue': MyApiGateway-restApiId restApiRootResourceId: 'Fn::ImportValue': MyApiGateway-rootResourceId + websocketApiId: + 'Fn::ImportValue': MyApiGateway-websocketApiId functions: service-a-functions @@ -1273,6 +1294,8 @@ provider: 'Fn::ImportValue': MyApiGateway-restApiId restApiRootResourceId: 'Fn::ImportValue': MyApiGateway-rootResourceId + websocketApiId: + 'Fn::ImportValue': MyApiGateway-websocketApiId functions: service-b-functions From 3ed9c479d66baaafee054548b628e19ccdc2273f Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 26 Jun 2019 11:00:22 +0200 Subject: [PATCH 404/504] Ensure deploy is triggered --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8cb78599e..02d055f07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,5 +83,7 @@ jobs: deploy: provider: npm email: services@serverless.com + on: + tags: true api_key: secure: EgoetjrRlGfvGnmVp8A0btr1CzB0hl7owVIpbfk4zXJzhGEbHoVu0CG0IdmyLN+JlaZa7EDJTjkDCd6g3fVAh9TT7ZCeaq8YwbZDrql7mAJj7xYQAyM4eSkc95BRzcFJBx7Mxr6H90IDLxKr6ZtB+HEdiHN+59XbepKYYJeb1jHfnKn5xzOqk4BdnZo6pKfudfeO+7/BwJJ0FwlFA40bY2HS/Lp+NG/2IedNR7k3m/5W83/XH5qlWP8jhBKlxrAzks27aNo+42xHkRCVyPViJKq0mfz1hl2bfswChWHgaCuajp+0amNL39pgIX9eXxFc3bNX9Iftox5t31elEhsw06vvuAaVkKEd+VEMaDySbQ9M+irKZeREg+NFYZLnc2WiEE3Sexo6hm9eM2q2KEZ7bleN9B0CQAut1XXLRQEts80rzss4Z2Q7AZb9cOYBQlj9Wf1X0Y74UqvnDn83a4Y38a+lhx7J2q691ZeM1UFSCdO0QfeJRkB55bSyHqUqrLAqUN7eNsKGdBH0kvYIGFREgGgReEpBRAuNqWuJ/5qexp63Kbf+09raG5IvfxSIM5fJ5KE5VxSduBdRnSH0GNKfjuq296/Rg4fmm/bygZ3Yk5L6Wd41SUU8uHzlZFBwtcvxAKDTQe6s+5JU24ilqxOx6J4Ut34X3dIbLLAmoB1ogdM= From 346faf73f5cd06cf81d9fb137d1d7b4c473c6f05 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 20 Jun 2019 15:34:20 +0200 Subject: [PATCH 405/504] Integrate new ESLint and Prettier config --- .eslintignore | 5 ----- .eslintrc.js | 22 ---------------------- package.json | 23 ++++++++++++++++------- prettier.config.js | 3 +++ 4 files changed, 19 insertions(+), 34 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.js create mode 100644 prettier.config.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index c29438b87..000000000 --- a/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -coverage -node_modules -tmp -tmpdirs-serverless -lib/plugins/create/templates/** diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index f73e9b60f..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports = { - "root": true, - "extends": "airbnb", - "plugins": [], - "rules": { - "arrow-body-style": "off", - "func-names": "off", - "global-require": "off", // Interfers with optional and eventual circular references - "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/scripts/**", "**/tests/**"]}], - "no-param-reassign": "off", - "no-use-before-define": "off", - "react/require-extension": "off", // Forced by airbnb, not applicable (also deprecated) - "strict": ["error", "safe"], // airbnb implies we're transpiling with babel, we're not - }, - "parserOptions": { - "sourceType": "script", // airbnb assumes ESM, while we're CJS - }, - "env": { - "mocha": true, - "jest": true - } -}; diff --git a/package.json b/package.json index e1ae79e3a..65567ee7e 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,19 @@ "integration-test-run-basic": "jest --maxWorkers=5 integration-basic", "integration-test-run-all": "jest --maxWorkers=5 integration-all", "integration-test-cleanup": "node tests/utils/aws-cleanup.js", - "postinstall": "node ./scripts/postinstall.js" + "postinstall": "node ./scripts/postinstall.js", + "prettier-check": "prettier -c --ignore-path .gitignore \"**/*.{css,html,js,json,md,yaml,yml}\"", + "prettier-check-updated": "pipe-git-updated --ext=css --ext=html --ext=js --ext=json --ext=md --ext=yaml --ext=yml -- prettier -c", + "prettify": "prettier --write --ignore-path .gitignore \"**/*.{css,html,js,json,md,yaml,yml}\"", + "prettify-updated": "pipe-git-updated --ext=css --ext=html --ext=js --ext=json --ext=md --ext=yaml --ext=yml -- prettier --write" }, + "eslintConfig": { + "extends": "@serverless/eslint-config/node", + "root": true + }, + "eslintIgnore": [ + "lib/plugins/create/templates/**" + ], "mocha": { "reporter": "tests/mocha-reporter", "timeout": 5000 @@ -77,17 +88,14 @@ "useStderr": true }, "devDependencies": { + "@serverless/eslint-config": "^1.0.0", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "child-process-ext": "^2.0.0", "cli-progress-footer": "^1.1.1", "coveralls": "^3.0.4", - "eslint": "^3.19.0", - "eslint-config-airbnb": "^10.0.1", - "eslint-config-airbnb-base": "^5.0.3", - "eslint-plugin-import": "^1.16.0", - "eslint-plugin-jsx-a11y": "^2.2.3", - "eslint-plugin-react": "^6.10.3", + "eslint": "^6.0.1", + "eslint-plugin-import": "^2.18.0", "git-list-updated": "^1.2.0", "jest-circus": "^24.8.0", "jest-cli": "^24.8.0", @@ -96,6 +104,7 @@ "mock-require": "^3.0.3", "nyc": "^14.1.1", "p-limit": "^2.2.0", + "prettier": "^1.18.2", "process-utils": "^2.3.1", "proxyquire": "^2.1.0", "sinon": "^7.3.2", diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 000000000..5f45afa55 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('@serverless/eslint-config/prettier.config'); From 72ba2326050af9e833032353ef16c7974210161c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 20 Jun 2019 15:48:12 +0200 Subject: [PATCH 406/504] Improve Travis CI configuration --- .travis.yml | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8cb78599e..ef95e3de9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,10 @@ language: node_js git: - depth: 1 # No need for commits history + # Do not take whole history, but ensure to not break things: + # - Merging multiple PR's around same time may introduce a case where it's not + # the last merge commit that is to be tested + depth: 10 branches: only: @@ -19,23 +22,30 @@ stages: jobs: include: - # To speed up Travis build, use one job per Platform + Node.js version combination - - name: "Lint, Unit Tests - Linux - Node.js v12" + # In most cases it's best to configure one job per Platform & Node.js version combination + # (job boot & setup takes ca 1 minute, one task run usually lasts seconds) + + # PR's + - name: ' Lint updated, Unit Tests - Linux - Node.js v12' + if: type = pull_request node_js: 12 env: - SLS_IGNORE_WARNING=* - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' # Combine with '&&' to not continue on fail - script: - - | - if [ $TRAVIS_PULL_REQUEST = false ] - then - npm run lint - else - npm run lint-updated - fi && - npm test - - name: "Unit Tests - Windows - Node.js v12" + script: npm run lint-updated && npm test + + # master branch and version tags + - name: 'Lint, Unit Tests - Linux - Node.js v12' + if: type != pull_request + node_js: 12 + env: + - SLS_IGNORE_WARNING=* + - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' + # Combine with '&&' to not continue on fail + script: npm run lint && npm test + + - name: 'Unit Tests - Windows - Node.js v12' os: windows node_js: 12 env: @@ -49,22 +59,24 @@ jobs: choco install python2 && export PATH="/c/Python27:/c/Python27/Scripts:$PATH" fi - - name: "Isolated Unit Tests, Package Integration Tests - Linux - Node.js v10" + + - name: 'Isolated Unit Tests, Package Integration Tests - Linux - Node.js v10' node_js: 10 env: - SLS_IGNORE_WARNING=* - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' # Combine with '&&' to not continue on fail script: npm run test-isolated && npm run integration-test-run-package - - name: "Unit Tests, Coverage - Linux - Node.js v8" + + - name: 'Unit Tests, Coverage - Linux - Node.js v8' node_js: 8 script: npm run coverage after_success: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage - - name: "Unit Tests - Linux - Node.js v6" + - name: 'Unit Tests - Linux - Node.js v6' node_js: 6 - stage: Integration Test - name: "Integration Tests - Linux - Node.js v12" + name: 'Integration Tests - Linux - Node.js v12' node_js: 12 env: - SLS_IGNORE_WARNING=* From 4e2ed4dd8297561cf575394450b83050758d1d09 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 20 Jun 2019 15:48:44 +0200 Subject: [PATCH 407/504] Add Prettier check to CI --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ef95e3de9..617f760e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,14 +26,14 @@ jobs: # (job boot & setup takes ca 1 minute, one task run usually lasts seconds) # PR's - - name: ' Lint updated, Unit Tests - Linux - Node.js v12' + - name: 'Prettier check updated, Lint updated, Unit Tests - Linux - Node.js v12' if: type = pull_request node_js: 12 env: - SLS_IGNORE_WARNING=* - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' # Combine with '&&' to not continue on fail - script: npm run lint-updated && npm test + script: npm run prettier-check-updated && npm run lint-updated && npm test # master branch and version tags - name: 'Lint, Unit Tests - Linux - Node.js v12' From f98b90ef2f9b01f77d0176c63f72b53d6f0a1c3c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 21 Jun 2019 10:04:21 +0200 Subject: [PATCH 408/504] Configure test-ci script It covers all checks needed for CI to pass --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 65567ee7e..52d791cb9 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ }, "scripts": { "test": "mocha \"!(node_modules)/**/*.test.js\"", + "test-ci": "npm run prettier-check-updated && npm run lint-updated && npm run test-isolated", "test-isolated": "node scripts/test-isolated.js", "coverage": "nyc --reporter=lcov --reporter=html --reporter=text-summary npm test", "lint": "eslint . --cache", From 81b419bad5ee0e24bd07ad96c6a999a2058fce3b Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 26 Jun 2019 12:31:00 +0200 Subject: [PATCH 409/504] Do not trim whitespace in markdown --- .editorconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.editorconfig b/.editorconfig index 49dafa5e2..28009b667 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,3 +8,6 @@ indent_size = 2 indent_style = space insert_final_newline = true trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false From 2a870d025147e18572536c5a5ddea6a5c1cd3725 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 24 Jun 2019 11:42:50 +0200 Subject: [PATCH 410/504] Document all validation checks in PR template --- .github/PULL_REQUEST_TEMPLATE.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 35838a3e7..1e97020d5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -34,13 +34,21 @@ Examples: ## Todos: -- [ ] Write tests +_Note: Run `npm run test-ci` to run all validation checks on proposed changes_ + +- [ ] Write tests and confirm existing functionality is not broken. + **Validate via `npm test`** - [ ] Write documentation -- [ ] Fix linting errors +- [ ] Ensure there are no lint errors. + **Validate via `npm run lint-updated`** + _Note: Some reported issues can be automatically fixed by running `npm run lint:fix`_ +- [ ] Ensure introduced changes match Prettier formatting. + **Validate via `npm run prettier-check-updated`** + _Note: All reported issues can be automatically fixed by running `npm run prettify-updated`_ - [ ] Make sure code coverage hasn't dropped - [ ] Provide verification config / commands / resources - [ ] Enable "Allow edits from maintainers" for this PR - [ ] Update the messages below -***Is this ready for review?:*** NO -***Is it a breaking change?:*** NO +**_Is this ready for review?:_** NO +**_Is it a breaking change?:_** NO From f46dc0321d7627d10c341ed792f28d1487073dea Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 21 Jun 2019 11:38:22 +0200 Subject: [PATCH 411/504] Add '.js' extension to binary --- bin/serverless | 66 ++++------------------------------------------- bin/serverless.js | 65 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 ++--- 3 files changed, 73 insertions(+), 64 deletions(-) create mode 100755 bin/serverless.js diff --git a/bin/serverless b/bin/serverless index b8cff829c..a0afa2c22 100755 --- a/bin/serverless +++ b/bin/serverless @@ -1,65 +1,9 @@ #!/usr/bin/env node +// TODO: Remove with file with next major (breaking) release +// Left as a fallback placeholder to not accidentally break things +// https://github.com/search?q=%22bin%2Fserverless%22&type=Code + 'use strict'; -const autocomplete = require('../lib/utils/autocomplete'); -const BbPromise = require('bluebird'); -const logError = require('../lib/classes/Error').logError; -const uuid = require('uuid'); -const initializeErrorReporter = require('../lib/utils/sentry').initializeErrorReporter; - -Error.stackTraceLimit = Infinity; - -if (process.env.SLS_DEBUG) { - // For performance reasons enabled only in SLS_DEBUG mode - BbPromise.config({ - longStackTraces: true, - }); -} - -process.on('unhandledRejection', (e) => { - logError(e); -}); -process.noDeprecation = true; - -const invocationId = uuid.v4(); - -// boot up error reporting via sentry before anything -(() => initializeErrorReporter(invocationId).then(() => { - if (process.argv[2] === 'completion') { - return autocomplete(); - } - // requiring here so that if anything went wrong, - // during require, it will be caught. - const Serverless = require('../lib/Serverless'); // eslint-disable-line global-require - - const serverless = new Serverless({ - interactive: typeof process.env.CI === 'undefined', - }); - - serverless.invocationId = invocationId; - - return serverless.init() - .then(() => serverless.run()) - .then(() => process.exit(0)) - .catch((err) => { - // If Enterprise Plugin, capture error - let enterpriseErrorHandler = null; - serverless.pluginManager.plugins.forEach((p) => { - if (p.enterprise && p.enterprise.errorHandler) { - enterpriseErrorHandler = p.enterprise.errorHandler; - } - }); - if (!enterpriseErrorHandler) { throw err; } - return enterpriseErrorHandler(err, invocationId) - .catch((error) => { - console.log(error) - }) - .then(() => { - throw err - }); - }) -}).catch(e => { - process.exitCode = 1; - logError(e); -}))(); +require('./serverless.js'); diff --git a/bin/serverless.js b/bin/serverless.js new file mode 100755 index 000000000..b8cff829c --- /dev/null +++ b/bin/serverless.js @@ -0,0 +1,65 @@ +#!/usr/bin/env node + +'use strict'; + +const autocomplete = require('../lib/utils/autocomplete'); +const BbPromise = require('bluebird'); +const logError = require('../lib/classes/Error').logError; +const uuid = require('uuid'); +const initializeErrorReporter = require('../lib/utils/sentry').initializeErrorReporter; + +Error.stackTraceLimit = Infinity; + +if (process.env.SLS_DEBUG) { + // For performance reasons enabled only in SLS_DEBUG mode + BbPromise.config({ + longStackTraces: true, + }); +} + +process.on('unhandledRejection', (e) => { + logError(e); +}); +process.noDeprecation = true; + +const invocationId = uuid.v4(); + +// boot up error reporting via sentry before anything +(() => initializeErrorReporter(invocationId).then(() => { + if (process.argv[2] === 'completion') { + return autocomplete(); + } + // requiring here so that if anything went wrong, + // during require, it will be caught. + const Serverless = require('../lib/Serverless'); // eslint-disable-line global-require + + const serverless = new Serverless({ + interactive: typeof process.env.CI === 'undefined', + }); + + serverless.invocationId = invocationId; + + return serverless.init() + .then(() => serverless.run()) + .then(() => process.exit(0)) + .catch((err) => { + // If Enterprise Plugin, capture error + let enterpriseErrorHandler = null; + serverless.pluginManager.plugins.forEach((p) => { + if (p.enterprise && p.enterprise.errorHandler) { + enterpriseErrorHandler = p.enterprise.errorHandler; + } + }); + if (!enterpriseErrorHandler) { throw err; } + return enterpriseErrorHandler(err, invocationId) + .catch((error) => { + console.log(error) + }) + .then(() => { + throw err + }); + }) +}).catch(e => { + process.exitCode = 1; + logError(e); +}))(); diff --git a/package.json b/package.json index 52d791cb9..0dd870d93 100644 --- a/package.json +++ b/package.json @@ -46,9 +46,9 @@ ], "main": "lib/Serverless.js", "bin": { - "serverless": "./bin/serverless", - "slss": "./bin/serverless", - "sls": "./bin/serverless" + "serverless": "./bin/serverless.js", + "slss": "./bin/serverless.js", + "sls": "./bin/serverless.js" }, "scripts": { "test": "mocha \"!(node_modules)/**/*.test.js\"", From 583197adfe8afa54f408d68595baa83660e7828e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 21 Jun 2019 11:40:13 +0200 Subject: [PATCH 412/504] Fix lint error --- bin/serverless.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/serverless.js b/bin/serverless.js index b8cff829c..c9de3a6cc 100755 --- a/bin/serverless.js +++ b/bin/serverless.js @@ -53,7 +53,7 @@ const invocationId = uuid.v4(); if (!enterpriseErrorHandler) { throw err; } return enterpriseErrorHandler(err, invocationId) .catch((error) => { - console.log(error) + process.stdout.write(`${error.stack}\n`) }) .then(() => { throw err From 4944f471b14bd9398ea9c05a35d81b7f2eb62b02 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 26 Jun 2019 12:02:01 +0200 Subject: [PATCH 413/504] Prettify --- .github/ISSUE_TEMPLATE/bug_report.md | 19 +- .github/ISSUE_TEMPLATE/feature_request.md | 7 +- 0.5.x-RESOURCES.md | 68 +- CHANGELOG.md | 13 +- CODE_OF_CONDUCT.md | 22 +- CONTRIBUTING.md | 10 +- README.md | 231 ++-- RELEASE_CHECKLIST.md | 3 +- VERSIONING.md | 16 +- bin/serverless.js | 78 +- docker-compose.yml | 4 +- docs/README.md | 2 + docs/providers/README.md | 2 + docs/providers/aws/README.md | 2 + docs/providers/aws/cli-reference/README.md | 4 +- .../aws/cli-reference/config-credentials.md | 2 + docs/providers/aws/cli-reference/create.md | 4 + .../aws/cli-reference/deploy-function.md | 5 +- .../aws/cli-reference/deploy-list.md | 2 + docs/providers/aws/cli-reference/deploy.md | 5 +- docs/providers/aws/cli-reference/info.md | 4 + docs/providers/aws/cli-reference/install.md | 4 + .../aws/cli-reference/invoke-local.md | 12 +- docs/providers/aws/cli-reference/invoke.md | 14 +- docs/providers/aws/cli-reference/login.md | 2 + docs/providers/aws/cli-reference/logs.md | 6 + docs/providers/aws/cli-reference/metrics.md | 2 + docs/providers/aws/cli-reference/package.md | 3 + .../aws/cli-reference/plugin-install.md | 4 + .../aws/cli-reference/plugin-list.md | 6 +- .../aws/cli-reference/plugin-search.md | 4 + .../aws/cli-reference/plugin-uninstall.md | 4 + docs/providers/aws/cli-reference/print.md | 2 + docs/providers/aws/cli-reference/remove.md | 6 +- .../aws/cli-reference/rollback-function.md | 5 +- docs/providers/aws/cli-reference/rollback.md | 7 +- docs/providers/aws/cli-reference/slstats.md | 6 +- docs/providers/aws/events/README.md | 2 + docs/providers/aws/events/alb.md | 2 + docs/providers/aws/events/alexa-skill.md | 2 + docs/providers/aws/events/alexa-smart-home.md | 2 + docs/providers/aws/events/apigateway.md | 388 +++--- docs/providers/aws/events/cloudwatch-event.md | 30 +- docs/providers/aws/events/cloudwatch-log.md | 2 + .../providers/aws/events/cognito-user-pool.md | 5 +- docs/providers/aws/events/iot.md | 8 +- docs/providers/aws/events/s3.md | 13 +- docs/providers/aws/events/schedule.md | 2 + docs/providers/aws/events/sns.md | 12 +- docs/providers/aws/events/sqs.md | 4 +- docs/providers/aws/events/streams.md | 7 +- docs/providers/aws/events/websocket.md | 50 +- docs/providers/aws/examples/README.md | 30 +- .../aws/examples/hello-world/README.md | 12 +- .../aws/examples/hello-world/csharp/README.md | 3 +- .../aws/examples/hello-world/fsharp/README.md | 160 +-- .../aws/examples/hello-world/go/README.md | 4 +- .../aws/examples/hello-world/node/README.md | 2 + .../aws/examples/hello-world/node/handler.js | 2 +- .../aws/examples/hello-world/python/README.md | 2 + .../aws/examples/hello-world/ruby/README.md | 6 +- docs/providers/aws/guide/README.md | 2 + docs/providers/aws/guide/credentials.md | 19 +- docs/providers/aws/guide/deploying.md | 66 +- docs/providers/aws/guide/events.md | 4 +- docs/providers/aws/guide/functions.md | 46 +- docs/providers/aws/guide/iam.md | 53 +- docs/providers/aws/guide/installation.md | 2 + docs/providers/aws/guide/intro.md | 46 +- docs/providers/aws/guide/layers.md | 7 +- docs/providers/aws/guide/packaging.md | 12 +- docs/providers/aws/guide/plugins.md | 89 +- docs/providers/aws/guide/quick-start.md | 43 +- docs/providers/aws/guide/resources.md | 70 +- docs/providers/aws/guide/serverless.yml.md | 27 +- docs/providers/aws/guide/services.md | 85 +- docs/providers/aws/guide/testing.md | 45 +- docs/providers/aws/guide/v0_to_v1.md | 2 + docs/providers/aws/guide/variables.md | 172 ++- docs/providers/aws/guide/workflow.md | 31 +- docs/providers/azure/README.md | 2 + docs/providers/azure/cli-reference/README.md | 4 +- docs/providers/azure/cli-reference/create.md | 7 +- .../azure/cli-reference/deploy-function.md | 5 +- docs/providers/azure/cli-reference/deploy.md | 5 +- docs/providers/azure/cli-reference/install.md | 4 + docs/providers/azure/cli-reference/invoke.md | 4 + docs/providers/azure/cli-reference/login.md | 2 + docs/providers/azure/cli-reference/logs.md | 3 + .../azure/cli-reference/plugin-install.md | 4 + .../azure/cli-reference/plugin-list.md | 6 +- .../azure/cli-reference/plugin-search.md | 4 + .../azure/cli-reference/plugin-uninstall.md | 4 + docs/providers/azure/cli-reference/print.md | 2 + docs/providers/azure/cli-reference/remove.md | 3 + docs/providers/azure/events/README.md | 4 +- docs/providers/azure/events/blobstorage.md | 10 +- docs/providers/azure/events/eventhubs.md | 12 +- docs/providers/azure/events/http.md | 30 +- docs/providers/azure/events/other.md | 24 +- docs/providers/azure/events/queuestorage.md | 8 +- docs/providers/azure/events/servicebus.md | 22 +- docs/providers/azure/events/timer.md | 8 +- docs/providers/azure/examples/README.md | 8 +- .../azure/examples/hello-world/README.md | 4 +- .../azure/examples/hello-world/node/README.md | 2 + .../examples/hello-world/node/serverless.yml | 14 +- docs/providers/azure/guide/README.md | 2 + docs/providers/azure/guide/credentials.md | 116 +- docs/providers/azure/guide/deploying.md | 22 +- docs/providers/azure/guide/events.md | 4 +- docs/providers/azure/guide/functions.md | 22 +- docs/providers/azure/guide/installation.md | 2 + docs/providers/azure/guide/intro.md | 29 +- docs/providers/azure/guide/packaging.md | 6 +- docs/providers/azure/guide/plugins.md | 99 +- docs/providers/azure/guide/quick-start.md | 60 +- docs/providers/azure/guide/services.md | 36 +- docs/providers/azure/guide/testing.md | 44 +- docs/providers/azure/guide/variables.md | 55 +- docs/providers/azure/guide/workflow.md | 12 +- docs/providers/cloudflare/README.md | 2 + .../cloudflare/cli-reference/README.md | 4 +- .../cloudflare/cli-reference/create.md | 32 +- .../cloudflare/cli-reference/deploy.md | 10 +- .../cloudflare/cli-reference/invoke.md | 22 +- .../cli-reference/plugin-install.md | 4 + .../cloudflare/cli-reference/plugin-list.md | 6 +- .../cloudflare/cli-reference/plugin-search.md | 4 + .../cli-reference/plugin-uninstall.md | 4 + .../cloudflare/cli-reference/remove.md | 8 +- docs/providers/cloudflare/events/README.md | 3 +- docs/providers/cloudflare/events/http.md | 16 +- docs/providers/cloudflare/guide/README.md | 2 + docs/providers/cloudflare/guide/debugging.md | 32 +- docs/providers/cloudflare/guide/deploying.md | 42 +- docs/providers/cloudflare/guide/events.md | 14 +- docs/providers/cloudflare/guide/functions.md | 57 +- .../cloudflare/guide/installation.md | 17 +- docs/providers/cloudflare/guide/intro.md | 84 +- .../providers/cloudflare/guide/quick-start.md | 52 +- docs/providers/cloudflare/guide/services.md | 148 +- docs/providers/cloudflare/guide/workflow.md | 33 +- docs/providers/fn/README.md | 2 + docs/providers/fn/cli-reference/README.md | 4 +- docs/providers/fn/cli-reference/create.md | 4 + docs/providers/fn/cli-reference/deploy.md | 4 + docs/providers/fn/cli-reference/info.md | 3 + docs/providers/fn/cli-reference/invoke.md | 4 + docs/providers/fn/cli-reference/logs.md | 3 + .../fn/cli-reference/plugin-install.md | 4 + .../providers/fn/cli-reference/plugin-list.md | 6 +- .../fn/cli-reference/plugin-search.md | 4 + .../fn/cli-reference/plugin-uninstall.md | 4 + docs/providers/fn/cli-reference/remove.md | 3 + docs/providers/fn/events/README.md | 5 +- docs/providers/fn/events/http.md | 6 +- docs/providers/fn/guide/README.md | 4 +- docs/providers/fn/guide/debugging.md | 26 +- docs/providers/fn/guide/deploying.md | 12 +- docs/providers/fn/guide/events.md | 2 + docs/providers/fn/guide/installation.md | 2 + docs/providers/fn/guide/intro.md | 33 +- docs/providers/fn/guide/quick-start.md | 41 +- docs/providers/fn/guide/workflow.md | 22 +- docs/providers/google/README.md | 2 + docs/providers/google/cli-reference/README.md | 4 +- docs/providers/google/cli-reference/create.md | 2 + docs/providers/google/cli-reference/deploy.md | 3 + docs/providers/google/cli-reference/info.md | 2 + .../providers/google/cli-reference/install.md | 2 + .../google/cli-reference/invoke-local.md | 14 +- docs/providers/google/cli-reference/invoke.md | 2 + docs/providers/google/cli-reference/login.md | 2 + docs/providers/google/cli-reference/logs.md | 2 + .../providers/google/cli-reference/package.md | 2 + .../google/cli-reference/plugin-install.md | 4 + .../google/cli-reference/plugin-list.md | 6 +- .../google/cli-reference/plugin-search.md | 4 + .../google/cli-reference/plugin-uninstall.md | 4 + docs/providers/google/cli-reference/print.md | 2 + docs/providers/google/cli-reference/remove.md | 2 + docs/providers/google/events/README.md | 2 + docs/providers/google/events/event.md | 2 + docs/providers/google/events/http.md | 7 +- docs/providers/google/examples/README.md | 10 +- .../google/examples/hello-world/README.md | 6 +- .../examples/hello-world/node/README.md | 2 + .../examples/hello-world/python/README.md | 2 + docs/providers/google/guide/README.md | 2 + docs/providers/google/guide/credentials.md | 8 +- docs/providers/google/guide/deploying.md | 4 +- docs/providers/google/guide/events.md | 2 + docs/providers/google/guide/functions.md | 10 +- docs/providers/google/guide/installation.md | 2 + docs/providers/google/guide/intro.md | 18 +- docs/providers/google/guide/packaging.md | 7 +- docs/providers/google/guide/plugins.md | 82 +- docs/providers/google/guide/services.md | 18 +- docs/providers/google/guide/variables.md | 53 +- docs/providers/google/guide/workflow.md | 8 +- docs/providers/kubeless/README.md | 2 + .../kubeless/cli-reference/README.md | 4 +- .../kubeless/cli-reference/create.md | 4 + .../kubeless/cli-reference/deploy.md | 4 + docs/providers/kubeless/cli-reference/info.md | 8 +- .../kubeless/cli-reference/invoke.md | 4 + docs/providers/kubeless/cli-reference/logs.md | 5 + .../kubeless/cli-reference/plugin-install.md | 4 + .../kubeless/cli-reference/plugin-list.md | 6 +- .../kubeless/cli-reference/plugin-search.md | 4 + .../cli-reference/plugin-uninstall.md | 4 + .../kubeless/cli-reference/remove.md | 4 + docs/providers/kubeless/events/README.md | 5 +- docs/providers/kubeless/events/http.md | 3 +- docs/providers/kubeless/events/pubsub.md | 2 + docs/providers/kubeless/events/scheduler.md | 2 + docs/providers/kubeless/guide/README.md | 4 +- docs/providers/kubeless/guide/debugging.md | 20 +- docs/providers/kubeless/guide/deploying.md | 7 +- docs/providers/kubeless/guide/events.md | 2 + docs/providers/kubeless/guide/functions.md | 26 +- docs/providers/kubeless/guide/installation.md | 3 +- docs/providers/kubeless/guide/intro.md | 31 +- docs/providers/kubeless/guide/packaging.md | 6 +- docs/providers/kubeless/guide/quick-start.md | 39 +- docs/providers/kubeless/guide/services.md | 30 +- docs/providers/kubeless/guide/workflow.md | 20 +- docs/providers/openwhisk/README.md | 2 + .../openwhisk/cli-reference/README.md | 4 +- .../cli-reference/config-credentials.md | 2 + .../openwhisk/cli-reference/create.md | 4 + .../cli-reference/deploy-function.md | 5 +- .../openwhisk/cli-reference/deploy.md | 6 +- .../providers/openwhisk/cli-reference/info.md | 4 + .../openwhisk/cli-reference/install.md | 4 + .../openwhisk/cli-reference/invoke-local.md | 5 +- .../openwhisk/cli-reference/invoke.md | 7 +- .../openwhisk/cli-reference/login.md | 2 + .../providers/openwhisk/cli-reference/logs.md | 5 + .../openwhisk/cli-reference/plugin-install.md | 4 + .../openwhisk/cli-reference/plugin-list.md | 6 +- .../openwhisk/cli-reference/plugin-search.md | 4 + .../cli-reference/plugin-uninstall.md | 4 + .../openwhisk/cli-reference/print.md | 14 +- .../openwhisk/cli-reference/remove.md | 3 + .../openwhisk/cli-reference/slstats.md | 4 + docs/providers/openwhisk/events/README.md | 4 +- docs/providers/openwhisk/events/apigateway.md | 6 +- docs/providers/openwhisk/events/cloudant.md | 63 +- docs/providers/openwhisk/events/messagehub.md | 70 +- docs/providers/openwhisk/events/schedule.md | 5 +- docs/providers/openwhisk/events/triggers.md | 7 +- docs/providers/openwhisk/examples/README.md | 16 +- .../openwhisk/examples/hello-world/README.md | 12 +- .../examples/hello-world/node/README.md | 10 +- .../examples/hello-world/node/handler.js | 2 +- .../examples/hello-world/node/serverless.yml | 4 +- .../examples/hello-world/php/README.md | 8 +- .../examples/hello-world/php/serverless.yml | 4 +- .../examples/hello-world/python/README.md | 8 +- .../hello-world/python/serverless.yml | 4 +- .../examples/hello-world/ruby/README.md | 12 +- .../examples/hello-world/swift/README.md | 8 +- .../examples/hello-world/swift/serverless.yml | 4 +- docs/providers/openwhisk/guide/README.md | 2 + docs/providers/openwhisk/guide/credentials.md | 21 +- docs/providers/openwhisk/guide/deploying.md | 30 +- docs/providers/openwhisk/guide/events.md | 6 +- docs/providers/openwhisk/guide/functions.md | 30 +- .../providers/openwhisk/guide/installation.md | 2 + docs/providers/openwhisk/guide/intro.md | 38 +- docs/providers/openwhisk/guide/packaging.md | 6 +- docs/providers/openwhisk/guide/plugins.md | 86 +- docs/providers/openwhisk/guide/quick-start.md | 56 +- .../openwhisk/guide/serverless.yml.md | 8 +- docs/providers/openwhisk/guide/services.md | 39 +- docs/providers/openwhisk/guide/testing.md | 46 +- docs/providers/openwhisk/guide/variables.md | 102 +- docs/providers/openwhisk/guide/web-actions.md | 25 +- docs/providers/openwhisk/guide/workflow.md | 22 +- docs/providers/spotinst/README.md | 2 + .../spotinst/cli-reference/README.md | 2 + .../cli-reference/config-credentials.md | 4 +- .../spotinst/cli-reference/create.md | 4 +- .../spotinst/cli-reference/deploy-function.md | 12 +- .../spotinst/cli-reference/deploy.md | 3 + docs/providers/spotinst/cli-reference/info.md | 3 + .../spotinst/cli-reference/invoke.md | 2 + docs/providers/spotinst/cli-reference/logs.md | 10 +- .../spotinst/cli-reference/plugin-install.md | 4 + .../spotinst/cli-reference/plugin-list.md | 6 +- .../spotinst/cli-reference/plugin-search.md | 4 + .../cli-reference/plugin-uninstall.md | 4 + .../spotinst/cli-reference/remove.md | 5 +- .../providers/spotinst/cli-reference/stage.md | 17 +- docs/providers/spotinst/events/README.md | 2 + docs/providers/spotinst/events/http.md | 4 +- docs/providers/spotinst/events/schedule.md | 8 +- docs/providers/spotinst/examples/README.md | 12 +- .../spotinst/examples/java8/README.md | 17 +- .../spotinst/examples/node/README.md | 17 +- .../spotinst/examples/python/README.md | 17 +- .../spotinst/examples/ruby/README.md | 8 +- docs/providers/spotinst/guide/IAM-roles.md | 14 +- docs/providers/spotinst/guide/README.md | 2 + .../spotinst/guide/active-versions.md | 21 +- docs/providers/spotinst/guide/cors.md | 43 +- docs/providers/spotinst/guide/create-token.md | 19 +- docs/providers/spotinst/guide/credentials.md | 8 +- docs/providers/spotinst/guide/endpoints.md | 27 +- docs/providers/spotinst/guide/intro.md | 33 +- docs/providers/spotinst/guide/quick-start.md | 59 +- .../spotinst/guide/serverless.yml.md | 18 +- docs/providers/spotinst/guide/variables.md | 8 +- lib/Serverless.js | 48 +- lib/Serverless.test.js | 51 +- lib/classes/CLI.js | 76 +- lib/classes/CLI.test.js | 55 +- lib/classes/Config.js | 2 - lib/classes/Error.js | 16 +- lib/classes/Error.test.js | 5 +- lib/classes/PluginManager.js | 209 +-- lib/classes/PluginManager.test.js | 585 ++++---- lib/classes/PromiseTracker.js | 47 +- lib/classes/PromiseTracker.test.js | 20 +- lib/classes/Service.js | 33 +- lib/classes/Service.test.js | 437 +++--- lib/classes/Utils.js | 126 +- lib/classes/Utils.test.js | 65 +- lib/classes/Variables.js | 257 ++-- lib/classes/Variables.test.js | 1087 ++++++++------- lib/classes/YamlParser.js | 3 +- lib/classes/YamlParser.test.js | 15 +- lib/plugins/aws/common/index.js | 37 +- lib/plugins/aws/common/index.test.js | 31 +- lib/plugins/aws/common/lib/artifacts.js | 6 +- .../aws/common/lib/cleanupTempDir.test.js | 21 +- .../configCredentials/awsConfigCredentials.js | 30 +- .../awsConfigCredentials.test.js | 24 +- lib/plugins/aws/deploy/index.js | 96 +- lib/plugins/aws/deploy/index.test.js | 131 +- lib/plugins/aws/deploy/lib/checkForChanges.js | 153 ++- .../aws/deploy/lib/checkForChanges.test.js | 529 ++++--- lib/plugins/aws/deploy/lib/cleanupS3Bucket.js | 17 +- .../aws/deploy/lib/cleanupS3Bucket.test.js | 149 +- lib/plugins/aws/deploy/lib/createStack.js | 67 +- .../aws/deploy/lib/createStack.test.js | 64 +- .../aws/deploy/lib/existsDeploymentBucket.js | 9 +- .../deploy/lib/existsDeploymentBucket.test.js | 64 +- .../aws/deploy/lib/extendedValidate.js | 39 +- .../aws/deploy/lib/extendedValidate.test.js | 22 +- lib/plugins/aws/deploy/lib/uploadArtifacts.js | 60 +- .../aws/deploy/lib/uploadArtifacts.test.js | 171 +-- .../aws/deploy/lib/validateTemplate.js | 13 +- .../aws/deploy/lib/validateTemplate.test.js | 8 +- lib/plugins/aws/deployFunction/index.js | 92 +- lib/plugins/aws/deployFunction/index.test.js | 313 +++-- lib/plugins/aws/deployList/index.js | 98 +- lib/plugins/aws/deployList/index.test.js | 76 +- lib/plugins/aws/info/display.js | 20 +- lib/plugins/aws/info/display.test.js | 31 +- lib/plugins/aws/info/getApiKeyValues.js | 15 +- lib/plugins/aws/info/getResourceCount.js | 17 +- lib/plugins/aws/info/getResourceCount.test.js | 134 +- lib/plugins/aws/info/getStackInfo.js | 91 +- lib/plugins/aws/info/getStackInfo.test.js | 26 +- lib/plugins/aws/info/index.js | 43 +- lib/plugins/aws/info/index.test.js | 36 +- lib/plugins/aws/invoke/index.js | 32 +- lib/plugins/aws/invoke/index.test.js | 122 +- lib/plugins/aws/invokeLocal/index.js | 379 ++--- lib/plugins/aws/invokeLocal/index.test.js | 618 ++++----- lib/plugins/aws/lib/getServiceState.test.js | 3 +- lib/plugins/aws/lib/monitorStack.js | 68 +- lib/plugins/aws/lib/monitorStack.test.js | 309 ++--- lib/plugins/aws/lib/naming.js | 129 +- lib/plugins/aws/lib/naming.test.js | 370 ++--- lib/plugins/aws/lib/normalizeFiles.js | 5 +- lib/plugins/aws/lib/setBucketName.js | 7 +- lib/plugins/aws/lib/setBucketName.test.js | 10 +- lib/plugins/aws/lib/updateStack.js | 63 +- lib/plugins/aws/lib/updateStack.test.js | 137 +- lib/plugins/aws/lib/validate.js | 5 +- lib/plugins/aws/lib/validate.test.js | 31 +- lib/plugins/aws/lib/validateS3BucketName.js | 53 +- .../aws/lib/validateS3BucketName.test.js | 63 +- lib/plugins/aws/logs/index.js | 101 +- lib/plugins/aws/logs/index.test.js | 170 ++- lib/plugins/aws/metrics/awsMetrics.js | 29 +- lib/plugins/aws/metrics/awsMetrics.test.js | 94 +- .../aws/package/compile/events/alb/index.js | 8 +- .../package/compile/events/alb/index.test.js | 27 +- .../compile/events/alb/lib/listenerRules.js | 13 +- .../events/alb/lib/listenerRules.test.js | 44 +- .../compile/events/alb/lib/permissions.js | 10 +- .../events/alb/lib/permissions.test.js | 28 +- .../compile/events/alb/lib/targetGroups.js | 13 +- .../events/alb/lib/targetGroups.test.js | 28 +- .../compile/events/alb/lib/validate.js | 20 +- .../compile/events/alb/lib/validate.test.js | 66 +- .../compile/events/alexaSkill/index.js | 25 +- .../compile/events/alexaSkill/index.test.js | 128 +- .../compile/events/alexaSmartHome/index.js | 32 +- .../events/alexaSmartHome/index.test.js | 118 +- .../compile/events/apiGateway/index.js | 3 +- .../compile/events/apiGateway/index.test.js | 51 +- .../compile/events/apiGateway/lib/apiKeys.js | 30 +- .../events/apiGateway/lib/apiKeys.test.js | 61 +- .../events/apiGateway/lib/authorizers.js | 14 +- .../events/apiGateway/lib/authorizers.test.js | 147 +- .../compile/events/apiGateway/lib/cors.js | 31 +- .../events/apiGateway/lib/cors.test.js | 185 ++- .../events/apiGateway/lib/deployment.js | 8 +- .../events/apiGateway/lib/deployment.test.js | 66 +- .../lib/hack/disassociateUsagePlan.js | 56 +- .../lib/hack/disassociateUsagePlan.test.js | 81 +- .../events/apiGateway/lib/hack/updateStage.js | 84 +- .../apiGateway/lib/hack/updateStage.test.js | 85 +- .../apiGateway/lib/method/authorization.js | 14 +- .../events/apiGateway/lib/method/index.js | 29 +- .../apiGateway/lib/method/index.test.js | 444 +++--- .../apiGateway/lib/method/integration.js | 29 +- .../events/apiGateway/lib/method/responses.js | 14 +- .../events/apiGateway/lib/permissions.js | 28 +- .../events/apiGateway/lib/permissions.test.js | 41 +- .../events/apiGateway/lib/resources.js | 79 +- .../events/apiGateway/lib/resources.test.js | 167 ++- .../compile/events/apiGateway/lib/restApi.js | 28 +- .../events/apiGateway/lib/restApi.test.js | 36 +- .../compile/events/apiGateway/lib/stage.js | 37 +- .../events/apiGateway/lib/stage.test.js | 93 +- .../events/apiGateway/lib/usagePlan.js | 60 +- .../events/apiGateway/lib/usagePlan.test.js | 158 ++- .../events/apiGateway/lib/usagePlanKeys.js | 25 +- .../apiGateway/lib/usagePlanKeys.test.js | 89 +- .../compile/events/apiGateway/lib/validate.js | 38 +- .../events/apiGateway/lib/validate.test.js | 235 ++-- .../compile/events/cloudWatchEvent/index.js | 36 +- .../events/cloudWatchEvent/index.test.js | 149 +- .../compile/events/cloudWatchLog/index.js | 45 +- .../events/cloudWatchLog/index.test.js | 199 +-- .../compile/events/cognitoUserPool/index.js | 116 +- .../events/cognitoUserPool/index.test.js | 253 ++-- .../aws/package/compile/events/iot/index.js | 35 +- .../package/compile/events/iot/index.test.js | 114 +- .../aws/package/compile/events/s3/index.js | 88 +- .../package/compile/events/s3/index.test.js | 88 +- .../package/compile/events/schedule/index.js | 52 +- .../compile/events/schedule/index.test.js | 100 +- .../aws/package/compile/events/sns/index.js | 75 +- .../package/compile/events/sns/index.test.js | 226 +-- .../aws/package/compile/events/sqs/index.js | 70 +- .../package/compile/events/sqs/index.test.js | 449 +++--- .../package/compile/events/stream/index.js | 84 +- .../compile/events/stream/index.test.js | 604 ++++---- .../compile/events/websockets/index.test.js | 40 +- .../compile/events/websockets/lib/api.js | 19 +- .../compile/events/websockets/lib/api.test.js | 69 +- .../events/websockets/lib/authorizers.js | 5 +- .../events/websockets/lib/authorizers.test.js | 29 +- .../events/websockets/lib/deployment.js | 15 +- .../events/websockets/lib/deployment.test.js | 18 +- .../events/websockets/lib/integrations.js | 8 +- .../websockets/lib/integrations.test.js | 19 +- .../events/websockets/lib/permissions.js | 46 +- .../events/websockets/lib/permissions.test.js | 51 +- .../compile/events/websockets/lib/routes.js | 26 +- .../events/websockets/lib/routes.test.js | 24 +- .../compile/events/websockets/lib/stage.js | 46 +- .../events/websockets/lib/stage.test.js | 93 +- .../compile/events/websockets/lib/validate.js | 39 +- .../events/websockets/lib/validate.test.js | 16 +- .../aws/package/compile/functions/index.js | 211 +-- .../package/compile/functions/index.test.js | 1224 ++++++++--------- .../aws/package/compile/layers/index.js | 37 +- .../aws/package/compile/layers/index.test.js | 126 +- lib/plugins/aws/package/index.js | 48 +- lib/plugins/aws/package/index.test.js | 65 +- .../lib/core-cloudformation-template.json | 4 +- .../lib/generateArtifactDirectoryName.js | 3 +- .../lib/generateArtifactDirectoryName.test.js | 7 +- .../aws/package/lib/generateCoreTemplate.js | 84 +- .../package/lib/generateCoreTemplate.test.js | 165 +-- .../iam-role-lambda-execution-template.json | 16 +- .../lib/mergeCustomProviderResources.test.js | 95 +- .../aws/package/lib/mergeIamTemplates.js | 145 +- .../aws/package/lib/mergeIamTemplates.test.js | 682 ++++----- .../aws/package/lib/saveCompiledTemplate.js | 6 +- .../package/lib/saveCompiledTemplate.test.js | 3 +- .../aws/package/lib/saveServiceState.js | 7 +- .../aws/package/lib/saveServiceState.test.js | 13 +- lib/plugins/aws/provider/awsProvider.js | 272 ++-- lib/plugins/aws/provider/awsProvider.test.js | 341 +++-- lib/plugins/aws/remove/index.js | 11 +- lib/plugins/aws/remove/index.test.js | 33 +- lib/plugins/aws/remove/lib/bucket.js | 33 +- lib/plugins/aws/remove/lib/bucket.test.js | 62 +- lib/plugins/aws/remove/lib/stack.js | 8 +- lib/plugins/aws/remove/lib/stack.test.js | 29 +- lib/plugins/aws/rollback/index.js | 34 +- lib/plugins/aws/rollback/index.test.js | 116 +- lib/plugins/aws/rollbackFunction/index.js | 56 +- .../aws/rollbackFunction/index.test.js | 68 +- .../aws/utils/findAndGroupDeployments.js | 6 +- .../aws/utils/findAndGroupDeployments.test.js | 14 +- lib/plugins/aws/utils/findReferences.test.js | 16 +- lib/plugins/aws/utils/formatLambdaLogEvent.js | 6 +- .../aws/utils/formatLambdaLogEvent.test.js | 6 +- .../aws/utils/getS3ObjectsFromStacks.js | 9 +- .../aws/utils/getS3ObjectsFromStacks.test.js | 10 +- lib/plugins/config/config.js | 9 +- lib/plugins/config/config.test.js | 9 +- lib/plugins/create/create.js | 63 +- lib/plugins/create/create.test.js | 364 ++--- .../aws-alexa-typescript/package.json | 3 +- .../aws-alexa-typescript/serverless.yml | 2 +- .../aws-alexa-typescript/tsconfig.json | 8 +- .../aws-clojure-gradle/serverless.yml | 1 - .../aws-clojurescript-gradle/README.md | 6 +- .../aws-clojurescript-gradle/serverless.yml | 13 +- .../templates/aws-csharp/serverless.yml | 1 - .../templates/aws-fsharp/serverless.yml | 1 - .../templates/aws-go-dep/serverless.yml | 11 +- .../templates/aws-go-mod/serverless.yml | 11 +- .../create/templates/aws-go/serverless.yml | 11 +- .../aws-groovy-gradle/serverless.yml | 1 - .../templates/aws-java-gradle/serverless.yml | 1 - .../templates/aws-java-maven/serverless.yml | 1 - .../aws-kotlin-jvm-gradle/serverless.yml | 1 - .../aws-kotlin-jvm-maven/serverless.yml | 1 - .../aws-kotlin-nodejs-gradle/serverless.yml | 1 - .../templates/aws-nodejs-ecma-script/first.js | 10 +- .../aws-nodejs-ecma-script/package.json | 2 +- .../aws-nodejs-ecma-script/second.js | 18 +- .../aws-nodejs-ecma-script/webpack.config.js | 14 +- .../aws-nodejs-typescript/package.json | 3 +- .../aws-nodejs-typescript/tsconfig.json | 8 +- .../create/templates/aws-nodejs/handler.js | 14 +- .../templates/aws-nodejs/serverless.yml | 1 - .../templates/aws-provided/serverless.yml | 1 - .../templates/aws-python/serverless.yml | 1 - .../templates/aws-python3/serverless.yml | 1 - .../create/templates/aws-ruby/serverless.yml | 1 - .../templates/aws-scala-sbt/serverless.yml | 1 - .../create/templates/azure-nodejs/README.md | 61 +- .../create/templates/azure-nodejs/goodbye.js | 29 +- .../create/templates/azure-nodejs/hello.js | 29 +- .../templates/azure-nodejs/package.json | 2 +- .../templates/azure-nodejs/serverless.yml | 15 +- .../cloudflare-workers-enterprise/bar.js | 12 +- .../helloWorld.js | 12 +- .../serverless.yml | 2 +- .../cloudflare-workers-rust/helloWorld.js | 4 +- .../cloudflare-workers-rust/serverless.yml | 3 +- .../cloudflare-workers-rust/webpack.config.js | 19 +- .../cloudflare-workers/helloWorld.js | 12 +- .../cloudflare-workers/serverless.yml | 4 +- .../create/templates/fn-go/hello/test.json | 46 +- .../create/templates/fn-go/serverless.yml | 6 +- .../create/templates/fn-nodejs/hello/func.js | 16 +- .../templates/fn-nodejs/hello/package.json | 18 +- .../templates/fn-nodejs/hello/test.json | 46 +- .../create/templates/fn-nodejs/serverless.yml | 10 +- .../create/templates/google-go/serverless.yml | 3 - .../templates/google-nodejs/serverless.yml | 1 - .../templates/google-python/serverless.yml | 1 - .../openwhisk-java-maven/serverless.yml | 5 +- .../templates/openwhisk-nodejs/README.md | 12 +- .../create/templates/openwhisk-php/README.md | 12 +- .../templates/openwhisk-python/README.md | 12 +- .../create/templates/openwhisk-ruby/README.md | 12 +- .../templates/openwhisk-swift/README.md | 12 +- lib/plugins/create/templates/plugin/index.js | 9 +- .../templates/spotinst-nodejs/handler.js | 14 +- lib/plugins/deploy/deploy.js | 51 +- lib/plugins/deploy/deploy.test.js | 38 +- lib/plugins/info/info.js | 4 +- lib/plugins/info/info.test.js | 6 +- lib/plugins/install/install.js | 20 +- lib/plugins/install/install.test.js | 17 +- lib/plugins/invoke/invoke.js | 36 +- lib/plugins/invoke/invoke.test.js | 11 +- lib/plugins/logs/logs.js | 6 +- lib/plugins/logs/logs.test.js | 15 +- lib/plugins/metrics/metrics.js | 4 +- lib/plugins/metrics/metrics.test.js | 4 +- lib/plugins/package/lib/packageService.js | 79 +- .../package/lib/packageService.test.js | 403 +++--- lib/plugins/package/lib/zipService.js | 208 +-- lib/plugins/package/lib/zipService.test.js | 908 ++++++------ lib/plugins/package/package.js | 16 +- lib/plugins/package/package.test.js | 21 +- lib/plugins/plugin/install/install.js | 125 +- lib/plugins/plugin/install/install.test.js | 229 ++- lib/plugins/plugin/lib/utils.js | 30 +- lib/plugins/plugin/lib/utils.test.js | 53 +- lib/plugins/plugin/list/list.js | 18 +- lib/plugins/plugin/list/list.test.js | 23 +- lib/plugins/plugin/plugin.js | 4 +- lib/plugins/plugin/plugin.test.js | 14 +- lib/plugins/plugin/search/search.js | 24 +- lib/plugins/plugin/search/search.test.js | 10 +- lib/plugins/plugin/uninstall/uninstall.js | 94 +- .../plugin/uninstall/uninstall.test.js | 266 ++-- lib/plugins/print/print.js | 146 +- lib/plugins/print/print.test.js | 6 +- lib/plugins/remove/remove.js | 4 +- lib/plugins/remove/remove.test.js | 7 +- lib/plugins/rollback/index.js | 9 +- lib/plugins/slstats/slstats.js | 14 +- lib/plugins/slstats/slstats.test.js | 26 +- lib/utils/autocomplete.js | 40 +- lib/utils/config/index.js | 2 +- lib/utils/downloadTemplateFromRepo.js | 45 +- lib/utils/downloadTemplateFromRepo.test.js | 44 +- lib/utils/fs/fileExists.js | 5 +- lib/utils/fs/fileExists.test.js | 8 +- lib/utils/fs/fileExistsSync.test.js | 2 +- lib/utils/fs/parse.test.js | 1 - lib/utils/fs/readFile.js | 3 +- lib/utils/fs/readFile.test.js | 18 +- lib/utils/fs/readFileIfExists.js | 15 +- lib/utils/fs/readFileIfExists.test.js | 8 +- lib/utils/fs/walkDirSync.js | 11 +- lib/utils/fs/walkDirSync.test.js | 2 +- lib/utils/fs/writeFile.js | 29 +- lib/utils/fs/writeFile.test.js | 27 +- lib/utils/fs/writeFileSync.js | 4 +- lib/utils/fs/writeFileSync.test.js | 10 +- lib/utils/getCacheFile.js | 15 +- lib/utils/getCacheFilePath.js | 7 +- lib/utils/getCommandSuggestion.js | 4 +- lib/utils/getServerlessConfigFile.js | 55 +- lib/utils/getServerlessConfigFile.test.js | 124 +- lib/utils/getTrackingConfigFileName.js | 2 +- lib/utils/log/consoleLog.js | 2 +- lib/utils/log/fileLog.js | 2 +- lib/utils/log/log.js | 4 +- lib/utils/log/serverlessLog.js | 2 +- lib/utils/renameService.js | 29 +- lib/utils/renameService.test.js | 12 +- lib/utils/segment.js | 2 +- lib/utils/userStats.js | 16 +- lib/utils/userStatsValidation.js | 3 +- lib/utils/yamlAstParser.js | 171 +-- lib/utils/yamlAstParser.test.js | 130 +- scripts/postinstall.js | 11 +- scripts/preuninstall.js | 9 +- tests/.eslintrc.js | 10 +- .../api-gateway/service/core.js | 24 +- tests/integration-all/api-gateway/tests.js | 130 +- tests/integration-basic/tests.js | 9 +- tests/integration-package/handler.js | 14 +- tests/integration-package/packaging.tests.js | 131 +- tests/integration-package/serverless.yml | 1 - tests/mocha-reporter.js | 2 +- tests/utils/api-gateway/index.js | 23 +- tests/utils/aws-cleanup.js | 14 +- tests/utils/cloudformation/index.js | 30 +- tests/utils/cognito/index.js | 8 +- tests/utils/fs/index.js | 10 +- tests/utils/iot/index.js | 3 +- tests/utils/misc/index.js | 29 +- tests/utils/plugins/index.js | 14 +- tests/utils/s3/index.js | 6 +- tests/utils/sns/index.js | 14 +- 667 files changed, 15521 insertions(+), 14498 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ded04c7a1..7dc59f594 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -16,17 +16,18 @@ about: Create a report to help us improve ## Description -* What went wrong? -* What did you expect should have happened? -* What was the config you used? -* What stacktrace or error message from your provider did you see? +- What went wrong? +- What did you expect should have happened? +- What was the config you used? +- What stacktrace or error message from your provider did you see? Similar or dependent issues: -* #12345 + +- #12345 ## Additional Data -* ***Serverless Framework Version you're using***: -* ***Operating System***: -* ***Stack Trace***: -* ***Provider Error messages***: +- **_Serverless Framework Version you're using_**: +- **_Operating System_**: +- **_Stack Trace_**: +- **_Provider Error messages_**: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 1374800f2..5c520999f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -16,8 +16,9 @@ about: Suggest an idea for serverless framework ## Description -* What is the use case that should be solved. The more detail you describe this in the easier it is to understand for us. -* If there is additional config how would it look +- What is the use case that should be solved. The more detail you describe this in the easier it is to understand for us. +- If there is additional config how would it look Similar or dependent issues: -* #12345 + +- #12345 diff --git a/0.5.x-RESOURCES.md b/0.5.x-RESOURCES.md index bde126bf2..a3aa0d860 100644 --- a/0.5.x-RESOURCES.md +++ b/0.5.x-RESOURCES.md @@ -5,37 +5,41 @@ Below are projects and plugins relating to version 0.5 and below. Note that thes You can read the v0.5.x documentation at [readme.io](https://serverless.readme.io/v0.5.0/docs). ## Projects (v0.5.x) -Serverless Projects are shareable and installable. You can publish them to npm and install them via the Serverless Framework CLI by using `$ serverless project install ` -* [serverless-graphql](https://github.com/serverless/serverless-graphql) - Official Serverless boilerplate to kick start your project -* [serverless-starter](https://github.com/serverless/serverless-starter) - A simple boilerplate for new projects (JavaScript) with a few architectural options -* [serverless-starter-python](https://github.com/alexcasalboni/serverless-starter-python) - A simple boilerplate for new projects (Python) with a few architectural options -* [serverless-graphql-blog](https://github.com/serverless/serverless-graphql-blog) - A blog boilerplate that leverages GraphQL in front of DynamoDB to offer a minimal REST API featuring only 1 endpoint -* [serverless-authentication-boilerplate](https://github.com/laardee/serverless-authentication-boilerplate) - A generic authentication boilerplate for Serverless framework -* [sc5-serverless-boilerplate](https://github.com/SC5/sc5-serverless-boilerplate) - A boilerplate for test driven development of REST endpoints -* [MoonMail] (https://github.com/microapps/MoonMail) - Build your own email marketing infrastructure using Lambda + SES + +Serverless Projects are shareable and installable. You can publish them to npm and install them via the Serverless Framework CLI by using `$ serverless project install ` + +- [serverless-graphql](https://github.com/serverless/serverless-graphql) - Official Serverless boilerplate to kick start your project +- [serverless-starter](https://github.com/serverless/serverless-starter) - A simple boilerplate for new projects (JavaScript) with a few architectural options +- [serverless-starter-python](https://github.com/alexcasalboni/serverless-starter-python) - A simple boilerplate for new projects (Python) with a few architectural options +- [serverless-graphql-blog](https://github.com/serverless/serverless-graphql-blog) - A blog boilerplate that leverages GraphQL in front of DynamoDB to offer a minimal REST API featuring only 1 endpoint +- [serverless-authentication-boilerplate](https://github.com/laardee/serverless-authentication-boilerplate) - A generic authentication boilerplate for Serverless framework +- [sc5-serverless-boilerplate](https://github.com/SC5/sc5-serverless-boilerplate) - A boilerplate for test driven development of REST endpoints +- [MoonMail](https://github.com/microapps/MoonMail) - Build your own email marketing infrastructure using Lambda + SES ## Plugins (v0.5.x) -Serverless is composed of Plugins. A group of default Plugins ship with the Framework, and here are some others you can add to improve/help your workflow: -* [Meta Sync](https://github.com/serverless/serverless-meta-sync) - Securely sync your the variables in your project's `_meta/variables` across your team. -* [Hook Scripts](https://github.com/kennu/serverless-plugin-hookscripts) - Easily create shell script hooks that are run whenever Serverless actions are executed. -* [CORS](https://github.com/joostfarla/serverless-cors-plugin) - Adds support for CORS (Cross-origin resource sharing). -* [Serve](https://github.com/Nopik/serverless-serve) - Simulate API Gateway locally, so all function calls can be run via localhost. -* [Webpack](https://github.com/asprouse/serverless-webpack-plugin) - Use Webpack to optimize your Serverless Node.js Functions. -* [Serverless Client](https://github.com/serverless/serverless-client-s3) - Deploy and config a web client for your Serverless project to S3. -* [Alerting](https://github.com/martinlindenberg/serverless-plugin-alerting) - This Plugin adds Cloudwatch Alarms with SNS notifications for your Lambda functions. -* [Optimizer](https://github.com/serverless/serverless-optimizer-plugin) - Optimizes your code for performance in Lambda. Supports coffeeify, babelify and other transforms -* [CloudFormation Validator](https://github.com/tmilewski/serverless-resources-validation-plugin) - Adds support for validating your CloudFormation template. -* [Prune](https://github.com/Nopik/serverless-lambda-prune-plugin) - Delete old versions of AWS lambdas from your account so that you don't exceed the code storage limit. -* [Base-Path](https://github.com/daffinity/serverless-base-path-plugin) - Sets a base path for all API Gateway endpoints in a Component. -* [Test](https://github.com/arabold/serverless-test-plugin) - A Simple Integration Test Framework for Serverless. -* [SNS Subscribe](https://github.com/martinlindenberg/serverless-plugin-sns) - This plugin easily subscribes your lambda functions to SNS notifications. -* [JSHint](https://github.com/joostfarla/serverless-jshint-plugin) - Detect errors and potential problems in your Lambda functions. -* [ESLint](https://github.com/nishantjain91/serverless-eslint-plugin) - Detect errors and potential problems in your Lambda functions using eslint. -* [Mocha](https://github.com/SC5/serverless-mocha-plugin) - Enable test driven development by creating test cases when creating new functions -* [Function-Package](https://github.com/HyperBrain/serverless-package-plugin) - Package your lambdas without deploying to AWS. -* [Sentry](https://github.com/arabold/serverless-sentry-plugin) - Automatically send errors and exceptions to [Sentry](https://getsentry.com). -* [Auto-Prune](https://github.com/arabold/serverless-autoprune-plugin) - Delete old AWS Lambda versions. -* [Serverless Secrets](https://github.com/trek10inc/serverless-secrets) - Easily encrypt and decrypt secrets in your Serverless projects -* [Serverless DynamoDB Local](https://github.com/99xt/serverless-dynamodb-local) - Simulate DynamoDB instance locally. -* [Serverless Dependency Install](https://github.com/99xt/serverless-dependency-install) - Manage node, serverless dependencies easily within the project. -* [Serverless Header Function](https://github.com/blackevil245/serverless-header-function) - Automatically run a javascript script on every Serverless action hooks. + +Serverless is composed of Plugins. A group of default Plugins ship with the Framework, and here are some others you can add to improve/help your workflow: + +- [Meta Sync](https://github.com/serverless/serverless-meta-sync) - Securely sync your the variables in your project's `_meta/variables` across your team. +- [Hook Scripts](https://github.com/kennu/serverless-plugin-hookscripts) - Easily create shell script hooks that are run whenever Serverless actions are executed. +- [CORS](https://github.com/joostfarla/serverless-cors-plugin) - Adds support for CORS (Cross-origin resource sharing). +- [Serve](https://github.com/Nopik/serverless-serve) - Simulate API Gateway locally, so all function calls can be run via localhost. +- [Webpack](https://github.com/asprouse/serverless-webpack-plugin) - Use Webpack to optimize your Serverless Node.js Functions. +- [Serverless Client](https://github.com/serverless/serverless-client-s3) - Deploy and config a web client for your Serverless project to S3. +- [Alerting](https://github.com/martinlindenberg/serverless-plugin-alerting) - This Plugin adds Cloudwatch Alarms with SNS notifications for your Lambda functions. +- [Optimizer](https://github.com/serverless/serverless-optimizer-plugin) - Optimizes your code for performance in Lambda. Supports coffeeify, babelify and other transforms +- [CloudFormation Validator](https://github.com/tmilewski/serverless-resources-validation-plugin) - Adds support for validating your CloudFormation template. +- [Prune](https://github.com/Nopik/serverless-lambda-prune-plugin) - Delete old versions of AWS lambdas from your account so that you don't exceed the code storage limit. +- [Base-Path](https://github.com/daffinity/serverless-base-path-plugin) - Sets a base path for all API Gateway endpoints in a Component. +- [Test](https://github.com/arabold/serverless-test-plugin) - A Simple Integration Test Framework for Serverless. +- [SNS Subscribe](https://github.com/martinlindenberg/serverless-plugin-sns) - This plugin easily subscribes your lambda functions to SNS notifications. +- [JSHint](https://github.com/joostfarla/serverless-jshint-plugin) - Detect errors and potential problems in your Lambda functions. +- [ESLint](https://github.com/nishantjain91/serverless-eslint-plugin) - Detect errors and potential problems in your Lambda functions using eslint. +- [Mocha](https://github.com/SC5/serverless-mocha-plugin) - Enable test driven development by creating test cases when creating new functions +- [Function-Package](https://github.com/HyperBrain/serverless-package-plugin) - Package your lambdas without deploying to AWS. +- [Sentry](https://github.com/arabold/serverless-sentry-plugin) - Automatically send errors and exceptions to [Sentry](https://getsentry.com). +- [Auto-Prune](https://github.com/arabold/serverless-autoprune-plugin) - Delete old AWS Lambda versions. +- [Serverless Secrets](https://github.com/trek10inc/serverless-secrets) - Easily encrypt and decrypt secrets in your Serverless projects +- [Serverless DynamoDB Local](https://github.com/99xt/serverless-dynamodb-local) - Simulate DynamoDB instance locally. +- [Serverless Dependency Install](https://github.com/99xt/serverless-dependency-install) - Manage node, serverless dependencies easily within the project. +- [Serverless Header Function](https://github.com/blackevil245/serverless-header-function) - Automatically run a javascript script on every Serverless action hooks. diff --git a/CHANGELOG.md b/CHANGELOG.md index ab82f978c..94322ee66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - [Feature/support external websocket api](https://github.com/serverless/serverless/pull/6272) ## Meta + - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.45.1...v1.46.0) # 1.45.1 (2019-06-12) @@ -31,8 +32,8 @@ - [Fix Travis CI deploy config](https://github.com/serverless/serverless/pull/6234) ## Meta -- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.45.0...v1.45.1) +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.45.0...v1.45.1) # 1.45.0 (2019-06-12) @@ -56,16 +57,16 @@ - [Update Scala version to 2.13.0 for aws-scala-sbt template](https://github.com/serverless/serverless/pull/6222) ## Meta -- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.1...v1.45.0) +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.1...v1.45.0) # 1.44.1 (2019-05-28) - [Fix enterprise plugin lookup in global yarn installs](https://github.com/serverless/serverless/pull/6183) ## Meta -- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.0...v1.44.1) +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.0...v1.44.1) # 1.44.0 (2019-05-28) @@ -80,8 +81,8 @@ - [Upgrade mocha, switch from istanbul to nyc, improve tests configuration](https://github.com/serverless/serverless/pull/6169) ## Meta -- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.43.0...v1.44.0) +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.43.0...v1.44.0) # 1.43.0 (2019-05-20) @@ -92,6 +93,7 @@ - [Fix tests setup issues](https://github.com/serverless/serverless/pull/6147) ## Meta + - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.42.3...v1.43.0) # 1.42.3 (2019-05-14) @@ -107,7 +109,8 @@ - [Improve handling of custom API Gateway options](https://github.com/serverless/serverless/pull/6129) ## Meta - - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.42.2...v1.42.3) + +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.42.2...v1.42.3) # 1.42.2 (2019-05-10) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 77ae89c1e..9d7afa9c9 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -14,21 +14,21 @@ orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ce9e1452..90d0863ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,11 +64,11 @@ During development, you can easily check coverage by running `npm test`, then op Please follow these Testing guidelines when writing your unit tests: -- Include a top-level `describe('ClassName')` block, with the name of the class you are testing -- Inside that top-level `describe()` block, create another `describe('#methodOne()')` block for each class method you might create or modify -- For each method, include an `it('should do something')` test case for each logical edge case in your changes -- As you write tests, check the code coverage and make sure all lines of code are covered. If not, just add more test cases until everything is covered -- For reference and inspiration, please check our `tests` directory +- Include a top-level `describe('ClassName')` block, with the name of the class you are testing +- Inside that top-level `describe()` block, create another `describe('#methodOne()')` block for each class method you might create or modify +- For each method, include an `it('should do something')` test case for each logical edge case in your changes +- As you write tests, check the code coverage and make sure all lines of code are covered. If not, just add more test cases until everything is covered +- For reference and inspiration, please check our `tests` directory ## Testing templates diff --git a/README.md b/README.md index e119df3eb..8068f35ae 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ [Website](http://www.serverless.com) • [Docs](https://serverless.com/framework/docs/) • [Newsletter](https://serverless.com/subscribe/) • [Gitter](https://gitter.im/serverless/serverless) • [Forum](http://forum.serverless.com) • [Meetups](https://github.com/serverless/meetups) • [Twitter](https://twitter.com/goserverless) • [We're Hiring](https://serverless.com/company/jobs/) • [Enterprise](https://serverless.com/enterprise/) -**The Serverless Framework** – Build applications comprised of microservices that run in response to events, auto-scale for you, and only charge you when they run. This lowers the total cost of maintaining your apps, enabling you to build more logic, faster. +**The Serverless Framework** – Build applications comprised of microservices that run in response to events, auto-scale for you, and only charge you when they run. This lowers the total cost of maintaining your apps, enabling you to build more logic, faster. -The Framework uses new event-driven compute services, like AWS Lambda, Google Cloud Functions, and more. It's a command-line tool, providing scaffolding, workflow automation and best practices for developing and deploying your serverless architecture. It's also completely extensible via plugins. +The Framework uses new event-driven compute services, like AWS Lambda, Google Cloud Functions, and more. It's a command-line tool, providing scaffolding, workflow automation and best practices for developing and deploying your serverless architecture. It's also completely extensible via plugins. Serverless is an MIT open-source project, actively maintained by a full-time, venture-backed team. @@ -24,76 +24,83 @@ Serverless is an MIT open-source project, actively maintained by a full-time, ve -* [Quick Start](#quick-start) -* [Examples](https://github.com/serverless/examples) -* [Services](#services) -* [Features](#features) -* [Plugins](https://github.com/serverless/plugins) -* [Contributing](#contributing) -* [Community](#community) -* [Consultants](#consultants) -* [Licensing](#licensing) -* [Previous Version 0.5.x](#v.5) +- [Quick Start](#quick-start) +- [Examples](https://github.com/serverless/examples) +- [Services](#services) +- [Features](#features) +- [Plugins](https://github.com/serverless/plugins) +- [Contributing](#contributing) +- [Community](#community) +- [Consultants](#consultants) +- [Licensing](#licensing) +- [Previous Version 0.5.x](#v.5) ## Quick Start [Watch the video guide here](https://serverless.com/framework/) or follow the steps below to create and deploy your first serverless microservice in minutes. 1. **Install via npm:** - ```bash - npm install -g serverless - ``` + +```bash +npm install -g serverless +``` 2. **Set-up your [Provider Credentials](./docs/providers/aws/guide/credentials.md)**. [Watch the video on setting up credentials](https://www.youtube.com/watch?v=HSd9uYj2LJA) 3. **Create a Service:** - You can create a new service or [install existing services](#how-to-install-a-service). - ```bash - # Create a new Serverless Service/Project - serverless create --template aws-nodejs --path my-service - # Change into the newly created directory - cd my-service - ``` +You can create a new service or [install existing services](#how-to-install-a-service). + +```bash +# Create a new Serverless Service/Project +serverless create --template aws-nodejs --path my-service +# Change into the newly created directory +cd my-service +``` 4. **Deploy a Service:** - Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. - ```bash - serverless deploy -v - ``` +Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. + +```bash +serverless deploy -v +``` 5. **Deploy the Function:** - Use this to quickly upload and overwrite your AWS Lambda code on AWS, allowing you to develop faster. - ```bash - serverless deploy function -f hello - ``` +Use this to quickly upload and overwrite your AWS Lambda code on AWS, allowing you to develop faster. + +```bash +serverless deploy function -f hello +``` 6. **Invoke the Function:** - Invokes an AWS Lambda Function on AWS and returns logs. - ```bash - serverless invoke -f hello -l - ``` +Invokes an AWS Lambda Function on AWS and returns logs. + +```bash +serverless invoke -f hello -l +``` 7. **Fetch the Function Logs:** - Open up a separate tab in your console and stream all logs for a specific Function using this command. - ```bash - serverless logs -f hello -t - ``` +Open up a separate tab in your console and stream all logs for a specific Function using this command. + +```bash +serverless logs -f hello -t +``` 8. **Remove the Service:** - Removes all Functions, Events and Resources from your AWS account. - ```bash - serverless remove - ``` +Removes all Functions, Events and Resources from your AWS account. + +```bash +serverless remove +``` ### How to Install a Service: -This is a convenience method to install a pre-made Serverless Service locally by downloading the Github repo and unzipping it. Services are listed below. +This is a convenience method to install a pre-made Serverless Service locally by downloading the Github repo and unzipping it. Services are listed below. ```bash serverless install -u https://github.com/your-url-to-the-serverless-service @@ -105,89 +112,93 @@ Check out the [Serverless Framework Guide](./docs/providers/aws/guide/README.md) The following are services you can instantly install and use by running `serverless install --url ` -* [serverless-examples](https://github.com/serverless/examples) -* [CRUD](https://github.com/pmuens/serverless-crud) - CRUD service, [Scala Port](https://github.com/jahangirmohammed/serverless-crud-scala) -* [CRUD with FaunaDB](https://github.com/faunadb/serverless-crud) - CRUD service using FaunaDB -* [CRUD with S3](https://github.com/tscanlin/serverless-s3-crud) - CRUD service using S3 -* [GraphQL Boilerplate](https://github.com/serverless/serverless-graphql) - GraphQL application Boilerplate service -* [Authentication](https://github.com/laardee/serverless-authentication-boilerplate) - Authentication boilerplate service -* [Mailer](https://github.com/eahefnawy/serverless-mailer) - Service for sending emails -* [Kinesis streams](https://github.com/pmuens/serverless-kinesis-streams) - Service to showcase Kinesis stream support -* [DynamoDB streams](https://github.com/pmuens/serverless-dynamodb-streams) - Service to showcase DynamoDB stream support -* [Landingpage backend](https://github.com/pmuens/serverless-landingpage-backend) - Landingpage backend service to store E-Mail addresses -* [Facebook Messenger Chatbot](https://github.com/pmuens/serverless-facebook-messenger-bot) - Chatbot for the Facebook Messenger platform -* [Lambda chaining](https://github.com/pmuens/serverless-lambda-chaining) - Service which chains Lambdas through SNS -* [Secured API](https://github.com/pmuens/serverless-secured-api) - Service which exposes an API key accessible API -* [Authorizer](https://github.com/eahefnawy/serverless-authorizer) - Service that uses API Gateway custom authorizers -* [Thumbnails](https://github.com/eahefnawy/serverless-thumbnails) - Service that takes an image url and returns a 100x100 thumbnail -* [Boilerplate](https://github.com/eahefnawy/serverless-boilerplate) - Opinionated boilerplate -* [ES6 + Jest](https://github.com/americansystems/serverless-es6-jest) - ES6 + Jest Boilerplate -* [PHP](https://github.com/ZeroSharp/serverless-php) - Call a PHP function from your lambda -* [Ruby](https://github.com/stewartlord/serverless-ruby) - Call a Ruby function from your lambda -* [Slack App](https://github.com/johnagan/serverless-slack-app) - Slack App Boilerplate with OAuth and Bot actions -* [Swift](https://github.com/choefele/swift-lambda-app) - Full-featured project template to develop Lambda functions in Swift -* [Cloudwatch Alerts on Slack](https://github.com/dav009/serverless-aws-alarms-notifier) - Get AWS Cloudwatch alerts notifications on Slack +- [serverless-examples](https://github.com/serverless/examples) +- [CRUD](https://github.com/pmuens/serverless-crud) - CRUD service, [Scala Port](https://github.com/jahangirmohammed/serverless-crud-scala) +- [CRUD with FaunaDB](https://github.com/faunadb/serverless-crud) - CRUD service using FaunaDB +- [CRUD with S3](https://github.com/tscanlin/serverless-s3-crud) - CRUD service using S3 +- [GraphQL Boilerplate](https://github.com/serverless/serverless-graphql) - GraphQL application Boilerplate service +- [Authentication](https://github.com/laardee/serverless-authentication-boilerplate) - Authentication boilerplate service +- [Mailer](https://github.com/eahefnawy/serverless-mailer) - Service for sending emails +- [Kinesis streams](https://github.com/pmuens/serverless-kinesis-streams) - Service to showcase Kinesis stream support +- [DynamoDB streams](https://github.com/pmuens/serverless-dynamodb-streams) - Service to showcase DynamoDB stream support +- [Landingpage backend](https://github.com/pmuens/serverless-landingpage-backend) - Landingpage backend service to store E-Mail addresses +- [Facebook Messenger Chatbot](https://github.com/pmuens/serverless-facebook-messenger-bot) - Chatbot for the Facebook Messenger platform +- [Lambda chaining](https://github.com/pmuens/serverless-lambda-chaining) - Service which chains Lambdas through SNS +- [Secured API](https://github.com/pmuens/serverless-secured-api) - Service which exposes an API key accessible API +- [Authorizer](https://github.com/eahefnawy/serverless-authorizer) - Service that uses API Gateway custom authorizers +- [Thumbnails](https://github.com/eahefnawy/serverless-thumbnails) - Service that takes an image url and returns a 100x100 thumbnail +- [Boilerplate](https://github.com/eahefnawy/serverless-boilerplate) - Opinionated boilerplate +- [ES6 + Jest](https://github.com/americansystems/serverless-es6-jest) - ES6 + Jest Boilerplate +- [PHP](https://github.com/ZeroSharp/serverless-php) - Call a PHP function from your lambda +- [Ruby](https://github.com/stewartlord/serverless-ruby) - Call a Ruby function from your lambda +- [Slack App](https://github.com/johnagan/serverless-slack-app) - Slack App Boilerplate with OAuth and Bot actions +- [Swift](https://github.com/choefele/swift-lambda-app) - Full-featured project template to develop Lambda functions in Swift +- [Cloudwatch Alerts on Slack](https://github.com/dav009/serverless-aws-alarms-notifier) - Get AWS Cloudwatch alerts notifications on Slack **Note**: the `serverless install` command will only work on V1.0 or later. ## Features -* Supports Node.js, Python, Java, Go, C#, Ruby, Swift, Kotlin, PHP, Scala, & F# -* Manages the lifecycle of your serverless architecture (build, deploy, update, delete). -* Safely deploy functions, events and their required resources together via provider resource managers (e.g., AWS CloudFormation). -* Functions can be grouped ("serverless services") for easy management of code, resources & processes, across large projects & teams. -* Minimal configuration and scaffolding. -* Built-in support for multiple stages. -* Optimized for CI/CD workflows. -* Loaded with automation, optimization and best practices. -* 100% Extensible: Extend or modify the Framework and its operations via Plugins. -* An ecosystem of serverless services and plugins. -* A passionate and welcoming community! +- Supports Node.js, Python, Java, Go, C#, Ruby, Swift, Kotlin, PHP, Scala, & F# +- Manages the lifecycle of your serverless architecture (build, deploy, update, delete). +- Safely deploy functions, events and their required resources together via provider resource managers (e.g., AWS CloudFormation). +- Functions can be grouped ("serverless services") for easy management of code, resources & processes, across large projects & teams. +- Minimal configuration and scaffolding. +- Built-in support for multiple stages. +- Optimized for CI/CD workflows. +- Loaded with automation, optimization and best practices. +- 100% Extensible: Extend or modify the Framework and its operations via Plugins. +- An ecosystem of serverless services and plugins. +- A passionate and welcoming community! ## Contributing + We love our contributors! Please read our [Contributing Document](CONTRIBUTING.md) to learn how you can start working on the Framework yourself. Check out our [help wanted](https://github.com/serverless/serverless/labels/help%20wanted) or [good first issue](https://github.com/serverless/serverless/labels/good%20first%20issue) labels to find issues we want to move forward on with your help. ## Community -* [Email Updates](http://eepurl.com/b8dv4P) -* [Serverless Forum](http://forum.serverless.com) -* [Gitter Chatroom](https://gitter.im/serverless/serverless) -* [Serverless Meetups](http://www.meetup.com/serverless/) -* [Stackoverflow](http://stackoverflow.com/questions/tagged/serverless-framework) -* [Facebook](https://www.facebook.com/serverless) -* [Twitter](https://twitter.com/goserverless) -* [Contact Us](mailto:hello@serverless.com) +- [Email Updates](http://eepurl.com/b8dv4P) +- [Serverless Forum](http://forum.serverless.com) +- [Gitter Chatroom](https://gitter.im/serverless/serverless) +- [Serverless Meetups](http://www.meetup.com/serverless/) +- [Stackoverflow](http://stackoverflow.com/questions/tagged/serverless-framework) +- [Facebook](https://www.facebook.com/serverless) +- [Twitter](https://twitter.com/goserverless) +- [Contact Us](mailto:hello@serverless.com) ## Consultants + These consultants use the Serverless Framework and can help you build your serverless projects. -* [Andrew Griffiths](https://www.andrewgriffithsonline.com/) - Independent consultant specialising in serverless technology -* [Trek10](https://www.trek10.com/) -* [Parallax](https://parall.ax/) – they also built the [David Guetta Campaign](https://serverlesscode.com/post/david-guetta-online-recording-with-lambda/) -* [Geniusee](https://geniusee.com) -* [SC5 Online](https://sc5.io) -* [Carrot Creative](https://carrot.is) -* [microapps](http://microapps.com) -* [Apiwise](http://www.apiwise.nl) -* [Useful IO](http://useful.io) - and [Hail Messaging](http://hail.io) -* [WhaleTech](https://whaletech.co/) -* [Hop Labs](http://www.hoplabs.com) -* [Webscale](https://webscale.fi/briefly-in-english/) -* [API talent](http://www.apitalent.co.nz) - who also run [Serverless-Auckland Meetup](http://www.meetup.com/Serverless-Auckland) -* [Branded Crate](https://www.brandedcrate.com/) -* [cloudonaut](https://cloudonaut.io/serverless-consulting/) -* [PromptWorks](https://www.promptworks.com/serverless/) -* [Craftship](https://craftship.io) -* [EPX Labs](http://www.epxlabs.com/) - runs [Serverless NYC Meetup](https://www.meetup.com/Serverless-NYC/) -* [Red Badger](https://red-badger.com) -* [Langa](http://langa.io/?utm_source=gh-serverless&utm_medium=github) - They built [Trails.js](http://github.com/trailsjs/trails) -* [Emerging Technology Advisors](https://www.emergingtechnologyadvisors.com) -* [OneSpeed](https://onespeed.io/) -* [Seraro](http://www.seraro.com/) - Who also runs Atlanta Serverless Meetup (https://www.meetup.com/Atlanta-CABI-Camp-Cloud-AI-Blockchain-IOT) and Delhi Serverless Meetup (https://www.meetup.com/Delhi-NCR-Serverless-Architecture-Meetup/) -* [superluminar](https://superluminar.io) - runs serverlessdays Hamburg and Serverless Meetup Hamburg -* [Onica](https://www.onica.com/aws-cloud-native-developers/) - AWS Premier Consulting Partner for Cloud Native Development and host of [eleven regional Meetup groups](https://www.onica.com/events/). ----- + +- [Andrew Griffiths](https://www.andrewgriffithsonline.com/) - Independent consultant specialising in serverless technology +- [Trek10](https://www.trek10.com/) +- [Parallax](https://parall.ax/) – they also built the [David Guetta Campaign](https://serverlesscode.com/post/david-guetta-online-recording-with-lambda/) +- [Geniusee](https://geniusee.com) +- [SC5 Online](https://sc5.io) +- [Carrot Creative](https://carrot.is) +- [microapps](http://microapps.com) +- [Apiwise](http://www.apiwise.nl) +- [Useful IO](http://useful.io) - and [Hail Messaging](http://hail.io) +- [WhaleTech](https://whaletech.co/) +- [Hop Labs](http://www.hoplabs.com) +- [Webscale](https://webscale.fi/briefly-in-english/) +- [API talent](http://www.apitalent.co.nz) - who also run [Serverless-Auckland Meetup](http://www.meetup.com/Serverless-Auckland) +- [Branded Crate](https://www.brandedcrate.com/) +- [cloudonaut](https://cloudonaut.io/serverless-consulting/) +- [PromptWorks](https://www.promptworks.com/serverless/) +- [Craftship](https://craftship.io) +- [EPX Labs](http://www.epxlabs.com/) - runs [Serverless NYC Meetup](https://www.meetup.com/Serverless-NYC/) +- [Red Badger](https://red-badger.com) +- [Langa](http://langa.io/?utm_source=gh-serverless&utm_medium=github) - They built [Trails.js](http://github.com/trailsjs/trails) +- [Emerging Technology Advisors](https://www.emergingtechnologyadvisors.com) +- [OneSpeed](https://onespeed.io/) +- [Seraro](http://www.seraro.com/) - Who also runs Atlanta Serverless Meetup (https://www.meetup.com/Atlanta-CABI-Camp-Cloud-AI-Blockchain-IOT) and Delhi Serverless Meetup (https://www.meetup.com/Delhi-NCR-Serverless-Architecture-Meetup/) +- [superluminar](https://superluminar.io) - runs serverlessdays Hamburg and Serverless Meetup Hamburg +- [Onica](https://www.onica.com/aws-cloud-native-developers/) - AWS Premier Consulting Partner for Cloud Native Development and host of [eleven regional Meetup groups](https://www.onica.com/events/). + +--- ## Licensing diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index e4200a133..4a3ebd11f 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -7,7 +7,7 @@ More info about our release process can be found in the [`RELEASE_PROCESS.md`](. ## Pre-Release - [ ] Look through all open issues and PRs (if any) of that milestone and close them / move them to another -milestone if still open + milestone if still open - [ ] Create a new branch for the release - [ ] Bump the version number in `package.json` - [ ] Run `./scripts/prs-since-last-tag ` @@ -37,4 +37,3 @@ milestone if still open ## Validate Release - [ ] Validate that `npm install` works (`npm install -g serverless@` or `npm install -g serverless` if latest is released) - diff --git a/VERSIONING.md b/VERSIONING.md index 1b57039b2..8254d943a 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -35,15 +35,15 @@ Any non-backward compatible changes leads to a major version bump. This includes #### What is considered a breaking change? - Everything which touches the public facing API - + CLI commands - + CLI options - + Methods accessible through `this.serverless` - + ... + - CLI commands + - CLI options + - Methods accessible through `this.serverless` + - ... - Output Serverless produces - + Files and their names - + Transient data which is available during runtime - + Formatted CLI outputs (e.g. via `--json`) **NOT:** standard outputs - + ... + - Files and their names + - Transient data which is available during runtime + - Formatted CLI outputs (e.g. via `--json`) **NOT:** standard outputs + - ... #### Example of a Breaking Change diff --git a/bin/serverless.js b/bin/serverless.js index c9de3a6cc..8c0dc90a9 100755 --- a/bin/serverless.js +++ b/bin/serverless.js @@ -17,7 +17,7 @@ if (process.env.SLS_DEBUG) { }); } -process.on('unhandledRejection', (e) => { +process.on('unhandledRejection', e => { logError(e); }); process.noDeprecation = true; @@ -25,41 +25,47 @@ process.noDeprecation = true; const invocationId = uuid.v4(); // boot up error reporting via sentry before anything -(() => initializeErrorReporter(invocationId).then(() => { - if (process.argv[2] === 'completion') { - return autocomplete(); - } - // requiring here so that if anything went wrong, - // during require, it will be caught. - const Serverless = require('../lib/Serverless'); // eslint-disable-line global-require +(() => + initializeErrorReporter(invocationId) + .then(() => { + if (process.argv[2] === 'completion') { + return autocomplete(); + } + // requiring here so that if anything went wrong, + // during require, it will be caught. + const Serverless = require('../lib/Serverless'); // eslint-disable-line global-require - const serverless = new Serverless({ - interactive: typeof process.env.CI === 'undefined', - }); - - serverless.invocationId = invocationId; - - return serverless.init() - .then(() => serverless.run()) - .then(() => process.exit(0)) - .catch((err) => { - // If Enterprise Plugin, capture error - let enterpriseErrorHandler = null; - serverless.pluginManager.plugins.forEach((p) => { - if (p.enterprise && p.enterprise.errorHandler) { - enterpriseErrorHandler = p.enterprise.errorHandler; - } - }); - if (!enterpriseErrorHandler) { throw err; } - return enterpriseErrorHandler(err, invocationId) - .catch((error) => { - process.stdout.write(`${error.stack}\n`) - }) - .then(() => { - throw err + const serverless = new Serverless({ + interactive: typeof process.env.CI === 'undefined', }); + + serverless.invocationId = invocationId; + + return serverless + .init() + .then(() => serverless.run()) + .then(() => process.exit(0)) + .catch(err => { + // If Enterprise Plugin, capture error + let enterpriseErrorHandler = null; + serverless.pluginManager.plugins.forEach(p => { + if (p.enterprise && p.enterprise.errorHandler) { + enterpriseErrorHandler = p.enterprise.errorHandler; + } + }); + if (!enterpriseErrorHandler) { + throw err; + } + return enterpriseErrorHandler(err, invocationId) + .catch(error => { + process.stdout.write(`${error.stack}\n`); + }) + .then(() => { + throw err; + }); + }); }) -}).catch(e => { - process.exitCode = 1; - logError(e); -}))(); + .catch(e => { + process.exitCode = 1; + logError(e); + }))(); diff --git a/docker-compose.yml b/docker-compose.yml index 8903c00f2..d1858994b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,8 +84,8 @@ services: aws-go-mod: image: golang:1.11 volumes: - - ./tmp/serverless-integration-test-aws-go-mod:/app - - ./tmp/serverless-integration-test-aws-go-mod:/go/src/app + - ./tmp/serverless-integration-test-aws-go-mod:/app + - ./tmp/serverless-integration-test-aws-go-mod:/go/src/app aws-nodejs-typescript: image: node:6.10.3 volumes: diff --git a/docs/README.md b/docs/README.md index f79ac8956..931dcb430 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,7 +17,9 @@ menuItems: --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/) + # Documentation diff --git a/docs/providers/README.md b/docs/providers/README.md index b4b86e631..377843edc 100644 --- a/docs/providers/README.md +++ b/docs/providers/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/) + # Serverless Infrastructure Providers diff --git a/docs/providers/aws/README.md b/docs/providers/aws/README.md index f6e3f7129..63ed9f440 100644 --- a/docs/providers/aws/README.md +++ b/docs/providers/aws/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/) + # AWS Provider Documentation diff --git a/docs/providers/aws/cli-reference/README.md b/docs/providers/aws/cli-reference/README.md index cddf4206f..29a771ecc 100644 --- a/docs/providers/aws/cli-reference/README.md +++ b/docs/providers/aws/cli-reference/README.md @@ -5,12 +5,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/) + # Serverless AWS Lambda CLI Reference -Welcome to the Serverless AWS Lambda CLI Reference! Please select a section on the left to get started. +Welcome to the Serverless AWS Lambda CLI Reference! Please select a section on the left to get started. **Note:** Before continuing [AWS system credentials](../guide/credentials.md) are required for using the CLI. diff --git a/docs/providers/aws/cli-reference/config-credentials.md b/docs/providers/aws/cli-reference/config-credentials.md index d4736c29b..d7eb8168c 100644 --- a/docs/providers/aws/cli-reference/config-credentials.md +++ b/docs/providers/aws/cli-reference/config-credentials.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/config-credentials) + # AWS - Config Credentials diff --git a/docs/providers/aws/cli-reference/create.md b/docs/providers/aws/cli-reference/create.md index c4be41434..e197045a0 100644 --- a/docs/providers/aws/cli-reference/create.md +++ b/docs/providers/aws/cli-reference/create.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/create) + # AWS - Create @@ -33,6 +35,7 @@ serverless create --template-url https://github.com/serverless/serverless/tree/m ``` ## Options + - `--template` or `-t` The name of one of the available templates. **Required if --template-url and --template-path are not present**. - `--template-url` or `-u` The name of one of the available templates. **Required if --template and --template-path are not present**. - `--template-path` The local path of your template. **Required if --template and --template-url are not present**. @@ -40,6 +43,7 @@ serverless create --template-url https://github.com/serverless/serverless/tree/m - `--name` or `-n` the name of the service in `serverless.yml`. ## Provided lifecycle events + - `create:create` ## Available Templates diff --git a/docs/providers/aws/cli-reference/deploy-function.md b/docs/providers/aws/cli-reference/deploy-function.md index 8b06da246..c9a22f4b2 100644 --- a/docs/providers/aws/cli-reference/deploy-function.md +++ b/docs/providers/aws/cli-reference/deploy-function.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/deploy-function) + # AWS - Deploy Function -The `sls deploy function` command deploys an individual function without AWS CloudFormation. This command simply swaps out the zip file that your CloudFormation stack is pointing toward. This is a much faster way of deploying changes in code. +The `sls deploy function` command deploys an individual function without AWS CloudFormation. This command simply swaps out the zip file that your CloudFormation stack is pointing toward. This is a much faster way of deploying changes in code. ```bash serverless deploy function -f functionName @@ -24,6 +26,7 @@ is out of sync with your CloudFormation stack. Use this for faster development cycles and not production deployments ## Options + - `--function` or `-f` The name of the function which should be deployed - `--stage` or `-s` The stage in your service that you want to deploy to. - `--region` or `-r` The region in that stage that you want to deploy to. diff --git a/docs/providers/aws/cli-reference/deploy-list.md b/docs/providers/aws/cli-reference/deploy-list.md index 2c61737aa..8382b356b 100644 --- a/docs/providers/aws/cli-reference/deploy-list.md +++ b/docs/providers/aws/cli-reference/deploy-list.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/deploy-list) + # AWS - Deploy List diff --git a/docs/providers/aws/cli-reference/deploy.md b/docs/providers/aws/cli-reference/deploy.md index 058f6ed4c..96bab909d 100644 --- a/docs/providers/aws/cli-reference/deploy.md +++ b/docs/providers/aws/cli-reference/deploy.md @@ -7,18 +7,21 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/deploy) + # AWS - deploy -The `sls deploy` command deploys your entire service via CloudFormation. Run this command when you have made infrastructure changes (i.e., you edited `serverless.yml`). Use `serverless deploy function -f myFunction` when you have made code changes and you want to quickly upload your updated code to AWS Lambda or just change function configuration. +The `sls deploy` command deploys your entire service via CloudFormation. Run this command when you have made infrastructure changes (i.e., you edited `serverless.yml`). Use `serverless deploy function -f myFunction` when you have made code changes and you want to quickly upload your updated code to AWS Lambda or just change function configuration. ```bash serverless deploy ``` ## Options + - `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--stage` or `-s` The stage in your service that you want to deploy to. - `--region` or `-r` The region in that stage that you want to deploy to. diff --git a/docs/providers/aws/cli-reference/info.md b/docs/providers/aws/cli-reference/info.md index 665f8ccc8..35af22dbd 100644 --- a/docs/providers/aws/cli-reference/info.md +++ b/docs/providers/aws/cli-reference/info.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/info) + # AWS - Info @@ -19,11 +21,13 @@ serverless info ``` ## Options + - `--stage` or `-s` The stage in your service you want to display information about. - `--region` or `-r` The region in your stage that you want to display information about. - `--verbose` or `-v` Shows displays any Stack Output. ## Provided lifecycle events + - `info:info` ## Examples diff --git a/docs/providers/aws/cli-reference/install.md b/docs/providers/aws/cli-reference/install.md index ddc4c3a68..2220121e1 100644 --- a/docs/providers/aws/cli-reference/install.md +++ b/docs/providers/aws/cli-reference/install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/install) + # AWS - Install @@ -19,10 +21,12 @@ serverless install --url https://github.com/some/service ``` ## Options + - `--url` or `-u` The services GitHub URL. **Required**. - `--name` or `-n` Name for the service. ## Provided lifecycle events + - `install:install` ## Examples diff --git a/docs/providers/aws/cli-reference/invoke-local.md b/docs/providers/aws/cli-reference/invoke-local.md index e9a51687d..f11ec6b02 100644 --- a/docs/providers/aws/cli-reference/invoke-local.md +++ b/docs/providers/aws/cli-reference/invoke-local.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/invoke-local) + # AWS - Invoke Local -This runs your code locally by emulating the AWS Lambda environment. Please keep in mind, it's not a 100% perfect emulation, there may be some differences, but it works for the vast majority of users. We mock the `context` with simple mock data. +This runs your code locally by emulating the AWS Lambda environment. Please keep in mind, it's not a 100% perfect emulation, there may be some differences, but it works for the vast majority of users. We mock the `context` with simple mock data. ```bash serverless invoke local --function functionName @@ -28,6 +30,7 @@ serverless invoke local --function functionName - `--raw` Pass data as a raw string even if it is JSON. If not set, JSON data are parsed and passed as an object. - `--contextPath` or `-x`, The path to a json file holding input context to be passed to the invoked function. This path is relative to the root directory of the service. - `--context` or `-c`, String data to be passed as a context to your function. Same like with `--data`, context included in `--contextPath` will overwrite the context you passed with `--context` flag. + * `--env` or `-e` String representing an environment variable to set when invoking your function, in the form `=`. Can be repeated for more than one environment variable. * `--docker` Enable docker support for NodeJS/Python/Ruby/Java. Enabled by default for other runtimes. @@ -80,7 +83,7 @@ This example will pass the json data in the `lib/data.json` file (relative to th { "resource": "/", "path": "/", - "httpMethod": "GET", + "httpMethod": "GET" // etc. // } ``` @@ -96,6 +99,7 @@ serverless invoke local --function functionName --context "hello world" ```bash serverless invoke local --function functionName --contextPath lib/context.json ``` + This example will pass the json context in the `lib/context.json` file (relative to the root of the service) while invoking the specified/deployed function. ### Local function invocation, setting environment variables @@ -120,7 +124,7 @@ Use of the `--docker` flag and runtimes other than NodeJs, Python, Java, & Ruby ## Resource permissions -Lambda functions assume an *IAM role* during execution: the framework creates this role, and set all the permission provided in the `iamRoleStatements` section of `serverless.yml`. +Lambda functions assume an _IAM role_ during execution: the framework creates this role, and set all the permission provided in the `iamRoleStatements` section of `serverless.yml`. Unless you explicitly state otherwise, every call to the AWS SDK inside the lambda function is made using this role (a temporary pair of key / secret is generated and set by AWS as environment variables, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`). @@ -131,4 +135,4 @@ Take a look to the official AWS documentation (in this particular instance, for - [http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html) - [http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-lambda.html](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-lambda.html) -Whatever approach you decide to implement, **be aware**: the set of permissions might be (and probably is) different, so you won't have an exact simulation of the *real* IAM policy in place. +Whatever approach you decide to implement, **be aware**: the set of permissions might be (and probably is) different, so you won't have an exact simulation of the _real_ IAM policy in place. diff --git a/docs/providers/aws/cli-reference/invoke.md b/docs/providers/aws/cli-reference/invoke.md index 515df23af..f3ab278a0 100644 --- a/docs/providers/aws/cli-reference/invoke.md +++ b/docs/providers/aws/cli-reference/invoke.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/invoke) + # AWS - Invoke @@ -21,6 +23,7 @@ serverless invoke [local] --function functionName **Note:** Please refer to [this guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-simple-proxy-for-lambda-input-format) for event data passing when your function uses the `http` event with a Lambda Proxy integration. ## Options + - `--function` or `-f` The name of the function in your service that you want to invoke. **Required**. - `--stage` or `-s` The stage in your service you want to invoke your function in. - `--region` or `-r` The region in your stage that you want to invoke your function in. @@ -31,6 +34,7 @@ serverless invoke [local] --function functionName - `--log` or `-l` If set to `true` and invocation type is `RequestResponse`, it will output logging data of the invocation. Default is `false`. ## Provided lifecycle events + - `invoke:invoke` # Invoke Local @@ -45,7 +49,7 @@ serverless invoke local --function functionName - `--function` or `-f` The name of the function in your service that you want to invoke locally. **Required**. - `--path` or `-p` The path to a json file holding input data to be passed to the invoked function as the `event`. This path is relative to the -root directory of the service. + root directory of the service. - `--data` or `-d` String data to be passed as an event to your function. Keep in mind that if you pass both `--path` and `--data`, the data included in the `--path` file will overwrite the data you passed with the `--data` flag. - `--raw` Pass data as a raw string even if it is JSON. If not set, JSON data are parsed and passed as an object. - `--contextPath` or `-x`, The path to a json file holding input context to be passed to the invoked function. This path is relative to the root directory of the service. @@ -97,7 +101,7 @@ the specified/deployed function. { "resource": "/", "path": "/", - "httpMethod": "GET", + "httpMethod": "GET" // etc. // } ``` @@ -109,9 +113,11 @@ serverless invoke local --function functionName --context "hello world" ``` ### Local function invocation with context passing + ```bash serverless invoke local --function functionName --contextPath lib/context.json ``` + This example will pass the json context in the `lib/context.json` file (relative to the root of the service) while invoking the specified/deployed function. ### Limitations @@ -120,7 +126,7 @@ Currently, `invoke local` only supports the NodeJs and Python runtimes. ## Resource permissions -Lambda functions assume an *IAM role* during execution: the framework creates this role, and set all the permission provided in the `iamRoleStatements` section of `serverless.yml`. +Lambda functions assume an _IAM role_ during execution: the framework creates this role, and set all the permission provided in the `iamRoleStatements` section of `serverless.yml`. Unless you explicitly state otherwise, every call to the AWS SDK inside the lambda function is made using this role (a temporary pair of key / secret is generated and set by AWS as environment variables, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`). @@ -131,4 +137,4 @@ Take a look to the official AWS documentation (in this particular instance, for - [http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html) - [http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-lambda.html](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-lambda.html) -Whatever approach you decide to implement, **be aware**: the set of permissions might be (and probably is) different, so you won't have an exact simulation of the *real* IAM policy in place. +Whatever approach you decide to implement, **be aware**: the set of permissions might be (and probably is) different, so you won't have an exact simulation of the _real_ IAM policy in place. diff --git a/docs/providers/aws/cli-reference/login.md b/docs/providers/aws/cli-reference/login.md index 81f42e851..ba6fc8e5e 100644 --- a/docs/providers/aws/cli-reference/login.md +++ b/docs/providers/aws/cli-reference/login.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/login) + # Login diff --git a/docs/providers/aws/cli-reference/logs.md b/docs/providers/aws/cli-reference/logs.md index 31e9b2b6e..8cae90f84 100644 --- a/docs/providers/aws/cli-reference/logs.md +++ b/docs/providers/aws/cli-reference/logs.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/logs) + # AWS - Logs @@ -65,16 +67,19 @@ This command returns as many log events as can fit in 1MB (up to 10,000 log even ```bash serverless logs -f hello ``` + This will fetch the logs from last 10 minutes as startTime was not given. ```bash serverless logs -f hello --startTime 5h ``` + This will fetch the logs that happened in the past 5 hours. ```bash serverless logs -f hello --startTime 1469694264 ``` + This will fetch the logs that happened starting at epoch `1469694264`. ```bash @@ -86,4 +91,5 @@ Serverless will tail the CloudWatch log output and print new log messages coming ```bash serverless logs -f hello --filter serverless ``` + This will fetch only the logs that contain the string `serverless` diff --git a/docs/providers/aws/cli-reference/metrics.md b/docs/providers/aws/cli-reference/metrics.md index d4b57f2ea..1ea7cad93 100644 --- a/docs/providers/aws/cli-reference/metrics.md +++ b/docs/providers/aws/cli-reference/metrics.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/metrics) + # AWS - Metrics diff --git a/docs/providers/aws/cli-reference/package.md b/docs/providers/aws/cli-reference/package.md index 665b80178..ed5e8ccd5 100644 --- a/docs/providers/aws/cli-reference/package.md +++ b/docs/providers/aws/cli-reference/package.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/package) + # AWS - package @@ -19,6 +21,7 @@ serverless package ``` ## Options + - `--stage` or `-s` The stage in your service that you want to deploy to. - `--region` or `-r` The region in that stage that you want to deploy to. - `--package` or `-p` path to the custom packaging directory you want. diff --git a/docs/providers/aws/cli-reference/plugin-install.md b/docs/providers/aws/cli-reference/plugin-install.md index 1c1f537ac..f57cad05d 100644 --- a/docs/providers/aws/cli-reference/plugin-install.md +++ b/docs/providers/aws/cli-reference/plugin-install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/plugin-install) + # Plugin Install @@ -22,9 +24,11 @@ serverless plugin install --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:install:install` ## Examples diff --git a/docs/providers/aws/cli-reference/plugin-list.md b/docs/providers/aws/cli-reference/plugin-list.md index 51881f451..4d0c945fc 100644 --- a/docs/providers/aws/cli-reference/plugin-list.md +++ b/docs/providers/aws/cli-reference/plugin-list.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/plugin-list) + # Plugin List @@ -19,7 +21,9 @@ serverless plugin list ``` ## Options -- *None* + +- _None_ ## Provided lifecycle events + - `plugin:list:list` diff --git a/docs/providers/aws/cli-reference/plugin-search.md b/docs/providers/aws/cli-reference/plugin-search.md index 2837b623c..af21b6da3 100644 --- a/docs/providers/aws/cli-reference/plugin-search.md +++ b/docs/providers/aws/cli-reference/plugin-search.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/plugin-search) + # Plugin Search @@ -19,9 +21,11 @@ serverless plugin search --query query ``` ## Options + - `--query` or `-q` The query you want to use for your search. **Required**. ## Provided lifecycle events + - `plugin:search:search` ## Examples diff --git a/docs/providers/aws/cli-reference/plugin-uninstall.md b/docs/providers/aws/cli-reference/plugin-uninstall.md index 9e40cd118..8516f7a2d 100644 --- a/docs/providers/aws/cli-reference/plugin-uninstall.md +++ b/docs/providers/aws/cli-reference/plugin-uninstall.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/plugin-uninstall) + # Plugin Uninstall @@ -19,9 +21,11 @@ serverless plugin uninstall --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:uninstall:uninstall` ## Examples diff --git a/docs/providers/aws/cli-reference/print.md b/docs/providers/aws/cli-reference/print.md index 39584d5b9..8fa979d95 100644 --- a/docs/providers/aws/cli-reference/print.md +++ b/docs/providers/aws/cli-reference/print.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/print) + # Print diff --git a/docs/providers/aws/cli-reference/remove.md b/docs/providers/aws/cli-reference/remove.md index ccb5db9d4..020705613 100644 --- a/docs/providers/aws/cli-reference/remove.md +++ b/docs/providers/aws/cli-reference/remove.md @@ -7,23 +7,27 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/remove) + # AWS - Remove -The `sls remove` command will remove the deployed service, defined in your current working directory, from the provider. +The `sls remove` command will remove the deployed service, defined in your current working directory, from the provider. ```bash serverless remove ``` ## Options + - `--stage` or `-s` The name of the stage in service. - `--region` or `-r` The name of the region in stage. - `--verbose` or `-v` Shows all stack events during deployment. ## Provided lifecycle events + - `remove:remove` ## Examples diff --git a/docs/providers/aws/cli-reference/rollback-function.md b/docs/providers/aws/cli-reference/rollback-function.md index e8e6fff80..6a398bb64 100644 --- a/docs/providers/aws/cli-reference/rollback-function.md +++ b/docs/providers/aws/cli-reference/rollback-function.md @@ -7,9 +7,10 @@ layout: Doc --> -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/rollback-function) - +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/rollback-function) + + # AWS - Rollback Function diff --git a/docs/providers/aws/cli-reference/rollback.md b/docs/providers/aws/cli-reference/rollback.md index de4a88eec..52d7d98be 100644 --- a/docs/providers/aws/cli-reference/rollback.md +++ b/docs/providers/aws/cli-reference/rollback.md @@ -7,9 +7,10 @@ layout: Doc --> -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/rollback) - +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/rollback) + + # AWS - Rollback @@ -22,10 +23,12 @@ serverless rollback --timestamp timestamp If `timestamp` is not specified, Framework will show your existing deployments. ## Options + - `--timestamp` or `-t` The deployment you want to rollback to. - `--verbose` or `-v` Shows any Stack Output. ## Provided lifecycle events + - `rollback:initialize` - `rollback:rollback` diff --git a/docs/providers/aws/cli-reference/slstats.md b/docs/providers/aws/cli-reference/slstats.md index 2dc6e5457..be607109d 100644 --- a/docs/providers/aws/cli-reference/slstats.md +++ b/docs/providers/aws/cli-reference/slstats.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/slstats) + # Serverless Statistics and Usage Tracking @@ -46,8 +48,8 @@ The following is a list of the events that we collect: - service_pluginUninstalled - service_installed - user_awsCredentialsConfigured -- user_enabledTracking -- user_disabledTracking +- user_enabledTracking +- user_disabledTracking - user_loggedIn - user_loggedOut diff --git a/docs/providers/aws/events/README.md b/docs/providers/aws/events/README.md index 8f2403e58..ec19c71f0 100644 --- a/docs/providers/aws/events/README.md +++ b/docs/providers/aws/events/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/) + # Serverless AWS Lambda Events diff --git a/docs/providers/aws/events/alb.md b/docs/providers/aws/events/alb.md index a367d17aa..b357c24fe 100644 --- a/docs/providers/aws/events/alb.md +++ b/docs/providers/aws/events/alb.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/alb) + # Application Load Balancer diff --git a/docs/providers/aws/events/alexa-skill.md b/docs/providers/aws/events/alexa-skill.md index e4e76acc4..092835613 100644 --- a/docs/providers/aws/events/alexa-skill.md +++ b/docs/providers/aws/events/alexa-skill.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/alexa-skill) + # Alexa Skill diff --git a/docs/providers/aws/events/alexa-smart-home.md b/docs/providers/aws/events/alexa-smart-home.md index cff1a86c1..92f52c086 100644 --- a/docs/providers/aws/events/alexa-smart-home.md +++ b/docs/providers/aws/events/alexa-smart-home.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/alexa-smart-home) + # Alexa Smart Home diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 37f806765..2edeaa6ce 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/apigateway) + # API Gateway @@ -62,15 +64,16 @@ _Are you looking for tutorials on using API Gateway? Check out the following res To create HTTP endpoints as Event sources for your AWS Lambda Functions, use the Serverless Framework's easy AWS API Gateway Events syntax. There are five ways you can configure your HTTP endpoints to integrate with your AWS Lambda Functions: -* `lambda-proxy` / `aws-proxy` / `aws_proxy` (Recommended) -* `lambda` / `aws` -* `http` -* `http-proxy` / `http_proxy` -* `mock` + +- `lambda-proxy` / `aws-proxy` / `aws_proxy` (Recommended) +- `lambda` / `aws` +- `http` +- `http-proxy` / `http_proxy` +- `mock` **The Framework uses the `lambda-proxy` method (i.e., everything is passed into your Lambda) by default unless another method is supplied by the user** -The difference between these is `lambda-proxy` (alternative writing styles are `aws-proxy` and `aws_proxy` for compatibility with the standard AWS integration type naming) automatically passes the content of the HTTP request into your AWS Lambda function (headers, body, etc.) and allows you to configure your response (headers, status code, body) in the code of your AWS Lambda Function. Whereas, the `lambda` method makes you explicitly define headers, status codes, and more in the configuration of each API Gateway Endpoint (not in code). We highly recommend using the `lambda-proxy` method if it supports your use-case, since the `lambda` method is highly tedious. +The difference between these is `lambda-proxy` (alternative writing styles are `aws-proxy` and `aws_proxy` for compatibility with the standard AWS integration type naming) automatically passes the content of the HTTP request into your AWS Lambda function (headers, body, etc.) and allows you to configure your response (headers, status code, body) in the code of your AWS Lambda Function. Whereas, the `lambda` method makes you explicitly define headers, status codes, and more in the configuration of each API Gateway Endpoint (not in code). We highly recommend using the `lambda-proxy` method if it supports your use-case, since the `lambda` method is highly tedious. Use `http` for integrating with an HTTP back end, `http-proxy` for integrating with the HTTP proxy integration or `mock` for testing without actually invoking the back end. @@ -99,22 +102,22 @@ functions: 'use strict'; module.exports.hello = function(event, context, callback) { + console.log(event); // Contains incoming request data (e.g., query params, headers and more) - console.log(event); // Contains incoming request data (e.g., query params, headers and more) + const response = { + statusCode: 200, + headers: { + 'x-custom-header': 'My Header Value', + }, + body: JSON.stringify({ message: 'Hello World!' }), + }; - const response = { - statusCode: 200, - headers: { - "x-custom-header" : "My Header Value" - }, - body: JSON.stringify({ "message": "Hello World!" }) - }; - - callback(null, response); + callback(null, response); }; ``` **Note:** When the body is a JSON-Document, you must parse it yourself: + ``` JSON.parse(event.body); ``` @@ -123,62 +126,62 @@ JSON.parse(event.body); ```json { - "resource": "/", - "path": "/", + "resource": "/", + "path": "/", + "httpMethod": "POST", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4", + "cache-control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "GB", + "content-type": "application/x-www-form-urlencoded", + "Host": "j3ap25j034.execute-api.eu-west-2.amazonaws.com", + "origin": "https://j3ap25j034.execute-api.eu-west-2.amazonaws.com", + "Referer": "https://j3ap25j034.execute-api.eu-west-2.amazonaws.com/dev/", + "upgrade-insecure-requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "Via": "2.0 a3650115c5e21e2b5d133ce84464bea3.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "0nDeiXnReyHYCkv8cc150MWCFCLFPbJoTs1mexDuKe2WJwK5ANgv2A==", + "X-Amzn-Trace-Id": "Root=1-597079de-75fec8453f6fd4812414a4cd", + "X-Forwarded-For": "50.129.117.14, 50.112.234.94", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "queryStringParameters": null, + "pathParameters": null, + "stageVariables": null, + "requestContext": { + "path": "/dev/", + "accountId": "125002137610", + "resourceId": "qdolsr1yhk", + "stage": "dev", + "requestId": "0f2431a2-6d2f-11e7-b799-5152aa497861", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "apiKey": "", + "sourceIp": "50.129.117.14", + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "user": null + }, + "resourcePath": "/", "httpMethod": "POST", - "headers": { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "Accept-Encoding": "gzip, deflate, br", - "Accept-Language": "en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4", - "cache-control": "max-age=0", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "GB", - "content-type": "application/x-www-form-urlencoded", - "Host": "j3ap25j034.execute-api.eu-west-2.amazonaws.com", - "origin": "https://j3ap25j034.execute-api.eu-west-2.amazonaws.com", - "Referer": "https://j3ap25j034.execute-api.eu-west-2.amazonaws.com/dev/", - "upgrade-insecure-requests": "1", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", - "Via": "2.0 a3650115c5e21e2b5d133ce84464bea3.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "0nDeiXnReyHYCkv8cc150MWCFCLFPbJoTs1mexDuKe2WJwK5ANgv2A==", - "X-Amzn-Trace-Id": "Root=1-597079de-75fec8453f6fd4812414a4cd", - "X-Forwarded-For": "50.129.117.14, 50.112.234.94", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "queryStringParameters": null, - "pathParameters": null, - "stageVariables": null, - "requestContext": { - "path": "/dev/", - "accountId": "125002137610", - "resourceId": "qdolsr1yhk", - "stage": "dev", - "requestId": "0f2431a2-6d2f-11e7-b799-5152aa497861", - "identity": { - "cognitoIdentityPoolId": null, - "accountId": null, - "cognitoIdentityId": null, - "caller": null, - "apiKey": "", - "sourceIp": "50.129.117.14", - "accessKey": null, - "cognitoAuthenticationType": null, - "cognitoAuthenticationProvider": null, - "userArn": null, - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", - "user": null - }, - "resourcePath": "/", - "httpMethod": "POST", - "apiId": "j3azlsj0c4" - }, - "body": "postcode=LS17FR", - "isBase64Encoded": false + "apiId": "j3azlsj0c4" + }, + "body": "postcode=LS17FR", + "isBase64Encoded": false } ``` @@ -199,6 +202,7 @@ functions: ``` ### Enabling CORS + To set CORS configurations for your HTTP endpoints, simply modify your event configurations as follows: ```yml @@ -263,10 +267,10 @@ functions: Wildcards are accepted. The following example will match all sub-domains of example.com over http: ```yml - cors: - origins: - - http://*.example.com - - http://example2.com +cors: + origins: + - http://*.example.com + - http://example2.com ``` Please note that since you can't send multiple values for [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin), this configuration uses a response template to check if the request origin matches one of your provided `origins` and overrides the header with the following code: @@ -276,7 +280,7 @@ Please note that since you can't send multiple values for [Access-Control-Allow- #if($origin == "http://example.com" || $origin == "http://*.amazonaws.com") #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end ``` -Configuring the `cors` property sets [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin), [Access-Control-Allow-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers), [Access-Control-Allow-Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods),[Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) headers in the CORS preflight response. +Configuring the `cors` property sets [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin), [Access-Control-Allow-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers), [Access-Control-Allow-Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods),[Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) headers in the CORS preflight response. To enable the `Access-Control-Max-Age` preflight response header, set the `maxAge` property in the `cors` object: @@ -326,17 +330,16 @@ If you want to use CORS with the lambda-proxy integration, remember to include t 'use strict'; module.exports.hello = function(event, context, callback) { + const response = { + statusCode: 200, + headers: { + 'Access-Control-Allow-Origin': '*', // Required for CORS support to work + 'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS + }, + body: JSON.stringify({ message: 'Hello World!' }), + }; - const response = { - statusCode: 200, - headers: { - "Access-Control-Allow-Origin" : "*", // Required for CORS support to work - "Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS - }, - body: JSON.stringify({ "message": "Hello World!" }) - }; - - callback(null, response); + callback(null, response); }; ``` @@ -371,7 +374,7 @@ functions: ### HTTP Endpoints with Custom Authorizers -Custom Authorizers allow you to run an AWS Lambda Function before your targeted AWS Lambda Function. This is useful for Microservice Architectures or when you simply want to do some Authorization before running your business logic. +Custom Authorizers allow you to run an AWS Lambda Function before your targeted AWS Lambda Function. This is useful for Microservice Architectures or when you simply want to do some Authorization before running your business logic. You can enable Custom Authorizers for your HTTP endpoint by setting the Authorizer in your `http` event to another function in the same service, as shown in the following example: @@ -388,6 +391,7 @@ functions: authorizerFunc: handler: handler.authorizerFunc ``` + Or, if you want to configure the Authorizer with more options, you can turn the `authorizer` property into an object as shown in the following example: @@ -511,7 +515,7 @@ functions: - http: path: posts/create method: post - async: true # default is false + async: true # default is false ``` ### Catching Exceptions In Your Lambda Function @@ -574,11 +578,11 @@ provider: name: aws apiKeys: - free: - - myFreeKey - - ${opt:stage}-myFreeKey + - myFreeKey + - ${opt:stage}-myFreeKey - paid: - - myPaidKey - - ${opt:stage}-myPaidKey + - myPaidKey + - ${opt:stage}-myPaidKey usagePlan: - free: quota: @@ -691,9 +695,7 @@ A sample schema contained in `create_request.json` might look something like thi "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "The Root Schema", - "required": [ - "username" - ], + "required": ["username"], "properties": { "username": { "type": "string", @@ -752,51 +754,51 @@ This method is more complicated and involves a lot more configuration of the `ht ```json { - "body": {}, - "method": "GET", - "principalId": "", - "stage": "dev", - "cognitoPoolClaims": { - "sub": "" - }, - "enhancedAuthContext": {}, - "headers": { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "Accept-Encoding": "gzip, deflate, br", - "Accept-Language": "en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "GB", - "Host": "ec5ycylws8.execute-api.us-east-1.amazonaws.com", - "upgrade-insecure-requests": "1", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", - "Via": "2.0 f165ce34daf8c0da182681179e863c24.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "l06CAg2QsrALeQcLAUSxGXbm8lgMoMIhR2AjKa4AiKuaVnnGsOFy5g==", - "X-Amzn-Trace-Id": "Root=1-5970ef20-3e249c0321b2eef14aa513ae", - "X-Forwarded-For": "94.117.120.169, 116.132.62.73", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "query": {}, - "path": {}, - "identity": { - "cognitoIdentityPoolId": "", - "accountId": "", - "cognitoIdentityId": "", - "caller": "", - "apiKey": "", - "sourceIp": "94.197.120.169", - "accessKey": "", - "cognitoAuthenticationType": "", - "cognitoAuthenticationProvider": "", - "userArn": "", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", - "user": "" - }, - "stageVariables": {} + "body": {}, + "method": "GET", + "principalId": "", + "stage": "dev", + "cognitoPoolClaims": { + "sub": "" + }, + "enhancedAuthContext": {}, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "GB", + "Host": "ec5ycylws8.execute-api.us-east-1.amazonaws.com", + "upgrade-insecure-requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "Via": "2.0 f165ce34daf8c0da182681179e863c24.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "l06CAg2QsrALeQcLAUSxGXbm8lgMoMIhR2AjKa4AiKuaVnnGsOFy5g==", + "X-Amzn-Trace-Id": "Root=1-5970ef20-3e249c0321b2eef14aa513ae", + "X-Forwarded-For": "94.117.120.169, 116.132.62.73", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "query": {}, + "path": {}, + "identity": { + "cognitoIdentityPoolId": "", + "accountId": "", + "cognitoIdentityId": "", + "caller": "", + "apiKey": "", + "sourceIp": "94.197.120.169", + "accessKey": "", + "cognitoAuthenticationType": "", + "cognitoAuthenticationProvider": "", + "userArn": "", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "user": "" + }, + "stageVariables": {} } ``` @@ -866,7 +868,8 @@ You can then access the query string `https://example.com/dev/whatever?bar=123` If you want to spread a string into multiple lines, you can use the `>` or `|` syntax, but the following strings have to be all indented with the same amount, [read more about `>` syntax](http://stackoverflow.com/questions/3790454/in-yaml-how-do-i-break-a-string-over-multiple-lines). #### Pass Through Behavior -API Gateway provides multiple ways to handle requests where the Content-Type header does not match any of the specified mapping templates. When this happens, the request payload will either be passed through the integration request *without transformation* or rejected with a `415 - Unsupported Media Type`, depending on the configuration. + +API Gateway provides multiple ways to handle requests where the Content-Type header does not match any of the specified mapping templates. When this happens, the request payload will either be passed through the integration request _without transformation_ or rejected with a `415 - Unsupported Media Type`, depending on the configuration. You can define this behaviour as follows (if not specified, a value of **NEVER** will be used): @@ -885,11 +888,11 @@ functions: There are 3 available options: -|Value | Passed Through When | Rejected When | -|----------------- | --------------------------------------------- | ----------------------------------------------------------------------- | -|NEVER | Never | No templates defined or Content-Type does not match a defined template | -|WHEN_NO_MATCH | Content-Type does not match defined template | Never | -|WHEN_NO_TEMPLATES | No templates were defined | One or more templates defined, but Content-Type does not match | +| Value | Passed Through When | Rejected When | +| ----------------- | -------------------------------------------- | ---------------------------------------------------------------------- | +| NEVER | Never | No templates defined or Content-Type does not match a defined template | +| WHEN_NO_MATCH | Content-Type does not match defined template | Never | +| WHEN_NO_TEMPLATES | No templates were defined | One or more templates defined, but Content-Type does not match | See the [api gateway documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/integration-passthrough-behaviors.html) for detailed descriptions of these options. @@ -953,26 +956,26 @@ the `${file(templatefile)}` syntax. Serverless ships with default status codes you can use to e.g. signal that a resource could not be found (404) or that the user is not authorized to perform the action (401). Those status codes are regex definitions that will be added to your API Gateway configuration. -***Note:*** Status codes as documented in this chapter relate to `lambda` integration method (as documented at the top of this page). If using default integration method `lambda-proxy` object with status code and message should be returned as in the example below: +**_Note:_** Status codes as documented in this chapter relate to `lambda` integration method (as documented at the top of this page). If using default integration method `lambda-proxy` object with status code and message should be returned as in the example below: ```javascript module.exports.hello = (event, context, callback) => { - callback(null, { statusCode: 404, body: "Not found", headers: { "Content-Type": "text/plain" } }); -} + callback(null, { statusCode: 404, body: 'Not found', headers: { 'Content-Type': 'text/plain' } }); +}; ``` #### Available Status Codes -| Status Code | Meaning | -| --- | --- | -| 400 | Bad Request | -| 401 | Unauthorized | -| 403 | Forbidden | -| 404 | Not Found | -| 422 | Unprocessable Entity | -| 500 | Internal Server Error | -| 502 | Bad Gateway | -| 504 | Gateway Timeout | +| Status Code | Meaning | +| ----------- | --------------------- | +| 400 | Bad Request | +| 401 | Unauthorized | +| 403 | Forbidden | +| 404 | Not Found | +| 422 | Unprocessable Entity | +| 500 | Internal Server Error | +| 502 | Bad Gateway | +| 504 | Gateway Timeout | #### Using Status Codes @@ -984,7 +987,7 @@ Here's an example which shows you how you can raise a 404 HTTP status from withi ```javascript module.exports.hello = (event, context, callback) => { callback(new Error('[404] Not found')); -} +}; ``` #### Custom Status Codes @@ -1009,13 +1012,13 @@ functions: Content-Type: "'text/html'" template: $input.path('$') statusCodes: - 201: - pattern: '' # Default response method - 409: - pattern: '.*"statusCode":409,.*' # JSON response - template: $input.path("$.errorMessage") # JSON return object - headers: - Content-Type: "'application/json+hal'" + 201: + pattern: '' # Default response method + 409: + pattern: '.*"statusCode":409,.*' # JSON response + template: $input.path("$.errorMessage") # JSON return object + headers: + Content-Type: "'application/json+hal'" ``` You can also create varying response templates for each code and content type by creating an object with the key as the content type @@ -1034,15 +1037,15 @@ functions: Content-Type: "'text/html'" template: $input.path('$') statusCodes: - 201: - pattern: '' # Default response method - 409: - pattern: '.*"statusCode":409,.*' # JSON response - template: - application/json: $input.path("$.errorMessage") # JSON return object - application/xml: $input.path("$.body.errorMessage") # XML return object - headers: - Content-Type: "'application/json+hal'" + 201: + pattern: '' # Default response method + 409: + pattern: '.*"statusCode":409,.*' # JSON response + template: + application/json: $input.path("$.errorMessage") # JSON return object + application/xml: $input.path("$.body.errorMessage") # XML return object + headers: + Content-Type: "'application/json+hal'" ``` ## Setting an HTTP Proxy on API Gateway @@ -1053,8 +1056,7 @@ one for method. These two templates will work together to construct your proxy. ```yml service: service-name provider: aws -functions: - ... +functions: ... resources: Resources: @@ -1106,12 +1108,9 @@ provider: websocketApiId: xxxxxxxxxx # Websocket API resource ID. Default is generated by the framewok description: Some Description # optional - description of deployment history -functions: - ... - +functions: ... ``` - If your application has many nested paths, you might also want to break them out into smaller services. ```yml @@ -1163,9 +1162,7 @@ provider: restApiResources: posts: xxxxxxxxxx -functions: - ... - +functions: ... ``` ```yml @@ -1179,9 +1176,7 @@ provider: restApiResources: /posts: xxxxxxxxxx -functions: - ... - +functions: ... ``` You can define more than one path resource, but by default, Serverless will generate them from the root resource. @@ -1199,7 +1194,6 @@ provider: /posts: xxxxxxxxxx /categories: xxxxxxxxx - functions: listPosts: handler: posts.list @@ -1214,7 +1208,6 @@ functions: - http: method: get path: /categories - ``` ### Easiest and CI/CD friendly example of using shared API Gateway and API Resources. @@ -1264,7 +1257,6 @@ resources: Ref: MyWebsocketApi Export: Name: MyApiGateway-websocketApiId - ``` This creates API gateway and then exports the `restApiId`, `rootResourceId` and `websocketApiId` values using cloudformation cross stack output. @@ -1282,9 +1274,9 @@ provider: websocketApiId: 'Fn::ImportValue': MyApiGateway-websocketApiId -functions: - service-a-functions +functions: service-a-functions ``` + ```yml service: service-b @@ -1297,15 +1289,13 @@ provider: websocketApiId: 'Fn::ImportValue': MyApiGateway-websocketApiId -functions: - service-b-functions +functions: service-b-functions ``` You can use this method to share your API Gateway across services in same region. Read about this limitation [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html). **Note:** We've noticed you can't use provider.tags together with `Fn::ImportValue` for `restApiId` and `restApiRootResourceId`. Doing so won't resolve the imported value, and therefore returns an error. - ### Manually Configuring shared API Gateway Use AWS console on browser, navigate to the API Gateway console. Select your already existing API Gateway. @@ -1340,7 +1330,6 @@ functions: arn: xxxxxxxxxxxxxxxxx #cognito/custom authorizer arn ``` - ```yml service: service-d @@ -1422,15 +1411,14 @@ provider: resourcePolicy: - Effect: Allow - Principal: "*" + Principal: '*' Action: execute-api:Invoke Resource: - execute-api:/*/*/* Condition: IpAddress: aws:SourceIp: - - "123.123.123.123" - + - '123.123.123.123' ``` ## Compression diff --git a/docs/providers/aws/events/cloudwatch-event.md b/docs/providers/aws/events/cloudwatch-event.md index 0236e6f1b..d4acdc254 100644 --- a/docs/providers/aws/events/cloudwatch-event.md +++ b/docs/providers/aws/events/cloudwatch-event.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/cloudwatch-event) + # CloudWatch Event @@ -25,9 +27,9 @@ functions: - cloudwatchEvent: event: source: - - "aws.ec2" + - 'aws.ec2' detail-type: - - "EC2 Instance State-change Notification" + - 'EC2 Instance State-change Notification' detail: state: - pending @@ -47,9 +49,9 @@ functions: - cloudwatchEvent: event: source: - - "aws.ec2" + - 'aws.ec2' detail-type: - - "EC2 Instance State-change Notification" + - 'EC2 Instance State-change Notification' detail: state: - pending @@ -68,9 +70,9 @@ functions: - cloudwatchEvent: event: source: - - "aws.ec2" + - 'aws.ec2' detail-type: - - "EC2 Instance State-change Notification" + - 'EC2 Instance State-change Notification' detail: state: - pending @@ -82,9 +84,9 @@ functions: - cloudwatchEvent: event: source: - - "aws.ec2" + - 'aws.ec2' detail-type: - - "EC2 Instance State-change Notification" + - 'EC2 Instance State-change Notification' detail: state: - pending @@ -92,9 +94,9 @@ functions: - cloudwatchEvent: event: source: - - "aws.ec2" + - 'aws.ec2' detail-type: - - "EC2 Instance State-change Notification" + - 'EC2 Instance State-change Notification' detail: state: - pending @@ -117,9 +119,9 @@ functions: description: 'CloudWatch Event triggered on EC2 Instance pending state' event: source: - - "aws.ec2" + - 'aws.ec2' detail-type: - - "EC2 Instance State-change Notification" + - 'EC2 Instance State-change Notification' detail: state: - pending @@ -138,9 +140,9 @@ functions: name: 'my-cloudwatch-event-name' event: source: - - "aws.ec2" + - 'aws.ec2' detail-type: - - "EC2 Instance State-change Notification" + - 'EC2 Instance State-change Notification' detail: state: - pending diff --git a/docs/providers/aws/events/cloudwatch-log.md b/docs/providers/aws/events/cloudwatch-log.md index 629dc5a35..d5021973b 100644 --- a/docs/providers/aws/events/cloudwatch-log.md +++ b/docs/providers/aws/events/cloudwatch-log.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/cloudwatch-log) + # CloudWatch Log diff --git a/docs/providers/aws/events/cognito-user-pool.md b/docs/providers/aws/events/cognito-user-pool.md index a4751364e..708d72aeb 100644 --- a/docs/providers/aws/events/cognito-user-pool.md +++ b/docs/providers/aws/events/cognito-user-pool.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/cognito-user-pool) + # Cognito User Pool @@ -114,5 +116,4 @@ resources: ``` [aws-triggers-guide]: http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html -[aws-triggers-list]: -https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cognito-userpool-lambdaconfig.html +[aws-triggers-list]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cognito-userpool-lambdaconfig.html diff --git a/docs/providers/aws/events/iot.md b/docs/providers/aws/events/iot.md index 095b9c2a2..cc9d06283 100644 --- a/docs/providers/aws/events/iot.md +++ b/docs/providers/aws/events/iot.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/iot) + # IoT @@ -51,9 +53,9 @@ functions: handler: myIoT.handler events: - iot: - name: "myIotEvent" + name: 'myIotEvent' sql: "SELECT * FROM 'some_topic'" - description: "My IoT Event Description" + description: 'My IoT Event Description' ``` ## Specify SQL Versions @@ -67,5 +69,5 @@ functions: events: - iot: sql: "SELECT * FROM 'some_topic'" - sqlVersion: "beta" + sqlVersion: 'beta' ``` diff --git a/docs/providers/aws/events/s3.md b/docs/providers/aws/events/s3.md index 16689ca45..d61638a80 100644 --- a/docs/providers/aws/events/s3.md +++ b/docs/providers/aws/events/s3.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/s3) + # S3 @@ -78,6 +80,7 @@ functions: If you need to configure the bucket itself, you'll need to create the bucket and the Lambda Permission manually in the Resources section while paying attention to some of the logical IDs. This relies on the Serverless naming convention. See the [Serverless Resource Reference](../guide/resources/#aws-cloudformation-resource-reference) for details. These are the logical IDs that require your attention: + - The logical ID of the custom bucket in the Resources section needs to match the bucket name in the S3 event after the Serverless naming convention is applied to it. - The Lambda Permission's logical ID needs to match the Serverless naming convention for Lambda Permissions for S3 events. - The `FunctionName` in the Lambda Permission configuration needs to match the logical ID generated for the target Lambda function as determined by the Serverless naming convention. @@ -99,15 +102,15 @@ resources: BucketName: my-custom-bucket-name # add additional custom bucket configuration here ResizeLambdaPermissionPhotosS3: - Type: "AWS::Lambda::Permission" + Type: 'AWS::Lambda::Permission' Properties: FunctionName: - "Fn::GetAtt": + 'Fn::GetAtt': - ResizeLambdaFunction - Arn - Principal: "s3.amazonaws.com" - Action: "lambda:InvokeFunction" + Principal: 's3.amazonaws.com' + Action: 'lambda:InvokeFunction' SourceAccount: Ref: AWS::AccountId - SourceArn: "arn:aws:s3:::my-custom-bucket-name" + SourceArn: 'arn:aws:s3:::my-custom-bucket-name' ``` diff --git a/docs/providers/aws/events/schedule.md b/docs/providers/aws/events/schedule.md index 162c276a9..5c3a09774 100644 --- a/docs/providers/aws/events/schedule.md +++ b/docs/providers/aws/events/schedule.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/schedule) + # Schedule diff --git a/docs/providers/aws/events/sns.md b/docs/providers/aws/events/sns.md index 9e4ab66ba..738f5e9b4 100644 --- a/docs/providers/aws/events/sns.md +++ b/docs/providers/aws/events/sns.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/sns) + # SNS @@ -69,11 +71,11 @@ functions: - sns: arn: Fn::Join: - - ":" - - - "arn:aws:sns" - - Ref: "AWS::Region" - - Ref: "AWS::AccountId" - - "MyCustomTopic" + - ':' + - - 'arn:aws:sns' + - Ref: 'AWS::Region' + - Ref: 'AWS::AccountId' + - 'MyCustomTopic' topicName: MyCustomTopic ``` diff --git a/docs/providers/aws/events/sqs.md b/docs/providers/aws/events/sqs.md index 77c5041f9..4ead9acf7 100644 --- a/docs/providers/aws/events/sqs.md +++ b/docs/providers/aws/events/sqs.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/sqs) + # SQS Queues @@ -37,7 +39,7 @@ functions: - sqs: arn: Fn::Join: - - ":" + - ':' - - arn - aws - sqs diff --git a/docs/providers/aws/events/streams.md b/docs/providers/aws/events/streams.md index 2fc9e0b4c..4f5491718 100644 --- a/docs/providers/aws/events/streams.md +++ b/docs/providers/aws/events/streams.md @@ -7,14 +7,17 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/streams) + # DynamoDB / Kinesis Streams This setup specifies that the `compute` function should be triggered whenever: - 1. the corresponding DynamoDB table is modified (e.g. a new entry is added). - 2. the Lambda checkpoint has not reached the end of the Kinesis stream (e.g. a new record is added). + +1. the corresponding DynamoDB table is modified (e.g. a new entry is added). +2. the Lambda checkpoint has not reached the end of the Kinesis stream (e.g. a new record is added). The ARN for the stream can be specified as a string, the reference to the ARN of a resource by logical ID, or the import of an ARN that was exported by a different service or CloudFormation stack. diff --git a/docs/providers/aws/events/websocket.md b/docs/providers/aws/events/websocket.md index 7df2f1327..b448748d2 100644 --- a/docs/providers/aws/events/websocket.md +++ b/docs/providers/aws/events/websocket.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/websocket) + # Websocket @@ -46,10 +48,11 @@ functions: ## Routes The API-Gateway provides [4 types of routes](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-overview.html) which relate to the lifecycle of a ws-client: -* `$connect` called on connect of a ws-client -* `$disconnect` called on disconnect of a ws-client (may not be called in some situations) -* `$default` called if there is no handler to use for the event -* custom routes - called if the route name is specified for a handler + +- `$connect` called on connect of a ws-client +- `$disconnect` called on disconnect of a ws-client (may not be called in some situations) +- `$default` called if there is no handler to use for the event +- custom routes - called if the route name is specified for a handler ### Example serverless.yaml @@ -84,6 +87,7 @@ functions: ``` ## Using Authorizers + You can enable an authorizer for your connect route by specifying the `authorizer` key in the websocket event definition. **Note:** AWS only supports authorizers for the `$connect` route. @@ -114,7 +118,6 @@ functions: By default, the `identitySource` property is set to `route.request.header.Auth`, meaning that your request must include the auth token in the `Auth` header of the request. You can overwrite this by specifying your own `identitySource` configuration: - ```yml functions: connectHandler: @@ -131,6 +134,7 @@ functions: auth: handler: handler.auth ``` + With the above configuration, you can now must pass the auth token in both the `Auth` query string as well as the `Auth` header. You can also supply an ARN instead of the name when using the object syntax for the authorizer: @@ -153,6 +157,7 @@ functions: ``` ## Send a message to a ws-client + To send a message to a ws-client the [@connection](https://docs.amazonaws.cn/en_us/apigateway/latest/developerguide/apigateway-how-to-call-websocket-api-connections.html) command is used. It uses the URL of the websocket API and most importantly the `connectionId` of the ws-client's connection. If you want to send a message to a ws-client from another function, you need this `connectionId` to address the ws-client. @@ -160,19 +165,26 @@ It uses the URL of the websocket API and most importantly the `connectionId` of Example on how to respond with the complete `event` to the same ws-client: ```js -const sendMessageToClient = (url, connectionId, payload) => new Promise((resolve, reject) => { - const apigatewaymanagementapi = new AWS.ApiGatewayManagementApi({apiVersion: '2018-11-29', endpoint: url}); - apigatewaymanagementapi.postToConnection({ - ConnectionId: connectionId, // connectionId of the receiving ws-client - Data: JSON.stringify(payload), - }, (err, data) => { - if (err) { - console.log('err is', err); - reject(err); - } - resolve(data); +const sendMessageToClient = (url, connectionId, payload) => + new Promise((resolve, reject) => { + const apigatewaymanagementapi = new AWS.ApiGatewayManagementApi({ + apiVersion: '2018-11-29', + endpoint: url, + }); + apigatewaymanagementapi.postToConnection( + { + ConnectionId: connectionId, // connectionId of the receiving ws-client + Data: JSON.stringify(payload), + }, + (err, data) => { + if (err) { + console.log('err is', err); + reject(err); + } + resolve(data); + } + ); }); -}); module.exports.defaultHandler = async (event, context) => { const domain = event.requestContext.domainName; @@ -182,9 +194,9 @@ module.exports.defaultHandler = async (event, context) => { await sendMessageToClient(callbackUrlForAWS, connectionId, event); return { - statusCode: 200 + statusCode: 200, }; -} +}; ``` ## Logs diff --git a/docs/providers/aws/examples/README.md b/docs/providers/aws/examples/README.md index bc23fc706..9c58f8cbb 100644 --- a/docs/providers/aws/examples/README.md +++ b/docs/providers/aws/examples/README.md @@ -5,26 +5,28 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/) + # Serverless AWS Lambda Examples Have an example? Submit a PR or [open an issue](https://github.com/serverless/examples/issues). ⚡️ -| Example | Runtime | -|:--------------------------- |:-----| -| [Aws Auth0 Api Gateway](https://serverless.com/examples/aws-node-auth0-custom-authorizers-api/)
    Demonstration of protecting API gateway endpoints with auth0 | nodeJS | -| [Env Variables Encrypted In A File](https://serverless.com/examples/aws-node-env-variables-encrypted-in-a-file/)
    Serverless example managing secrets in an encrypted file | nodeJS | -| [Serverless Node Env Variables](https://serverless.com/examples/aws-node-env-variables/)
    This example demonstrates how to use environment variables for AWS Lambdas. | nodeJS | -| [Fetch File And Store In S3](https://serverless.com/examples/aws-node-fetch-file-and-store-in-s3/)
    Fetch an image from remote source (URL) and then upload the image to a S3 bucket. | nodeJS | -| [Function Compiled With Babel](https://serverless.com/examples/aws-node-function-compiled-with-babel/)
    Demonstrating how to compile all your code with Babel | nodeJS | -| [Serverless Rest With Dynamodb](https://serverless.com/examples/aws-node-rest-api-with-dynamodb/)
    Serverless CRUD service exposing a REST HTTP interface | nodeJS | -| [Serverless Aws Cron Job Example](https://serverless.com/examples/aws-node-scheduled-cron/)
    Example of creating a function that runs as a cron job using the serverless `schedule` event | nodeJS | -| [Aws Node Serve Dynamic Html Via Http Endpoint](https://serverless.com/examples/aws-node-serve-dynamic-html-via-http-endpoint/)
    Hookup an AWS API Gateway endpoint to a Lambda function to render HTML on a `GET` request | nodeJS | -| [Aws Node Serve Dynamic Html Via Http Endpoint](https://serverless.com/examples/aws-node-simple-http-endpoint/)
    Example demonstrates how to setup a simple HTTP GET endpoint | nodeJS | -| [Single Page App Via Cloudfront](https://serverless.com/examples/aws-node-single-page-app-via-cloudfront/)
    Demonstrating how to deploy a Single Page Application with Serverless | nodeJS | -| [Serverless Data Pipeline](https://serverless.com/examples/aws-node-text-analysis-via-sns-post-processing/)
    Example demonstrates how to setup a simple data processing pipeline | nodeJS | -| [Aws Python Simple Http Endpoint](https://serverless.com/examples/aws-python-simple-http-endpoint/)
    Example demonstrates how to setup a simple HTTP GET endpoint with python | python | +| Example | Runtime | +| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------ | +| [Aws Auth0 Api Gateway](https://serverless.com/examples/aws-node-auth0-custom-authorizers-api/)
    Demonstration of protecting API gateway endpoints with auth0 | nodeJS | +| [Env Variables Encrypted In A File](https://serverless.com/examples/aws-node-env-variables-encrypted-in-a-file/)
    Serverless example managing secrets in an encrypted file | nodeJS | +| [Serverless Node Env Variables](https://serverless.com/examples/aws-node-env-variables/)
    This example demonstrates how to use environment variables for AWS Lambdas. | nodeJS | +| [Fetch File And Store In S3](https://serverless.com/examples/aws-node-fetch-file-and-store-in-s3/)
    Fetch an image from remote source (URL) and then upload the image to a S3 bucket. | nodeJS | +| [Function Compiled With Babel](https://serverless.com/examples/aws-node-function-compiled-with-babel/)
    Demonstrating how to compile all your code with Babel | nodeJS | +| [Serverless Rest With Dynamodb](https://serverless.com/examples/aws-node-rest-api-with-dynamodb/)
    Serverless CRUD service exposing a REST HTTP interface | nodeJS | +| [Serverless Aws Cron Job Example](https://serverless.com/examples/aws-node-scheduled-cron/)
    Example of creating a function that runs as a cron job using the serverless `schedule` event | nodeJS | +| [Aws Node Serve Dynamic Html Via Http Endpoint](https://serverless.com/examples/aws-node-serve-dynamic-html-via-http-endpoint/)
    Hookup an AWS API Gateway endpoint to a Lambda function to render HTML on a `GET` request | nodeJS | +| [Aws Node Serve Dynamic Html Via Http Endpoint](https://serverless.com/examples/aws-node-simple-http-endpoint/)
    Example demonstrates how to setup a simple HTTP GET endpoint | nodeJS | +| [Single Page App Via Cloudfront](https://serverless.com/examples/aws-node-single-page-app-via-cloudfront/)
    Demonstrating how to deploy a Single Page Application with Serverless | nodeJS | +| [Serverless Data Pipeline](https://serverless.com/examples/aws-node-text-analysis-via-sns-post-processing/)
    Example demonstrates how to setup a simple data processing pipeline | nodeJS | +| [Aws Python Simple Http Endpoint](https://serverless.com/examples/aws-python-simple-http-endpoint/)
    Example demonstrates how to setup a simple HTTP GET endpoint with python | python | If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://forum.serverless.com/) diff --git a/docs/providers/aws/examples/hello-world/README.md b/docs/providers/aws/examples/hello-world/README.md index 378b05677..41e20235f 100644 --- a/docs/providers/aws/examples/hello-world/README.md +++ b/docs/providers/aws/examples/hello-world/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/) + # Hello World Serverless Example 🌍 @@ -15,10 +17,10 @@ Welcome to the Hello World example. Pick your language of choice: -* [JavaScript](./node) -* [Python](./python) -* [C#](./csharp) -* [F#](./fsharp) -* [Go](./go) +- [JavaScript](./node) +- [Python](./python) +- [C#](./csharp) +- [F#](./fsharp) +- [Go](./go) [View all examples](https://www.serverless.com/framework/docs/providers/aws/examples/) diff --git a/docs/providers/aws/examples/hello-world/csharp/README.md b/docs/providers/aws/examples/hello-world/csharp/README.md index 499d24224..397ae33c2 100644 --- a/docs/providers/aws/examples/hello-world/csharp/README.md +++ b/docs/providers/aws/examples/hello-world/csharp/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/csharp/) + # Hello World C# Example @@ -57,7 +59,6 @@ sls deploy This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. - ## 4. Invoke deployed function ``` diff --git a/docs/providers/aws/examples/hello-world/fsharp/README.md b/docs/providers/aws/examples/hello-world/fsharp/README.md index f1e14c97d..b49d4ec2e 100644 --- a/docs/providers/aws/examples/hello-world/fsharp/README.md +++ b/docs/providers/aws/examples/hello-world/fsharp/README.md @@ -1,79 +1,81 @@ - - - -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/fsharp/) - - -# Hello World F# Example - -Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). - -Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. - -``` -$ sls - -Commands -* You can run commands with "serverless" or the shortcut "sls" -* Pass "--verbose" to this command to get in-depth plugin info -* Pass "--no-color" to disable CLI colors -* Pass "--help" after any for contextual help -``` - -## 1. Create a service - -``` -sls create --template aws-fsharp --path myService -``` - -Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-fsharp with the `--template` or shorthand `-t` flag. - -The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. - -## 2. Build using .NET Core 2.X CLI tools and create zip package - -``` -# Linux or Mac OS -./build.sh -``` - -``` -# Windows PowerShell -./build.cmd -``` - -## 3. Deploy - -``` -sls deploy -``` - -This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. - -## 4. Invoke deployed function - -``` -sls invoke -f hello -``` - -Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. - -In your terminal window you should see the response from AWS Lambda. - -```bash -{ - "Message": "Go Serverless v1.0! Your function executed successfully!", - "Request": { - "Key1": null, - "Key2": null, - "Key3": null - } -} -``` - -Congrats you have deployed and ran your Hello World function! + + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/fsharp/) + + + +# Hello World F# Example + +Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). + +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + +## 1. Create a service + +``` +sls create --template aws-fsharp --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-fsharp with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. + +## 2. Build using .NET Core 2.X CLI tools and create zip package + +``` +# Linux or Mac OS +./build.sh +``` + +``` +# Windows PowerShell +./build.cmd +``` + +## 3. Deploy + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. + +## 4. Invoke deployed function + +``` +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. + +In your terminal window you should see the response from AWS Lambda. + +```bash +{ + "Message": "Go Serverless v1.0! Your function executed successfully!", + "Request": { + "Key1": null, + "Key2": null, + "Key3": null + } +} +``` + +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/go/README.md b/docs/providers/aws/examples/hello-world/go/README.md index 05a233a33..e01d6e533 100644 --- a/docs/providers/aws/examples/hello-world/go/README.md +++ b/docs/providers/aws/examples/hello-world/go/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/go/) + # Hello World Go Example @@ -63,7 +65,7 @@ Change directories into 'myService' folder and you can see this project has 2 ha │ └── main.go ``` -This because a `main()` function is required as entry point for each handler executable. +This because a `main()` function is required as entry point for each handler executable. ## 2. Build using go build to create static binaries diff --git a/docs/providers/aws/examples/hello-world/node/README.md b/docs/providers/aws/examples/hello-world/node/README.md index fb927951f..dbcc620a0 100644 --- a/docs/providers/aws/examples/hello-world/node/README.md +++ b/docs/providers/aws/examples/hello-world/node/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/node/) + # Hello World Node.js Example diff --git a/docs/providers/aws/examples/hello-world/node/handler.js b/docs/providers/aws/examples/hello-world/node/handler.js index 12a311f42..6c0df8241 100644 --- a/docs/providers/aws/examples/hello-world/node/handler.js +++ b/docs/providers/aws/examples/hello-world/node/handler.js @@ -1,7 +1,7 @@ 'use strict'; // Your function handler -module.exports.helloWorldHandler = function (event, context, callback) { +module.exports.helloWorldHandler = function(event, context, callback) { const message = { message: 'Hello World', event, diff --git a/docs/providers/aws/examples/hello-world/python/README.md b/docs/providers/aws/examples/hello-world/python/README.md index c6380de14..11f1c597a 100644 --- a/docs/providers/aws/examples/hello-world/python/README.md +++ b/docs/providers/aws/examples/hello-world/python/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/python/) + # Hello World Python Example diff --git a/docs/providers/aws/examples/hello-world/ruby/README.md b/docs/providers/aws/examples/hello-world/ruby/README.md index 720642ead..2f73539b0 100644 --- a/docs/providers/aws/examples/hello-world/ruby/README.md +++ b/docs/providers/aws/examples/hello-world/ruby/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/ruby/) + # Hello World Ruby Example @@ -55,8 +57,8 @@ In your terminal window you should see the response from AWS Lambda. ```json { - "statusCode": 200, - "body": "\"Go Serverless v1.0! Your function executed successfully!\"" + "statusCode": 200, + "body": "\"Go Serverless v1.0! Your function executed successfully!\"" } ``` diff --git a/docs/providers/aws/guide/README.md b/docs/providers/aws/guide/README.md index 12835d123..67bca12a0 100644 --- a/docs/providers/aws/guide/README.md +++ b/docs/providers/aws/guide/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/) + # Serverless AWS Lambda Guide diff --git a/docs/providers/aws/guide/credentials.md b/docs/providers/aws/guide/credentials.md index 2fb055262..b1308b372 100644 --- a/docs/providers/aws/guide/credentials.md +++ b/docs/providers/aws/guide/credentials.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/credentials) + # AWS - Credentials @@ -28,14 +30,13 @@ If you're new to Amazon Web Services, make sure you put in a credit card. All AWS users get access to the Free Tier for [AWS Lambda](https://aws.amazon.com/lambda/pricing/). AWS Lambda is part of the non-expiring [AWS Free Tier](https://aws.amazon.com/free/#AWS_FREE_TIER). For additional pricing information for AWS Lambda and Gateway [here](https://aws.amazon.com/lambda/pricing/). If you're using additional AWS services, they could incur additional costs. Please review pricing for you services on AWS [here](https://aws.amazon.com/pricing/). - If you don't have a credit card set up, you may not be able to deploy your resources and you may run into this error: ``` AWS Access Key Id needs a subscription for the service ``` -While in the AWS Free Tier, you can build an entire application on AWS Lambda, AWS API Gateway, and more, without getting charged for 1 year... As long as you don't exceed the resources in the free tier, of course. +While in the AWS Free Tier, you can build an entire application on AWS Lambda, AWS API Gateway, and more, without getting charged for 1 year... As long as you don't exceed the resources in the free tier, of course. ### Creating AWS Access Keys @@ -45,17 +46,17 @@ To let the Serverless Framework access your AWS account, we're going to **create 2. Click on **Users** and then **Add user**. Enter a name in the first field to remind you this User is the Framework, like `serverless-agent`. Enable **Programmatic access** by clicking the checkbox. Click **Next** to go through to the Permissions page. Click on **Create policy**. Select the **JSON** tab, add the following JSON file you'll find in [this gist](https://gist.github.com/ServerlessBot/7618156b8671840a539f405dea2704c8). -When you are finished, select **Review policy**. You can assign this policy a **Name** and **Description**, then choose **Create Policy**. Check everything looks good and click **Create user**. Later, you can create different IAM Users for different apps and different stages of those apps. That is, if you don't use separate AWS accounts for stages/apps, which is most common. +When you are finished, select **Review policy**. You can assign this policy a **Name** and **Description**, then choose **Create Policy**. Check everything looks good and click **Create user**. Later, you can create different IAM Users for different apps and different stages of those apps. That is, if you don't use separate AWS accounts for stages/apps, which is most common. 3. View and copy the **API Key** & **Secret** to a temporary place. You'll need it in the next step. -As you add additional functions and services, your permission needs will change. Though not advised, you can **create an IAM User with Admin access**, which can configure the services in your AWS account. This IAM User will have its own set of AWS Access Keys. +As you add additional functions and services, your permission needs will change. Though not advised, you can **create an IAM User with Admin access**, which can configure the services in your AWS account. This IAM User will have its own set of AWS Access Keys. -**Note:** In a production environment, we recommend reducing the permissions to the IAM User which the Framework uses. Unfortunately, the Framework's functionality is growing so fast, we can't yet offer you a finite set of permissions it needs (we're working on this). Consider using a separate AWS account in the interim, if you cannot get permission to your organization's primary AWS accounts. +**Note:** In a production environment, we recommend reducing the permissions to the IAM User which the Framework uses. Unfortunately, the Framework's functionality is growing so fast, we can't yet offer you a finite set of permissions it needs (we're working on this). Consider using a separate AWS account in the interim, if you cannot get permission to your organization's primary AWS accounts. 1. Create or login to your Amazon Web Services Account and go to the Identity & Access Management (IAM) page. -2. Click on **Users** and then **Add user**. Enter a name in the first field to remind you this User is the Framework, like `serverless-admin`. Enable **Programmatic access** by clicking the checkbox. Click **Next** to go through to the Permissions page. Click on **Attach existing policies directly**. Search for and select **AdministratorAccess** then click **Next: Review**. Check everything looks good and click **Create user**. Later, you can create different IAM Users for different apps and different stages of those apps. That is, if you don't use separate AWS accounts for stages/apps, which is most common. +2. Click on **Users** and then **Add user**. Enter a name in the first field to remind you this User is the Framework, like `serverless-admin`. Enable **Programmatic access** by clicking the checkbox. Click **Next** to go through to the Permissions page. Click on **Attach existing policies directly**. Search for and select **AdministratorAccess** then click **Next: Review**. Check everything looks good and click **Create user**. Later, you can create different IAM Users for different apps and different stages of those apps. That is, if you don't use separate AWS accounts for stages/apps, which is most common. 3. View and copy the **API Key** & **Secret** to a temporary place. You'll need it in the next step. @@ -76,7 +77,8 @@ serverless deploy # 'export' command is valid only for unix shells. In Windows - use 'set' instead of 'export' ``` -**Please note:** *If you are using a self-signed certificate you'll need to do one of the following:* +**Please note:** _If you are using a self-signed certificate you'll need to do one of the following:_ + ```bash # String example: # if using the 'ca' variable, your certificate contents should replace the newline character with '\n' @@ -94,7 +96,6 @@ export cafile="/path/to/cafile1.pem,/path/to/cafile2.pem" # 'export' command is valid only for unix shells. In Windows - use 'set' instead of 'export' ``` - #### Using AWS Profiles For a more permanent solution you can also set up credentials through AWS profiles. Here are different methods you can use to do so. @@ -191,4 +192,4 @@ custom: **Be aware!** Due to the way AWS IAM and the local environment works, if you invoke your lambda functions locally using the CLI command `serverless invoke local -f ...` the IAM role/profile could be (and probably is) different from the one set in the `serverless.yml` configuration file. Thus, most likely, a different set of permissions will be in place, altering the interaction between your lambda functions and other AWS resources. -*Please, refer to the [`invoke local`](https://serverless.com/framework/docs/providers/aws/cli-reference/invoke-local/#aws---invoke-local) CLI command documentation for more details.* +_Please, refer to the [`invoke local`](https://serverless.com/framework/docs/providers/aws/cli-reference/invoke-local/#aws---invoke-local) CLI command documentation for more details._ diff --git a/docs/providers/aws/guide/deploying.md b/docs/providers/aws/guide/deploying.md index 30a4d5191..15a70b8ee 100644 --- a/docs/providers/aws/guide/deploying.md +++ b/docs/providers/aws/guide/deploying.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/deploying) + # AWS - Deploying -The Serverless Framework was designed to provision your AWS Lambda Functions, Events and infrastructure Resources safely and quickly. It does this via a couple of methods designed for different types of deployments. +The Serverless Framework was designed to provision your AWS Lambda Functions, Events and infrastructure Resources safely and quickly. It does this via a couple of methods designed for different types of deployments. ## Deploy All @@ -24,32 +26,33 @@ serverless deploy Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to Amazon Web Services. -**Note:** You can always enforce a deployment using the `--force` option, or specify a different configuration file name with the the `--config` option. +**Note:** You can always enforce a deployment using the `--force` option, or specify a different configuration file name with the the `--config` option. ### How It Works -The Serverless Framework translates all syntax in `serverless.yml` to a single AWS CloudFormation template. By depending on CloudFormation for deployments, users of the Serverless Framework get the safety and reliability of CloudFormation. +The Serverless Framework translates all syntax in `serverless.yml` to a single AWS CloudFormation template. By depending on CloudFormation for deployments, users of the Serverless Framework get the safety and reliability of CloudFormation. -* An AWS CloudFormation template is created from your `serverless.yml`. -* If a Stack has not yet been created, then it is created with no resources except for an S3 Bucket, which will store zip files of your Function code. -* The code of your Functions is then packaged into zip files. -* Serverless fetches the hashes for all files of the previous deployment (if any) and compares them against the hashes of the local files. -* Serverless terminates the deployment process if all file hashes are the same. -* Zip files of your Functions' code are uploaded to your Code S3 Bucket. -* Any IAM Roles, Functions, Events and Resources are added to the AWS CloudFormation template. -* The CloudFormation Stack is updated with the new CloudFormation template. -* Each deployment publishes a new version for each function in your service. +- An AWS CloudFormation template is created from your `serverless.yml`. +- If a Stack has not yet been created, then it is created with no resources except for an S3 Bucket, which will store zip files of your Function code. +- The code of your Functions is then packaged into zip files. +- Serverless fetches the hashes for all files of the previous deployment (if any) and compares them against the hashes of the local files. +- Serverless terminates the deployment process if all file hashes are the same. +- Zip files of your Functions' code are uploaded to your Code S3 Bucket. +- Any IAM Roles, Functions, Events and Resources are added to the AWS CloudFormation template. +- The CloudFormation Stack is updated with the new CloudFormation template. +- Each deployment publishes a new version for each function in your service. ### Tips -* Use this in your CI/CD systems, as it is the safest method of deployment. -* You can print the progress during the deployment if you use `verbose` mode, like this: +- Use this in your CI/CD systems, as it is the safest method of deployment. +- You can print the progress during the deployment if you use `verbose` mode, like this: ``` serverless deploy --verbose ``` -* This method uses the AWS CloudFormation Stack Update method. CloudFormation is slow, so this method is slower. If you want to develop more quickly, use the `serverless deploy function` command (described below) +- This method uses the AWS CloudFormation Stack Update method. CloudFormation is slow, so this method is slower. If you want to develop more quickly, use the `serverless deploy function` command (described below) + +- This method defaults to `dev` stage and `us-east-1` region. You can change the default stage and region in your `serverless.yml` file by setting the `stage` and `region` properties inside a `provider` object as the following example shows: -* This method defaults to `dev` stage and `us-east-1` region. You can change the default stage and region in your `serverless.yml` file by setting the `stage` and `region` properties inside a `provider` object as the following example shows: ```yml # serverless.yml @@ -60,47 +63,46 @@ The Serverless Framework translates all syntax in `serverless.yml` to a single A region: us-west-2 ``` -* You can also deploy to different stages and regions by passing in flags to the command: +- You can also deploy to different stages and regions by passing in flags to the command: + ``` serverless deploy --stage production --region eu-central-1 ``` -* You can specify your own S3 bucket which should be used to store all the deployment artifacts. +- You can specify your own S3 bucket which should be used to store all the deployment artifacts. The `deploymentBucket` config which is nested under `provider` lets you e.g. set the `name` or the `serverSideEncryption` method for this bucket. If you don't provide your own bucket, Serverless will create a bucket which uses default AES256 encryption. -* You can specify your own S3 prefix which should be used to store all the deployment artifacts. +- You can specify your own S3 prefix which should be used to store all the deployment artifacts. The `deploymentPrefix` config which is nested under `provider` lets you set the prefix under which the deployment artifacts will be stored. If not specified, defaults to `serverless`. -* You can make uploading to S3 faster by adding `--aws-s3-accelerate` +- You can make uploading to S3 faster by adding `--aws-s3-accelerate` Check out the [deploy command docs](../cli-reference/deploy.md) for all details and options. - -* For information on multi-region deployments, [checkout this article](https://serverless.com/blog/build-multiregion-multimaster-application-dynamodb-global-tables). +- For information on multi-region deployments, [checkout this article](https://serverless.com/blog/build-multiregion-multimaster-application-dynamodb-global-tables). ## Deploy Function -This deployment method does not touch your AWS CloudFormation Stack. Instead, it simply overwrites the zip file of the current function on AWS. This method is much faster, since it does not rely on CloudFormation. +This deployment method does not touch your AWS CloudFormation Stack. Instead, it simply overwrites the zip file of the current function on AWS. This method is much faster, since it does not rely on CloudFormation. ```bash serverless deploy function --function myFunction ``` --**Note:** You can always enforce a deployment using the `--force` option. --**Note:** You can use `--update-config` to change only Lambda configuration without deploying code. +-**Note:** You can always enforce a deployment using the `--force` option. -**Note:** You can use `--update-config` to change only Lambda configuration without deploying code. ### How It Works -* The Framework packages up the targeted AWS Lambda Function into a zip file. -* The Framework fetches the hash of the already uploaded function .zip file and compares it to the local .zip file hash. -* The Framework terminates if both hashes are the same. -* That zip file is uploaded to your S3 bucket using the same name as the previous function, which the CloudFormation stack is pointing to. +- The Framework packages up the targeted AWS Lambda Function into a zip file. +- The Framework fetches the hash of the already uploaded function .zip file and compares it to the local .zip file hash. +- The Framework terminates if both hashes are the same. +- That zip file is uploaded to your S3 bucket using the same name as the previous function, which the CloudFormation stack is pointing to. ### Tips -* Use this when you are developing and want to test on AWS because it's much faster. -* During development, people will often run this command several times, as opposed to `serverless deploy` which is only run when larger infrastructure provisioning is required. +- Use this when you are developing and want to test on AWS because it's much faster. +- During development, people will often run this command several times, as opposed to `serverless deploy` which is only run when larger infrastructure provisioning is required. Check out the [deploy command docs](../cli-reference/deploy.md) for all details and options. @@ -116,5 +118,3 @@ serverless deploy --package path-to-package - The argument to the `--package` flag is a directory that has been previously packaged by Serverless (with `serverless package`). - The deploy process bypasses the package step and uses the existing package to deploy and update CloudFormation stacks. - - diff --git a/docs/providers/aws/guide/events.md b/docs/providers/aws/guide/events.md index f98ddf222..701584b91 100644 --- a/docs/providers/aws/guide/events.md +++ b/docs/providers/aws/guide/events.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/events) + # AWS - Events @@ -60,7 +62,7 @@ functions: ## Types -The Serverless Framework supports all of the AWS Lambda events and more. Instead of listing them here, we've put them in a separate section, since they have a lot of configurations and functionality. [Check out the events section for more information.](../events) +The Serverless Framework supports all of the AWS Lambda events and more. Instead of listing them here, we've put them in a separate section, since they have a lot of configurations and functionality. [Check out the events section for more information.](../events) ## Deploying diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index af8154cc6..dc0153ef0 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/functions) + # AWS - Functions -If you are using AWS as a provider, all *functions* inside the service are AWS Lambda functions. +If you are using AWS as a provider, all _functions_ inside the service are AWS Lambda functions. ## Configuration @@ -47,7 +49,7 @@ The `handler` property points to the file and module containing the code you wan ```javascript // handler.js -module.exports.functionOne = function(event, context, callback) {} +module.exports.functionOne = function(event, context, callback) {}; ``` You can add as many functions as you want within this property. @@ -107,8 +109,7 @@ You can specify an array of functions, which is useful if you separate your func ```yml # serverless.yml -... - +--- functions: - ${file(./foo-functions.yml)} - ${file(./bar-functions.yml)} @@ -122,10 +123,9 @@ deleteFoo: handler: handler.foo ``` - ## Permissions -Every AWS Lambda function needs permission to interact with other AWS infrastructure resources within your account. These permissions are set via an AWS IAM Role. You can set permission policy statements within this role via the `provider.iamRoleStatements` property. +Every AWS Lambda function needs permission to interact with other AWS infrastructure resources within your account. These permissions are set via an AWS IAM Role. You can set permission policy statements within this role via the `provider.iamRoleStatements` property. ```yml # serverless.yml @@ -144,7 +144,7 @@ provider: - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem - Resource: "arn:aws:dynamodb:us-east-1:*:*" + Resource: 'arn:aws:dynamodb:us-east-1:*:*' functions: functionOne: @@ -160,21 +160,21 @@ service: myService provider: name: aws iamRoleStatements: - - Effect: "Allow" - Action: - - "s3:ListBucket" - # You can put CloudFormation syntax in here. No one will judge you. - # Remember, this all gets translated to CloudFormation. - Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket"} ] ] } - - Effect: "Allow" - Action: - - "s3:PutObject" - Resource: - Fn::Join: - - "" - - - "arn:aws:s3:::" - - "Ref" : "ServerlessDeploymentBucket" - - "/*" + - Effect: 'Allow' + Action: + - 's3:ListBucket' + # You can put CloudFormation syntax in here. No one will judge you. + # Remember, this all gets translated to CloudFormation. + Resource: { 'Fn::Join': ['', ['arn:aws:s3:::', { 'Ref': 'ServerlessDeploymentBucket' }]] } + - Effect: 'Allow' + Action: + - 's3:PutObject' + Resource: + Fn::Join: + - '' + - - 'arn:aws:s3:::' + - 'Ref': 'ServerlessDeploymentBucket' + - '/*' functions: functionOne: @@ -293,6 +293,7 @@ functions: environment: TABLE_NAME: tableName2 ``` + If you want your function's environment variables to have the same values from your machine's environment variables, please read the documentation about [Referencing Environment Variables](./variables.md). ## Tags @@ -359,7 +360,6 @@ To publish Lambda Layers, check out the [Layers](./layers.md) documentation. By default, the framework will create LogGroups for your Lambdas. This makes it easy to clean up your log groups in the case you remove your service, and make the lambda IAM permissions much more specific and secure. - ## Versioning Deployed Functions By default, the framework creates function versions for every deploy. This behavior is optional, and can be turned off in cases where you don't invoke past versions by their qualifier. If you would like to do this, you can invoke your functions as `arn:aws:lambda:....:function/myFunc:3` to invoke version 3 for example. diff --git a/docs/providers/aws/guide/iam.md b/docs/providers/aws/guide/iam.md index ca982db00..f08eef33c 100644 --- a/docs/providers/aws/guide/iam.md +++ b/docs/providers/aws/guide/iam.md @@ -7,18 +7,20 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/iam) + # IAM -Every AWS Lambda function needs permission to interact with other AWS infrastructure resources within your account. These permissions are set via an AWS IAM Role which the Serverless Framework automatically creates for each Serverless Service, and is shared by all of your Functions. The Framework allows you to modify this Role or create Function-specific Roles, easily. +Every AWS Lambda function needs permission to interact with other AWS infrastructure resources within your account. These permissions are set via an AWS IAM Role which the Serverless Framework automatically creates for each Serverless Service, and is shared by all of your Functions. The Framework allows you to modify this Role or create Function-specific Roles, easily. ## The Default IAM Role By default, one IAM Role is shared by all of the Lambda functions in your service. Also by default, your Lambda functions have permission to create and write to CloudWatch logs. When VPC configuration is provided the default AWS `AWSLambdaVPCAccessExecutionRole` will be associated in order to communicate with your VPC resources. -To add specific rights to this service-wide Role, define statements in `provider.iamRoleStatements` which will be merged into the generated policy. As those statements will be merged into the CloudFormation template, you can use `Join`, `Ref` or any other CloudFormation method or feature. +To add specific rights to this service-wide Role, define statements in `provider.iamRoleStatements` which will be merged into the generated policy. As those statements will be merged into the CloudFormation template, you can use `Join`, `Ref` or any other CloudFormation method or feature. ```yml service: new-service @@ -26,26 +28,27 @@ service: new-service provider: name: aws iamRoleStatements: - - Effect: "Allow" - Action: - - "s3:ListBucket" - Resource: - Fn::Join: - - "" - - - "arn:aws:s3:::" - - Ref: ServerlessDeploymentBucket - - Effect: "Allow" - Action: - - "s3:PutObject" - Resource: - Fn::Join: - - "" - - - "arn:aws:s3:::" - - Ref: ServerlessDeploymentBucket - - "/*" - + - Effect: 'Allow' + Action: + - 's3:ListBucket' + Resource: + Fn::Join: + - '' + - - 'arn:aws:s3:::' + - Ref: ServerlessDeploymentBucket + - Effect: 'Allow' + Action: + - 's3:PutObject' + Resource: + Fn::Join: + - '' + - - 'arn:aws:s3:::' + - Ref: ServerlessDeploymentBucket + - '/*' ``` + Alongside `provider.iamRoleStatements` managed policies can also be added to this service-wide Role, define managed policies in `provider.iamManagedPolicies`. These will also be merged into the generated IAM Role so you can use `Join`, `Ref` or any other CloudFormation method or feature here too. + ```yml service: new-service @@ -180,7 +183,7 @@ resources: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - Resource: + Resource: - 'Fn::Join': - ':' - @@ -218,7 +221,7 @@ resources: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - Resource: + Resource: - 'Fn::Join': - ':' - @@ -277,7 +280,7 @@ resources: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - Resource: + Resource: - 'Fn::Join': - ':' - @@ -316,7 +319,7 @@ resources: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - Resource: + Resource: - 'Fn::Join': - ':' - @@ -331,5 +334,5 @@ resources: - ec2:DetachNetworkInterface - ec2:DeleteNetworkInterface Resource: "*" - + ``` diff --git a/docs/providers/aws/guide/installation.md b/docs/providers/aws/guide/installation.md index 3b8806f6a..657d02a6c 100644 --- a/docs/providers/aws/guide/installation.md +++ b/docs/providers/aws/guide/installation.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/installation) + # AWS - Installation diff --git a/docs/providers/aws/guide/intro.md b/docs/providers/aws/guide/intro.md index e4ea181a5..057f4217a 100644 --- a/docs/providers/aws/guide/intro.md +++ b/docs/providers/aws/guide/intro.md @@ -7,16 +7,19 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/intro) + # AWS - Introduction -The Serverless Framework helps you develop and deploy your AWS Lambda functions, along with the AWS infrastructure resources they require. It's a CLI that offers structure, automation and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). +The Serverless Framework helps you develop and deploy your AWS Lambda functions, along with the AWS infrastructure resources they require. It's a CLI that offers structure, automation and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). The Serverless Framework is different from other application frameworks because: -* It manages your code as well as your infrastructure -* It supports multiple languages (Node.js, Python, Java, and more) + +- It manages your code as well as your infrastructure +- It supports multiple languages (Node.js, Python, Java, and more) ## Core Concepts @@ -24,24 +27,24 @@ Here are the Framework's main concepts and how they pertain to AWS and Lambda... ### Functions -A Function is an AWS Lambda function. It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: +A Function is an AWS Lambda function. It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: -* *Saving a user to the database* -* *Processing a file in a database* -* *Performing a scheduled task* +- _Saving a user to the database_ +- _Processing a file in a database_ +- _Performing a scheduled task_ -You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. +You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. ### Events -Anything that triggers an AWS Lambda Function to execute is regarded by the Framework as an **Event**. Events are infrastructure events on AWS such as: +Anything that triggers an AWS Lambda Function to execute is regarded by the Framework as an **Event**. Events are infrastructure events on AWS such as: -* *An AWS API Gateway HTTP endpoint request (e.g., for a REST API)* -* *An AWS S3 bucket upload (e.g., for an image)* -* *A CloudWatch timer (e.g., run every 5 minutes)* -* *An AWS SNS topic (e.g., a message)* -* *A CloudWatch Alert (e.g., something happened)* -* *And more...* +- _An AWS API Gateway HTTP endpoint request (e.g., for a REST API)_ +- _An AWS S3 bucket upload (e.g., for an image)_ +- _A CloudWatch timer (e.g., run every 5 minutes)_ +- _An AWS SNS topic (e.g., a message)_ +- _A CloudWatch Alert (e.g., something happened)_ +- _And more..._ When you define an event for your AWS Lambda functions in the Serverless Framework, the Framework will automatically create any infrastructure necessary for that event (e.g., an API Gateway endpoint) and configure your AWS Lambda Functions to listen to it. @@ -49,16 +52,16 @@ When you define an event for your AWS Lambda functions in the Serverless Framewo **Resources** are AWS infrastructure components which your Functions use such as: -* *An AWS DynamoDB Table (e.g., for saving Users/Posts/Comments data)* -* *An AWS S3 Bucket (e.g., for saving images or files)* -* *An AWS SNS Topic (e.g., for sending messages asynchronously)* -* *Anything that can be defined in CloudFormation is supported by the Serverless Framework* +- _An AWS DynamoDB Table (e.g., for saving Users/Posts/Comments data)_ +- _An AWS S3 Bucket (e.g., for saving images or files)_ +- _An AWS SNS Topic (e.g., for sending messages asynchronously)_ +- _Anything that can be defined in CloudFormation is supported by the Serverless Framework_ The Serverless Framework not only deploys your Functions and the Events that trigger them, but it also deploys the AWS infrastructure components your Functions depend upon. ### Services -A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml # serverless.yml @@ -75,11 +78,12 @@ functions: # Your "Functions" resources: # The "Resources" your "Functions" use. Raw AWS CloudFormation goes in here. ``` + When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` is deployed at once. ### Plugins -You can overwrite or extend the functionality of the Framework using **Plugins**. Every `serverless.yml` can contain a `plugins:` property, which features multiple plugins. +You can overwrite or extend the functionality of the Framework using **Plugins**. Every `serverless.yml` can contain a `plugins:` property, which features multiple plugins. ```yml # serverless.yml diff --git a/docs/providers/aws/guide/layers.md b/docs/providers/aws/guide/layers.md index fa895be1c..5c9cb1491 100644 --- a/docs/providers/aws/guide/layers.md +++ b/docs/providers/aws/guide/layers.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/layers) + # AWS - Layers -If you are using AWS as a provider, all *layers* inside the service are [AWS Lambda +If you are using AWS as a provider, all _layers_ inside the service are [AWS Lambda layers](https://aws.amazon.com/blogs/aws/new-for-aws-lambda-use-any-programming-language-and-share-common-components/). ## Configuration @@ -109,7 +111,6 @@ layers: artifact: layerSource.zip ``` - ## Permissions You can make your layers usable by other accounts by setting the `allowedAccounts` property: @@ -170,5 +171,5 @@ functions: hello: handler: handler.hello layers: - - {Ref: TestLambdaLayer} + - { Ref: TestLambdaLayer } ``` diff --git a/docs/providers/aws/guide/packaging.md b/docs/providers/aws/guide/packaging.md index cbe3b6fd0..2655f067d 100644 --- a/docs/providers/aws/guide/packaging.md +++ b/docs/providers/aws/guide/packaging.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/packaging) + # AWS - Packaging @@ -45,12 +47,12 @@ previously excluded files and directories. By default, serverless will exclude the following patterns: -- .git/** +- .git/\*\* - .gitignore - .DS_Store - npm-debug.log -- .serverless/** -- .serverless_plugins/** +- .serverless/\*\* +- .serverless_plugins/\*\* and the serverless configuration file being used (i.e. `serverless.yml`) @@ -58,7 +60,7 @@ and the serverless configuration file being used (i.e. `serverless.yml`) Exclude all node_modules but then re-include a specific modules (in this case node-fetch) using `exclude` exclusively -``` yml +```yml package: exclude: - node_modules/** @@ -67,7 +69,7 @@ package: Exclude all files but `handler.js` using `exclude` and `include` -``` yml +```yml package: exclude: - src/** diff --git a/docs/providers/aws/guide/plugins.md b/docs/providers/aws/guide/plugins.md index c8414137f..0e2646c09 100644 --- a/docs/providers/aws/guide/plugins.md +++ b/docs/providers/aws/guide/plugins.md @@ -7,19 +7,21 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/plugins) + # Plugins -A Plugin is custom Javascript code that creates new or extends existing commands within the Serverless Framework. The Serverless Framework is merely a group of Plugins that are provided in the core. If you or your organization have a specific workflow, install a pre-written Plugin or write a plugin to customize the Framework to your needs. External Plugins are written exactly the same way as the core Plugins. +A Plugin is custom Javascript code that creates new or extends existing commands within the Serverless Framework. The Serverless Framework is merely a group of Plugins that are provided in the core. If you or your organization have a specific workflow, install a pre-written Plugin or write a plugin to customize the Framework to your needs. External Plugins are written exactly the same way as the core Plugins. - [How to create serverless plugins - Part 1](https://serverless.com/blog/writing-serverless-plugins/) - [How to create serverless plugins - Part 2](https://serverless.com/blog/writing-serverless-plugins-2/) ## Installing Plugins -External Plugins are added on a per service basis and are not applied globally. Make sure you are in your Service's root directory, then install the corresponding Plugin with the help of NPM: +External Plugins are added on a per service basis and are not applied globally. Make sure you are in your Service's root directory, then install the corresponding Plugin with the help of NPM: ``` npm install --save custom-serverless-plugin @@ -33,9 +35,11 @@ We need to tell Serverless that we want to use the plugin inside our service. We plugins: - custom-serverless-plugin ``` + The `plugins` section supports two formats: Array object: + ```yml plugins: - plugin1 @@ -43,6 +47,7 @@ plugins: ``` Enhanced plugins object: + ```yml plugins: localPath: './custom_serverless_plugins' @@ -64,23 +69,27 @@ custom: ## Service local plugin If you are working on a plugin or have a plugin that is just designed for one project, it can be loaded from the local `.serverless_plugins` folder at the root of your service. Local plugins can be added in the `plugins` array in `serverless.yml`. + ```yml plugins: - custom-serverless-plugin ``` Local plugins folder can be changed by enhancing `plugins` object: + ```yml plugins: localPath: './custom_serverless_plugins' modules: - custom-serverless-plugin ``` + The `custom-serverless-plugin` will be loaded from the `custom_serverless_plugins` directory at the root of your service. If the `localPath` is not provided or empty, the `.serverless_plugins` directory will be used. The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `localPath` folder (`.serverless_plugins` by default). If you want to load a plugin from a specific directory without affecting other plugins, you can also specify a path relative to the root of your service: + ```yaml plugins: # This plugin will be loaded from the `.serverless_plugins/` or `node_modules/` directories @@ -109,15 +118,15 @@ In this case `plugin1` is loaded before `plugin2`. #### Plugin -Code which defines *Commands*, any *Events* within a *Command*, and any *Hooks* assigned to a *Lifecycle Event*. +Code which defines _Commands_, any _Events_ within a _Command_, and any _Hooks_ assigned to a _Lifecycle Event_. -* Command // CLI configuration, commands, subcommands, options - * LifecycleEvent(s) // Events that happen sequentially when the command is run - * Hook(s) // Code that runs when a Lifecycle Event happens during a Command +- Command // CLI configuration, commands, subcommands, options + - LifecycleEvent(s) // Events that happen sequentially when the command is run + - Hook(s) // Code that runs when a Lifecycle Event happens during a Command #### Command -A CLI *Command* that can be called by a user, e.g. `serverless deploy`. A Command has no logic, but simply defines the CLI configuration (e.g. command, subcommands, parameters) and the *Lifecycle Events* for the command. Every command defines its own lifecycle events. +A CLI _Command_ that can be called by a user, e.g. `serverless deploy`. A Command has no logic, but simply defines the CLI configuration (e.g. command, subcommands, parameters) and the _Lifecycle Events_ for the command. Every command defines its own lifecycle events. ```javascript 'use strict'; @@ -126,10 +135,7 @@ class MyPlugin { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ] + lifecycleEvents: ['resources', 'functions'], }, }; } @@ -140,7 +146,7 @@ module.exports = MyPlugin; #### Lifecycle Events -Events that fire sequentially during a Command. The above example lists two Events. However, for each Event, an additional `before` and `after` event is created. Therefore, six Events exist in the above example: +Events that fire sequentially during a Command. The above example lists two Events. However, for each Event, an additional `before` and `after` event is created. Therefore, six Events exist in the above example: - `before:deploy:resources` - `deploy:resources` @@ -162,17 +168,14 @@ class Deploy { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ] + lifecycleEvents: ['resources', 'functions'], }, }; this.hooks = { 'before:deploy:resources': this.beforeDeployResources, 'deploy:resources': this.deployResources, - 'after:deploy:functions': this.afterDeployFunctions + 'after:deploy:functions': this.afterDeployFunctions, }; } @@ -203,20 +206,14 @@ class MyPlugin { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ], + lifecycleEvents: ['resources', 'functions'], commands: { function: { - lifecycleEvents: [ - 'package', - 'deploy' - ], + lifecycleEvents: ['package', 'deploy'], }, }, }, - } + }; } } @@ -233,7 +230,7 @@ Option Shortcuts are passed in with a single dash (`-`) like this: `serverless f The `options` object will be passed in as the second parameter to the constructor of your plugin. -In it, you can optionally add a `shortcut` property, as well as a `required` property. The Framework will return an error if a `required` Option is not included. You can also set a `default` property if your option is not required. +In it, you can optionally add a `shortcut` property, as well as a `required` property. The Framework will return an error if a `required` Option is not included. You can also set a `default` property if your option is not required. **Note:** At this time, the Serverless Framework does not use parameters. @@ -247,27 +244,25 @@ class Deploy { this.commands = { deploy: { - lifecycleEvents: [ - 'functions' - ], + lifecycleEvents: ['functions'], options: { function: { usage: 'Specify the function you want to deploy (e.g. "--function myFunction")', shortcut: 'f', - required: true + required: true, }, stage: { usage: 'Specify the stage you want to deploy to. (e.g. "--stage prod")', shortcut: 's', - default: 'dev' - } - } + default: 'dev', + }, + }, }, }; this.hooks = { - 'deploy:functions': this.deployFunction.bind(this) - } + 'deploy:functions': this.deployFunction.bind(this), + }; } deployFunction() { @@ -299,21 +294,19 @@ class ProviderDeploy { this.commands = { deploy: { - lifecycleEvents: [ - 'functions' - ], + lifecycleEvents: ['functions'], options: { function: { usage: 'Specify the function you want to deploy (e.g. "--function myFunction")', - required: true - } - } + required: true, + }, + }, }, }; this.hooks = { - 'deploy:functions': this.deployFunction.bind(this) - } + 'deploy:functions': this.deployFunction.bind(this), + }; } deployFunction() { @@ -340,15 +333,13 @@ class MyPlugin { this.commands = { log: { - lifecycleEvents: [ - 'serverless' - ], + lifecycleEvents: ['serverless'], }, }; this.hooks = { - 'log:serverless': this.logServerless.bind(this) - } + 'log:serverless': this.logServerless.bind(this), + }; } logServerless() { diff --git a/docs/providers/aws/guide/quick-start.md b/docs/providers/aws/guide/quick-start.md index 3c1144832..0831aef70 100644 --- a/docs/providers/aws/guide/quick-start.md +++ b/docs/providers/aws/guide/quick-start.md @@ -11,8 +11,8 @@ layout: Doc ## Pre-requisites 1. Node.js `v6.5.0` or later. -2. Serverless CLI `v1.9.0` or later. You can run -`npm install -g serverless` to install it. +2. Serverless CLI `v1.9.0` or later. You can run + `npm install -g serverless` to install it. 3. An AWS account. If you don't already have one, you can sign up for a [free trial](https://aws.amazon.com/s/dm/optimization/server-side-test/free-tier/free_np/) that includes 1 million free Lambda requests per month. 4. **Set-up your [Provider Credentials](./credentials.md)** -> [Watch the video on setting up credentials](https://www.youtube.com/watch?v=KngM5bfpttA) @@ -40,34 +40,35 @@ $ cd my-service 1. **Deploy the Service** - Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. - - ```bash - serverless deploy -v - ``` +Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. + +```bash +serverless deploy -v +``` 2. **Deploy the Function** - Use this to quickly upload and overwrite your function code, allowing you to develop faster. - - ```bash - serverless deploy function -f hello - ``` +Use this to quickly upload and overwrite your function code, allowing you to develop faster. + +```bash +serverless deploy function -f hello +``` 3. **Invoke the Function** - Invokes a Function and returns logs. - - ```bash - serverless invoke -f hello -l - ``` +Invokes a Function and returns logs. + +```bash +serverless invoke -f hello -l +``` 4. **Fetch the Function Logs** - Open up a separate tab in your console, set your [Provider Credentials](./credentials.md) and stream all logs for a specific Function using this command. - ```bash - serverless logs -f hello -t - ``` +Open up a separate tab in your console, set your [Provider Credentials](./credentials.md) and stream all logs for a specific Function using this command. + +```bash +serverless logs -f hello -t +``` ## Cleanup diff --git a/docs/providers/aws/guide/resources.md b/docs/providers/aws/guide/resources.md index ca3b0aff6..11e2a7331 100644 --- a/docs/providers/aws/guide/resources.md +++ b/docs/providers/aws/guide/resources.md @@ -7,20 +7,22 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/resources) + # AWS - Resources -If you are using AWS as a provider for your Service, all [*Resources*](./intro.md#resources) are other AWS infrastructure resources which the AWS Lambda functions in your [*Service*](./intro.md#services) depend on, like AWS DynamoDB or AWS S3. +If you are using AWS as a provider for your Service, all [_Resources_](./intro.md#resources) are other AWS infrastructure resources which the AWS Lambda functions in your [_Service_](./intro.md#services) depend on, like AWS DynamoDB or AWS S3. Using the Serverless Framework, you can define the infrastructure resources you need in `serverless.yml`, and easily deploy them. ## Configuration -Every stage you deploy to with `serverless.yml` using the `aws` provider is a single AWS CloudFormation stack. This is where your AWS Lambda functions and their event configurations are defined and it's how they are deployed. When you add `resources` those resources are added into your CloudFormation stack upon `serverless deploy`. +Every stage you deploy to with `serverless.yml` using the `aws` provider is a single AWS CloudFormation stack. This is where your AWS Lambda functions and their event configurations are defined and it's how they are deployed. When you add `resources` those resources are added into your CloudFormation stack upon `serverless deploy`. -Define your AWS resources in a property titled `resources`. What goes in this property is raw CloudFormation template syntax, in YAML, like this: +Define your AWS resources in a property titled `resources`. What goes in this property is raw CloudFormation template syntax, in YAML, like this: ```yml # serverless.yml @@ -29,7 +31,7 @@ service: usersCrud provider: aws functions: -resources: # CloudFormation template syntax +resources: # CloudFormation template syntax Resources: usersTable: Type: AWS::DynamoDB::Table @@ -46,7 +48,7 @@ resources: # CloudFormation template syntax WriteCapacityUnits: 1 ``` -You can overwrite/attach any kind of resource to your CloudFormation stack. You can add `Resources`, `Outputs` or even overwrite the `Description`. You can also use [Serverless Variables](./variables.md) for sensitive data or reusable configuration in your resources templates. Please be cautious as overwriting existing parts of your CloudFormation stack might introduce unexpected behavior. +You can overwrite/attach any kind of resource to your CloudFormation stack. You can add `Resources`, `Outputs` or even overwrite the `Description`. You can also use [Serverless Variables](./variables.md) for sensitive data or reusable configuration in your resources templates. Please be cautious as overwriting existing parts of your CloudFormation stack might introduce unexpected behavior. ## AWS CloudFormation Resource Reference @@ -54,10 +56,10 @@ To have consistent naming in the CloudFormation Templates that get deployed we u `{Function Name}{Cloud Formation Resource Type}{Resource Name}{SequentialID, instanceId or Random String}` -* `Function Name` - This is optional for Resources that should be recreated when the function name gets changed. Those resources are also called *function bound* -* `Cloud Formation Resource Type` - E.g., S3Bucket -* `Resource Name` - An identifier for the specific resource, e.g. for an S3 Bucket the configured bucket name. -* `SequentialID, instanceId or Random String` - For a few resources we need to add an optional sequential id, the Serverless instanceId (accessible via `${sls:instanceId}`) or a random string to identify them +- `Function Name` - This is optional for Resources that should be recreated when the function name gets changed. Those resources are also called _function bound_ +- `Cloud Formation Resource Type` - E.g., S3Bucket +- `Resource Name` - An identifier for the specific resource, e.g. for an S3 Bucket the configured bucket name. +- `SequentialID, instanceId or Random String` - For a few resources we need to add an optional sequential id, the Serverless instanceId (accessible via `${sls:instanceId}`) or a random string to identify them All resource names that are deployed by Serverless have to follow this naming scheme. The only exception (for backwards compatibility reasons) is the S3 Bucket that is used to upload artifacts so they can be deployed to your function. @@ -66,30 +68,30 @@ We're also using the term `normalizedName` or similar terms in this guide. This _Tip:_ If you are unsure how a resource is named, that you want to reference from your custom resources, you can issue a `serverless package`. This will create the CloudFormation template for your service in the `.serverless` folder (it is named `cloudformation-template-update-stack.json`). Just open the file and check for the generated resource name. -| AWS Resource | Name Template | Example | -|--- |--- | --- | -| S3::Bucket | S3Bucket{normalizedBucketName} | S3BucketMybucket | -|IAM::Role | IamRoleLambdaExecution | IamRoleLambdaExecution | -|Lambda::Function | {normalizedFunctionName}LambdaFunction | HelloLambdaFunction | -|Lambda::Version | {normalizedFunctionName}LambdaVersion{sha256} | HelloLambdaVersionr3pgoTvv1xT4E4NiCL6JG02fl6vIyi7OS1aW0FwAI | -|Logs::LogGroup | {normalizedFunctionName}LogGroup | HelloLogGroup | -|Lambda::Permission |
    • **Schedule**: {normalizedFunctionName}LambdaPermissionEventsRuleSchedule{index}
    • **CloudWatch Event**: {normalizedFunctionName}LambdaPermissionEventsRuleCloudWatchEvent{index}
    • **CloudWatch Log**: {normalizedFunctionName}LambdaPermissionLogsSubscriptionFilterCloudWatchLog{index}
    • **IoT**: {normalizedFunctionName}LambdaPermissionIotTopicRule{index}
    • **S3**: {normalizedFunctionName}LambdaPermission{normalizedBucketName}S3
    • **APIG**: {normalizedFunctionName}LambdaPermissionApiGateway
    • **SNS**: {normalizedFunctionName}LambdaPermission{normalizedTopicName}SNS
    • **Alexa Skill**: {normalizedFunctionName}LambdaPermissionAlexaSkill
    • **Alexa Smart Home**: {normalizedFunctionName}LambdaPermissionAlexaSmartHome{index}
    • **Cognito User Pool Trigger Source**: {normalizedFunctionName}LambdaPermissionCognitoUserPool{normalizedPoolId}TriggerSource{triggerSource}
    |
    • **Schedule**: HelloLambdaPermissionEventsRuleSchedule1
    • **CloudWatch Event**: HelloLambdaPermissionEventsRuleCloudWatchEvent1
    • **CloudWatch Log**: HelloLambdaPermissionLogsSubscriptionFilterCloudWatchLog1
    • **IoT**: HelloLambdaPermissionIotTopicRule1
    • **S3**: HelloLambdaPermissionBucketS3
    • **APIG**: HelloLambdaPermissionApiGateway
    • **SNS**: HelloLambdaPermissionTopicSNS
    • **Alexa Skill**: HelloLambdaPermissionAlexaSkill
    • **Alexa Smart Home**: HelloLambdaPermissionAlexaSmartHome1
    • **Cognito User Pool Trigger Source**: HelloLambdaPermissionCognitoUserPoolMyPoolTriggerSourceCustomMessage
    | -|Events::Rule |
    • **Schedule**: {normalizedFunctionName}EventsRuleSchedule{SequentialID}
    • **CloudWatch Event**: {normalizedFunctionName}EventsRuleCloudWatchEvent{SequentialID}
    |
    • **Schedule**: HelloEventsRuleSchedule1
    • **CloudWatch Event**: HelloEventsRuleCloudWatchEvent1
    | -|AWS::Logs::SubscriptionFilter | {normalizedFunctionName}LogsSubscriptionFilterCloudWatchLog{SequentialID} | HelloLogsSubscriptionFilterCloudWatchLog1 | -|AWS::IoT::TopicRule | {normalizedFunctionName}IotTopicRule{SequentialID} | HelloIotTopicRule1 | -|ApiGateway::RestApi | ApiGatewayRestApi | ApiGatewayRestApi | -|ApiGateway::Resource | ApiGatewayResource{normalizedPath} | ApiGatewayResourceUsers | -|ApiGateway::Method | ApiGatewayMethod{normalizedPath}{normalizedMethod} | ApiGatewayMethodUsersGet | -|ApiGateway::Authorizer | {normalizedFunctionName}ApiGatewayAuthorizer | HelloApiGatewayAuthorizer | -|ApiGateway::Deployment | ApiGatewayDeployment{instanceId} | ApiGatewayDeployment12356789 | -|ApiGateway::ApiKey | ApiGatewayApiKey{OptionalNormalizedName}{SequentialID} | ApiGatewayApiKeyFree1 | -|ApiGateway::UsagePlan | ApiGatewayUsagePlan{OptionalNormalizedName} | ApiGatewayUsagePlanFree | -|ApiGateway::UsagePlanKey | ApiGatewayUsagePlanKey{OptionalNormalizedName}{SequentialID} | ApiGatewayUsagePlanKeyFree1 | -|ApiGateway::Stage | ApiGatewayStage | ApiGatewayStage | -|SNS::Topic | SNSTopic{normalizedTopicName} | SNSTopicSometopic | -|SNS::Subscription | {normalizedFunctionName}SnsSubscription{normalizedTopicName} | HelloSnsSubscriptionSomeTopic | -|AWS::Lambda::EventSourceMapping |
    • **DynamoDB:** {normalizedFunctionName}EventSourceMappingDynamodb{tableName}
    • **Kinesis:** {normalizedFunctionName}EventSourceMappingKinesis{streamName}
    |
    • **DynamoDB:** HelloLambdaEventSourceMappingDynamodbUsers
    • **Kinesis:** HelloLambdaEventSourceMappingKinesisMystream
    | -|Cognito::UserPool | CognitoUserPool{normalizedPoolId} | CognitoUserPoolPoolId | +| AWS Resource | Name Template | Example | +| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| S3::Bucket | S3Bucket{normalizedBucketName} | S3BucketMybucket | +| IAM::Role | IamRoleLambdaExecution | IamRoleLambdaExecution | +| Lambda::Function | {normalizedFunctionName}LambdaFunction | HelloLambdaFunction | +| Lambda::Version | {normalizedFunctionName}LambdaVersion{sha256} | HelloLambdaVersionr3pgoTvv1xT4E4NiCL6JG02fl6vIyi7OS1aW0FwAI | +| Logs::LogGroup | {normalizedFunctionName}LogGroup | HelloLogGroup | +| Lambda::Permission |
    • **Schedule**: {normalizedFunctionName}LambdaPermissionEventsRuleSchedule{index}
    • **CloudWatch Event**: {normalizedFunctionName}LambdaPermissionEventsRuleCloudWatchEvent{index}
    • **CloudWatch Log**: {normalizedFunctionName}LambdaPermissionLogsSubscriptionFilterCloudWatchLog{index}
    • **IoT**: {normalizedFunctionName}LambdaPermissionIotTopicRule{index}
    • **S3**: {normalizedFunctionName}LambdaPermission{normalizedBucketName}S3
    • **APIG**: {normalizedFunctionName}LambdaPermissionApiGateway
    • **SNS**: {normalizedFunctionName}LambdaPermission{normalizedTopicName}SNS
    • **Alexa Skill**: {normalizedFunctionName}LambdaPermissionAlexaSkill
    • **Alexa Smart Home**: {normalizedFunctionName}LambdaPermissionAlexaSmartHome{index}
    • **Cognito User Pool Trigger Source**: {normalizedFunctionName}LambdaPermissionCognitoUserPool{normalizedPoolId}TriggerSource{triggerSource}
    |
    • **Schedule**: HelloLambdaPermissionEventsRuleSchedule1
    • **CloudWatch Event**: HelloLambdaPermissionEventsRuleCloudWatchEvent1
    • **CloudWatch Log**: HelloLambdaPermissionLogsSubscriptionFilterCloudWatchLog1
    • **IoT**: HelloLambdaPermissionIotTopicRule1
    • **S3**: HelloLambdaPermissionBucketS3
    • **APIG**: HelloLambdaPermissionApiGateway
    • **SNS**: HelloLambdaPermissionTopicSNS
    • **Alexa Skill**: HelloLambdaPermissionAlexaSkill
    • **Alexa Smart Home**: HelloLambdaPermissionAlexaSmartHome1
    • **Cognito User Pool Trigger Source**: HelloLambdaPermissionCognitoUserPoolMyPoolTriggerSourceCustomMessage
    | +| Events::Rule |
    • **Schedule**: {normalizedFunctionName}EventsRuleSchedule{SequentialID}
    • **CloudWatch Event**: {normalizedFunctionName}EventsRuleCloudWatchEvent{SequentialID}
    |
    • **Schedule**: HelloEventsRuleSchedule1
    • **CloudWatch Event**: HelloEventsRuleCloudWatchEvent1
    | +| AWS::Logs::SubscriptionFilter | {normalizedFunctionName}LogsSubscriptionFilterCloudWatchLog{SequentialID} | HelloLogsSubscriptionFilterCloudWatchLog1 | +| AWS::IoT::TopicRule | {normalizedFunctionName}IotTopicRule{SequentialID} | HelloIotTopicRule1 | +| ApiGateway::RestApi | ApiGatewayRestApi | ApiGatewayRestApi | +| ApiGateway::Resource | ApiGatewayResource{normalizedPath} | ApiGatewayResourceUsers | +| ApiGateway::Method | ApiGatewayMethod{normalizedPath}{normalizedMethod} | ApiGatewayMethodUsersGet | +| ApiGateway::Authorizer | {normalizedFunctionName}ApiGatewayAuthorizer | HelloApiGatewayAuthorizer | +| ApiGateway::Deployment | ApiGatewayDeployment{instanceId} | ApiGatewayDeployment12356789 | +| ApiGateway::ApiKey | ApiGatewayApiKey{OptionalNormalizedName}{SequentialID} | ApiGatewayApiKeyFree1 | +| ApiGateway::UsagePlan | ApiGatewayUsagePlan{OptionalNormalizedName} | ApiGatewayUsagePlanFree | +| ApiGateway::UsagePlanKey | ApiGatewayUsagePlanKey{OptionalNormalizedName}{SequentialID} | ApiGatewayUsagePlanKeyFree1 | +| ApiGateway::Stage | ApiGatewayStage | ApiGatewayStage | +| SNS::Topic | SNSTopic{normalizedTopicName} | SNSTopicSometopic | +| SNS::Subscription | {normalizedFunctionName}SnsSubscription{normalizedTopicName} | HelloSnsSubscriptionSomeTopic | +| AWS::Lambda::EventSourceMapping |
    • **DynamoDB:** {normalizedFunctionName}EventSourceMappingDynamodb{tableName}
    • **Kinesis:** {normalizedFunctionName}EventSourceMappingKinesis{streamName}
    |
    • **DynamoDB:** HelloLambdaEventSourceMappingDynamodbUsers
    • **Kinesis:** HelloLambdaEventSourceMappingKinesisMystream
    | +| Cognito::UserPool | CognitoUserPool{normalizedPoolId} | CognitoUserPoolPoolId | ## Override AWS CloudFormation Resource @@ -117,5 +119,5 @@ resources: WriteDashPostLogGroup: Type: AWS::Logs::LogGroup Properties: - RetentionInDays: "30" + RetentionInDays: '30' ``` diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index 8456f3402..c9e6d573f 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml) + # Serverless.yml Reference @@ -21,7 +23,7 @@ service: name: myService awsKmsKeyArn: arn:aws:kms:us-east-1:XXXXXX:key/some-hash # Optional KMS key arn which will be used for encryption for all functions -frameworkVersion: ">=1.0.0 <2.0.0" +frameworkVersion: '>=1.0.0 <2.0.0' provider: name: aws @@ -89,12 +91,12 @@ provider: - Ref: ServerlessDeploymentBucket stackPolicy: # Optional CF stack policy. The example below allows updates to all resources except deleting/replacing EC2 instances (use with caution!) - Effect: Allow - Principal: "*" - Action: "Update:*" - Resource: "*" + Principal: '*' + Action: 'Update:*' + Resource: '*' - Effect: Deny - Principal: "*" - Resource: "*" + Principal: '*' + Resource: '*' Action: - Update:Replace - Update:Delete @@ -113,14 +115,14 @@ provider: - 'arn:aws:sns:us-east-1:XXXXXX:mytopic' resourcePolicy: - Effect: Allow - Principal: "*" + Principal: '*' Action: execute-api:Invoke Resource: - execute-api:/*/*/* Condition: IpAddress: aws:SourceIp: - - "123.123.123.123" + - '123.123.123.123' tags: # Optional service wide function tags foo: bar baz: qux @@ -142,7 +144,6 @@ package: # Optional deployment packaging configuration artifact: path/to/my-artifact.zip # Own package that should be used. You must provide this file. individually: true # Enables individual packaging for each function. If true you must provide package for each function. Defaults to false - functions: usersCreate: # A Function handler: users.create # The file and module for this specific function. @@ -186,7 +187,7 @@ functions: private: true # Requires clients to add API keys values in the `x-api-key` header of their request authorizer: # An AWS API Gateway custom authorizer function name: authorizerFunc # The name of the authorizer function (must be in this service) - arn: xxx:xxx:Lambda-Name # Can be used instead of name to reference a function outside of service + arn: xxx:xxx:Lambda-Name # Can be used instead of name to reference a function outside of service resultTtlInSeconds: 0 identitySource: method.request.header.Authorization identityValidationExpression: someRegex @@ -247,9 +248,9 @@ functions: - cloudwatchEvent: event: source: - - "aws.ec2" + - 'aws.ec2' detail-type: - - "EC2 Instance State-change Notification" + - 'EC2 Instance State-change Notification' detail: state: - pending @@ -310,7 +311,7 @@ resources: UsersTableArn: Description: The ARN for the User's Table Value: - "Fn::GetAtt": [ usersTable, Arn ] + 'Fn::GetAtt': [usersTable, Arn] Export: Name: ${self:service}:${opt:stage}:UsersTableArn # see Fn::ImportValue to use in other services and http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html for documentation on use. ``` diff --git a/docs/providers/aws/guide/services.md b/docs/providers/aws/guide/services.md index b2d73b8ba..53d053a0e 100644 --- a/docs/providers/aws/guide/services.md +++ b/docs/providers/aws/guide/services.md @@ -7,25 +7,27 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/services) + # Services -A `service` is like a project. It's where you define your AWS Lambda Functions, the `events` that trigger them and any AWS infrastructure `resources` they require, all in a file called `serverless.yml`. +A `service` is like a project. It's where you define your AWS Lambda Functions, the `events` that trigger them and any AWS infrastructure `resources` they require, all in a file called `serverless.yml`. To get started building your first Serverless Framework project, create a `service`. ## Organization -In the beginning of an application, many people use a single Service to define all of the Functions, Events and Resources for that project. This is what we recommend in the beginning. +In the beginning of an application, many people use a single Service to define all of the Functions, Events and Resources for that project. This is what we recommend in the beginning. ```bash myService/ serverless.yml # Contains all functions and infrastructure resources ``` -However, as your application grows, you can break it out into multiple services. A lot of people organize their services by workflows or data models, and group the functions related to those workflows and data models together in the service. +However, as your application grows, you can break it out into multiple services. A lot of people organize their services by workflows or data models, and group the functions related to those workflows and data models together in the service. ```bash users/ @@ -35,11 +37,12 @@ posts/ comments/ serverless.yml # Contains 4 functions that do Comments CRUD operations and the Comments database ``` + This makes sense since related functions usually use common infrastructure resources, and you want to keep those functions and resources together as a single unit of deployment, for better organization and separation of concerns. ## Creation -To create a service, use the `create` command. You must also pass in a runtime (e.g., node.js, python etc.) you would like to write the service in. You can also pass in a path to create a directory and auto-name your service: +To create a service, use the `create` command. You must also pass in a runtime (e.g., node.js, python etc.) you would like to write the service in. You can also pass in a path to create a directory and auto-name your service: ```bash # Create service with nodeJS template in the folder ./myService @@ -48,32 +51,33 @@ serverless create --template aws-nodejs --path myService Here are the available templates for AWS Lambda: -* aws-clojurescript-gradle -* aws-clojure-gradle -* aws-nodejs -* aws-nodejs-typescript -* aws-alexa-typescript -* aws-nodejs-ecma-script -* aws-python -* aws-python3 -* aws-ruby -* aws-provided -* aws-kotlin-jvm-maven -* aws-kotlin-nodejs-gradle -* aws-groovy-gradle -* aws-java-gradle -* aws-java-maven -* aws-scala-sbt -* aws-csharp -* aws-fsharp -* aws-go -* aws-go-dep +- aws-clojurescript-gradle +- aws-clojure-gradle +- aws-nodejs +- aws-nodejs-typescript +- aws-alexa-typescript +- aws-nodejs-ecma-script +- aws-python +- aws-python3 +- aws-ruby +- aws-provided +- aws-kotlin-jvm-maven +- aws-kotlin-nodejs-gradle +- aws-groovy-gradle +- aws-java-gradle +- aws-java-maven +- aws-scala-sbt +- aws-csharp +- aws-fsharp +- aws-go +- aws-go-dep Check out the [create command docs](../cli-reference/create) for all the details and options. ## Contents You'll see the following files in your working directory: + - `serverless.yml` - `handler.js` @@ -81,14 +85,14 @@ You'll see the following files in your working directory: Each `service` configuration is managed in the `serverless.yml` file. The main responsibilities of this file are: - - Declare a Serverless service - - Define one or more functions in the service - - Define the provider the service will be deployed to (and the runtime if provided) - - Define any custom plugins to be used - - Define events that trigger each function to execute (e.g. HTTP requests) - - Define a set of resources (e.g. 1 DynamoDB table) required by the functions in this service - - Allow events listed in the `events` section to automatically create the resources required for the event upon deployment - - Allow flexible configuration using Serverless Variables +- Declare a Serverless service +- Define one or more functions in the service +- Define the provider the service will be deployed to (and the runtime if provided) +- Define any custom plugins to be used +- Define events that trigger each function to execute (e.g. HTTP requests) +- Define a set of resources (e.g. 1 DynamoDB table) required by the functions in this service +- Allow events listed in the `events` section to automatically create the resources required for the event upon deployment +- Allow flexible configuration using Serverless Variables You can see the name of the service, the provider configuration and the first function inside the `functions` definition which points to the `handler.js` file. Any further service configuration will be done in this file. @@ -115,18 +119,18 @@ provider: deploymentPrefix: serverless # Overwrite the default S3 prefix under which deployed artifacts should be stored. Default is serverless versionFunctions: false # Optional function versioning stackTags: # Optional CF stack tags - key: value + key: value stackPolicy: # Optional CF stack policy. The example below allows updates to all resources except deleting/replacing EC2 instances (use with caution!) - Effect: Allow - Principal: "*" - Action: "Update:*" - Resource: "*" + Principal: '*' + Action: 'Update:*' + Resource: '*' - Effect: Deny - Principal: "*" + Principal: '*' Action: - Update:Replace - Update:Delete - Resource: "*" + Resource: '*' Condition: StringEquals: ResourceType: @@ -139,7 +143,7 @@ functions: - http: post users/create usersDelete: # A Function handler: users.delete - events: # The Events that trigger this Function + events: # The Events that trigger this Function - http: delete users/delete # The "Resources" your "Functions" use. Raw AWS CloudFormation goes in here. @@ -194,7 +198,7 @@ Deployment defaults to `dev` stage and `us-east-1` region on AWS, unless you spe serverless deploy --stage prod --region us-east-1 ``` -Check out the [deployment guide](https://serverless.com/framework/docs/providers/aws/guide/deploying/) to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy) for all the details and options. +Check out the [deployment guide](https://serverless.com/framework/docs/providers/aws/guide/deploying/) to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy) for all the details and options. ## Removal @@ -266,6 +270,7 @@ npm install serverless --save-dev To execute the locally installed Serverless executable you have to reference the binary out of the node modules directory. Example: + ``` node ./node_modules/serverless/bin/serverless deploy ``` diff --git a/docs/providers/aws/guide/testing.md b/docs/providers/aws/guide/testing.md index b09aadf3d..35c79e54c 100644 --- a/docs/providers/aws/guide/testing.md +++ b/docs/providers/aws/guide/testing.md @@ -7,22 +7,24 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/testing) + # Testing -While the Serverless Architecture introduces a lot of simplicity when it comes to serving business logic, some of its characteristics present challenges for testing. They are: +While the Serverless Architecture introduces a lot of simplicity when it comes to serving business logic, some of its characteristics present challenges for testing. They are: -* The Serverless Architecture is an integration of separate, distributed services, which must be tested both independently, and together. -* The Serverless Architecture is dependent on internet/cloud services, which are hard to emulate locally. -* The Serverless Architecture can feature event-driven, asynchronous workflows, which are hard to emulate entirely. +- The Serverless Architecture is an integration of separate, distributed services, which must be tested both independently, and together. +- The Serverless Architecture is dependent on internet/cloud services, which are hard to emulate locally. +- The Serverless Architecture can feature event-driven, asynchronous workflows, which are hard to emulate entirely. To get through these challenges, and to keep the [test pyramid](http://martinfowler.com/bliki/TestPyramid.html) in mind, keep the following principles in mind: -* Write your business logic so that it is separate from your FaaS provider (e.g., AWS Lambda), to keep it provider-independent, reusable and more easily testable. -* When your business logic is written separately from the FaaS provider, you can write traditional Unit Tests to ensure it is working properly. -* Write Integration Tests to verify integrations with other services are working correctly. +- Write your business logic so that it is separate from your FaaS provider (e.g., AWS Lambda), to keep it provider-independent, reusable and more easily testable. +- When your business logic is written separately from the FaaS provider, you can write traditional Unit Tests to ensure it is working properly. +- Write Integration Tests to verify integrations with other services are working correctly. ## A Poor Example @@ -35,10 +37,10 @@ const mailer = require('mailer'); module.exports.saveUser = (event, context, callback) => { const user = { email: event.email, - created_at: Date.now() - } + created_at: Date.now(), + }; - db.saveUser(user, function (err) { + db.saveUser(user, function(err) { if (err) { callback(err); } else { @@ -51,8 +53,8 @@ module.exports.saveUser = (event, context, callback) => { There are two main problems with this function: -* The business logic is not separate from the FaaS Provider. It's bounded to how AWS Lambda passes incoming data (Lambda's `event` object). -* Testing this function will rely on separate services. Specifically, running a database instance and a mail server. +- The business logic is not separate from the FaaS Provider. It's bounded to how AWS Lambda passes incoming data (Lambda's `event` object). +- Testing this function will rely on separate services. Specifically, running a database instance and a mail server. ## Writing Testable AWS Lambda Functions @@ -68,17 +70,17 @@ class Users { save(email, callback) { const user = { email: email, - created_at: Date.now() - } + created_at: Date.now(), + }; - this.db.saveUser(user, function (err) { + this.db.saveUser(user, function(err) { if (err) { callback(err); } else { this.mailer.sendWelcomeEmail(email); callback(); } - }); + }); } } @@ -97,14 +99,15 @@ module.exports.saveUser = (event, context, callback) => { }; ``` -Now, the above class keeps business logic separate. Further, the code responsible for setting up dependencies, injecting them, calling business logic functions and interacting with AWS Lambda is in its own file, which will be changed less often. This way, the business logic is not provider dependent, easier to re-use, and easier to test. +Now, the above class keeps business logic separate. Further, the code responsible for setting up dependencies, injecting them, calling business logic functions and interacting with AWS Lambda is in its own file, which will be changed less often. This way, the business logic is not provider dependent, easier to re-use, and easier to test. -Further, this code doesn't require running any external services. Instead of a real `db` and `mailer` services, we can pass mocks and assert if `saveUser` and `sendWelcomeEmail` has been called with proper arguments. +Further, this code doesn't require running any external services. Instead of a real `db` and `mailer` services, we can pass mocks and assert if `saveUser` and `sendWelcomeEmail` has been called with proper arguments. -Unit Tests can easily be written to cover the above class. An integration test can be added by invoking the function (`serverless invoke`) with fixture email address, check if user is actually saved to DB and check if email was received to see if everything is working together. +Unit Tests can easily be written to cover the above class. An integration test can be added by invoking the function (`serverless invoke`) with fixture email address, check if user is actually saved to DB and check if email was received to see if everything is working together. ## Other Here are a few links to services which can help you test locally: -* [dynamodb-local](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html) -* [kinesalite](https://github.com/mhart/kinesalite) + +- [dynamodb-local](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html) +- [kinesalite](https://github.com/mhart/kinesalite) diff --git a/docs/providers/aws/guide/v0_to_v1.md b/docs/providers/aws/guide/v0_to_v1.md index b250ed7ce..3d52a3b71 100644 --- a/docs/providers/aws/guide/v0_to_v1.md +++ b/docs/providers/aws/guide/v0_to_v1.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/v0_to_v1) + # Comparison Of Serverless Framework V.1 & V.0 diff --git a/docs/providers/aws/guide/variables.md b/docs/providers/aws/guide/variables.md index 895269374..9aa6cf0b1 100644 --- a/docs/providers/aws/guide/variables.md +++ b/docs/providers/aws/guide/variables.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/variables) + # Variables @@ -73,6 +75,7 @@ Likewise, if `sls deploy --stage prod` is run the `config.prod.json` file would If no `--stage` flag is provided, the second parameter defined in `${opt:stage, 'dev'}` a.k.a `dev` will be used and result in `${file(./config.dev.json):CREDS}`. ## Reference Properties In serverless.yml + To self-reference properties in `serverless.yml`, use the `${self:someProperty}` syntax in your `serverless.yml`. `someProperty` can contain the empty string for a top-level self-reference or a dotted attribute reference to any depth of attribute, so you can go as shallow or deep in the object tree as you want. ```yml @@ -87,13 +90,13 @@ custom: functions: hello: - handler: handler.hello - events: - - schedule: ${self:custom.globalSchedule} + handler: handler.hello + events: + - schedule: ${self:custom.globalSchedule} world: - handler: handler.world - events: - - schedule: ${self:custom.globalSchedule} + handler: handler.world + events: + - schedule: ${self:custom.globalSchedule} resources: Outputs: NewServiceExport: @@ -105,6 +108,7 @@ resources: In the above example you're setting a global schedule for all functions by referencing the `globalSchedule` property in the same `serverless.yml` file. This way, you can easily change the schedule for all functions whenever you like. ## Referencing Serverless Core Variables + Serverless initializes core variables which are used internally by the Framework itself. Those values are exposed via the Serverless Variables system and can be re-used with the `{sls:}` variable prefix. The following variables are available: @@ -126,7 +130,8 @@ functions: ``` ## Referencing Environment Variables -To reference environment variables, use the `${env:SOME_VAR}` syntax in your `serverless.yml` configuration file. It is valid to use the empty string in place of `SOME_VAR`. This looks like "`${env:}`" and the result of declaring this in your `serverless.yml` is to embed the complete `process.env` object (i.e. all the variables defined in your environment). + +To reference environment variables, use the `${env:SOME_VAR}` syntax in your `serverless.yml` configuration file. It is valid to use the empty string in place of `SOME_VAR`. This looks like "`${env:}`" and the result of declaring this in your `serverless.yml` is to embed the complete `process.env` object (i.e. all the variables defined in your environment). **Note:** @@ -147,38 +152,43 @@ functions: In the above example you're dynamically adding a prefix to the function names by referencing the `FUNC_PREFIX` env var. So you can easily change that prefix for all functions by changing the `FUNC_PREFIX` env var. ## Referencing CLI Options -To reference CLI options that you passed, use the `${opt:some_option}` syntax in your `serverless.yml` configuration file. It is valid to use the empty string in place of `some_option`. This looks like "`${opt:}`" and the result of declaring this in your `serverless.yml` is to embed the complete `options` object (i.e. all the command line options from your `serverless` command). + +To reference CLI options that you passed, use the `${opt:some_option}` syntax in your `serverless.yml` configuration file. It is valid to use the empty string in place of `some_option`. This looks like "`${opt:}`" and the result of declaring this in your `serverless.yml` is to embed the complete `options` object (i.e. all the command line options from your `serverless` command). ```yml service: new-service provider: aws functions: hello: - name: ${opt:stage}-hello - handler: handler.hello + name: ${opt:stage}-hello + handler: handler.hello world: - name: ${opt:stage}-world - handler: handler.world + name: ${opt:stage}-world + handler: handler.world ``` In the above example, you're dynamically adding a prefix to the function names by referencing the `stage` option that you pass in the CLI when you run `serverless deploy --stage dev`. So when you deploy, the function name will always include the stage you're deploying to. ## Reference CloudFormation Outputs + You can reference CloudFormation stack output values as the source of your variables to use in your service with the `cf:stackName.outputKey` syntax. For example: + ```yml service: new-service provider: aws functions: hello: - name: ${cf:another-service-dev.functionPrefix}-hello - handler: handler.hello + name: ${cf:another-service-dev.functionPrefix}-hello + handler: handler.hello world: - name: ${cf:another-stack.functionPrefix}-world - handler: handler.world + name: ${cf:another-stack.functionPrefix}-world + handler: handler.world ``` + In that case, the framework will fetch the values of those `functionPrefix` outputs from the provided stack names and populate your variables. There are many use cases for this functionality and it allows your service to communicate with other services/stacks. You can add such custom output to CloudFormation stack. For example: + ```yml service: another-service provider: @@ -191,7 +201,7 @@ functions: name: ${self:custom.functionPrefix}hello handler: handler.hello custom: - functionPrefix: "my-prefix-" + functionPrefix: 'my-prefix-' resources: Outputs: functionPrefix: @@ -205,16 +215,17 @@ resources: ``` You can also reference CloudFormation stack in another regions with the `cf.REGION:stackName.outputKey` syntax. For example: + ```yml service: new-service provider: aws functions: hello: - name: ${cf.us-west-2:another-service-dev.functionPrefix}-hello - handler: handler.hello + name: ${cf.us-west-2:another-service-dev.functionPrefix}-hello + handler: handler.hello world: - name: ${cf.ap-northeast-1:another-stack.functionPrefix}-world - handler: handler.world + name: ${cf.ap-northeast-1:another-stack.functionPrefix}-world + handler: handler.world ``` You can reference [CloudFormation stack outputs export values](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html) as well. For example: @@ -238,18 +249,22 @@ provider: ``` ## Referencing S3 Objects + You can reference S3 values as the source of your variables to use in your service with the `s3:bucketName/key` syntax. For example: + ```yml service: new-service provider: aws functions: hello: - name: ${s3:myBucket/myKey}-hello - handler: handler.hello + name: ${s3:myBucket/myKey}-hello + handler: handler.hello ``` + In the above example, the value for `myKey` in the `myBucket` S3 bucket will be looked up and used to populate the variable. ## Reference Variables using the SSM Parameter Store + You can reference SSM Parameters as the source of your variables with the `ssm:/path/to/param` syntax. For example: ```yml @@ -280,8 +295,8 @@ custom: In this example, the serverless variable will contain the decrypted value of the SecureString. ## Reference Variables using AWS Secrets Manager -Variables in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) can be referenced [using SSM](https://docs.aws.amazon.com/systems-manager/latest/userguide/integration-ps-secretsmanager.html). Use the `ssm:/aws/reference/secretsmanager/secret_ID_in_Secrets_Manager~true` syntax(note `~true` as secrets are always encrypted). For example: +Variables in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) can be referenced [using SSM](https://docs.aws.amazon.com/systems-manager/latest/userguide/integration-ps-secretsmanager.html). Use the `ssm:/aws/reference/secretsmanager/secret_ID_in_Secrets_Manager~true` syntax(note `~true` as secrets are always encrypted). For example: ```yml service: new-service @@ -326,9 +341,9 @@ custom: - false ``` - ## Reference Variables in Other Files -You can reference variables in other YAML or JSON files. To reference variables in other YAML files use the `${file(./myFile.yml):someProperty}` syntax in your `serverless.yml` configuration file. To reference variables in other JSON files use the `${file(./myFile.json):someProperty}` syntax. It is important that the file you are referencing has the correct suffix, or file extension, for its file type (`.yml` for YAML or `.json` for JSON) in order for it to be interpreted correctly. Here's an example: + +You can reference variables in other YAML or JSON files. To reference variables in other YAML files use the `${file(./myFile.yml):someProperty}` syntax in your `serverless.yml` configuration file. To reference variables in other JSON files use the `${file(./myFile.json):someProperty}` syntax. It is important that the file you are referencing has the correct suffix, or file extension, for its file type (`.yml` for YAML or `.json` for JSON) in order for it to be interpreted correctly. Here's an example: ```yml # myCustomFile.yml @@ -342,32 +357,34 @@ provider: aws custom: ${file(./myCustomFile.yml)} # You can reference the entire file functions: hello: - handler: handler.hello - events: - - schedule: ${file(./myCustomFile.yml):globalSchedule} # Or you can reference a specific property + handler: handler.hello + events: + - schedule: ${file(./myCustomFile.yml):globalSchedule} # Or you can reference a specific property world: - handler: handler.world - events: - - schedule: ${self:custom.globalSchedule} # This would also work in this case + handler: handler.world + events: + - schedule: ${self:custom.globalSchedule} # This would also work in this case ``` -In the above example, you're referencing the entire `myCustomFile.yml` file in the `custom` property. You need to pass the path relative to your service directory. You can also request specific properties in that file as shown in the `schedule` property. It's completely recursive and you can go as deep as you want. Additionally you can request properties that contain arrays from either YAML or JSON reference files. Here's a YAML example for an events array: +In the above example, you're referencing the entire `myCustomFile.yml` file in the `custom` property. You need to pass the path relative to your service directory. You can also request specific properties in that file as shown in the `schedule` property. It's completely recursive and you can go as deep as you want. Additionally you can request properties that contain arrays from either YAML or JSON reference files. Here's a YAML example for an events array: ```yml myevents: - schedule: - rate: rate(1 minute) + rate: rate(1 minute) ``` and for JSON: ```json { - "myevents": [{ - "schedule" : { - "rate" : "rate(1 minute)" + "myevents": [ + { + "schedule": { + "rate": "rate(1 minute)" + } } - }] + ] } ``` @@ -402,21 +419,21 @@ Here are other examples: ```js // scheduleConfig.js module.exports.rate = () => { - // Code that generates dynamic data - return 'rate (10 minutes)'; -} + // Code that generates dynamic data + return 'rate (10 minutes)'; +}; ``` ```js // config.js -module.exports = (serverless) => { +module.exports = serverless => { serverless.cli.consoleLog('You can access Serverless config and methods as well!'); return { property1: 'some value', - property2: 'some other value' - } -} + property2: 'some other value', + }; +}; ``` ```yml @@ -428,12 +445,12 @@ custom: ${file(./config.js)} functions: hello: - handler: handler.hello - events: - - schedule: ${file(./scheduleConfig.js):rate} # Reference a specific module + handler: handler.hello + events: + - schedule: ${file(./scheduleConfig.js):rate} # Reference a specific module ``` -You can also return an object and reference a specific property. Just make sure you are returning a valid object and referencing a valid property: +You can also return an object and reference a specific property. Just make sure you are returning a valid object and referencing a valid property: ```yml # serverless.yml @@ -441,22 +458,23 @@ service: new-service provider: aws functions: scheduledFunction: - handler: handler.scheduledFunction - events: - - schedule: ${file(./myCustomFile.js):schedule.ten} + handler: handler.scheduledFunction + events: + - schedule: ${file(./myCustomFile.js):schedule.ten} ``` ```js // myCustomFile.js module.exports.schedule = () => { - // Code that generates dynamic data - return { - ten: 'rate(10 minutes)', - twenty: 'rate(20 minutes)', - thirty: 'rate(30 minutes)' - }; -} + // Code that generates dynamic data + return { + ten: 'rate(10 minutes)', + twenty: 'rate(20 minutes)', + thirty: 'rate(30 minutes)', + }; +}; ``` + If your use case requires handling dynamic/async data sources (ie. DynamoDB, API calls...etc), you can also return a Promise that would be resolved as the value of the variable: ```yml @@ -465,17 +483,17 @@ service: new-service provider: aws functions: scheduledFunction: - handler: handler.scheduledFunction - events: - - schedule: ${file(./myCustomFile.js):promised} + handler: handler.scheduledFunction + events: + - schedule: ${file(./myCustomFile.js):promised} ``` ```js // myCustomFile.js module.exports.promised = () => { - // Async code that fetches the rate config... - return Promise.resolve('rate(10 minutes)'); -} + // Async code that fetches the rate config... + return Promise.resolve('rate(10 minutes)'); +}; ``` ## Multiple Configuration Files @@ -516,6 +534,7 @@ Resources: ``` ## Nesting Variable References + The Serverless variable system allows you to nest variable references within each other for ultimate flexibility. So you can reference certain variables based on other variables. Here's an example: ```yml @@ -526,12 +545,13 @@ custom: functions: hello: - handler: handler.hello + handler: handler.hello ``` In the above example, if you pass `dev` as a stage option, the framework will look for the `dev_arn` environment variable. If you pass `production`, the framework will look for `production_arn`, and so on. This allows you to creatively use multiple variables by using a certain naming pattern without having to update the values of these variables constantly. You can go as deep as you want in your nesting, and can reference variables at any level of nesting from any source (env, opt, self or file). ## Overwriting Variables + The Serverless framework gives you an intuitive way to reference multiple variables as a fallback strategy in case one of the variables is missing. This way you'll be able to use a default value from a certain source, if the variable from another source is missing. For example, if you want to reference the stage you're deploying to, but you don't want to keep on providing the `stage` option in the CLI. What you can do in `serverless.yml` is: @@ -547,7 +567,7 @@ custom: functions: hello: - handler: handler.hello + handler: handler.hello ``` What this says is to use the `stage` CLI option if it exists, if not, use the default stage (which lives in `provider.stage`). So during development you can safely deploy with `serverless deploy`, but during production you can do `serverless deploy --stage production` and the stage will be picked up for you without having to make any changes to `serverless.yml`. @@ -555,6 +575,7 @@ What this says is to use the `stage` CLI option if it exists, if not, use the de You can have as many variable references as you want, from any source you want, and each of them can be of different type and different name. ## Using Custom Variable Syntax + In some cases, the `${xxx}` variable syntax conflicts with some CloudFormation functionality. In that case you can provide a custom syntax to overwrite our default `${xxx}` syntax by setting the `provider.variableSyntax` property to the desired regex: ```yml @@ -569,9 +590,11 @@ provider: custom: myStage: ${{opt:stage}} ``` + In this example, we're overwriting the default regex for our variable syntax. So whenever you define variables, you now need to use `${{}}` instead of `${}` (double curly brackets). ## Migrating serverless.env.yml + Previously we used the `serverless.env.yml` file to track Serverless Variables. It was a completely different system with different concepts. To migrate your variables from `serverless.env.yml`, you'll need to decide where you want to store your variables. **Using a config file:** You can still use `serverless.env.yml`, but the difference now is that you can structure the file however you want, and you'll need to reference each variable/property correctly in `serverless.yml`. For more info, you can check the file reference section above. @@ -591,12 +614,11 @@ You can reference [AWS Pseudo Parameters](http://docs.aws.amazon.com/AWSCloudFor Here's an example: ```yml - Resources: - - 'Fn::Join': - - ':' - - - - 'arn:aws:logs' - - Ref: 'AWS::Region' - - Ref: 'AWS::AccountId' - - 'log-group:/aws/lambda/*:*:*' +Resources: + - 'Fn::Join': + - ':' + - - 'arn:aws:logs' + - Ref: 'AWS::Region' + - Ref: 'AWS::AccountId' + - 'log-group:/aws/lambda/*:*:*' ``` diff --git a/docs/providers/aws/guide/workflow.md b/docs/providers/aws/guide/workflow.md index 297cbdf28..58a6cf75e 100644 --- a/docs/providers/aws/guide/workflow.md +++ b/docs/providers/aws/guide/workflow.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/guide/workflow) + # Workflow @@ -20,54 +22,69 @@ Quick recommendations and tips for various processes. 2. Use `serverless deploy` only when you've made changes to `serverless.yml` and in CI/CD systems. For more information on setting up CI/CD for your Serverless app, read [this article](https://serverless.com/blog/ci-cd-workflow-serverless-apps-with-circleci). 3. Use `serverless deploy function -f myFunction` to rapidly deploy changes when you are working on a specific AWS Lambda Function. 4. Use `serverless invoke -f myFunction -l` to test your AWS Lambda Functions on AWS. -5. Open up a separate tab in your console and stream logs in there via `serverless logs -f myFunction -t`. +5. Open up a separate tab in your console and stream logs in there via `serverless logs -f myFunction -t`. 6. Write tests to run locally. ### Using stages -* At the very least, use a `dev` and `production` stage. -* Use different AWS accounts for stages. -* In larger teams, each member should use a separate AWS account and their own stage for development. + +- At the very least, use a `dev` and `production` stage. +- Use different AWS accounts for stages. +- In larger teams, each member should use a separate AWS account and their own stage for development. ### Larger Projects -* Break your application/project into multiple Serverless Services. -* Model your Serverless Services around Data Models or Workflows. -* Keep the Functions and Resources in your Serverless Services to a minimum. + +- Break your application/project into multiple Serverless Services. +- Model your Serverless Services around Data Models or Workflows. +- Keep the Functions and Resources in your Serverless Services to a minimum. ## Cheat Sheet + A handy list of commands to use when developing with the Serverless Framework. ##### Create A Service: + Creates a new Service + ``` serverless create -p [SERVICE NAME] -t aws-nodejs ``` ##### Install A Service + This is a convenience method to install a pre-made Serverless Service locally by downloading the Github repo and unzipping it. + ``` serverless install -u [GITHUB URL OF SERVICE] ``` ##### Deploy All + Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. + ``` serverless deploy -s [STAGE NAME] -r [REGION NAME] -v ``` ##### Deploy Function + Use this to quickly overwrite your AWS Lambda code on AWS, allowing you to develop faster. + ``` serverless deploy function -f [FUNCTION NAME] -s [STAGE NAME] -r [REGION NAME] ``` ##### Invoke Function + Invokes an AWS Lambda Function on AWS and returns logs. + ``` serverless invoke -f [FUNCTION NAME] -s [STAGE NAME] -r [REGION NAME] -l ``` ##### Streaming Logs + Open up a separate tab in your console and stream all logs for a specific Function using this command. + ``` serverless logs -f [FUNCTION NAME] -s [STAGE NAME] -r [REGION NAME] ``` diff --git a/docs/providers/azure/README.md b/docs/providers/azure/README.md index 0bd1b70be..6f513e280 100644 --- a/docs/providers/azure/README.md +++ b/docs/providers/azure/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/) + # Azure Functions Provider Documentation diff --git a/docs/providers/azure/cli-reference/README.md b/docs/providers/azure/cli-reference/README.md index 716163f19..bb3eea48c 100644 --- a/docs/providers/azure/cli-reference/README.md +++ b/docs/providers/azure/cli-reference/README.md @@ -5,12 +5,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/) + # Serverless Azure Functions CLI Reference -Welcome to the Serverless Azure Functions CLI Reference! Please select a section +Welcome to the Serverless Azure Functions CLI Reference! Please select a section on the left to get started. If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/). diff --git a/docs/providers/azure/cli-reference/create.md b/docs/providers/azure/cli-reference/create.md index f9bddeeaa..b51a3aef0 100644 --- a/docs/providers/azure/cli-reference/create.md +++ b/docs/providers/azure/cli-reference/create.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/create) + # Azure - Create @@ -28,6 +30,7 @@ serverless create --template azure-nodejs --path myService ``` ## Options + - `--template` or `-t` The name of one of the available templates. **Required if --template-url and --template-path are not present**. - `--template-url` or `-u` The name of one of the available templates. **Required if --template and --template-path are not present**. - `--template-path` The local path of your template. **Required if --template and --template-url are not present**. @@ -35,6 +38,7 @@ serverless create --template azure-nodejs --path myService - `--name` or `-n` the name of the service in `serverless.yml`. ## Provided lifecycle events + - `create:create` ## Available Templates @@ -64,8 +68,7 @@ serverless create --template azure-nodejs --path my-new-service ``` This example will generate scaffolding for a service with `Azure` as a provider -and `nodejs` as runtime. The scaffolding will be generated in the `my-new- -service` directory. This directory will be created if not present. Otherwise +and `nodejs` as runtime. The scaffolding will be generated in the `my-new- service` directory. This directory will be created if not present. Otherwise Serverless will use the already present directory. Additionally Serverless will rename the service according to the path you diff --git a/docs/providers/azure/cli-reference/deploy-function.md b/docs/providers/azure/cli-reference/deploy-function.md index 8fc4d465c..ad3cf3818 100644 --- a/docs/providers/azure/cli-reference/deploy-function.md +++ b/docs/providers/azure/cli-reference/deploy-function.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/deploy-function) + # Azure - Deploy Function -The `serverless deploy function` command deploys an individual function. This +The `serverless deploy function` command deploys an individual function. This command simply compiles a deployment package with a single function handler. This is a much faster way of deploying changes in code. @@ -24,4 +26,5 @@ serverless deploy function -f functionName properties such as environment variables and events will **not** be deployed. ## Options + - `--function` or `-f` The name of the function which should be deployed diff --git a/docs/providers/azure/cli-reference/deploy.md b/docs/providers/azure/cli-reference/deploy.md index 3c2266462..b544e8172 100644 --- a/docs/providers/azure/cli-reference/deploy.md +++ b/docs/providers/azure/cli-reference/deploy.md @@ -7,14 +7,16 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/deploy) + # Azure - Deploy The `serverless deploy` command deploys your entire service via the Azure Resource Manager API. Run this command when you have made service changes (i.e., -you edited `serverless.yml`). Use `serverless deploy function -f myFunction` +you edited `serverless.yml`). Use `serverless deploy function -f myFunction` when you have made code changes and you want to quickly upload your updated code to Azure Functions. @@ -23,6 +25,7 @@ serverless deploy ``` ## Options + - `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--noDeploy` or `-n` Skips the deployment steps and leaves artifacts in the `.serverless` directory - `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. diff --git a/docs/providers/azure/cli-reference/install.md b/docs/providers/azure/cli-reference/install.md index 916115f8d..805b24b2d 100644 --- a/docs/providers/azure/cli-reference/install.md +++ b/docs/providers/azure/cli-reference/install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/install) + # Azure - Install @@ -19,10 +21,12 @@ serverless install --url https://github.com/some/service ``` ## Options + - `--url` or `-u` The services GitHub URL. **Required**. - `--name` or `-n` Name for the service. ## Provided lifecycle events + - `install:install` ## Examples diff --git a/docs/providers/azure/cli-reference/invoke.md b/docs/providers/azure/cli-reference/invoke.md index f7479d215..cf7d3c400 100644 --- a/docs/providers/azure/cli-reference/invoke.md +++ b/docs/providers/azure/cli-reference/invoke.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/invoke) + # Azure - Invoke @@ -20,10 +22,12 @@ serverless invoke --function functionName ``` ## Options + - `--function` or `-f` The name of the function in your service that you want to invoke. **Required**. - `--path` or `-p` The path to a json file with input data to be passed to the invoked function. This path is relative to the root directory of the service. ## Provided lifecycle events + - `invoke:invoke` ## Examples diff --git a/docs/providers/azure/cli-reference/login.md b/docs/providers/azure/cli-reference/login.md index 15becc6c3..9a1368ca5 100644 --- a/docs/providers/azure/cli-reference/login.md +++ b/docs/providers/azure/cli-reference/login.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/login) + # Login diff --git a/docs/providers/azure/cli-reference/logs.md b/docs/providers/azure/cli-reference/logs.md index b3965137c..01cbf6eda 100644 --- a/docs/providers/azure/cli-reference/logs.md +++ b/docs/providers/azure/cli-reference/logs.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/logs) + # Azure - Logs @@ -29,4 +31,5 @@ serverless logs -f hello ```bash serverless logs -f hello ``` + This will stream all future logs for a given Function. diff --git a/docs/providers/azure/cli-reference/plugin-install.md b/docs/providers/azure/cli-reference/plugin-install.md index 354aa62b4..43e346c4b 100644 --- a/docs/providers/azure/cli-reference/plugin-install.md +++ b/docs/providers/azure/cli-reference/plugin-install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/plugin-install) + # Plugin Install @@ -22,9 +24,11 @@ serverless plugin install --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:install:install` ## Examples diff --git a/docs/providers/azure/cli-reference/plugin-list.md b/docs/providers/azure/cli-reference/plugin-list.md index b54edae88..b82201b1a 100644 --- a/docs/providers/azure/cli-reference/plugin-list.md +++ b/docs/providers/azure/cli-reference/plugin-list.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/plugin-list) + # Plugin List @@ -19,7 +21,9 @@ serverless plugin list ``` ## Options -- *None* + +- _None_ ## Provided lifecycle events + - `plugin:list:list` diff --git a/docs/providers/azure/cli-reference/plugin-search.md b/docs/providers/azure/cli-reference/plugin-search.md index ff3044b0d..b0097164b 100644 --- a/docs/providers/azure/cli-reference/plugin-search.md +++ b/docs/providers/azure/cli-reference/plugin-search.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/plugin-search) + # Plugin Search @@ -19,9 +21,11 @@ serverless plugin search --query query ``` ## Options + - `--query` or `-q` The query you want to use for your search. **Required**. ## Provided lifecycle events + - `plugin:search:search` ## Examples diff --git a/docs/providers/azure/cli-reference/plugin-uninstall.md b/docs/providers/azure/cli-reference/plugin-uninstall.md index 92f5af2db..77bf0f599 100644 --- a/docs/providers/azure/cli-reference/plugin-uninstall.md +++ b/docs/providers/azure/cli-reference/plugin-uninstall.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/plugin-uninstall) + # Plugin Uninstall @@ -19,9 +21,11 @@ serverless plugin uninstall --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:uninstall:uninstall` ## Examples diff --git a/docs/providers/azure/cli-reference/print.md b/docs/providers/azure/cli-reference/print.md index e402e8e7f..8e1d5ece5 100644 --- a/docs/providers/azure/cli-reference/print.md +++ b/docs/providers/azure/cli-reference/print.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/print) + # Print diff --git a/docs/providers/azure/cli-reference/remove.md b/docs/providers/azure/cli-reference/remove.md index aa769f3cf..4b5856a20 100644 --- a/docs/providers/azure/cli-reference/remove.md +++ b/docs/providers/azure/cli-reference/remove.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/remove) + # Azure - Remove @@ -20,6 +22,7 @@ serverless remove ``` ## Provided lifecycle events + - `remove:remove` ## Examples diff --git a/docs/providers/azure/events/README.md b/docs/providers/azure/events/README.md index b3ac196a0..1d6a50046 100644 --- a/docs/providers/azure/events/README.md +++ b/docs/providers/azure/events/README.md @@ -1,11 +1,13 @@ + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/events/) + # Serverless Azure Functions Events diff --git a/docs/providers/azure/events/blobstorage.md b/docs/providers/azure/events/blobstorage.md index 60475b39b..f8a9eea49 100644 --- a/docs/providers/azure/events/blobstorage.md +++ b/docs/providers/azure/events/blobstorage.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/events/blobstorage) + # Blob Storage Trigger @@ -35,9 +37,9 @@ functions: events: - blob: x-azure-settings: - name: item #, default - "myBlob", specifies which name it's available on `context.bindings` - path: hello/{name} - connection: AzureWebJobsStorage #, default - "AzureWebJobsStorage", App Setting/environment variable which contains Storage Account Connection String + name: item #, default - "myBlob", specifies which name it's available on `context.bindings` + path: hello/{name} + connection: AzureWebJobsStorage #, default - "AzureWebJobsStorage", App Setting/environment variable which contains Storage Account Connection String ``` ```javascript @@ -46,7 +48,7 @@ functions: 'use strict'; module.exports.hello = function(context, item) { - context.log("Received item: ${item}"); + context.log('Received item: ${item}'); context.done(); }; ``` diff --git a/docs/providers/azure/events/eventhubs.md b/docs/providers/azure/events/eventhubs.md index 8c738d146..3cee537a1 100644 --- a/docs/providers/azure/events/eventhubs.md +++ b/docs/providers/azure/events/eventhubs.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/events/eventhubs) + # Event Hubs Trigger @@ -34,10 +36,10 @@ functions: events: - eventHub: x-azure-settings: - name: item #, default - "myEventHubMessage", specifies which name it's available on `context.bindings` - path: hello #, specifies the Name of the Event Hub - consumerGroup: $Default #, default - "$Default", specifies the consumerGroup to listen with - connection: EventHubsConnection #, App Setting/environment variable which contains Event Hubs Namespace Connection String + name: item #, default - "myEventHubMessage", specifies which name it's available on `context.bindings` + path: hello #, specifies the Name of the Event Hub + consumerGroup: $Default #, default - "$Default", specifies the consumerGroup to listen with + connection: EventHubsConnection #, App Setting/environment variable which contains Event Hubs Namespace Connection String ``` ```javascript @@ -46,7 +48,7 @@ functions: 'use strict'; module.exports.hello = function(context, item) { - context.log("Received item: ${item}"); + context.log('Received item: ${item}'); context.done(); }; ``` diff --git a/docs/providers/azure/events/http.md b/docs/providers/azure/events/http.md index 7717009a2..951be22b7 100644 --- a/docs/providers/azure/events/http.md +++ b/docs/providers/azure/events/http.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/events/http) + # HTTP Trigger @@ -40,11 +42,11 @@ functions: events: - http: true x-azure-settings: - name: req #, default - "req", specifies which name it's available on `context.bindings` - methods: # [GET, POST, PUT, DELETE], default - all - - get - route: example/hello #, default - - authLevel: anonymous # + name: req #, default - "req", specifies which name it's available on `context.bindings` + methods: # [GET, POST, PUT, DELETE], default - all + - get + route: example/hello #, default - + authLevel: anonymous # ``` URL paths for the serverless functions are prefixed with "api" by default, e.g. @@ -58,8 +60,8 @@ URL paths for the serverless functions are prefixed with "api" by default, e.g. module.exports.hello = function(context, req) { context.res = { - body: "Hello world!" - } + body: 'Hello world!', + }; context.done(); }; ``` @@ -87,13 +89,13 @@ module.exports.hello = function(context, req) { const rawBody = req.rawBody; // unparsed body context.res = { - headers:{ - "content-type":"application/json" - }, - body: { - "hello":"world" - } - } + headers: { + 'content-type': 'application/json', + }, + body: { + hello: 'world', + }, + }; context.done(); }; ``` diff --git a/docs/providers/azure/events/other.md b/docs/providers/azure/events/other.md index b7b7167a2..e39673181 100644 --- a/docs/providers/azure/events/other.md +++ b/docs/providers/azure/events/other.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/events/other) + ## Other Bindings @@ -33,16 +35,16 @@ functions: events: - queue: hello x-azure-settings: - name: item #, default - "myQueueItem", specifies which name it's available on `context.bindings` - connection: AzureWebJobsStorage #, default - "AzureWebJobsStorage", environment variable which contains Storage Account Connection String + name: item #, default - "myQueueItem", specifies which name it's available on `context.bindings` + connection: AzureWebJobsStorage #, default - "AzureWebJobsStorage", environment variable which contains Storage Account Connection String - documentDB: x-azure-settings: - name: record # Name of input parameter in function signature>", - databaseName: myDocs # "", - collectionName: todo # "", - createIfNotExists: true - connection: docDBAppSetting # "", - direction: out + name: record # Name of input parameter in function signature>", + databaseName: myDocs # "", + collectionName: todo # "", + createIfNotExists: true + connection: docDBAppSetting # "", + direction: out ``` ```javascript @@ -51,10 +53,10 @@ functions: 'use strict'; module.exports.hello = function(context, item) { - context.log("Received item: ${item}"); + context.log('Received item: ${item}'); context.bindings.record = { - hello: "world" - } + hello: 'world', + }; context.done(); }; ``` diff --git a/docs/providers/azure/events/queuestorage.md b/docs/providers/azure/events/queuestorage.md index 763911382..1dd0e8e7d 100644 --- a/docs/providers/azure/events/queuestorage.md +++ b/docs/providers/azure/events/queuestorage.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/events/queuestorage) + # Queue Storage Trigger @@ -34,8 +36,8 @@ functions: events: - queue: hello x-azure-settings: - name: item #, default - "myQueueItem", specifies which name it's available on `context.bindings` - connection: AzureWebJobsStorage #, default - "AzureWebJobsStorage", environment variable which contains Storage Account Connection String + name: item #, default - "myQueueItem", specifies which name it's available on `context.bindings` + connection: AzureWebJobsStorage #, default - "AzureWebJobsStorage", environment variable which contains Storage Account Connection String ``` ```javascript @@ -44,7 +46,7 @@ functions: 'use strict'; module.exports.hello = function(context, item) { - context.log("Received item: ${item}"); + context.log('Received item: ${item}'); context.done(); }; ``` diff --git a/docs/providers/azure/events/servicebus.md b/docs/providers/azure/events/servicebus.md index 28a1dfbf8..bd539e5b0 100644 --- a/docs/providers/azure/events/servicebus.md +++ b/docs/providers/azure/events/servicebus.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/events/servicebus) + # Service Bus Trigger @@ -32,10 +34,10 @@ functions: events: - serviceBus: x-azure-settings: - name: item #, default - "mySbMsg", specifies which name it's available on `context.bindings` - queueName: hello #, specifies the queue name to listen on - accessRights: manage #, specifies the permission to use when listening on the queue (manage will create queue if not exists) - connection: ServiceBusConnection #, environment variable which contains Service Bus Namespace Connection String + name: item #, default - "mySbMsg", specifies which name it's available on `context.bindings` + queueName: hello #, specifies the queue name to listen on + accessRights: manage #, specifies the permission to use when listening on the queue (manage will create queue if not exists) + connection: ServiceBusConnection #, environment variable which contains Service Bus Namespace Connection String ``` ```javascript @@ -44,7 +46,7 @@ functions: 'use strict'; module.exports.hello = function(context, item) { - context.log("Received item: ${item}"); + context.log('Received item: ${item}'); context.done(); }; ``` @@ -65,10 +67,10 @@ functions: events: - serviceBus: x-azure-settings: - name: item #, default - "mySbMsg", specifies which name it's available on `context.bindings` - topicName: "hello" #, topic to listen on - subscriptionName: "hello" #, subscription to listen on - connection: ServiceBusConnection #, environment variable which contains Service Bus Namespace Connection String + name: item #, default - "mySbMsg", specifies which name it's available on `context.bindings` + topicName: 'hello' #, topic to listen on + subscriptionName: 'hello' #, subscription to listen on + connection: ServiceBusConnection #, environment variable which contains Service Bus Namespace Connection String ``` ```javascript @@ -77,7 +79,7 @@ functions: 'use strict'; module.exports.hello = function(context, item) { - context.log("Received item: ${item}"); + context.log('Received item: ${item}'); context.done(); }; ``` diff --git a/docs/providers/azure/events/timer.md b/docs/providers/azure/events/timer.md index 929d0bc11..d06972570 100644 --- a/docs/providers/azure/events/timer.md +++ b/docs/providers/azure/events/timer.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/events/timer) + # Timer Trigger @@ -33,8 +35,8 @@ functions: events: - timer: x-azure-settings: - name: timerObj #, default - "myTimer", specifies which name it's available on `context.bindings` - schedule: 0 */5 * * * * #, cron expression to run on + name: timerObj #, default - "myTimer", specifies which name it's available on `context.bindings` + schedule: 0 */5 * * * * #, cron expression to run on ``` ```javascript @@ -43,7 +45,7 @@ functions: 'use strict'; module.exports.hello = function(context, timerObj) { - context.log("Timer ran"); + context.log('Timer ran'); context.done(); }; ``` diff --git a/docs/providers/azure/examples/README.md b/docs/providers/azure/examples/README.md index 1ca7cecb4..1ee444cd5 100644 --- a/docs/providers/azure/examples/README.md +++ b/docs/providers/azure/examples/README.md @@ -5,15 +5,17 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/examples/) + # Serverless Azure Functions Examples Have an example? Submit a PR or [open an issue](https://github.com/serverless/examples/issues). ⚡️ -| Example | Runtime | -|:--------------------------- |:-----| -| [azure Node Simple](https://serverless.com/examples/azure-node-simple-http-endpoint/)
    Boilerplate project repository for Azure provider with Serverless Framework. | nodeJS | +| Example | Runtime | +| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | +| [azure Node Simple](https://serverless.com/examples/azure-node-simple-http-endpoint/)
    Boilerplate project repository for Azure provider with Serverless Framework. | nodeJS | If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://forum.serverless.com/) diff --git a/docs/providers/azure/examples/hello-world/README.md b/docs/providers/azure/examples/hello-world/README.md index 309d3f7c8..557e5f536 100644 --- a/docs/providers/azure/examples/hello-world/README.md +++ b/docs/providers/azure/examples/hello-world/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/examples/hello-world/) + # Hello World Serverless Example 🌍 @@ -15,6 +17,6 @@ Welcome to the Hello World example. Pick your language of choice: -* [JavaScript](./node) +- [JavaScript](./node) [View all examples](https://www.serverless.com/framework/docs/providers/azure/examples/) diff --git a/docs/providers/azure/examples/hello-world/node/README.md b/docs/providers/azure/examples/hello-world/node/README.md index 2a33bfb5a..707c22dea 100644 --- a/docs/providers/azure/examples/hello-world/node/README.md +++ b/docs/providers/azure/examples/hello-world/node/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/examples/hello-world/node/) + # Hello World Node.js Example diff --git a/docs/providers/azure/examples/hello-world/node/serverless.yml b/docs/providers/azure/examples/hello-world/node/serverless.yml index 67ea99864..da261f3bb 100644 --- a/docs/providers/azure/examples/hello-world/node/serverless.yml +++ b/docs/providers/azure/examples/hello-world/node/serverless.yml @@ -9,11 +9,9 @@ plugins: - serverless-azure-functions functions: - hello: - handler: templates/handler.hello - events: - - http: true - x-azure-settings: - authLevel : anonymous - - \ No newline at end of file + hello: + handler: templates/handler.hello + events: + - http: true + x-azure-settings: + authLevel: anonymous diff --git a/docs/providers/azure/guide/README.md b/docs/providers/azure/guide/README.md index ee493844e..3a6892ef5 100644 --- a/docs/providers/azure/guide/README.md +++ b/docs/providers/azure/guide/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/) + # Serverless Azure Functions Guide diff --git a/docs/providers/azure/guide/credentials.md b/docs/providers/azure/guide/credentials.md index c110a64a6..7efb99cbf 100644 --- a/docs/providers/azure/guide/credentials.md +++ b/docs/providers/azure/guide/credentials.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/credentials) + # Azure - Credentials @@ -27,7 +29,7 @@ Here's how to get started… - Sign up for a free account @ [azure.com](https://azure.microsoft.com/en-us/services/functions/) Azure comes with a [free trial](https://azure.microsoft.com/en-us/free/) that -includes $200 of free credit. +includes \$200 of free credit. ### Interactive Login @@ -35,8 +37,8 @@ Upon running `$ serverless deploy`, you will automatically be prompted to login via your browser. Simply follow the instructions. > Note: Once you've authenticated, a new Azure "service principal" will be -created, and used for subsequent deployments. This prevents you from needing to -manually login again. +> created, and used for subsequent deployments. This prevents you from needing to +> manually login again. ### Azure Account Credentials @@ -51,74 +53,74 @@ you set your Azure endpoint before logging in. 1. Get the Azure CLI - Follow the guide on [docs.microsoft.com](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) - or use the [Azure Cloud Shell](https://docs.microsoft.com/en-us/azure/cloud-shell/overview). + Follow the guide on [docs.microsoft.com](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) + or use the [Azure Cloud Shell](https://docs.microsoft.com/en-us/azure/cloud-shell/overview). 2. Login to Azure - ```sh - $ az login - ``` + ```sh + $ az login + ``` - This will give you a code and prompt you to visit - [aka.ms/devicelogin](https://aka.ms/devicelogin). Provide the code and then - login with your Azure identity (this may happen automatically if you're - already logged in). You'll then be able to access your account via the CLI. + This will give you a code and prompt you to visit + [aka.ms/devicelogin](https://aka.ms/devicelogin). Provide the code and then + login with your Azure identity (this may happen automatically if you're + already logged in). You'll then be able to access your account via the CLI. 3. Get your subscription and tenant id - ```sh - $ az account list - { - "cloudName": "AzureCloud", - "id": "c6e5c9a2-a4dd-4c05-81b4-6bed04f913ea", - "isDefault": true, - "name": "My Azure Subscription", - "registeredProviders": [], - "state": "Enabled", - "tenantId": "5bc10873-159c-4cbe-a7c9-bce05cb065c1", - "user": { - "name": "hello@example.com", - "type": "user" - } - } - ``` + ```sh + $ az account list + { + "cloudName": "AzureCloud", + "id": "c6e5c9a2-a4dd-4c05-81b4-6bed04f913ea", + "isDefault": true, + "name": "My Azure Subscription", + "registeredProviders": [], + "state": "Enabled", + "tenantId": "5bc10873-159c-4cbe-a7c9-bce05cb065c1", + "user": { + "name": "hello@example.com", + "type": "user" + } + } + ``` - Save the ID of the subscription for step 5. + Save the ID of the subscription for step 5. 4. Create a service principal - ```sh - $ az ad sp create-for-rbac - { - "appId": "19f7b7c1-fc4e-4c92-8aaf-21fffc93b4c9", - "displayName": "azure-cli-1970-01-01-00-00-00", - "name": "http://azure-cli-1970-01-01-00-00-00", - "password": "48d82644-00f2-4e64-80c5-65192f9bb2d0", - "tenant": "16f63fe8-17db-476f-b2b3-ba3752a03a33" - } - ``` + ```sh + $ az ad sp create-for-rbac + { + "appId": "19f7b7c1-fc4e-4c92-8aaf-21fffc93b4c9", + "displayName": "azure-cli-1970-01-01-00-00-00", + "name": "http://azure-cli-1970-01-01-00-00-00", + "password": "48d82644-00f2-4e64-80c5-65192f9bb2d0", + "tenant": "16f63fe8-17db-476f-b2b3-ba3752a03a33" + } + ``` - This will return an JSON object containing the other pieces that you need to - authenticate with Azure. + This will return an JSON object containing the other pieces that you need to + authenticate with Azure. 5. Set up environment variables - Finally, create environment variables for subscription ID (from step 3), - tenant, name, and password. + Finally, create environment variables for subscription ID (from step 3), + tenant, name, and password. - ```sh - # bash - export azureSubId='' # From step 3 - export azureServicePrincipalTenantId='' - export azureServicePrincipalClientId='' - export azureServicePrincipalPassword='' - ``` + ```sh + # bash + export azureSubId='' # From step 3 + export azureServicePrincipalTenantId='' + export azureServicePrincipalClientId='' + export azureServicePrincipalPassword='' + ``` - ```powershell - # PowerShell - $env:azureSubId='' # From step 3 - $env:azureServicePrincipalTenantId='' - $env:azureServicePrincipalClientId='' - $env:azureServicePrincipalPassword='' - ``` + ```powershell + # PowerShell + $env:azureSubId='' # From step 3 + $env:azureServicePrincipalTenantId='' + $env:azureServicePrincipalClientId='' + $env:azureServicePrincipalPassword='' + ``` diff --git a/docs/providers/azure/guide/deploying.md b/docs/providers/azure/guide/deploying.md index 0189364cf..c7a89b9f9 100644 --- a/docs/providers/azure/guide/deploying.md +++ b/docs/providers/azure/guide/deploying.md @@ -7,13 +7,15 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/deploying) + # Azure - Deploying The Serverless Framework was designed to provision your Azure Functions -Functions, Triggers and Rules safely and quickly. It does this via a couple of +Functions, Triggers and Rules safely and quickly. It does this via a couple of methods designed for different types of deployments. ## Deploy All @@ -28,20 +30,20 @@ Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to Azure Functions. -**Note:** You can specify a different configuration file name with the the `--config` option. +**Note:** You can specify a different configuration file name with the the `--config` option. ### How It Works The Serverless Framework translates all syntax in `serverless.yml` to an Azure Resource Manager Template and Azure Function project. -* Provider plugin parses `serverless.yml` configuration and translates to Azure resources. -* The code of your Functions is then packaged into a directory and zipped. -* Resources are deployed in the following order: *ARM template, Functions* +- Provider plugin parses `serverless.yml` configuration and translates to Azure resources. +- The code of your Functions is then packaged into a directory and zipped. +- Resources are deployed in the following order: _ARM template, Functions_ ### Tips -* Use this in your CI/CD systems, as it is the safest method of deployment. +- Use this in your CI/CD systems, as it is the safest method of deployment. Check out the [deploy command docs](../cli-reference/deploy.md) for all details and options. @@ -57,13 +59,13 @@ serverless deploy function --function myFunction ### How It Works -* The Framework packages up the targeted Azure Function into a zip file. -* That zip file is deployed to the Function App using the kudu zip API. +- The Framework packages up the targeted Azure Function into a zip file. +- That zip file is deployed to the Function App using the kudu zip API. ### Tips -* Use this when you are developing and want to test on Azure Functions because it's much faster. -* During development, people will often run this command several times, as opposed to `serverless deploy` which is only run when larger infrastructure provisioning is required. +- Use this when you are developing and want to test on Azure Functions because it's much faster. +- During development, people will often run this command several times, as opposed to `serverless deploy` which is only run when larger infrastructure provisioning is required. Check out the [deploy command docs](../cli-reference/deploy.md) for all details and options. diff --git a/docs/providers/azure/guide/events.md b/docs/providers/azure/guide/events.md index fe07c4e26..8b0ce0561 100644 --- a/docs/providers/azure/guide/events.md +++ b/docs/providers/azure/guide/events.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/events) + # Azure - Events @@ -48,7 +50,7 @@ queuejs: events: - queue: YourQueueName x-azure-settings: - connection : StorageAppSettingName + connection: StorageAppSettingName - blob: x-azure-settings: name: bindingName diff --git a/docs/providers/azure/guide/functions.md b/docs/providers/azure/guide/functions.md index 699eb6fd9..2b049a1e1 100644 --- a/docs/providers/azure/guide/functions.md +++ b/docs/providers/azure/guide/functions.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/functions) + # Azure - Functions -If you are using Azure Functions as a provider, all *functions* inside the service are Azure Functions. +If you are using Azure Functions as a provider, all _functions_ inside the service are Azure Functions. ## Configuration @@ -32,11 +34,11 @@ plugins: functions: hello: - handler: templates/handler.hello - events: - - http: true - x-azure-settings: - authLevel : anonymous + handler: templates/handler.hello + events: + - http: true + x-azure-settings: + authLevel: anonymous ``` The `handler` property points to the file (default filename: handler.js) and @@ -44,15 +46,14 @@ module containing the code you want to run in your function. ```javascript // handler.js -exports.handler = function(params) {} +exports.handler = function(params) {}; ``` You can add as many functions as you want within this property. ```yml # serverless.yml -... - +--- functions: functionOne: handler: handler.functionOne @@ -67,8 +68,7 @@ You can specify an array of functions, which is useful if you separate your func ```yml # serverless.yml -... - +--- functions: - ${file(./foo-functions.yml)} - ${file(./bar-functions.yml)} diff --git a/docs/providers/azure/guide/installation.md b/docs/providers/azure/guide/installation.md index 1a9b6d48f..b816bec9c 100644 --- a/docs/providers/azure/guide/installation.md +++ b/docs/providers/azure/guide/installation.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/installation) + # Azure - Installation diff --git a/docs/providers/azure/guide/intro.md b/docs/providers/azure/guide/intro.md index 685157c13..750a664d5 100644 --- a/docs/providers/azure/guide/intro.md +++ b/docs/providers/azure/guide/intro.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/intro) + # Azure - Introduction @@ -19,8 +21,9 @@ driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). The Serverless Framework is different than other application frameworks because: -* It manages your code as well as your infrastructure -* It supports multiple languages (Node.js, Python, Java, and more) + +- It manages your code as well as your infrastructure +- It supports multiple languages (Node.js, Python, Java, and more) ## Core Concepts @@ -33,9 +36,9 @@ It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: -* *Saving a user to the database* -* *Processing a file in a database* -* *Performing a scheduled task* +- _Saving a user to the database_ +- _Processing a file in a database_ +- _Performing a scheduled task_ You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed @@ -46,12 +49,12 @@ to help you easily develop and deploy Functions, as well as manage lots of them. Anything that triggers an Azure Function to execute is regarded by the Framework as an **Event**. Events are platform events on Azure Functions such as: -* *An HTTP Trigger (e.g., for a REST API)* -* *A scheduled timer (e.g., run every 5 minutes)* -* *A Service Bus Queue trigger (e.g. a workitem from another Function)* -* *An IoT/Event Hub message (e.g., a message from a device or service)* -* *A Webhook fires (e.g., Github project update)* -* *And more...* +- _An HTTP Trigger (e.g., for a REST API)_ +- _A scheduled timer (e.g., run every 5 minutes)_ +- _A Service Bus Queue trigger (e.g. a workitem from another Function)_ +- _An IoT/Event Hub message (e.g., a message from a device or service)_ +- _A Webhook fires (e.g., Github project update)_ +- _And more..._ When you define an event for your Azure Function in the Serverless Framework, the Framework will automatically translate this into @@ -78,7 +81,7 @@ functions: # Your "Functions" x-azure-settings: name: req methods: - - post + - post route: /users/create usersDelete: events: @@ -86,7 +89,7 @@ functions: # Your "Functions" x-azure-settings: name: req methods: - - delete + - delete route: /users/delete ``` diff --git a/docs/providers/azure/guide/packaging.md b/docs/providers/azure/guide/packaging.md index e6ffd6b87..3a3147836 100644 --- a/docs/providers/azure/guide/packaging.md +++ b/docs/providers/azure/guide/packaging.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/packaging) + # Azure - Packaging @@ -57,7 +59,7 @@ files and directories. Exclude all node_modules but then re-include a specific modules (in this case node-fetch) using `exclude` exclusively -``` yml +```yml package: exclude: - node_modules/** @@ -66,7 +68,7 @@ package: Exclude all files but `handler.js` using `exclude` and `include` -``` yml +```yml package: exclude: - src/** diff --git a/docs/providers/azure/guide/plugins.md b/docs/providers/azure/guide/plugins.md index 5263fa3b9..e1a7e9ad1 100644 --- a/docs/providers/azure/guide/plugins.md +++ b/docs/providers/azure/guide/plugins.md @@ -7,16 +7,18 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/plugins) + # Azure - Plugins A Plugin is custom JavaScript code that creates new or extends existing commands -within the Serverless Framework. The Serverless Framework is merely a group of -Plugins that are provided in the core. If you or your organization have a +within the Serverless Framework. The Serverless Framework is merely a group of +Plugins that are provided in the core. If you or your organization have a specific workflow, install a pre-written Plugin or write a plugin to customize -the Framework to your needs. External Plugins are written exactly the same way +the Framework to your needs. External Plugins are written exactly the same way as the core Plugins. - [How to create serverless plugins - Part 1](https://serverless.com/blog/writing-serverless-plugins/) @@ -42,9 +44,11 @@ do this by adding the name of the Plugin to the `plugins` section in the plugins: - custom-serverless-plugin ``` + The `plugins` section supports two formats: Array object: + ```yml plugins: - plugin1 @@ -52,6 +56,7 @@ plugins: ``` Enhanced plugins object: + ```yml plugins: localPath: './custom_serverless_plugins' @@ -75,18 +80,21 @@ custom: If you are working on a plugin or have a plugin that is just designed for one project they can be loaded from the local folder. Local plugins can be added in the `plugins` array in `serverless.yml`. By default local plugins can be added to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. + ```yml plugins: - custom-serverless-plugin ``` Local plugins folder can be changed by enhancing `plugins` object: + ```yml plugins: localPath: './custom_serverless_plugins' modules: - custom-serverless-plugin ``` + The `custom-serverless-plugin` will be loaded from the `custom_serverless_plugins` directory at the root of your service. If the `localPath` is not provided or empty `.serverless_plugins` directory will be taken as the `localPath`. The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `localPath` folder (`.serverless_plugins` by default). @@ -111,18 +119,18 @@ In this case `plugin1` is loaded before `plugin2`. #### Plugin -Code which defines *Commands*, any *Events* within a *Command*, and any *Hooks* -assigned to an *Lifecycle Event*. +Code which defines _Commands_, any _Events_ within a _Command_, and any _Hooks_ +assigned to an _Lifecycle Event_. -* Command // CLI configuration, commands, subcommands, options - * LifecycleEvent(s) // Events that happen sequentially when the command is run - * Hook(s) // Code that runs when a Lifecycle Event happens during a Command +- Command // CLI configuration, commands, subcommands, options + - LifecycleEvent(s) // Events that happen sequentially when the command is run + - Hook(s) // Code that runs when a Lifecycle Event happens during a Command #### Command -A CLI *Command* that can be called by a user, e.g. `serverless deploy`. A +A CLI _Command_ that can be called by a user, e.g. `serverless deploy`. A Command has no logic, but simply defines the CLI configuration (e.g. command, -subcommands, parameters) and the *Lifecycle Events* for the command. Every +subcommands, parameters) and the _Lifecycle Events_ for the command. Every command defines its own lifecycle events. ```javascript @@ -132,10 +140,7 @@ class MyPlugin { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ] + lifecycleEvents: ['resources', 'functions'], }, }; } @@ -146,9 +151,9 @@ module.exports = MyPlugin; #### Lifecycle Events -Events that fire sequentially during a Command. The above example list two -Events. However, for each Event, and additional `before` and `after` event is -created. Therefore, six Events exist in the above example: +Events that fire sequentially during a Command. The above example list two +Events. However, for each Event, and additional `before` and `after` event is +created. Therefore, six Events exist in the above example: - `before:deploy:resources` - `deploy:resources` @@ -170,17 +175,14 @@ class Deploy { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ] + lifecycleEvents: ['resources', 'functions'], }, }; this.hooks = { 'before:deploy:resources': this.beforeDeployResources, 'deploy:resources': this.deployResources, - 'after:deploy:functions': this.afterDeployFunctions + 'after:deploy:functions': this.afterDeployFunctions, }; } @@ -202,8 +204,7 @@ module.exports = Deploy; ### Nesting Commands -You can also nest commands, e.g. if you want to provide a command `serverless -deploy single`. Those nested commands have their own lifecycle events and do not +You can also nest commands, e.g. if you want to provide a command `serverless deploy single`. Those nested commands have their own lifecycle events and do not inherit them from their parents. ```javascript @@ -213,20 +214,14 @@ class MyPlugin { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ], + lifecycleEvents: ['resources', 'functions'], commands: { function: { - lifecycleEvents: [ - 'package', - 'deploy' - ], + lifecycleEvents: ['package', 'deploy'], }, }, }, - } + }; } } @@ -245,7 +240,7 @@ The `options` object will be passed in as the second parameter to the constructo of your plugin. In it, you can optionally add a `shortcut` property, as well as a `required` -property. The Framework will return an error if a `required` Option is not +property. The Framework will return an error if a `required` Option is not included. **Note:** At this time, the Serverless Framework does not use parameters. @@ -260,22 +255,20 @@ class Deploy { this.commands = { deploy: { - lifecycleEvents: [ - 'functions' - ], + lifecycleEvents: ['functions'], options: { function: { usage: 'Specify the function you want to deploy (e.g. "--function myFunction")', shortcut: 'f', - required: true - } - } + required: true, + }, + }, }, }; this.hooks = { - 'deploy:functions': this.deployFunction.bind(this) - } + 'deploy:functions': this.deployFunction.bind(this), + }; } deployFunction() { @@ -308,21 +301,19 @@ class ProviderDeploy { this.commands = { deploy: { - lifecycleEvents: [ - 'functions' - ], + lifecycleEvents: ['functions'], options: { function: { usage: 'Specify the function you want to deploy (e.g. "--function myFunction")', - required: true - } - } + required: true, + }, + }, }, }; this.hooks = { - 'deploy:functions': this.deployFunction.bind(this) - } + 'deploy:functions': this.deployFunction.bind(this), + }; } deployFunction() { @@ -352,15 +343,13 @@ class MyPlugin { this.commands = { log: { - lifecycleEvents: [ - 'serverless' - ], + lifecycleEvents: ['serverless'], }, }; this.hooks = { - 'log:serverless': this.logServerless.bind(this) - } + 'log:serverless': this.logServerless.bind(this), + }; } logServerless() { diff --git a/docs/providers/azure/guide/quick-start.md b/docs/providers/azure/guide/quick-start.md index e9350a48a..f1143e1d7 100644 --- a/docs/providers/azure/guide/quick-start.md +++ b/docs/providers/azure/guide/quick-start.md @@ -10,11 +10,11 @@ layout: Doc ## Pre-requisites -1. Node.js `v6.5.0` or later. *(v6.5.0 is the minimum runtime version supported by Azure Functions)* +1. Node.js `v6.5.0` or later. _(v6.5.0 is the minimum runtime version supported by Azure Functions)_ 2. Serverless CLI `v1.9.0` or later. You can run -`npm install -g serverless` to install it. + `npm install -g serverless` to install it. 3. Azure plugin that allows you to work with Azure Functions `npm install -g serverless-azure-functions` -4. An Azure account. If you don't already have one, you can sign up for a [free trial](https://azure.microsoft.com/en-us/free/) that includes $200 of free credit. +4. An Azure account. If you don't already have one, you can sign up for a [free trial](https://azure.microsoft.com/en-us/free/) that includes \$200 of free credit. 5. **Set-up your [Provider Credentials](./credentials.md)**. ## Create a new service @@ -55,46 +55,46 @@ Note: The file `{function name}/function.json` is included in the template for t 1. **Deploy the Service:** - 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. +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. - ```bash - serverless deploy - ``` +```bash +serverless deploy +``` - > Note: Once you've authenticated, a new Azure "service principal" will be - created, and used for subsequent deployments. This prevents you from needing to - manually login again. See [below](#advanced-authentication) if you'd prefer to - use a custom service principal instead. +> Note: Once you've authenticated, a new Azure "service principal" will be +> created, and used for subsequent deployments. This prevents you from needing to +> manually login again. See [below](#advanced-authentication) if you'd prefer to +> use a custom service principal instead. 2. **Deploy the Function** - Use this to quickly upload and overwrite your function code,allowing you to - develop faster. If you're working on a single function, you can simply deploy - the specified function instead of the entire service. +Use this to quickly upload and overwrite your function code,allowing you to +develop faster. If you're working on a single function, you can simply deploy +the specified function instead of the entire service. - ```bash - serverless deploy function -f hello - ``` +```bash +serverless deploy function -f hello +``` 3. **Invoke the Function** - Invoke a function, in order to test that it works: +Invoke a function, in order to test that it works: - ```bash - serverless invoke -f hello - ``` +```bash +serverless invoke -f hello +``` 4. **Fetch the Function Logs** - Open up a separate tab in your console and stream all logs for a specific - Function using this command. +Open up a separate tab in your console and stream all logs for a specific +Function using this command. - ```bash - serverless logs -f hello -t - ``` +```bash +serverless logs -f hello -t +``` ## Cleanup @@ -117,6 +117,7 @@ yourself, you can indicate that this plugin should use its credentials instead, by setting the following environment variables: **Bash** + ```bash export azureSubId='' export azureServicePrincipalTenantId='' @@ -125,6 +126,7 @@ export azureServicePrincipalPassword='' ``` **Powershell** + ```powershell $env:azureSubId='' $env:azureServicePrincipalTenantId='' diff --git a/docs/providers/azure/guide/services.md b/docs/providers/azure/guide/services.md index 1ab4bdbe6..40b9a2762 100644 --- a/docs/providers/azure/guide/services.md +++ b/docs/providers/azure/guide/services.md @@ -7,13 +7,15 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/services) + # Azure - Services -A `service` is like a project. It's where you define your Azure Functions, the -`events` that trigger them and any `resources` they require, all in a file +A `service` is like a project. It's where you define your Azure Functions, the +`events` that trigger them and any `resources` they require, all in a file called `serverless.yml`. To get started building your first Serverless Framework project, create a @@ -22,7 +24,7 @@ To get started building your first Serverless Framework project, create a ## Organization In the beginning of an application, many people use a single Service to define -all of the Functions, Events and Resources for that project. This is what we +all of the Functions, Events and Resources for that project. This is what we recommend in the beginning. ```bash @@ -60,6 +62,7 @@ serverless create -t azure-nodejs --path ## Contents You'll see the following files in your working directory: + - `serverless.yml` - `handler.js` @@ -69,11 +72,11 @@ Each `service` configuration is managed in the `serverless.yml` file. The main r - Declare a Serverless service - Define one or more functions in the service - - Define the provider the service will be deployed to (and the runtime if provided) - - Define any custom plugins to be used - - Define events that trigger each function to execute (e.g. HTTP requests) - - Allow events listed in the `events` section to automatically create the resources required for the event upon deployment - - Allow flexible configuration using Serverless Variables + - Define the provider the service will be deployed to (and the runtime if provided) + - Define any custom plugins to be used + - Define events that trigger each function to execute (e.g. HTTP requests) + - Allow events listed in the `events` section to automatically create the resources required for the event upon deployment + - Allow flexible configuration using Serverless Variables You can see the name of the service, the provider configuration and the first function inside the `functions` definition which points to the `handler.js` file. Any further service configuration will be done in this file. @@ -91,11 +94,11 @@ plugins: functions: hello: - handler: handler.hello - events: - - http: true - x-azure-settings: - authLevel : anonymous + handler: handler.hello + events: + - http: true + x-azure-settings: + authLevel: anonymous ``` ### handler.js @@ -128,7 +131,7 @@ serverless deploy ``` Check out the [deployment guide](https://serverless.com/framework/docs/providers/azure/guide/deploying/) -to learn more about deployments and how they work. Or, check out the +to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy) for all the details and options. ## Removal @@ -150,8 +153,7 @@ on. ## Version Pinning -The Serverless framework is usually installed globally via `npm install -g -serverless`. This way you have the Serverless CLI available for all your +The Serverless framework is usually installed globally via `npm install -g serverless`. This way you have the Serverless CLI available for all your services. Installing tools globally has the downside that the version can't be pinned @@ -192,7 +194,6 @@ frameworkVersion: ">=1.0.0 <2.0.0" … ``` - ## Installing Serverless in an existing service If you already have a Serverless service, and would prefer to lock down the @@ -210,6 +211,7 @@ To execute the locally installed Serverless executable you have to reference the binary out of the node modules directory. Example: + ``` node ./node_modules/serverless/bin/serverless deploy ``` diff --git a/docs/providers/azure/guide/testing.md b/docs/providers/azure/guide/testing.md index dae305f0c..2a0be29fb 100644 --- a/docs/providers/azure/guide/testing.md +++ b/docs/providers/azure/guide/testing.md @@ -7,26 +7,28 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/testing) + # Azure - Testing While the Serverless Architecture introduces a lot of simplicity when it comes to serving business logic, some of its characteristics present challenges for -testing. They are: +testing. They are: -* The Serverless Architecture is an integration of separate, distributed services, which must be tested both independently, and together. -* The Serverless Architecture is dependent on internet/cloud services, which are hard to emulate locally. -* The Serverless Architecture can feature event-driven, asynchronous workflows, which are hard to emulate entirely. +- The Serverless Architecture is an integration of separate, distributed services, which must be tested both independently, and together. +- The Serverless Architecture is dependent on internet/cloud services, which are hard to emulate locally. +- The Serverless Architecture can feature event-driven, asynchronous workflows, which are hard to emulate entirely. To get through these challenges, and to keep the [test pyramid](http://martinfowler.com/bliki/TestPyramid.html) in mind, keep the following principles in mind: -* Write your business logic so that it is separate from your FaaS provider (e.g., Azure Functions), to keep it provider-independent, reusable and more easily testable. -* When your business logic is written separately from the FaaS provider, you can write traditional Unit Tests to ensure it is working properly. -* Write Integration Tests to verify integrations with other services are working correctly. +- Write your business logic so that it is separate from your FaaS provider (e.g., Azure Functions), to keep it provider-independent, reusable and more easily testable. +- When your business logic is written separately from the FaaS provider, you can write traditional Unit Tests to ensure it is working properly. +- Write Integration Tests to verify integrations with other services are working correctly. ## A Poor Example @@ -42,10 +44,10 @@ module.exports.saveUser = (context, req) => { return Promise((resolve, reject) => { const user = { email: req.params.email, - created_at: Date.now() - } + created_at: Date.now(), + }; - db.saveUser(user, function (err) { + db.saveUser(user, function(err) { if (err) { reject(err); } else { @@ -53,14 +55,14 @@ module.exports.saveUser = (context, req) => { resolve(); } }); - }) + }); }; ``` There are two main problems with this function: -* The business logic is not separate from the FaaS Provider. It's bounded to how Azure Functions passes incoming data (`params` object). -* Testing this function will rely on separate services. Specifically, running a database instance and a mail server. +- The business logic is not separate from the FaaS Provider. It's bounded to how Azure Functions passes incoming data (`params` object). +- Testing this function will rely on separate services. Specifically, running a database instance and a mail server. ## Writing Testable Azure Functions Functions @@ -78,10 +80,10 @@ class Users { return new Promise((resolve, reject) => { const user = { email: email, - created_at: Date.now() - } + created_at: Date.now(), + }; - this.db.saveUser(user, function (err) { + this.db.saveUser(user, function(err) { if (err) { reject(err); } else { @@ -89,7 +91,7 @@ class Users { resolve(); } }); - }) + }); } } @@ -108,17 +110,17 @@ module.exports.saveUser = (context, req) => { }; ``` -Now, the above class keeps business logic separate. Further, the code +Now, the above class keeps business logic separate. Further, the code responsible for setting up dependencies, injecting them, calling business logic functions and interacting with Azure Functions is in its own file, which will be -changed less often. This way, the business logic is not provider dependent, +changed less often. This way, the business logic is not provider dependent, easier to re-use, and easier to test. -Further, this code doesn't require running any external services. Instead of a +Further, this code doesn't require running any external services. Instead of a real `db` and `mailer` services, we can pass mocks and assert if `saveUser` and `sendWelcomeEmail` has been called with proper arguments. -Unit Tests can easily be written to cover the above class. An integration test +Unit Tests can easily be written to cover the above class. An integration test can be added by invoking the function (`serverless invoke`) with fixture email address, check if user is actually saved to DB and check if email was received to see if everything is working together. diff --git a/docs/providers/azure/guide/variables.md b/docs/providers/azure/guide/variables.md index 57648171b..12220124c 100644 --- a/docs/providers/azure/guide/variables.md +++ b/docs/providers/azure/guide/variables.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/variables) + # Azure - Variables @@ -55,7 +57,8 @@ referencing the `globalSchedule` property in the same `serverless.yml` file. Thi way, you can easily change the schedule for all functions whenever you like. ## Reference Variables in other Files -You can reference variables in other YAML or JSON files. To reference variables in other YAML files use the `${file(./myFile.yml):someProperty}` syntax in your `serverless.yml` configuration file. To reference variables in other JSON files use the `${file(./myFile.json):someProperty}` syntax. It is important that the file you are referencing has the correct suffix, or file extension, for its file type (`.yml` for YAML or `.json` for JSON) in order for it to be interpreted correctly. Here's an example: + +You can reference variables in other YAML or JSON files. To reference variables in other YAML files use the `${file(./myFile.yml):someProperty}` syntax in your `serverless.yml` configuration file. To reference variables in other JSON files use the `${file(./myFile.json):someProperty}` syntax. It is important that the file you are referencing has the correct suffix, or file extension, for its file type (`.yml` for YAML or `.json` for JSON) in order for it to be interpreted correctly. Here's an example: ```yml # myCustomFile.yml @@ -80,8 +83,7 @@ functions: - timer: ${self:custom.cron} # This would also work in this case ``` - -In the above example, you're referencing the entire `myCustomFile.yml` file in the `custom` property. You need to pass the path relative to your service directory. You can also request specific properties in that file as shown in the `cron` property. It's completely recursive and you can go as deep as you want. Additionally you can request properties that contain arrays from either YAML or JSON reference files. Here's a YAML example for an events array: +In the above example, you're referencing the entire `myCustomFile.yml` file in the `custom` property. You need to pass the path relative to your service directory. You can also request specific properties in that file as shown in the `cron` property. It's completely recursive and you can go as deep as you want. Additionally you can request properties that contain arrays from either YAML or JSON reference files. Here's a YAML example for an events array: ```yml myevents: @@ -89,15 +91,19 @@ myevents: ``` and for JSON: + ```json { - "myevents": [{ - "timer" : "cron(0 * * * *)" - }] + "myevents": [ + { + "timer": "cron(0 * * * *)" + } + ] } ``` In your serverless.yml, depending on the type of your source file, either have the following syntax for YAML + ```yml functions: hello: @@ -106,6 +112,7 @@ functions: ``` or for a JSON reference file use this sytax: + ```yml functions: hello: @@ -124,9 +131,9 @@ References can be either named or unnamed exports. To use the exported `someModu ```js // scheduleConfig.js module.exports.cron = () => { - // Code that generates dynamic data - return 'cron(0 * * * *)'; -} + // Code that generates dynamic data + return 'cron(0 * * * *)'; +}; ``` ```js @@ -134,9 +141,9 @@ module.exports.cron = () => { module.exports = () => { return { property1: 'some value', - property2: 'some other value' - } -} + property2: 'some other value', + }; +}; ``` ```yml @@ -148,12 +155,12 @@ custom: ${file(./config.js)} functions: hello: - handler: handler.hello - events: - - timer: ${file(./scheduleConfig.js):cron} # Reference a specific module + handler: handler.hello + events: + - timer: ${file(./scheduleConfig.js):cron} # Reference a specific module ``` -You can also return an object and reference a specific property. Just make sure +You can also return an object and reference a specific property. Just make sure you are returning a valid object and referencing a valid property: ```yml @@ -162,19 +169,19 @@ service: new-service provider: azure functions: scheduledFunction: - handler: handler.scheduledFunction - events: - - timer: ${file(./myCustomFile.js):schedule.hour} + handler: handler.scheduledFunction + events: + - timer: ${file(./myCustomFile.js):schedule.hour} ``` ```js // myCustomFile.js module.exports.schedule = () => { - // Code that generates dynamic data - return { - hour: 'cron(0 * * * *)' - }; -} + // Code that generates dynamic data + return { + hour: 'cron(0 * * * *)', + }; +}; ``` ## Multiple Configuration Files diff --git a/docs/providers/azure/guide/workflow.md b/docs/providers/azure/guide/workflow.md index 3bd43a0cd..e59439bbf 100644 --- a/docs/providers/azure/guide/workflow.md +++ b/docs/providers/azure/guide/workflow.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/guide/workflow) + # Azure - Workflow @@ -19,16 +21,18 @@ Intro. Quick recommendations and tips for various processes. 1. Write your functions 2. Use `serverless deploy` only when you've made changes to `serverless.yml` and in CI/CD systems. 3. Use `serverless deploy function -f myFunction` to rapidly deploy changes when you are working on a specific Azure Functions Function. -4. Use `serverless invoke -f myFunction ` to test your Azure Functions. +4. Use `serverless invoke -f myFunction` to test your Azure Functions. 5. Open up a separate tab in your console and stream logs in there via `serverless logs -f myFunction`. 6. Write tests to run locally. ### Larger Projects -* Break your application/project into multiple Serverless Services. -* Model your Serverless Services around Data Models or Workflows. -* Keep the Functions and Resources in your Serverless Services to a minimum. + +- Break your application/project into multiple Serverless Services. +- Model your Serverless Services around Data Models or Workflows. +- Keep the Functions and Resources in your Serverless Services to a minimum. ## Cheat Sheet + A handy list of commands to use when developing with the Serverless Framework. ##### Create A Service: diff --git a/docs/providers/cloudflare/README.md b/docs/providers/cloudflare/README.md index 5cdc83273..98c39c31f 100644 --- a/docs/providers/cloudflare/README.md +++ b/docs/providers/cloudflare/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/) + # Cloudflare Provider Documentation diff --git a/docs/providers/cloudflare/cli-reference/README.md b/docs/providers/cloudflare/cli-reference/README.md index 5e37d8038..18961ec88 100644 --- a/docs/providers/cloudflare/cli-reference/README.md +++ b/docs/providers/cloudflare/cli-reference/README.md @@ -5,11 +5,13 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/cli-reference/) + # Serverless Cloudflare Workers CLI Reference -Welcome to the Serverless Cloudflare Workers CLI Reference! Please select a section on the left to get started. +Welcome to the Serverless Cloudflare Workers CLI Reference! Please select a section on the left to get started. If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/). diff --git a/docs/providers/cloudflare/cli-reference/create.md b/docs/providers/cloudflare/cli-reference/create.md index 645dc4726..7db43ea39 100644 --- a/docs/providers/cloudflare/cli-reference/create.md +++ b/docs/providers/cloudflare/cli-reference/create.md @@ -7,71 +7,83 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/cli-reference/create) + - # Cloudflare Workers - Create + Creates a new Serverless service in the current working directory based on the provided template. - + **Create service in current working directory:** - -```bash + +```bash serverless create --template cloudflare-workers ``` Or for Enterprise Cloudflare accounts: -```bash +```bash serverless create --template cloudflare-workers-enterprise ``` **Create service in new folder:** - + ```bash serverless create --template cloudflare-workers --path my-service ``` Or for Enterprise Cloudflare accounts: -```bash +```bash serverless create --template cloudflare-workers-enterprise --path my-service ``` ## Options + - `--template` or `-t` The name of one of the available templates. Required if --template-url and --template-path are not present. - `--template-url` or `-u` The name of one of the available templates. Required if --template and --template-path are not present. - `--template-path` The local path of your template. Required if --template and --template-url are not present. - `--path` or `-p` The path where the service should be created. - `--name` or `-n` the name of the service in `serverless.yml`. + ## Provided lifecycle events + - `create:create` + ## Available Templates for Cloudflare Workers + To see a list of available templates run `serverless create --help` These are the current available templates for Cloudflare Workers: - + - cloudflare-workers - cloudflare-workers-enterprise - cloudflare-workers-rust ## Examples + ### Creating a new service + ```bash serverless create --template cloudflare-workers --name my-special-service ``` This example will generate scaffolding for a service with `Cloudflare` as a provider. The scaffolding will be generated in the current working directory. - + ### Creating a named service in a (new) directory + ```bash serverless create --template cloudflare-workers --path my-new-service ``` This example will generate scaffolding for a service with `Cloudflare` as a provider. The scaffolding will be generated in the `my-new-service` directory. This directory will be created if not present. Otherwise, Serverless will use the already present directory. Additionally, Serverless will rename the service according to the path you provide. In this example, the service will be renamed to `my-new-service`. - + ### Creating a new service using a local template + ```bash serverless create --template-path path/to/my/template/folder --path path/to/my/service --name my-new-service ``` + This will copy the `path/to/my/template/folder` folder into `path/to/my/service` and rename the service to `my-new-service`. diff --git a/docs/providers/cloudflare/cli-reference/deploy.md b/docs/providers/cloudflare/cli-reference/deploy.md index 46d185bef..0fb6fc741 100644 --- a/docs/providers/cloudflare/cli-reference/deploy.md +++ b/docs/providers/cloudflare/cli-reference/deploy.md @@ -7,11 +7,13 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/cli-reference/deploy) + - # Cloudflare Workers - Deploy + In order to be able to deploy any Cloudflare Workers, You will need to set your Global API key from Cloudflare as an environmental variable named `CLOUDFLARE_AUTH_KEY`, and your Cloudflare account email as an environmental variable named `CLOUDFLARE_AUTH_EMAIL`. You can get your Global API key from your [Cloudflare profile](https://dash.cloudflare.com/profile) page. You will also need to set `accountId` and `zoneId` in `serverless.yml` under `service.config`. The first part of the path when you open [Cloudflare dashboard](https://dash.cloudflare.com/) as a logged in user is your `accountId`, e.g. `dash.cloudflare.com/{accountId}`. And the `zoneId` can be found from the overview tab after selecting the desired zone from the [Cloudflare dashboard](https://dash.cloudflare.com/). Environmental variables are variables that live inside your terminal. @@ -34,18 +36,20 @@ You’ll need to redefine your environmental variables after each time you close The `serverless deploy` command deploys your entire service via the Cloudflare Workers API. Run this command when you have made service changes (i.e., you edited `serverless.yml`). Use `serverless deploy -f my-function` when you have made code changes and you want to quickly upload your updated code to Cloudflare. - + ```bash serverless deploy ``` This is the simplest deployment usage possible. With this command, Serverless will deploy your service to Cloudflare. - + ## Options + - `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--verbose` or `-v`: Shows all stack events during deployment, and display any Stack Output. - `--function` or `-f`: Invokes `deploy function` (see above). Convenience shortcut ## Provided lifecycle events + - `deploy:deploy` - `deploy:function:deploy` diff --git a/docs/providers/cloudflare/cli-reference/invoke.md b/docs/providers/cloudflare/cli-reference/invoke.md index fc029c205..170302dbc 100644 --- a/docs/providers/cloudflare/cli-reference/invoke.md +++ b/docs/providers/cloudflare/cli-reference/invoke.md @@ -7,13 +7,15 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare-workers/cli-reference/invoke) + - # Cloudflare Workers - Invoke + Invokes a deployed function. It allows you to send an event to a deployed function, which can be useful for testing. Cloudflare Workers only support `GET` requests for now. The optional `headers` field allows you to specify headers that will be sent to your Worker along with your request. - + ```bash serverless invoke --function functionName ``` @@ -22,7 +24,7 @@ In the following example, you could run: ```bash serverless invoke --function helloWorld -``` +``` ```yml # serverless.yml @@ -43,23 +45,27 @@ functions: ``` ## Options -* `--function` or `-f` The name of the function in your service that you want to invoke. Required. -* `--data` or `-d` String data to be passed as an event to your function. By default data is read from standard input. -* `--path` or `-p` The path to a json file with input data to be passed to the invoked function. This path is relative to the root directory of the service. + +- `--function` or `-f` The name of the function in your service that you want to invoke. Required. +- `--data` or `-d` String data to be passed as an event to your function. By default data is read from standard input. +- `--path` or `-p` The path to a json file with input data to be passed to the invoked function. This path is relative to the root directory of the service. ## Provided lifecycle events + - `invoke:invoke` ## Examples ### Cloudflare Workers + ```bash serverless invoke --function functionName ``` This example will invoke your deployed function on the configured Cloudflare Workers API URL endpoint. This will output the result of the request in your terminal. - + #### Function invocation with data + ```bash serverless invoke --function functionName -``` \ No newline at end of file +``` diff --git a/docs/providers/cloudflare/cli-reference/plugin-install.md b/docs/providers/cloudflare/cli-reference/plugin-install.md index 0bb19f137..95fbcfe48 100644 --- a/docs/providers/cloudflare/cli-reference/plugin-install.md +++ b/docs/providers/cloudflare/cli-reference/plugin-install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/cli-reference/plugin-install) + # Plugin Install @@ -22,9 +24,11 @@ serverless plugin install --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:install:install` ## Examples diff --git a/docs/providers/cloudflare/cli-reference/plugin-list.md b/docs/providers/cloudflare/cli-reference/plugin-list.md index c7b74d804..09536b034 100644 --- a/docs/providers/cloudflare/cli-reference/plugin-list.md +++ b/docs/providers/cloudflare/cli-reference/plugin-list.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/cli-reference/plugin-list) + # Plugin List @@ -19,7 +21,9 @@ serverless plugin list ``` ## Options -- *None* + +- _None_ ## Provided lifecycle events + - `plugin:list:list` diff --git a/docs/providers/cloudflare/cli-reference/plugin-search.md b/docs/providers/cloudflare/cli-reference/plugin-search.md index 4f793f035..ff49eadc8 100644 --- a/docs/providers/cloudflare/cli-reference/plugin-search.md +++ b/docs/providers/cloudflare/cli-reference/plugin-search.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/cli-reference/plugin-search) + # Plugin Search @@ -19,9 +21,11 @@ serverless plugin search --query query ``` ## Options + - `--query` or `-q` The query you want to use for your search. **Required**. ## Provided lifecycle events + - `plugin:search:search` ## Examples diff --git a/docs/providers/cloudflare/cli-reference/plugin-uninstall.md b/docs/providers/cloudflare/cli-reference/plugin-uninstall.md index 74852fb6b..a1288d470 100644 --- a/docs/providers/cloudflare/cli-reference/plugin-uninstall.md +++ b/docs/providers/cloudflare/cli-reference/plugin-uninstall.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/cli-reference/plugin-uninstall) + # Plugin Uninstall @@ -19,9 +21,11 @@ serverless plugin uninstall --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:uninstall:uninstall` ## Examples diff --git a/docs/providers/cloudflare/cli-reference/remove.md b/docs/providers/cloudflare/cli-reference/remove.md index 505bc0df1..15d566627 100644 --- a/docs/providers/cloudflare/cli-reference/remove.md +++ b/docs/providers/cloudflare/cli-reference/remove.md @@ -7,15 +7,21 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/cli-reference/remove) + # Cloudflare Workers - Remove + The `serverless remove` command will remove the deployed service, defined in your current working directory, from the provider. - + ```bash serverless remove ``` + It will remove the Cloudflare Worker functions from the Cloudflare. + ## Provided lifecycle events + - `remove:remove` diff --git a/docs/providers/cloudflare/events/README.md b/docs/providers/cloudflare/events/README.md index 09b25c7a8..9d103379a 100644 --- a/docs/providers/cloudflare/events/README.md +++ b/docs/providers/cloudflare/events/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/events/) + # Serverless Cloudflare Workers Events @@ -15,4 +17,3 @@ Welcome to the Serverless Cloudflare Workers Events Glossary! Please select a section on the left to get started. If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/) - diff --git a/docs/providers/cloudflare/events/http.md b/docs/providers/cloudflare/events/http.md index e618a0f4e..ecff7e8ab 100644 --- a/docs/providers/cloudflare/events/http.md +++ b/docs/providers/cloudflare/events/http.md @@ -5,20 +5,22 @@ menuOrder: 1 description: HTTP Events in Cloudflare Workers layout: Doc --> - + + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/events/http) + - # Cloudflare Workers - HTTP Events - + ## Serverless Yml -When creating a service your serverless yml will define which endpoint is used for your function and when you run the [`serverless invoke`](../cli-reference/invoke.md) command. - + +When creating a service your serverless yml will define which endpoint is used for your function and when you run the [`serverless invoke`](../cli-reference/invoke.md) command. + ```yml # serverless.yml -... +--- functions: helloWorld: # What the script will be called on Cloudflare (this property value must match the function name one line above) @@ -33,4 +35,4 @@ functions: greeting: hi ``` -The events section in the yml above makes it so that the Function helloWorld will be used for request to the `example.com/hello/user` endpoint. This configuration would send a GET request with a header called `greeting` that has a value of `hi` to the `example.com/hello/user` endpoint when you run `serverless invoke -f helloWorld`. \ No newline at end of file +The events section in the yml above makes it so that the Function helloWorld will be used for request to the `example.com/hello/user` endpoint. This configuration would send a GET request with a header called `greeting` that has a value of `hi` to the `example.com/hello/user` endpoint when you run `serverless invoke -f helloWorld`. diff --git a/docs/providers/cloudflare/guide/README.md b/docs/providers/cloudflare/guide/README.md index bf10653c8..de54e8a50 100644 --- a/docs/providers/cloudflare/guide/README.md +++ b/docs/providers/cloudflare/guide/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/) + # Serverless Cloudflare Workers Guide diff --git a/docs/providers/cloudflare/guide/debugging.md b/docs/providers/cloudflare/guide/debugging.md index 99d917538..5c4406434 100644 --- a/docs/providers/cloudflare/guide/debugging.md +++ b/docs/providers/cloudflare/guide/debugging.md @@ -7,29 +7,32 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/debugging) + # Cloudflare Workers - Debugging + How can we debug errors in our Cloudflare Workers functions? - + Let's imagine that we have deployed the following code as a Cloudflare Worker function using Serverless: - + ```javascript addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request)) - }) - async function handleRequest(request) { - const answer = request.headers.get("greeting") || "hello" - return new Response(answer + " world") - } - + event.respondWith(handleRequest(event.request)); +}); +async function handleRequest(request) { + const answer = request.headers.get('greeting') || 'hello'; + return new Response(answer + ' world'); +} ``` + And its corresponding Serverless yml file: - + ```yml # serverless.yml -... +--- functions: helloWorld: # What the script will be called on Cloudflare (this property value must match the function name one line above) @@ -46,7 +49,7 @@ functions: ``` Let's invoke correctly that function - + ```bash serverless invoke --function helloWorld @@ -54,9 +57,8 @@ serverless invoke --function helloWorld hi world ``` - If we were to call the above function without any headers, you would get `hello world` back instead of `hi world`, so we know that our worker is properly reading the greeting header. - + ## Cloudflare Workers Playground - + Cloudflare Workers also have a [Playground](https://cloudflareworkers.com/#) you can use to modify a Cloudflare Worker and see the results live on the same screen. The Cloudflare Workers Playground is another great way to debug your worker. diff --git a/docs/providers/cloudflare/guide/deploying.md b/docs/providers/cloudflare/guide/deploying.md index f0e528c6d..b2864272c 100644 --- a/docs/providers/cloudflare/guide/deploying.md +++ b/docs/providers/cloudflare/guide/deploying.md @@ -5,18 +5,21 @@ menuOrder: 7 description: How to deploy your Cloudflare Workers functions and their required infrastructure layout: Doc --> - + + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/deploying) + # Cloudflare Workers - Deploying + The Serverless Framework was designed to provision your Cloudflare Workers Functions and Events. It does this via a couple of methods designed for different types of deployments. - + ## prerequisites - + In order to deploy your Cloudflare Worker, you need to set your Cloudflare email as an environmental variable called `CLOUDFLARE_AUTH_EMAIL`, and your Cloudflare Global API Key as an environmental variable called `CLOUDFLARE_AUTH_KEY`. You will also need to set `accountId` and `zoneId` in `serverless.yml` under `service.config`. The first part of the path when you open [Cloudflare dashboard](https://dash.cloudflare.com/) as a logged in user is your `accountId`, e.g. `dash.cloudflare.com/{accountId}`. And the `zoneId` can be found from the overview tab after selecting the desired zone from the [Cloudflare dashboard](https://dash.cloudflare.com/). - + Environmental variables are variables that live inside your terminal. For Mac and Linux users, you can set environmental variables like this: @@ -34,35 +37,35 @@ set CLOUDFLARE_AUTH_EMAIL=YOUR_CLOUDFLARE_EMAIL ``` You’ll need to redefine your environmental variables after each time you close your terminal. - - ## Deploy All + This is the main method for doing deployments with the Serverless Framework: - + ```bash serverless deploy ``` Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to your Cloudflare Worker. If you've made changes to any of your routes since last deploying, the Serverless Framework will update them on the server for you. -**Note:** You can specify a different configuration file name with the the `--config` option. +**Note:** You can specify a different configuration file name with the the `--config` option. ### How It Works + The Serverless Framework reads in `serverless.yml` and uses it to provision your Functions. - -For each defined Function in your `serverless.yml` file, the Framework will create a Cloudflare Workers script. This means that only enterprise customers can declare more than one Function, but anyone can use [webpack](https://developers.cloudflare.com/workers/writing-workers/using-npm-modules/) to package their application into a single script. - + +For each defined Function in your `serverless.yml` file, the Framework will create a Cloudflare Workers script. This means that only enterprise customers can declare more than one Function, but anyone can use [webpack](https://developers.cloudflare.com/workers/writing-workers/using-npm-modules/) to package their application into a single script. + For example, let's take the following example `serverless.yml` file: - + ```yml # serverless.yml service: - name: hello-world - config: - accountId: CLOUDFLARE_ACCOUNT_ID - zoneId: CLOUDFLARE_ZONE_ID + name: hello-world + config: + accountId: CLOUDFLARE_ACCOUNT_ID + zoneId: CLOUDFLARE_ZONE_ID provider: name: cloudflare @@ -84,7 +87,6 @@ functions: headers: greeting: hi - # Only Enterprise accounts would be allowed to add this second function foo: name: foo @@ -96,10 +98,11 @@ functions: ``` After deploying that file, you’ll be able to hit the specified top-level routes of your zone, `example.com/hello/*` and `example.com/foo/*`, and any endpoints that resolve to these routes, like `example.com/hello/user` and `example.com/foo/bar`. - + ## Deploy Function + This deployment method updates or deploys a single function. It performs the platform API call to deploy your package without the other resources. It is much faster than re-deploying your whole service each time. - + ```bash serverless deploy --function myFunction ``` @@ -107,4 +110,5 @@ serverless deploy --function myFunction If you've made changes to the routes corresponding to this Function since last deploying, the Serverless Framework will update them on the server for you. ### Tips + Check out the [deploy command docs](../cli-reference/deploy.md) for all details and options. diff --git a/docs/providers/cloudflare/guide/events.md b/docs/providers/cloudflare/guide/events.md index e10ccfa64..a6bed643a 100644 --- a/docs/providers/cloudflare/guide/events.md +++ b/docs/providers/cloudflare/guide/events.md @@ -7,19 +7,21 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/events) + - # Cloudflare Workers - Events + Simply put, events are the things that trigger your functions to run. - + If you are using Cloudflare Workers as your provider, all `events` in the service are HTTP Events, because that is the only event that Cloudflare Workers currently support. - + ```yml # serverless.yml … - + functions: helloWorld: # What the script will be called on Cloudflare (this property value must match the function name one line above) @@ -51,12 +53,12 @@ addEventListener('fetch', event => { }); import hello from './includeMe'; - + async function handleRequest(request) { return new Response(hello.hello()) } ``` If your handler script looks like the above, the includeMe script will be packed into the final script on deployment. - + [View the Cloudflare Workers events section for more information on HTTP events](../events). diff --git a/docs/providers/cloudflare/guide/functions.md b/docs/providers/cloudflare/guide/functions.md index 9f9a8f6ca..b9fc1eea3 100644 --- a/docs/providers/cloudflare/guide/functions.md +++ b/docs/providers/cloudflare/guide/functions.md @@ -5,30 +5,32 @@ menuOrder: 5 description: How to configure Cloudflare Workers functions in the Serverless Framework layout: Doc --> - + + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/functions) + - + # Cloudflare Workers - Functions - -If you are using Cloudflare as a provider, all *functions* inside the service are Cloudflare Workers. - + +If you are using Cloudflare as a provider, all _functions_ inside the service are Cloudflare Workers. + ## Configuration - + All of the Cloudflare Workers in your serverless service can be found in `serverless.yml` under the `functions` property. - + ```yml # serverless.yml - + service: - name: hello-world + name: hello-world provider: name: cloudflare config: - accountId: CLOUDFLARE_ACCOUNT_ID - zoneId: CLOUDFLARE_ZONE_ID + accountId: CLOUDFLARE_ACCOUNT_ID + zoneId: CLOUDFLARE_ZONE_ID plugins: - serverless-cloudflare-workers @@ -47,34 +49,34 @@ functions: headers: someKey: someValue ``` - + The `script` property points to the file containing your Cloudflare Worker. - + ```javascript // helloWorld.js - + addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request)) - }) - - async function handleRequest(request) { - return new Response("Hello world") - } + event.respondWith(handleRequest(event.request)); +}); + +async function handleRequest(request) { + return new Response('Hello world'); +} ``` - + If you have an Enterprise Cloudflare account, you can add multiple Cloudflare Workers to your project. - + ```yml # serverless.yml - + service: - name: hello-world + name: hello-world provider: name: cloudflare config: - accountId: CLOUDFLARE_ACCOUNT_ID - zoneId: CLOUDFLARE_ZONE_ID + accountId: CLOUDFLARE_ACCOUNT_ID + zoneId: CLOUDFLARE_ZONE_ID plugins: - serverless-cloudflare-workers @@ -93,7 +95,6 @@ functions: headers: someKey: someValue - # Only Enterprise accounts would be allowed to add this second function and its corresponding route above foo: name: foo @@ -103,5 +104,5 @@ functions: url: example.com/foo/* method: GET ``` + The `script` property is what the Cloudflare Worker will be called on Cloudflare’s data centers. - diff --git a/docs/providers/cloudflare/guide/installation.md b/docs/providers/cloudflare/guide/installation.md index 193f5867b..9baf7a82d 100644 --- a/docs/providers/cloudflare/guide/installation.md +++ b/docs/providers/cloudflare/guide/installation.md @@ -7,12 +7,15 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/installation) + # Cloudflare Workers - Installation - + ## Installing Cloudflare Workers + Cloudflare Workers don’t actually require any installation to run. However, You will need to set your Global API key from Cloudflare as an environmental variable named `CLOUDFLARE_AUTH_KEY`, and your Cloudflare account email as an environmental variable named `CLOUDFLARE_AUTH_EMAIL`. You can get your Global API key from your [Cloudflare profile](https://dash.cloudflare.com/profile) page. Environmental variables are variables that live inside your terminal. @@ -34,22 +37,23 @@ set CLOUDFLARE_AUTH_EMAIL=YOUR_CLOUDFLARE_EMAIL You’ll need to redefine your environmental variables after each time you close your terminal. ## Installing the Serverless Framework + Next, install the Serverless Framework via [npm](https://npmjs.org) which was already installed when you installed Node.js. - + Open up a terminal and type `npm install -g serverless` to install Serverless. - + ```bash npm install -g serverless ``` Once the installation process is done you can verify that Serverless is installed successfully by running the following command in your terminal: - + ```bash serverless ``` To see which version of serverless you have installed run: - + ```bash serverless --version ``` @@ -57,4 +61,5 @@ serverless --version Remember, you need at least version 1.31.0 to use Cloudflare Workers with Serverless. ## Installing the serverless-cloudflare-workers plugin -Finally, add our `serverless-cloudflare-workers` plugin to your project by running `npm install --save serverless-cloudflare-workers`. \ No newline at end of file + +Finally, add our `serverless-cloudflare-workers` plugin to your project by running `npm install --save serverless-cloudflare-workers`. diff --git a/docs/providers/cloudflare/guide/intro.md b/docs/providers/cloudflare/guide/intro.md index d38aa516d..5b3a58246 100644 --- a/docs/providers/cloudflare/guide/intro.md +++ b/docs/providers/cloudflare/guide/intro.md @@ -7,30 +7,32 @@ layout: Doc --> -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/intro) - +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/intro) + + # Cloudflare Workers - Introduction -The Serverless Framework helps you develop and deploy serverless applications using [Cloudflare Workers](https://www.cloudflare.com/products/cloudflare-workers/). It's a CLI that offers structure, automation, and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). [One config file](#serverlessyml) directs where exactly this Worker will live, so you can modify code and have it re-built and re-deployed in moments. No visits to the browser required. +The Serverless Framework helps you develop and deploy serverless applications using [Cloudflare Workers](https://www.cloudflare.com/products/cloudflare-workers/). It's a CLI that offers structure, automation, and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). [One config file](#serverlessyml) directs where exactly this Worker will live, so you can modify code and have it re-built and re-deployed in moments. No visits to the browser required. The Serverless Framework is different than other application frameworks because: -* It manages your code as well as your infrastructure -* It supports multiple languages + +- It manages your code as well as your infrastructure +- It supports multiple languages # Serverless.yml -The `serverless.yml` file is what molds the Worker(s) of your project. Using the [Serverless Cloudflare Workers plugin](https://github.com/cloudflare/serverless-cloudflare-workers), a `serverless.yml` will look like: +The `serverless.yml` file is what molds the Worker(s) of your project. Using the [Serverless Cloudflare Workers plugin](https://github.com/cloudflare/serverless-cloudflare-workers), a `serverless.yml` will look like: ```yml # serverless.yml service: - name: hello - webpack: true | PATH_TO_CONFIG - config: - accountId: ${env:CLOUDFLARE_ACCOUNT_ID} - zoneId: ${env:CLOUDFLARE_ZONE_ID} + name: hello + webpack: true | PATH_TO_CONFIG + config: + accountId: ${env:CLOUDFLARE_ACCOUNT_ID} + zoneId: ${env:CLOUDFLARE_ZONE_ID} provider: name: cloudflare @@ -43,15 +45,15 @@ functions: .. #### Services -A **Service** is the Serverless Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the routes they will live on, all in one file entitled `serverless.yml`: +A **Service** is the Serverless Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the routes they will live on, all in one file entitled `serverless.yml`: ```yml # serverless.yml service: - name: hello - config: - accountId: ${env:CLOUDFLARE_ACCOUNT_ID} - zoneId: ${env:CLOUDFLARE_ZONE_ID} + name: hello + config: + accountId: ${env:CLOUDFLARE_ACCOUNT_ID} + zoneId: ${env:CLOUDFLARE_ZONE_ID} provider: name: cloudflare @@ -66,15 +68,15 @@ functions: .. `config`: -- `accountId`: the account that *owns* the zone that you wish to deploy Workers too. Note: this may not be the account ID you are signed in as, but will be the account ID you see in the URL once you've selected the zone +- `accountId`: the account that _owns_ the zone that you wish to deploy Workers too. Note: this may not be the account ID you are signed in as, but will be the account ID you see in the URL once you've selected the zone - `zoneId`: the zone desired to deploy Workers to To find your zoneId and accountId, please see [API documentation on resource IDs](https://api.cloudflare.com/#getting-started-resource-ids) -#### Provider +#### Provider -A Provider tells the serverless frame what cloud provider you are using, in this case Cloudflare. +A Provider tells the serverless frame what cloud provider you are using, in this case Cloudflare. ``` provider: @@ -94,31 +96,31 @@ provider: A Function is a Cloudflare Worker - a single script including its bindings, routes and other config. It's an independent unit of deployment, like a microservice. It's merely code, deployed on Cloudflare’s 155+ PoPs points of presence, that is most often written to perform a single job as a Worker script. - `serverless.yml`: +`serverless.yml`: ```yml - functions: - bar: - name: scriptName - script: filename - webpack: true - environment: - some_key: - resources: ... - events: ... +functions: + bar: + name: scriptName + script: filename + webpack: true + environment: + some_key: + resources: ... + events: ... ``` `name`: overwrite the default name generated (e.g. replaces [`hello-foo-bar`](#name)) for the Worker script name -`script`: the path to the script from the current directory omitting the extension `.js` +`script`: the path to the script from the current directory omitting the extension `.js` -`webpack`(*optional*): specifies what webpack operation to perform on this individual Worker script. See webpack +`webpack`(_optional_): specifies what webpack operation to perform on this individual Worker script. See webpack -`environment`(*optional*) : any environment variables set as a global inside the script. See more in [Environment](#environment) +`environment`(_optional_) : any environment variables set as a global inside the script. See more in [Environment](#environment) -`resources`(*optional*) : see Resources below +`resources`(_optional_) : see Resources below -`events`(*optional*) : Any routing for a Worker is configured here. See Events below +`events`(_optional_) : Any routing for a Worker is configured here. See Events below ##### Webpack @@ -140,7 +142,7 @@ To get this working in your worker project, simply add `webpack: true | ` will deploy your worker and run the HTTP request(s) specified by the `url` and `method` against this deployed worker. This is useful for defining specific hooks into your application for testing. To truly test your worker, you can run [`cURL`](https://curl.haxx.se/) against your domain since the Worker will be deployed. +`serverless invoke ` will deploy your worker and run the HTTP request(s) specified by the `url` and `method` against this deployed worker. This is useful for defining specific hooks into your application for testing. To truly test your worker, you can run [`cURL`](https://curl.haxx.se/) against your domain since the Worker will be deployed. ##### name @@ -196,4 +198,4 @@ plugins: You can add our `serverless-cloudflare-workers` plugin to your project by running `npm install --save serverless-cloudflare-workers`. -* *`workers.dev` domains are not currently supported using Serverless, but you can track our progress on [this Github issue](https://github.com/cloudflare/serverless-cloudflare-workers/issues/36).* +- _`workers.dev` domains are not currently supported using Serverless, but you can track our progress on [this Github issue](https://github.com/cloudflare/serverless-cloudflare-workers/issues/36)._ diff --git a/docs/providers/cloudflare/guide/quick-start.md b/docs/providers/cloudflare/guide/quick-start.md index 964e6173f..458be2c94 100644 --- a/docs/providers/cloudflare/guide/quick-start.md +++ b/docs/providers/cloudflare/guide/quick-start.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/quick-start) + # Cloudflare Workers - Quickstart @@ -16,15 +18,17 @@ layout: Doc This guide is a walk through of using the Serverless Plugin to deploy Cloudflare Workers to a zone already proxied on Cloudflare. -*Note:`workers.dev` domains are not currently supported using Serverless, but you can track our progress on [this Github issue](https://github.com/cloudflare/serverless-cloudflare-workers/issues/36).* +_Note:`workers.dev` domains are not currently supported using Serverless, but you can track our progress on [this Github issue](https://github.com/cloudflare/serverless-cloudflare-workers/issues/36)._ ## Pre-requisites + Node.js `v10.X` or later. Serverless CLI `v1.31.0` or later. You can run `npm install -g serverless` to install it. you also need our `serverless-cloudflare-workers` plugin. You can install it in your project with `npm install --save serverless-cloudflare-workers`. ## Create a new service To create a new service, you can use the `cloudflare-workers` template. Optionally specify a unique name and an optional path for your service. + ```bash # Create a new Serverless Service/Project $ serverless create --template cloudflare-workers --path new-project @@ -34,29 +38,29 @@ $ cd new-project $ npm install ``` -# Setup +# Setup ### Config -To deploy, you will need either the environment variables set or manually input the `accountId` and `zoneId` in your `serverless.yml` according to the *zone* you wish the Worker(s) to deploy to. +To deploy, you will need either the environment variables set or manually input the `accountId` and `zoneId` in your `serverless.yml` according to the _zone_ you wish the Worker(s) to deploy to. ```yaml # serverless.yml service: - name: hello - config: - accountId: ${env:CLOUDFLARE_ACCOUNT_ID} - zoneId: ${env:CLOUDFLARE_ZONE_ID} - functions: - functionName: - worker: scriptName - script: filename - events: ... + name: hello + config: + accountId: ${env:CLOUDFLARE_ACCOUNT_ID} + zoneId: ${env:CLOUDFLARE_ZONE_ID} + functions: + functionName: + worker: scriptName + script: filename + events: ... ``` Configure the [functions](#function) according to your specific routing and naming conventions or leave `functions` as is from what the template generated. When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` will be deployed at once. -### Environment Variables +### Environment Variables You will need to set your Global API key from Cloudflare as an environmental variable named `CLOUDFLARE_AUTH_KEY`, and your Cloudflare account email as an environmental variable named `CLOUDFLARE_AUTH_EMAIL`. See: [How to find your API keys](https://support.cloudflare.com/hc/en-us/articles/200167836) @@ -107,7 +111,7 @@ async function helloWorld(request) { } ``` -*Note: Serverless plugin omits the extension `.js` in the `serverless.yml` file when referring to what script to run* +_Note: Serverless plugin omits the extension `.js` in the `serverless.yml` file when referring to what script to run_ ## Deploy, test and diagnose your service @@ -139,23 +143,23 @@ serverless invoke --function helloWorld Hello world ``` -Your Function must have the `events` field populated in order for the `serverless` tool to know exactly which route to request. +Your Function must have the `events` field populated in order for the `serverless` tool to know exactly which route to request. ```yml # serverless.yml -... +--- foo: - name: foo - script: bar - events: - - http: - url: example.com/foo/bar - # Defines the method used by serverless when the `invoke` command is used. Cloudflare Workers only support GET requests for now - method: GET + name: foo + script: bar + events: + - http: + url: example.com/foo/bar + # Defines the method used by serverless when the `invoke` command is used. Cloudflare Workers only support GET requests for now + method: GET ``` - ## Cleanup + If at any point, you no longer need your service, you can run the following command to remove the Functions, Events and Resources that were created. ```bash diff --git a/docs/providers/cloudflare/guide/services.md b/docs/providers/cloudflare/guide/services.md index 315cf8305..1aa44617d 100644 --- a/docs/providers/cloudflare/guide/services.md +++ b/docs/providers/cloudflare/guide/services.md @@ -5,28 +5,30 @@ menuOrder: 4 description: How to manage and configure Serverless services, which contain your Cloudflare Workers and their events. layout: Doc --> - + + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/services) + - + # Cloudflare Workers - Services - + A `service` is like a project. It's where you define your Cloudflare Workers and the `events` you test them with, all in a file called `serverless.yml`. - + To get started building your first Serverless Framework project, create a `service`. - + ## Organization - + In the beginning of an application created by a team with an Enterprise Cloudflare account, and for the lifespan of an application made by a team with a Non-Enterprise Cloudflare account, we recommend you use a single Service to define all of the Functions and Events for that project. - + ```bash myService/ serverless.yml # Contains all functions and infrastructure resources ``` - -However, as your application grows as an Enterprise Cloudflare user, you can break it out into multiple services. A lot of people organize their services by workflows or data models, and group the functions related to those workflows and data models together in the service. - + +However, as your application grows as an Enterprise Cloudflare user, you can break it out into multiple services. A lot of people organize their services by workflows or data models, and group the functions related to those workflows and data models together in the service. + ```bash users/ serverless.yml # Contains 4 functions that do Users CRUD operations and the Users database @@ -35,57 +37,57 @@ posts/ comments/ serverless.yml # Contains 4 functions that do Comments CRUD operations and the Comments database ``` - + This makes sense since related functions usually use common infrastructure resources, and you want to keep those functions and resources together as a single unit of deployment, for better organization and separation of concerns. - + ## Creation - + To create a service, use the `create` command. You can also pass in a path to create a directory and auto-name your service: - + ```bash # Create service with cloudflare-workers template in the folder ./my-service serverless create --template cloudflare-workers --path my-service ``` - + Here are the available runtimes for Cloudflare Workers: - -* cloudflare-workers -* cloudflare-workers-enterprise -* cloudflare-workers-rust - + +- cloudflare-workers +- cloudflare-workers-enterprise +- cloudflare-workers-rust + Check out the [create command docs](../cli-reference/create) for all the details and options. - + ## Contents - + You'll see the following files in your working directory: - + - `serverless.yml` - `helloWorld.js` - + ### serverless.yml - + Each `service` configuration is managed in the `serverless.yml` file. The main responsibilities of this file are: - + - Declare a Serverless service - Define one or more functions in the service - - Define the provider the service will be deployed to - - Define any custom plugins to be used - - Define events that trigger each function to execute (e.g. HTTP requests) - - Allow events listed in the `events` section to automatically create the resources required for the `serverless invoke` command - +- Define the provider the service will be deployed to +- Define any custom plugins to be used +- Define events that trigger each function to execute (e.g. HTTP requests) +- Allow events listed in the `events` section to automatically create the resources required for the `serverless invoke` command + You can see the name of the service, the provider configuration and the first function inside the `functions` definition. Any further service configuration will be done in this file. - + ```yml # serverless.yml - + service: - name: hello-world + name: hello-world provider: name: cloudflare config: - accountId: CLOUDFLARE_ACCOUNT_ID - zoneId: CLOUDFLARE_ZONE_ID + accountId: CLOUDFLARE_ACCOUNT_ID + zoneId: CLOUDFLARE_ZONE_ID plugins: - serverless-cloudflare-workers @@ -104,7 +106,6 @@ functions: headers: someKey: someValue - # Only Enterprise accounts would be allowed to add this second function and its corresponding route above foo: name: foo @@ -113,84 +114,83 @@ functions: - http: url: example.com/foo/bar method: GET - ``` - + ### helloWorld.js - + The `helloWorld.js` file contains a barebones Cloudflare Worker that returns ‘hello world’. - + ## Deployment - + When you deploy a Service, all of the Functions, and Events in your `serverless.yml` are translated into calls to Cloudflare to create your Cloudflare Worker(s). - + To deploy a service, first `cd` into the relevant service directory: - + ```bash cd my-service ``` - + Then use the `deploy` command: - + ```bash serverless deploy ``` - -Check out the [deployment guide](./deploying.md) to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy.md) for all the details and options. - + +Check out the [deployment guide](./deploying.md) to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy.md) for all the details and options. + ## Removal - + To easily remove your Service from Cloudflare’s data centers, you can use the `remove` command. - + Run `serverless remove` to trigger the removal process. - + Serverless will start the removal and informs you about it's process on the console. A success message is printed once the whole service is removed. - + The removal process will only remove the service on your provider's infrastructure. The service directory will still remain on your local machine so you can still modify and (re)deploy it to another stage, region or provider later on. - + ## Version Pinning - + The Serverless framework is usually installed globally via `npm install -g serverless`. This way you have the Serverless CLI available for all your services. - + Installing tools globally has the downside that the version can't be pinned inside package.json. This can lead to issues if you upgrade Serverless, but your colleagues or CI system don't. You can use a feature in your serverless.yml without worrying that your CI system will deploy with an old version of Serverless. - + ### Pinning a Version - + To configure version pinning define a `frameworkVersion` property in your serverless.yaml. Whenever you run a Serverless command from the CLI it checks if your current Serverless version is matching the `frameworkVersion` range. The CLI uses [Semantic Versioning](http://semver.org/) so you can pin it to an exact version or provide a range. In general we recommend to pin to an exact version to ensure everybody in your team has the exact same setup and no unexpected problems happen. - + ### Examples - + #### Exact Version - + ```yml # serverless.yml - -frameworkVersion: "=1.0.3" + +frameworkVersion: '=1.0.3' ``` - + #### Version Range - + ```yml # serverless.yml - -frameworkVersion: ">=1.0.0 <2.0.0" + +frameworkVersion: '>=1.0.0 <2.0.0' ``` - - + ## Installing Serverless in an existing service - + If you already have a Serverless service, and would prefer to lock down the framework version using `package.json`, then you can install Serverless as follows: - + ```bash # from within a service npm install serverless --save-dev ``` - + ### Invoking Serverless locally - + To execute the locally installed Serverless executable you have to reference the binary out of the node modules directory. - + Example: + ``` node ./node_modules/serverless/bin/serverless deploy ``` diff --git a/docs/providers/cloudflare/guide/workflow.md b/docs/providers/cloudflare/guide/workflow.md index c6dda1f8d..ade5cc0b7 100644 --- a/docs/providers/cloudflare/guide/workflow.md +++ b/docs/providers/cloudflare/guide/workflow.md @@ -7,52 +7,61 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/cloudflare/guide/workflow) + - # Cloudflare Workers - Workflow + Generally, Cloudflare Workers can be written locally, deployed with serverless, and tested with the [`serverless invoke`](../cli-reference/invoke.md) command. However, using the [Cloudflare Workers Playground](https://cloudflareworkers.com/#) can help you test and view your worker’s results live if you need more insight while developing your Cloudflare Worker. - + Below is a list of general tips for developing Cloudflare Workers with Serverless. - + ### Development Workflow + Write your functions Use `serverless deploy` only when you've made changes to `serverless.yml` and in CI/CD systems. Use `serverless deploy -f myFunction` to rapidly deploy changes when you are working on a specific Cloudflare Workers Function. Use `serverless invoke -f myFunction` to test your Cloudflare Workers Functions. + ### Larger Projects -* For Non-Enterprise Cloudflare customers, combining multiple workers into one file or using [webpack](https://developers.cloudflare.com/workers/writing-workers/using-npm-modules/). -* Keep the Functions and Resources in your Serverless Services to a minimum. + +- For Non-Enterprise Cloudflare customers, combining multiple workers into one file or using [webpack](https://developers.cloudflare.com/workers/writing-workers/using-npm-modules/). +- Keep the Functions and Resources in your Serverless Services to a minimum. + ## Cheat Sheet + A handy list of commands to use when developing with the Serverless Framework. - + ##### Create A Service: + Creates a new Service: - + ```bash serverless create -p [SERVICE NAME] -t cloudflare-workers ``` ##### Deploy All + Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. - + ```bash serverless deploy ``` - ##### Deploy Function + Use this to quickly overwrite your Cloudflare Workers Functions, allowing you to develop faster if you have an Enterprise account that supports deploying multiple functions. - + ```bash serverless deploy -f [FUNCTION NAME] ``` - ##### Invoke Function + Invokes a Cloudflare Workers Function. - + ```bash serverless invoke -f [FUNCTION NAME] ``` diff --git a/docs/providers/fn/README.md b/docs/providers/fn/README.md index c263b37f2..77ded9d43 100644 --- a/docs/providers/fn/README.md +++ b/docs/providers/fn/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/) + # Fn Provider Documentation diff --git a/docs/providers/fn/cli-reference/README.md b/docs/providers/fn/cli-reference/README.md index 100b43b03..536372d02 100644 --- a/docs/providers/fn/cli-reference/README.md +++ b/docs/providers/fn/cli-reference/README.md @@ -5,11 +5,13 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/) + # Serverless Fn CLI Reference -Welcome to the Serverless Fn CLI Reference! Please select a section on the left to get started. +Welcome to the Serverless Fn CLI Reference! Please select a section on the left to get started. If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/). diff --git a/docs/providers/fn/cli-reference/create.md b/docs/providers/fn/cli-reference/create.md index ebe93c90b..dc89079ba 100644 --- a/docs/providers/fn/cli-reference/create.md +++ b/docs/providers/fn/cli-reference/create.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/create) + # Fn - Create @@ -35,6 +37,7 @@ serverless create --template fn-nodejs --path my-service ``` ## Options + - `--template` or `-t` The name of one of the available templates. **Required if --template-url and --template-path are not present**. - `--template-url` or `-u` The name of one of the available templates. **Required if --template and --template-path are not present**. - `--template-path` The local path of your template. **Required if --template and --template-url are not present**. @@ -42,6 +45,7 @@ serverless create --template fn-nodejs --path my-service - `--name` or `-n` the name of the service in `serverless.yml`. ## Provided lifecycle events + - `create:create` ## Available Templates for Fn diff --git a/docs/providers/fn/cli-reference/deploy.md b/docs/providers/fn/cli-reference/deploy.md index 43efd9c3a..1a558f9ec 100644 --- a/docs/providers/fn/cli-reference/deploy.md +++ b/docs/providers/fn/cli-reference/deploy.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/deploy) + # Fn - Deploy @@ -23,10 +25,12 @@ serverless deploy This is the simplest deployment usage possible. With this command Serverless will deploy your service to the configured Fn server. ## Options + - `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. - `--function` or `-f` Invoke `deploy function` (see above). Convenience shortcut ## Provided lifecycle events + - `deploy:deploy` - `deploy:function:deploy` diff --git a/docs/providers/fn/cli-reference/info.md b/docs/providers/fn/cli-reference/info.md index 8288ce57c..c2b86fc12 100644 --- a/docs/providers/fn/cli-reference/info.md +++ b/docs/providers/fn/cli-reference/info.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/info) + # Fn - Info @@ -19,6 +21,7 @@ serverless info ``` ## Provided lifecycle events + - `info:info` ## Examples diff --git a/docs/providers/fn/cli-reference/invoke.md b/docs/providers/fn/cli-reference/invoke.md index a3bfdf030..89fdf3d61 100644 --- a/docs/providers/fn/cli-reference/invoke.md +++ b/docs/providers/fn/cli-reference/invoke.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/invoke) + # Fn - Invoke @@ -19,12 +21,14 @@ serverless invoke --function functionName ``` ## Options + - `--function` or `-f` The name of the function in your service that you want to invoke. **Required**. - `--data` or `-d` String data to be passed as an event to your function. By default data is read from standard input. - `--path` or `-p` The path to a json file with input data to be passed to the invoked function. This path is relative to the root directory of the service. - `--log` or `-l` If set to `true`, it will output logging data of the invocation. Default is `false`. ## Provided lifecycle events + - `invoke:invoke` ## Examples diff --git a/docs/providers/fn/cli-reference/logs.md b/docs/providers/fn/cli-reference/logs.md index 19d89b80e..a90ed0fc2 100644 --- a/docs/providers/fn/cli-reference/logs.md +++ b/docs/providers/fn/cli-reference/logs.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/logs) + # Fn - Logs @@ -27,4 +29,5 @@ serverless logs -f hello ```bash serverless logs -f hello ``` + This will fetch the logs for hello for the most recent calls to it. diff --git a/docs/providers/fn/cli-reference/plugin-install.md b/docs/providers/fn/cli-reference/plugin-install.md index 432922f8a..6992e270b 100644 --- a/docs/providers/fn/cli-reference/plugin-install.md +++ b/docs/providers/fn/cli-reference/plugin-install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/plugin-install) + # Plugin Install @@ -22,9 +24,11 @@ serverless plugin install --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:install:install` ## Examples diff --git a/docs/providers/fn/cli-reference/plugin-list.md b/docs/providers/fn/cli-reference/plugin-list.md index d8282edbf..aff2b3888 100644 --- a/docs/providers/fn/cli-reference/plugin-list.md +++ b/docs/providers/fn/cli-reference/plugin-list.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/plugin-list) + # Plugin List @@ -19,7 +21,9 @@ serverless plugin list ``` ## Options -- *None* + +- _None_ ## Provided lifecycle events + - `plugin:list:list` diff --git a/docs/providers/fn/cli-reference/plugin-search.md b/docs/providers/fn/cli-reference/plugin-search.md index c73b944f9..68256016d 100644 --- a/docs/providers/fn/cli-reference/plugin-search.md +++ b/docs/providers/fn/cli-reference/plugin-search.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/plugin-search) + # Plugin Search @@ -19,9 +21,11 @@ serverless plugin search --query query ``` ## Options + - `--query` or `-q` The query you want to use for your search. **Required**. ## Provided lifecycle events + - `plugin:search:search` ## Examples diff --git a/docs/providers/fn/cli-reference/plugin-uninstall.md b/docs/providers/fn/cli-reference/plugin-uninstall.md index f19402e9b..18279bbea 100644 --- a/docs/providers/fn/cli-reference/plugin-uninstall.md +++ b/docs/providers/fn/cli-reference/plugin-uninstall.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/plugin-uninstall) + # Plugin Uninstall @@ -19,9 +21,11 @@ serverless plugin uninstall --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:uninstall:uninstall` ## Examples diff --git a/docs/providers/fn/cli-reference/remove.md b/docs/providers/fn/cli-reference/remove.md index 0738d220d..a45ea0e5b 100644 --- a/docs/providers/fn/cli-reference/remove.md +++ b/docs/providers/fn/cli-reference/remove.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/remove) + # Fn - Remove @@ -21,4 +23,5 @@ serverless remove It will remove the Fn Function functions from your Fn server. ## Provided lifecycle events + - `remove:remove` diff --git a/docs/providers/fn/events/README.md b/docs/providers/fn/events/README.md index d87c6c698..03c396f83 100644 --- a/docs/providers/fn/events/README.md +++ b/docs/providers/fn/events/README.md @@ -1,11 +1,13 @@ + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/events/) + # Serverless Fn Events @@ -15,4 +17,3 @@ Welcome to the Serverless Fn Events Glossary! Please select a section on the left to get started. If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/) - diff --git a/docs/providers/fn/events/http.md b/docs/providers/fn/events/http.md index 6d37b4ad9..886e41b28 100644 --- a/docs/providers/fn/events/http.md +++ b/docs/providers/fn/events/http.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/events/http) + # Fn HTTP Events @@ -29,8 +31,8 @@ functions: # Your "Functions" version: 0.0.1 runtime: go events: - - http: - path: /hello + - http: + path: /hello ``` The events section in the yaml above makes it so that the Function hi will be diff --git a/docs/providers/fn/guide/README.md b/docs/providers/fn/guide/README.md index 3a725c7c1..02e5be26b 100644 --- a/docs/providers/fn/guide/README.md +++ b/docs/providers/fn/guide/README.md @@ -1,11 +1,13 @@ + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/) + # Serverless Fn Guide diff --git a/docs/providers/fn/guide/debugging.md b/docs/providers/fn/guide/debugging.md index ba10ed4c6..09cd4c46a 100644 --- a/docs/providers/fn/guide/debugging.md +++ b/docs/providers/fn/guide/debugging.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/debugging) + # Fn - Debugging @@ -19,15 +21,15 @@ Let's imagine that we have deployed the following Nodejs code as a Fn function u ```javascript const fdk = require('@fnproject/fdk'); -fdk.handle((input) => { - input = JSON.parse(input) - let name = 'World'; - if (input.name) { - name = input.name; - } - const response = { message: `Hello ${name}` }; - console.error(`I show up in the logs name was: ${name}`); - return response; +fdk.handle(input => { + input = JSON.parse(input); + let name = 'World'; + if (input.name) { + name = input.name; + } + const response = { message: `Hello ${name}` }; + console.error(`I show up in the logs name was: ${name}`); + return response; }); ``` @@ -35,7 +37,7 @@ And its corresponding Serverless YAML file: ```yml service: - name: hello-world + name: hello-world provider: name: fn @@ -49,8 +51,8 @@ functions: runtime: node format: json events: - - http: - path: /hello + - http: + path: /hello ``` Let's invoke correctly that function diff --git a/docs/providers/fn/guide/deploying.md b/docs/providers/fn/guide/deploying.md index 45ba71290..30b2967e6 100644 --- a/docs/providers/fn/guide/deploying.md +++ b/docs/providers/fn/guide/deploying.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/deploying) + # Fn - Deploying -The Serverless Framework was designed to provision your Fn Functions and Events. It does this via a couple of methods designed for different types of deployments. +The Serverless Framework was designed to provision your Fn Functions and Events. It does this via a couple of methods designed for different types of deployments. ## Deploy All @@ -24,7 +26,7 @@ serverless deploy Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to your Fn cluster. -**Note:** You can specify a different configuration file name with the the `--config` option. +**Note:** You can specify a different configuration file name with the the `--config` option. ### How It Works @@ -35,7 +37,6 @@ For each function in your `serverless.yml` file, Fn will create an Fn Function. For example, let's take the following example `serverless.yml` file: ```yaml - service: hello-world functions: # Your "Functions" @@ -44,9 +45,8 @@ functions: # Your "Functions" version: 0.0.1 runtime: go events: - - http: - path: /hello - + - http: + path: /hello ``` When deploying that file FN will provide you with one endpoint that you can hit at: `FN_API_URL/r/hello-world/hello` diff --git a/docs/providers/fn/guide/events.md b/docs/providers/fn/guide/events.md index f9910e491..6b05a8c2e 100644 --- a/docs/providers/fn/guide/events.md +++ b/docs/providers/fn/guide/events.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/events) + # Fn - Events diff --git a/docs/providers/fn/guide/installation.md b/docs/providers/fn/guide/installation.md index d3803b8d0..97b895562 100644 --- a/docs/providers/fn/guide/installation.md +++ b/docs/providers/fn/guide/installation.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/installation) + # Fn - Installation diff --git a/docs/providers/fn/guide/intro.md b/docs/providers/fn/guide/intro.md index b9581a716..d01400dc0 100644 --- a/docs/providers/fn/guide/intro.md +++ b/docs/providers/fn/guide/intro.md @@ -7,16 +7,19 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/intro) + # Fn - Introduction -The Serverless Framework helps you develop and deploy serverless applications using Fn. It's a CLI that offers structure, automation and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). +The Serverless Framework helps you develop and deploy serverless applications using Fn. It's a CLI that offers structure, automation and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). The Serverless Framework is different than other application frameworks because: -* It manages your code as well as your infrastructure -* It supports multiple languages (Node.js, Python, Ruby, Go) + +- It manages your code as well as your infrastructure +- It supports multiple languages (Node.js, Python, Ruby, Go) ## Core Concepts @@ -24,25 +27,25 @@ Here are the Serverless Framework's main concepts and how they pertain to Fn. ### Functions -A Function is a [Fn Function](http://fnproject.io/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: +A Function is a [Fn Function](http://fnproject.io/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: -* *Saving a user to the database* -* *Processing a file in a database* -* *Performing a scheduled task* (To be added in newer versions) +- _Saving a user to the database_ +- _Processing a file in a database_ +- _Performing a scheduled task_ (To be added in newer versions) -You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. +You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. ### Events -Anything that triggers an Fn Event to execute is regarded by the Framework as an **Event**. Events are platform events on Fn such as: +Anything that triggers an Fn Event to execute is regarded by the Framework as an **Event**. Events are platform events on Fn such as: -* *An API Gateway HTTP endpoint (e.g., for a REST API)* -* *A Kafka queue message (e.g., a message)* -* *A scheduled timer (e.g., run every 5 minutes)* (To be added in newer versions) +- _An API Gateway HTTP endpoint (e.g., for a REST API)_ +- _A Kafka queue message (e.g., a message)_ +- _A scheduled timer (e.g., run every 5 minutes)_ (To be added in newer versions) ### Services -A **Service** is the Serverless Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the Events that trigger them, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Serverless Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the Events that trigger them, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml # serverless.yml @@ -55,8 +58,8 @@ functions: # Your "Functions" version: 0.0.1 runtime: go events: - - http: - path: /hello + - http: + path: /hello ``` When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` (or the file specified with the `--config` option) is deployed at once. diff --git a/docs/providers/fn/guide/quick-start.md b/docs/providers/fn/guide/quick-start.md index f5949e9d3..c7256f67c 100644 --- a/docs/providers/fn/guide/quick-start.md +++ b/docs/providers/fn/guide/quick-start.md @@ -12,7 +12,7 @@ layout: Doc 1. Node.js `v6.5.0` or later. 2. Serverless CLI `v1.20` or later. You can run -`npm install -g serverless` to install it. + `npm install -g serverless` to install it. 3. Install Fn & Dependencies(./installation.md). ## Create a new service @@ -32,37 +32,38 @@ $ npm install 1. **Deploy the Service** - Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. +Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. - ```bash - serverless deploy -v - ``` +```bash +serverless deploy -v +``` 2. **Deploy the Function** - Use this to quickly upload and overwrite your function code, allowing you to develop faster. +Use this to quickly upload and overwrite your function code, allowing you to develop faster. - ```bash - serverless deploy -f hello - ``` +```bash +serverless deploy -f hello +``` 3. **Invoke the Function** - Invokes the Function and returns results. +Invokes the Function and returns results. - ```bash - $ serverless invoke --function hello --data '{"name":"Bob"}' -l - Serverless: Calling Function: hello - { message: 'Hello Bob' } - I show up in the logs name was: Bob - ``` +```bash +$ serverless invoke --function hello --data '{"name":"Bob"}' -l +Serverless: Calling Function: hello +{ message: 'Hello Bob' } +I show up in the logs name was: Bob +``` 4. **Fetch the Function Logs** - Open up a separate tab in your console and view logs for a specific Function using this command. - ```bash - serverless logs -f hello - ``` +Open up a separate tab in your console and view logs for a specific Function using this command. + +```bash +serverless logs -f hello +``` ## Cleanup diff --git a/docs/providers/fn/guide/workflow.md b/docs/providers/fn/guide/workflow.md index 37feee16b..e229b67ca 100644 --- a/docs/providers/fn/guide/workflow.md +++ b/docs/providers/fn/guide/workflow.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/workflow) + # Fn - Workflow @@ -20,46 +22,58 @@ Intro. Quick recommendations and tips for various processes. 2. Use `serverless deploy` only when you've made changes to `serverless.yml` and in CI/CD systems. 3. Use `serverless deploy -f myFunction` to rapidly deploy changes when you are working on a specific Fn Function. 4. Use `serverless invoke -f myFunction -l` to test your Fn Functions. -6. Write tests to run locally. +5. Write tests to run locally. ### Larger Projects -* Break your application/project into multiple Serverless Services. -* Model your Serverless Services around Data Models or Workflows. -* Keep the Functions and Resources in your Serverless Services to a minimum. + +- Break your application/project into multiple Serverless Services. +- Model your Serverless Services around Data Models or Workflows. +- Keep the Functions and Resources in your Serverless Services to a minimum. ## Cheat Sheet + A handy list of commands to use when developing with the Serverless Framework. ##### Create A Service: + Creates a new Service ``` serverless create -p [SERVICE NAME] -t fn-nodejs ``` + ``` serverless create -p [SERVICE NAME] -t fn-go ``` ##### Deploy All + Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. + ``` serverless deploy ``` ##### Deploy Function + Use this to quickly overwrite your Fn Functions, allowing you to develop faster. + ``` serverless deploy -f [FUNCTION NAME] ``` ##### Invoke Function + Invokes an Fn Function and returns logs. + ``` serverless invoke -f [FUNCTION NAME] -l ``` ##### Streaming Logs + Open up a separate tab in your console and stream all logs for a specific Function using this command. + ``` serverless logs -f [FUNCTION NAME] ``` diff --git a/docs/providers/google/README.md b/docs/providers/google/README.md index 521bc3cf9..b7e64fa85 100644 --- a/docs/providers/google/README.md +++ b/docs/providers/google/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/) + # Serverless Google Cloud Functions Provider Documentation diff --git a/docs/providers/google/cli-reference/README.md b/docs/providers/google/cli-reference/README.md index 3b930b8ff..9597b4621 100644 --- a/docs/providers/google/cli-reference/README.md +++ b/docs/providers/google/cli-reference/README.md @@ -5,12 +5,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/) + # Serverless Google Cloud Functions CLI Reference -Welcome to the Serverless Google Cloud Functions CLI Reference! Please select a section on the left to get started. +Welcome to the Serverless Google Cloud Functions CLI Reference! Please select a section on the left to get started. If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/). diff --git a/docs/providers/google/cli-reference/create.md b/docs/providers/google/cli-reference/create.md index ded7d0eac..f7a7f12f2 100644 --- a/docs/providers/google/cli-reference/create.md +++ b/docs/providers/google/cli-reference/create.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/create) + # Google - Create diff --git a/docs/providers/google/cli-reference/deploy.md b/docs/providers/google/cli-reference/deploy.md index 7cc741b44..73757016d 100644 --- a/docs/providers/google/cli-reference/deploy.md +++ b/docs/providers/google/cli-reference/deploy.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/deploy) + # Deploy @@ -19,6 +21,7 @@ serverless deploy ``` ## Options + - `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. ## Artifacts diff --git a/docs/providers/google/cli-reference/info.md b/docs/providers/google/cli-reference/info.md index 37d24eaf0..75eebec85 100644 --- a/docs/providers/google/cli-reference/info.md +++ b/docs/providers/google/cli-reference/info.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/info) + # Google - Info diff --git a/docs/providers/google/cli-reference/install.md b/docs/providers/google/cli-reference/install.md index baa2ee4be..4a2cfda3c 100644 --- a/docs/providers/google/cli-reference/install.md +++ b/docs/providers/google/cli-reference/install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/install) + # Google - Install diff --git a/docs/providers/google/cli-reference/invoke-local.md b/docs/providers/google/cli-reference/invoke-local.md index 3d82f15ea..4dcc28c3d 100644 --- a/docs/providers/google/cli-reference/invoke-local.md +++ b/docs/providers/google/cli-reference/invoke-local.md @@ -22,13 +22,13 @@ serverless invoke local -f functionName ## Options -* `--function` or `-f` The name of the function in your service that you want to invoke. **Required**. -* `--data` or `-d` Data you want to pass into the function -* `--path` or `-p` Path to JSON or YAML file holding input data. This path is relative to the root directory of the service. -* `--raw` Pass data as a raw string even if it is JSON. If not set, JSON data are parsed and passed as an object. -* `--contextPath` or `-x`, The path to a json file holding input context to be passed to the invoked function. This path is relative to the root directory of the service. -* `--context` or `-c`, String data to be passed as a context to your function. Same like with `--data`, context included in `--contextPath` will overwrite the context you passed with `--context` flag. -* `--env` or `-e` String representing an environment variable to set when invoking your function, in the form `=`. Can be repeated for more than one environment variable. +- `--function` or `-f` The name of the function in your service that you want to invoke. **Required**. +- `--data` or `-d` Data you want to pass into the function +- `--path` or `-p` Path to JSON or YAML file holding input data. This path is relative to the root directory of the service. +- `--raw` Pass data as a raw string even if it is JSON. If not set, JSON data are parsed and passed as an object. +- `--contextPath` or `-x`, The path to a json file holding input context to be passed to the invoked function. This path is relative to the root directory of the service. +- `--context` or `-c`, String data to be passed as a context to your function. Same like with `--data`, context included in `--contextPath` will overwrite the context you passed with `--context` flag. +- `--env` or `-e` String representing an environment variable to set when invoking your function, in the form `=`. Can be repeated for more than one environment variable. > Keep in mind that if you pass both `--path` and `--data`, the data included in the `--path` file will overwrite the data you passed with the `--data` flag. diff --git a/docs/providers/google/cli-reference/invoke.md b/docs/providers/google/cli-reference/invoke.md index f49dacdb6..ec17e40fc 100644 --- a/docs/providers/google/cli-reference/invoke.md +++ b/docs/providers/google/cli-reference/invoke.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/invoke) + # Google - Invoke diff --git a/docs/providers/google/cli-reference/login.md b/docs/providers/google/cli-reference/login.md index 6097a15eb..a0977cebc 100644 --- a/docs/providers/google/cli-reference/login.md +++ b/docs/providers/google/cli-reference/login.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/login) + # Login diff --git a/docs/providers/google/cli-reference/logs.md b/docs/providers/google/cli-reference/logs.md index fd1a6444a..7afb0e7a6 100644 --- a/docs/providers/google/cli-reference/logs.md +++ b/docs/providers/google/cli-reference/logs.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/logs) + # Google - Logs diff --git a/docs/providers/google/cli-reference/package.md b/docs/providers/google/cli-reference/package.md index 023575d6a..76d4ddf0d 100644 --- a/docs/providers/google/cli-reference/package.md +++ b/docs/providers/google/cli-reference/package.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/package) + # Google - package diff --git a/docs/providers/google/cli-reference/plugin-install.md b/docs/providers/google/cli-reference/plugin-install.md index 03b41ef35..5270a9346 100644 --- a/docs/providers/google/cli-reference/plugin-install.md +++ b/docs/providers/google/cli-reference/plugin-install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/plugin-install) + # Plugin Install @@ -22,9 +24,11 @@ serverless plugin install --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:install:install` ## Examples diff --git a/docs/providers/google/cli-reference/plugin-list.md b/docs/providers/google/cli-reference/plugin-list.md index d9b5a17b7..67cdf426c 100644 --- a/docs/providers/google/cli-reference/plugin-list.md +++ b/docs/providers/google/cli-reference/plugin-list.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/plugin-list) + # Plugin List @@ -19,7 +21,9 @@ serverless plugin list ``` ## Options -- *None* + +- _None_ ## Provided lifecycle events + - `plugin:list:list` diff --git a/docs/providers/google/cli-reference/plugin-search.md b/docs/providers/google/cli-reference/plugin-search.md index c0f1130bd..c1c13282b 100644 --- a/docs/providers/google/cli-reference/plugin-search.md +++ b/docs/providers/google/cli-reference/plugin-search.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/plugin-search) + # Plugin Search @@ -19,9 +21,11 @@ serverless plugin search --query query ``` ## Options + - `--query` or `-q` The query you want to use for your search. **Required**. ## Provided lifecycle events + - `plugin:search:search` ## Examples diff --git a/docs/providers/google/cli-reference/plugin-uninstall.md b/docs/providers/google/cli-reference/plugin-uninstall.md index 8e0e2f8ab..81d5e86bc 100644 --- a/docs/providers/google/cli-reference/plugin-uninstall.md +++ b/docs/providers/google/cli-reference/plugin-uninstall.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/plugin-uninstall) + # Plugin Uninstall @@ -19,9 +21,11 @@ serverless plugin uninstall --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:uninstall:uninstall` ## Examples diff --git a/docs/providers/google/cli-reference/print.md b/docs/providers/google/cli-reference/print.md index bfeb5427d..d47f768c1 100644 --- a/docs/providers/google/cli-reference/print.md +++ b/docs/providers/google/cli-reference/print.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/print) + # Print diff --git a/docs/providers/google/cli-reference/remove.md b/docs/providers/google/cli-reference/remove.md index e091effb3..592483a2d 100644 --- a/docs/providers/google/cli-reference/remove.md +++ b/docs/providers/google/cli-reference/remove.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/remove) + # Google - Remove diff --git a/docs/providers/google/events/README.md b/docs/providers/google/events/README.md index e171761a2..34e2039b5 100644 --- a/docs/providers/google/events/README.md +++ b/docs/providers/google/events/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/events/) + # Serverless Google Cloud Functions Events diff --git a/docs/providers/google/events/event.md b/docs/providers/google/events/event.md index 3978158ec..1e29b7faf 100644 --- a/docs/providers/google/events/event.md +++ b/docs/providers/google/events/event.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/events/event) + ## Event diff --git a/docs/providers/google/events/http.md b/docs/providers/google/events/http.md index bd486e147..28e3eaa1d 100644 --- a/docs/providers/google/events/http.md +++ b/docs/providers/google/events/http.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/events/http) + # HTTP @@ -39,7 +41,6 @@ functions: ```javascript // index.js - exports.first = (request, response) => { response.status(200).send('Hello World!'); }; @@ -49,7 +50,7 @@ exports.first = (request, response) => { The configuration for Google Cloud Functions is a bit different than some other providers: -* Your deployed endpoint from above will accept GET, POST, and all other HTTP verbs. If you want to disallow certain verbs, [you can do that within the method body](https://cloud.google.com/functions/docs/writing/http#handling_http_methods). -* All Google Cloud Functions are deployed as just the handler name at the root of the URL pathname. In the example above, this means your function is deployed to `https://YOUR_URL/http`. As a result, you cannot configure nested routes such as `http/hello` in your `serverless.yml` file. Instead, Google passes all URLs that appear to be subdirectories of your URL to your handler function so that you can determine the appropriate behavior. The complete path is still available as `req.path` and can be parsed to provide nested routes, path/URL parameters, and more. +- Your deployed endpoint from above will accept GET, POST, and all other HTTP verbs. If you want to disallow certain verbs, [you can do that within the method body](https://cloud.google.com/functions/docs/writing/http#handling_http_methods). +- All Google Cloud Functions are deployed as just the handler name at the root of the URL pathname. In the example above, this means your function is deployed to `https://YOUR_URL/http`. As a result, you cannot configure nested routes such as `http/hello` in your `serverless.yml` file. Instead, Google passes all URLs that appear to be subdirectories of your URL to your handler function so that you can determine the appropriate behavior. The complete path is still available as `req.path` and can be parsed to provide nested routes, path/URL parameters, and more. **Note:** See the documentation about the [function handlers](../guide/functions.md) to learn how your handler signature should look like to work with this type of event. diff --git a/docs/providers/google/examples/README.md b/docs/providers/google/examples/README.md index 8b9c291e9..8c4a43126 100644 --- a/docs/providers/google/examples/README.md +++ b/docs/providers/google/examples/README.md @@ -5,16 +5,18 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/examples/) + # Serverless Google Cloud Functions Examples Have an example? Submit a PR or [open an issue](https://github.com/serverless/examples/issues). ⚡️ -| Example | Runtime | -|:--------------------------- |:-----| -| [Google Node Simple](https://serverless.com/examples/google-node-simple-http-endpoint/)
    Boilerplate project repository for Google Cloud provider with Serverless Framework. | nodeJS | -| [Google Python Simple](https://serverless.com/examples/google-python-simple-http-endpoint/)
    Boilerplate project repository for Google Cloud provider with Serverless Framework. | Python | +| Example | Runtime | +| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------ | +| [Google Node Simple](https://serverless.com/examples/google-node-simple-http-endpoint/)
    Boilerplate project repository for Google Cloud provider with Serverless Framework. | nodeJS | +| [Google Python Simple](https://serverless.com/examples/google-python-simple-http-endpoint/)
    Boilerplate project repository for Google Cloud provider with Serverless Framework. | Python | If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://forum.serverless.com/) diff --git a/docs/providers/google/examples/hello-world/README.md b/docs/providers/google/examples/hello-world/README.md index b19c3e07c..328371843 100644 --- a/docs/providers/google/examples/hello-world/README.md +++ b/docs/providers/google/examples/hello-world/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/examples/hello-world/) + # Hello World Serverless Example 🌍 @@ -15,7 +17,7 @@ Welcome to the Hello World example. Pick your language of choice: -* [JavaScript](./node) -* [Python](./python) +- [JavaScript](./node) +- [Python](./python) [View all examples](https://www.serverless.com/framework/docs/providers/google/examples/) diff --git a/docs/providers/google/examples/hello-world/node/README.md b/docs/providers/google/examples/hello-world/node/README.md index 5802b9779..00e3d064b 100644 --- a/docs/providers/google/examples/hello-world/node/README.md +++ b/docs/providers/google/examples/hello-world/node/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/examples/hello-world/node/) + # Hello World Node.js Example diff --git a/docs/providers/google/examples/hello-world/python/README.md b/docs/providers/google/examples/hello-world/python/README.md index 37a7a0935..403c166a5 100644 --- a/docs/providers/google/examples/hello-world/python/README.md +++ b/docs/providers/google/examples/hello-world/python/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/examples/hello-world/python/) + # Hello World Python Example diff --git a/docs/providers/google/guide/README.md b/docs/providers/google/guide/README.md index 12c45109c..4fb44333d 100644 --- a/docs/providers/google/guide/README.md +++ b/docs/providers/google/guide/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/) + # Serverless Google Cloud Functions Guide diff --git a/docs/providers/google/guide/credentials.md b/docs/providers/google/guide/credentials.md index b34e1a92e..4d51dd4bc 100644 --- a/docs/providers/google/guide/credentials.md +++ b/docs/providers/google/guide/credentials.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/credentials) + # Google - Credentials @@ -31,9 +33,9 @@ A Google Cloud Project is required to use Google Cloud Functions. Here's how to 1. Go to the Google Cloud Console. 2. There is a dropdown near the top left of the screen (near the search bar that lists your projects). Click it and select "Create Project". 3. Enter a Project name and select the Billing Account you created in the steps above (or any Billing Account with a valid credit card attached). -3. Click on "Create" to start the creation process. -4. Wait until the Project was successfully created and Google will redirect you to your new Project. -5. Verify your currently within your new Project by looking at the dropdown next to the search bar. This should mark your new Project as selected. +4. Click on "Create" to start the creation process. +5. Wait until the Project was successfully created and Google will redirect you to your new Project. +6. Verify your currently within your new Project by looking at the dropdown next to the search bar. This should mark your new Project as selected. ## Enable the necessary APIs diff --git a/docs/providers/google/guide/deploying.md b/docs/providers/google/guide/deploying.md index 5542a4acf..3b3d05f32 100644 --- a/docs/providers/google/guide/deploying.md +++ b/docs/providers/google/guide/deploying.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/deploying) + # Google - Deploying @@ -24,7 +26,7 @@ serverless deploy Use this method when you have updated your Function, Events or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to the Google Cloud. -**Note:** You can specify a different configuration file name with the the `--config` option. +**Note:** You can specify a different configuration file name with the the `--config` option. ### How It Works diff --git a/docs/providers/google/guide/events.md b/docs/providers/google/guide/events.md index b736f7ebb..8b55e2e5c 100644 --- a/docs/providers/google/guide/events.md +++ b/docs/providers/google/guide/events.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/events) + # Google - Events diff --git a/docs/providers/google/guide/functions.md b/docs/providers/google/guide/functions.md index 47ac6f789..3c437afc6 100644 --- a/docs/providers/google/guide/functions.md +++ b/docs/providers/google/guide/functions.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/functions) + # Google - Functions -If you are using Google Cloud Functions as a provider, all *functions* inside the service are Google Cloud Functions. +If you are using Google Cloud Functions as a provider, all _functions_ inside the service are Google Cloud Functions. ## Configuration @@ -39,8 +41,7 @@ You can specify an array of functions, which is useful if you separate your func ```yml # serverless.yml -... - +--- functions: - ${file(./foo-functions.yml)} - ${file(./bar-functions.yml)} @@ -54,7 +55,6 @@ deleteFoo: handler: handler.foo ``` - ## Handler The `handler` property should be the function name you've exported in your entrypoint file. @@ -63,7 +63,7 @@ When you e.g. export a function with the name `http` in `index.js` your `handler ```javascript // index.js -exports.http = (request, response) => {} +exports.http = (request, response) => {}; ``` **A note about index.js and the entrypoint file** diff --git a/docs/providers/google/guide/installation.md b/docs/providers/google/guide/installation.md index 914c7d1c4..a63e22385 100644 --- a/docs/providers/google/guide/installation.md +++ b/docs/providers/google/guide/installation.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/installation) + # Google - Installation diff --git a/docs/providers/google/guide/intro.md b/docs/providers/google/guide/intro.md index d044ae36a..cfd0be40a 100644 --- a/docs/providers/google/guide/intro.md +++ b/docs/providers/google/guide/intro.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/intro) + # Google - Introduction @@ -27,9 +29,9 @@ Here are the Framework's main concepts and how they pertain to Google Cloud Func A Function is a [Google Cloud Function](https://cloud.google.com/functions/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: -- *Saving a user to the database* -- *Processing a file in a database* -- *Performing a scheduled task* +- _Saving a user to the database_ +- _Processing a file in a database_ +- _Performing a scheduled task_ You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. @@ -37,16 +39,16 @@ You can perform multiple jobs in your code, but we don't recommend doing that wi Anything that triggers a Google Cloud Function to execute is regarded by the Framework as an **Event**. Events are platform events on Google Cloud Functions such as: -- *An HTTP Trigger (e.g., for a REST API)* -- *A pubSub event (e.g., run function when message is sent to topic)* -- *A Storage event (e.g., Image uploaded into bucket)* -- *And more...* +- _An HTTP Trigger (e.g., for a REST API)_ +- _A pubSub event (e.g., run function when message is sent to topic)_ +- _A Storage event (e.g., Image uploaded into bucket)_ +- _And more..._ When you define an event for your Google Cloud Function in the Serverless Framework, the Framework will automatically translate the event with its function into a corresponding [deployment resource](https://cloud.google.com/deployment-manager/docs/configuration/supported-resource-types). This way the event is configured so that your functions can listen to it. ### Services -A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml # serverless.yml diff --git a/docs/providers/google/guide/packaging.md b/docs/providers/google/guide/packaging.md index 5d0e8d424..686185d56 100644 --- a/docs/providers/google/guide/packaging.md +++ b/docs/providers/google/guide/packaging.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/packaging) + # Google - Packaging @@ -41,7 +43,7 @@ previously excluded files and directories. Exclude all node_modules but then re-include a specific modules (in this case node-fetch) using `exclude` exclusively -``` yml +```yml package: exclude: - node_modules/** @@ -50,7 +52,7 @@ package: Exclude all files but `handler.js` using `exclude` and `include` -``` yml +```yml package: exclude: - src/** @@ -111,6 +113,7 @@ functions: exclude: - some-file.js ``` + You can also select which functions to be packaged separately, and have the rest use the service package by setting the `individually` flag at the function level: ```yml diff --git a/docs/providers/google/guide/plugins.md b/docs/providers/google/guide/plugins.md index 78c8a6e24..e7095ee0e 100644 --- a/docs/providers/google/guide/plugins.md +++ b/docs/providers/google/guide/plugins.md @@ -7,19 +7,21 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/plugins) + # Google - Plugins -A Plugin is custom Javascript code that creates new or extends existing commands within the Serverless Framework. The Serverless Framework is merely a group of Plugins that are provided in the core. If you or your organization have a specific workflow, install a pre-written Plugin or write a plugin to customize the Framework to your needs. External Plugins are written exactly the same way as the core Plugins. +A Plugin is custom Javascript code that creates new or extends existing commands within the Serverless Framework. The Serverless Framework is merely a group of Plugins that are provided in the core. If you or your organization have a specific workflow, install a pre-written Plugin or write a plugin to customize the Framework to your needs. External Plugins are written exactly the same way as the core Plugins. - [How to create serverless plugins - Part 1](https://serverless.com/blog/writing-serverless-plugins/) - [How to create serverless plugins - Part 2](https://serverless.com/blog/writing-serverless-plugins-2/) ## Installing Plugins -External Plugins are added on a per service basis and are not applied globally. Make sure you are in your Service's root directory, then install the corresponding Plugin with the help of npm: +External Plugins are added on a per service basis and are not applied globally. Make sure you are in your Service's root directory, then install the corresponding Plugin with the help of npm: ``` npm install --save custom-serverless-plugin @@ -33,9 +35,11 @@ We need to tell Serverless that we want to use the plugin inside our service. We plugins: - custom-serverless-plugin ``` + The `plugins` section supports two formats: Array object: + ```yml plugins: - plugin1 @@ -43,6 +47,7 @@ plugins: ``` Enhanced plugins object: + ```yml plugins: localPath: './custom_serverless_plugins' @@ -66,18 +71,21 @@ custom: If you are working on a plugin or have a plugin that is just designed for one project they can be loaded from the local folder. Local plugins can be added in the `plugins` array in `serverless.yml`. By default local plugins can be added to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. + ```yml plugins: - custom-serverless-plugin ``` Local plugins folder can be changed by enhancing `plugins` object: + ```yml plugins: localPath: './custom_serverless_plugins' modules: - custom-serverless-plugin ``` + The `custom-serverless-plugin` will be loaded from the `custom_serverless_plugins` directory at the root of your service. If the `localPath` is not provided or empty `.serverless_plugins` directory will be taken as the `localPath`. The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `localPath` folder (`.serverless_plugins` by default). @@ -102,15 +110,15 @@ In this case `plugin1` is loaded before `plugin2`. #### Plugin -Code which defines *Commands*, any *Events* within a *Command*, and any *Hooks* assigned to an *Lifecycle Event*. +Code which defines _Commands_, any _Events_ within a _Command_, and any _Hooks_ assigned to an _Lifecycle Event_. - Command // CLI configuration, commands, subcommands, options - LifecycleEvent(s) // Events that happen sequentially when the command is run - - Hook(s) // Code that runs when a Lifecycle Event happens during a Command + - Hook(s) // Code that runs when a Lifecycle Event happens during a Command #### Command -A CLI *Command* that can be called by a user, e.g. `serverless deploy`. A Command has no logic, but simply defines the CLI configuration (e.g. command, subcommands, parameters) and the *Lifecycle Events* for the command. Every command defines its own lifecycle events. +A CLI _Command_ that can be called by a user, e.g. `serverless deploy`. A Command has no logic, but simply defines the CLI configuration (e.g. command, subcommands, parameters) and the _Lifecycle Events_ for the command. Every command defines its own lifecycle events. ```javascript 'use strict'; @@ -119,10 +127,7 @@ class MyPlugin { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ] + lifecycleEvents: ['resources', 'functions'], }, }; } @@ -133,7 +138,7 @@ module.exports = MyPlugin; #### Lifecycle Events -Events that fire sequentially during a Command. The above example list two Events. However, for each Event, and additional `before` and `after` event is created. Therefore, six Events exist in the above example: +Events that fire sequentially during a Command. The above example list two Events. However, for each Event, and additional `before` and `after` event is created. Therefore, six Events exist in the above example: - `before:deploy:resources` - `deploy:resources` @@ -155,17 +160,14 @@ class Deploy { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ] + lifecycleEvents: ['resources', 'functions'], }, }; this.hooks = { 'before:deploy:resources': this.beforeDeployResources, 'deploy:resources': this.deployResources, - 'after:deploy:functions': this.afterDeployFunctions + 'after:deploy:functions': this.afterDeployFunctions, }; } @@ -196,20 +198,14 @@ class MyPlugin { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ], + lifecycleEvents: ['resources', 'functions'], commands: { function: { - lifecycleEvents: [ - 'package', - 'deploy' - ], + lifecycleEvents: ['package', 'deploy'], }, }, }, - } + }; } } @@ -226,7 +222,7 @@ Option Shortcuts are passed in with a single dash (`-`) like this: `serverless f The `options` object will be passed in as the second parameter to the constructor of your plugin. -In it, you can optionally add a `shortcut` property, as well as a `required` property. The Framework will return an error if a `required` Option is not included. +In it, you can optionally add a `shortcut` property, as well as a `required` property. The Framework will return an error if a `required` Option is not included. **Note:** At this time, the Serverless Framework does not use parameters. @@ -240,22 +236,20 @@ class Deploy { this.commands = { deploy: { - lifecycleEvents: [ - 'functions' - ], + lifecycleEvents: ['functions'], options: { function: { usage: 'Specify the function you want to deploy (e.g. "--function myFunction")', shortcut: 'f', - required: true - } - } + required: true, + }, + }, }, }; this.hooks = { - 'deploy:functions': this.deployFunction.bind(this) - } + 'deploy:functions': this.deployFunction.bind(this), + }; } deployFunction() { @@ -287,21 +281,19 @@ class ProviderDeploy { this.commands = { deploy: { - lifecycleEvents: [ - 'functions' - ], + lifecycleEvents: ['functions'], options: { function: { usage: 'Specify the function you want to deploy (e.g. "--function myFunction")', - required: true - } - } + required: true, + }, + }, }, }; this.hooks = { - 'deploy:functions': this.deployFunction.bind(this) - } + 'deploy:functions': this.deployFunction.bind(this), + }; } deployFunction() { @@ -328,15 +320,13 @@ class MyPlugin { this.commands = { log: { - lifecycleEvents: [ - 'serverless' - ], + lifecycleEvents: ['serverless'], }, }; this.hooks = { - 'log:serverless': this.logServerless.bind(this) - } + 'log:serverless': this.logServerless.bind(this), + }; } logServerless() { diff --git a/docs/providers/google/guide/services.md b/docs/providers/google/guide/services.md index bb8423f0e..6fbaf9fd7 100644 --- a/docs/providers/google/guide/services.md +++ b/docs/providers/google/guide/services.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/services) + # Google - Services @@ -25,7 +27,7 @@ myService/ serverless.yml # Contains all functions and infrastructure resources ``` -However, as your application grows, you can break it out into multiple services. A lot of people organize their services by workflows or data models, and group the functions related to those workflows and data models together in the service. +However, as your application grows, you can break it out into multiple services. A lot of people organize their services by workflows or data models, and group the functions related to those workflows and data models together in the service. ```bash users/ @@ -49,9 +51,9 @@ serverless create --template google-nodejs --path my-service Here are the available runtimes for Google Cloud Functions: -* google-nodejs -* google-go -* google-python +- google-nodejs +- google-go +- google-python Check out the [create command docs](../cli-reference/create) for all the details and options. @@ -114,7 +116,7 @@ Then use the `deploy` command: serverless deploy ``` -Check out the [deployment guide](./deploying.md) to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy.md) for all the details and options. +Check out the [deployment guide](./deploying.md) to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy.md) for all the details and options. ## Removal @@ -143,7 +145,7 @@ To configure version pinning define a `frameworkVersion` property in your server ```yml # serverless.yml -frameworkVersion: "=1.0.3" +frameworkVersion: '=1.0.3' ``` #### Version Range @@ -151,10 +153,9 @@ frameworkVersion: "=1.0.3" ```yml # serverless.yml -frameworkVersion: ">=1.0.0 <2.0.0" +frameworkVersion: '>=1.0.0 <2.0.0' ``` - ## Installing Serverless in an existing service If you already have a Serverless service, and would prefer to lock down the framework version using `package.json`, then you can install Serverless as follows: @@ -169,6 +170,7 @@ npm install serverless --save-dev To execute the locally installed Serverless executable you have to reference the binary out of the node modules directory. Example: + ``` node ./node_modules/serverless/bin/serverless deploy ``` diff --git a/docs/providers/google/guide/variables.md b/docs/providers/google/guide/variables.md index 965107e18..fa12f7bf8 100644 --- a/docs/providers/google/guide/variables.md +++ b/docs/providers/google/guide/variables.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/variables) + # Google - Variables @@ -24,6 +26,7 @@ The Serverless framework provides a powerful variable system which allows you to **Note:** You can only use variables in `serverless.yml` property **values**, not property keys. So you can't use variables to generate dynamic logical IDs in the custom resources section for example. ## Reference Properties In serverless.yml + To self-reference properties in `serverless.yml`, use the `${self:someProperty}` syntax in your `serverless.yml`. This functionality is recursive, so you can go as deep in the object tree as you want. ```yml @@ -51,7 +54,8 @@ functions: In the above example you're setting a global event resource for all functions by referencing the `resource` property in the same `serverless.yml` file. This way, you can easily change the event resource for all functions whenever you like. ## Reference Variables in other Files -You can reference variables in other YAML or JSON files. To reference variables in other YAML files use the `${file(./myFile.yml):someProperty}` syntax in your `serverless.yml` configuration file. To reference variables in other JSON files use the `${file(./myFile.json):someProperty}` syntax. It is important that the file you are referencing has the correct suffix, or file extension, for its file type (`.yml` for YAML or `.json` for JSON) in order for it to be interpreted correctly. Here's an example: + +You can reference variables in other YAML or JSON files. To reference variables in other YAML files use the `${file(./myFile.yml):someProperty}` syntax in your `serverless.yml` configuration file. To reference variables in other JSON files use the `${file(./myFile.json):someProperty}` syntax. It is important that the file you are referencing has the correct suffix, or file extension, for its file type (`.yml` for YAML or `.json` for JSON) in order for it to be interpreted correctly. Here's an example: ```yml # myCustomFile.yml @@ -73,13 +77,13 @@ functions: eventType: providers/cloud.pubsub/eventTypes/topics.publish resource: ${file(./myCustomFile.yml):topic} # Or you can reference a specific property world: - handler: pubSub.hello - events: - eventType: providers/cloud.pubsub/eventTypes/topics.publish - resource: ${self:custom.topic} # This would also work in this case + handler: pubSub.hello + events: + eventType: providers/cloud.pubsub/eventTypes/topics.publish + resource: ${self:custom.topic} # This would also work in this case ``` -In the above example, you're referencing the entire `myCustomFile.yml` file in the `custom` property. You need to pass the path relative to your service directory. You can also request specific properties in that file as shown in the `topic` property. It's completely recursive and you can go as deep as you want. Additionally you can request properties that contain arrays from either YAML or JSON reference files. Here's a YAML example for an events array: +In the above example, you're referencing the entire `myCustomFile.yml` file in the `custom` property. You need to pass the path relative to your service directory. You can also request specific properties in that file as shown in the `topic` property. It's completely recursive and you can go as deep as you want. Additionally you can request properties that contain arrays from either YAML or JSON reference files. Here's a YAML example for an events array: ```yml myevents: @@ -89,18 +93,22 @@ myevents: ``` and for JSON: + ```json { - "myevents": [{ - "event" : { - "eventType": "providers/cloud.pubsub/eventTypes/topic.publish", - "resource" : "projects/*/topics/my-topic" + "myevents": [ + { + "event": { + "eventType": "providers/cloud.pubsub/eventTypes/topic.publish", + "resource": "projects/*/topics/my-topic" + } } - }] + ] } ``` In your serverless.yml, depending on the type of your source file, either have the following syntax for YAML + ```yml functions: hello: @@ -109,6 +117,7 @@ functions: ``` or for a JSON reference file use this sytax: + ```yml functions: hello: @@ -127,9 +136,9 @@ References can be either named or unnamed exports. To use the exported `someModu ```javascript // resources.js module.exports.topic = () => { - // Code that generates dynamic data - return 'projects/*/topics/my-topic'; -} + // Code that generates dynamic data + return 'projects/*/topics/my-topic'; +}; ``` ```js @@ -137,9 +146,9 @@ module.exports.topic = () => { module.exports = () => { return { property1: 'some value', - property2: 'some other value' - } -} + property2: 'some other value', + }; +}; ``` ```yml @@ -164,11 +173,11 @@ You can also return an object and reference a specific property. Just make sure ```javascript // myCustomFile.js module.exports.pubSub = () => { - // Code that generates dynamic data - return { - resource: 'projects/*/topics/my-topic' - }; -} + // Code that generates dynamic data + return { + resource: 'projects/*/topics/my-topic', + }; +}; ``` ```yml diff --git a/docs/providers/google/guide/workflow.md b/docs/providers/google/guide/workflow.md index 8b2db4cf6..6eeda3fd0 100644 --- a/docs/providers/google/guide/workflow.md +++ b/docs/providers/google/guide/workflow.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/guide/workflow) + # Google - Workflow @@ -18,15 +20,15 @@ Intro. Quick recommendations and tips for various processes. 1. Write your functions 2. Use `serverless deploy` when you've made changes to `serverless.yml` and in CI/CD systems -3. Use `serverless invoke --function myFunction ` to test your Google Cloud Functions +3. Use `serverless invoke --function myFunction` to test your Google Cloud Functions 4. Open up a separate tab in your console and see logs in there via `serverless logs --function myFunction` 5. Write tests to run locally ### Larger Projects - Break your application / project into multiple Serverless Services -- Model your Serverless Services around Data Models or Workflows -- Keep the Functions and Resources in your Serverless Services to a minimum +- Model your Serverless Services around Data Models or Workflows +- Keep the Functions and Resources in your Serverless Services to a minimum ## Cheat Sheet diff --git a/docs/providers/kubeless/README.md b/docs/providers/kubeless/README.md index 49489494a..396fb8002 100644 --- a/docs/providers/kubeless/README.md +++ b/docs/providers/kubeless/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/) + # Kubeless Provider Documentation diff --git a/docs/providers/kubeless/cli-reference/README.md b/docs/providers/kubeless/cli-reference/README.md index 246c0aed1..f2ea6d0f9 100644 --- a/docs/providers/kubeless/cli-reference/README.md +++ b/docs/providers/kubeless/cli-reference/README.md @@ -5,11 +5,13 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/) + # Serverless Kubeless CLI Reference -Welcome to the Serverless Kubeless CLI Reference! Please select a section on the left to get started. +Welcome to the Serverless Kubeless CLI Reference! Please select a section on the left to get started. If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/). diff --git a/docs/providers/kubeless/cli-reference/create.md b/docs/providers/kubeless/cli-reference/create.md index 68a1bd6b3..42e39abb9 100644 --- a/docs/providers/kubeless/cli-reference/create.md +++ b/docs/providers/kubeless/cli-reference/create.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/create) + # Kubeless - Create @@ -35,6 +37,7 @@ serverless create --template kubeless-nodejs --path my-service ``` ## Options + - `--template` or `-t` The name of one of the available templates. **Required if --template-url and --template-path are not present**. - `--template-url` or `-u` The name of one of the available templates. **Required if --template and --template-path are not present**. - `--template-path` The local path of your template. **Required if --template and --template-url are not present**. @@ -42,6 +45,7 @@ serverless create --template kubeless-nodejs --path my-service - `--name` or `-n` the name of the service in `serverless.yml`. ## Provided lifecycle events + - `create:create` ## Available Templates for Kubeless diff --git a/docs/providers/kubeless/cli-reference/deploy.md b/docs/providers/kubeless/cli-reference/deploy.md index b4898ffc4..35bf7d9de 100644 --- a/docs/providers/kubeless/cli-reference/deploy.md +++ b/docs/providers/kubeless/cli-reference/deploy.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/deploy) + # Kubeless - Deploy @@ -23,6 +25,7 @@ serverless deploy This is the simplest deployment usage possible. With this command Serverless will deploy your service to the default Kubernetes cluster in your kubeconfig file. ## Options + - `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--noDeploy` or `-n` Skips the deployment steps and leaves artifacts in the `.serverless` directory. - `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. @@ -34,6 +37,7 @@ This is the simplest deployment usage possible. With this command Serverless wil After the `serverless deploy` command runs all created deployment artifacts are placed in the `.serverless` folder of the service. ## Provided lifecycle events + - `deploy:cleanup` - `deploy:initialize` - `deploy:setupProviderConfiguration` diff --git a/docs/providers/kubeless/cli-reference/info.md b/docs/providers/kubeless/cli-reference/info.md index a38028071..b3b3927e2 100644 --- a/docs/providers/kubeless/cli-reference/info.md +++ b/docs/providers/kubeless/cli-reference/info.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/info) + # Kubeless - Info @@ -19,9 +21,11 @@ serverless info ``` ## Options + - `--verbose` or `-v` Shows the metadata of the Kubernetes objects. ## Provided lifecycle events + - `info:info` ## Examples @@ -38,7 +42,7 @@ $ serverless info -v Service Information "hello" Cluster IP: 10.0.0.203 Type: ClusterIP -Ports: +Ports: Protocol: TCP Port: 8080 Target Port: 8080 @@ -50,7 +54,7 @@ Function Info Handler: handler.hello Runtime: python2.7 Trigger: HTTP -Dependencies: +Dependencies: Metadata: Self Link: /apis/k8s.io/v1/namespaces/default/functions/hello UID: 7c214cab-8976-11e7-b8c4-0800275c88b3 diff --git a/docs/providers/kubeless/cli-reference/invoke.md b/docs/providers/kubeless/cli-reference/invoke.md index cee892cc5..792cf6e00 100644 --- a/docs/providers/kubeless/cli-reference/invoke.md +++ b/docs/providers/kubeless/cli-reference/invoke.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/invoke) + # Kubeless - Invoke @@ -19,12 +21,14 @@ serverless invoke --function functionName ``` ## Options + - `--function` or `-f` The name of the function in your service that you want to invoke. **Required**. - `--data` or `-d` String data to be passed as an event to your function. By default data is read from standard input. - `--path` or `-p` The path to a json file with input data to be passed to the invoked function. This path is relative to the root directory of the service. - `--log` or `-l` If set to `true`, it will output logging data of the invocation. Default is `false`. ## Provided lifecycle events + - `invoke:invoke` ## Examples diff --git a/docs/providers/kubeless/cli-reference/logs.md b/docs/providers/kubeless/cli-reference/logs.md index a6eeeb6fc..d68d669b8 100644 --- a/docs/providers/kubeless/cli-reference/logs.md +++ b/docs/providers/kubeless/cli-reference/logs.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/logs) + # Kubeless - Logs @@ -54,11 +56,13 @@ serverless logs -f hello ```bash serverless logs -f hello --startTime 5h ``` + This will fetch the logs that happened in the past 5 hours. ```bash serverless logs -f hello --startTime 1469694264 ``` + This will fetch the logs that happened starting at epoch `1469694264`. ```bash @@ -70,4 +74,5 @@ Serverless will tail the platform log output and print new log messages coming i ```bash serverless logs -f hello --filter serverless ``` + This will fetch only the logs that contain the string `serverless` diff --git a/docs/providers/kubeless/cli-reference/plugin-install.md b/docs/providers/kubeless/cli-reference/plugin-install.md index d59a3e875..d8b96883d 100644 --- a/docs/providers/kubeless/cli-reference/plugin-install.md +++ b/docs/providers/kubeless/cli-reference/plugin-install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/plugin-install) + # Plugin Install @@ -22,9 +24,11 @@ serverless plugin install --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:install:install` ## Examples diff --git a/docs/providers/kubeless/cli-reference/plugin-list.md b/docs/providers/kubeless/cli-reference/plugin-list.md index bf1a25fb3..d0aef3df8 100644 --- a/docs/providers/kubeless/cli-reference/plugin-list.md +++ b/docs/providers/kubeless/cli-reference/plugin-list.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/plugin-list) + # Plugin List @@ -19,7 +21,9 @@ serverless plugin list ``` ## Options -- *None* + +- _None_ ## Provided lifecycle events + - `plugin:list:list` diff --git a/docs/providers/kubeless/cli-reference/plugin-search.md b/docs/providers/kubeless/cli-reference/plugin-search.md index f594936b1..e7a77070a 100644 --- a/docs/providers/kubeless/cli-reference/plugin-search.md +++ b/docs/providers/kubeless/cli-reference/plugin-search.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/plugin-search) + # Plugin Search @@ -19,9 +21,11 @@ serverless plugin search --query query ``` ## Options + - `--query` or `-q` The query you want to use for your search. **Required**. ## Provided lifecycle events + - `plugin:search:search` ## Examples diff --git a/docs/providers/kubeless/cli-reference/plugin-uninstall.md b/docs/providers/kubeless/cli-reference/plugin-uninstall.md index 9e9bc7472..abc0548b4 100644 --- a/docs/providers/kubeless/cli-reference/plugin-uninstall.md +++ b/docs/providers/kubeless/cli-reference/plugin-uninstall.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/plugin-uninstall) + # Plugin Uninstall @@ -19,9 +21,11 @@ serverless plugin uninstall --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:uninstall:uninstall` ## Examples diff --git a/docs/providers/kubeless/cli-reference/remove.md b/docs/providers/kubeless/cli-reference/remove.md index ea1bd14b6..06260ce55 100644 --- a/docs/providers/kubeless/cli-reference/remove.md +++ b/docs/providers/kubeless/cli-reference/remove.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/cli-reference/remove) + # Kubeless - Remove @@ -21,7 +23,9 @@ serverless remove It will remove the Kubeless Function objects from your Kubernetes cluster, the Kubernetes Deployments and the Kubernetes Services associated with the Serverless service. ## Options + - `--verbose` or `-v` Shows additional information during the removal. ## Provided lifecycle events + - `remove:remove` diff --git a/docs/providers/kubeless/events/README.md b/docs/providers/kubeless/events/README.md index 05e2139b9..75ac29613 100644 --- a/docs/providers/kubeless/events/README.md +++ b/docs/providers/kubeless/events/README.md @@ -1,11 +1,13 @@ + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/events/) + # Serverless Kubeless Events @@ -15,4 +17,3 @@ Welcome to the Serverless Kubeless Events Glossary! Please select a section on the left to get started. If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/) - diff --git a/docs/providers/kubeless/events/http.md b/docs/providers/kubeless/events/http.md index 3bb72182e..79d32acb3 100644 --- a/docs/providers/kubeless/events/http.md +++ b/docs/providers/kubeless/events/http.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/events/http) + # Kubeless HTTP Events @@ -77,7 +79,6 @@ functions: events: - http: path: /delete - ``` If the events HTTP definitions contain a `path` attribute, when deploying this Serverless YAML definition, Kubeless will create the needed [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) rules to redirect each of the requests to the right service. You will need to create an [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers) to make use of your Ingress rule(s): diff --git a/docs/providers/kubeless/events/pubsub.md b/docs/providers/kubeless/events/pubsub.md index cd2f707ae..bf95f47bc 100644 --- a/docs/providers/kubeless/events/pubsub.md +++ b/docs/providers/kubeless/events/pubsub.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/events/pubsub) + # Kubeless PubSub Events diff --git a/docs/providers/kubeless/events/scheduler.md b/docs/providers/kubeless/events/scheduler.md index 387bcfe63..7c2a027a6 100644 --- a/docs/providers/kubeless/events/scheduler.md +++ b/docs/providers/kubeless/events/scheduler.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/events/schedule) + # Kubeless Scheduled Events diff --git a/docs/providers/kubeless/guide/README.md b/docs/providers/kubeless/guide/README.md index 52f54ab66..0b3c1641f 100644 --- a/docs/providers/kubeless/guide/README.md +++ b/docs/providers/kubeless/guide/README.md @@ -1,11 +1,13 @@ + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/) + # Serverless Kubeless Guide diff --git a/docs/providers/kubeless/guide/debugging.md b/docs/providers/kubeless/guide/debugging.md index 4dc5b4eb3..0f151a73b 100644 --- a/docs/providers/kubeless/guide/debugging.md +++ b/docs/providers/kubeless/guide/debugging.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/debugging) + # Kubeless - Debugging @@ -22,12 +24,12 @@ import json def find(event, context): term = event['data']['term'] - url = "https://feeds.capitalbikeshare.com/stations/stations.json" + url = "https://feeds.capitalbikeshare.com/stations/stations.json" response = urllib2.urlopen(url) stations = json.loads(response.read()) - + hits = [] - + for station in stations["stationBeanList"]: if station["stAddress1"].find(term) > -1: hits.append(station) @@ -78,26 +80,26 @@ Serverless: Calling function: bikesearch... availableBikes: 9, id: 80, location: '' } ] -``` +``` -What happens when something goes wrong? The function currently has no error handling, so that's easy enough to test. Let's invoke the function again with a typo (use *trm* as the name of the input parameter instead of *term*): +What happens when something goes wrong? The function currently has no error handling, so that's easy enough to test. Let's invoke the function again with a typo (use _trm_ as the name of the input parameter instead of _term_): ``` serverless invoke --function bikesearch --data '{"trm":"Albemarle"}' -l # Output Serverless: Calling function: bikesearch... - + Serverless Error --------------------------------------- - + Internal Server Error - + Get Support -------------------------------------------- Docs: docs.serverless.com Bugs: github.com/serverless/serverless/issues Forums: forum.serverless.com Chat: gitter.im/serverless/serverless - + Your Environment Information ----------------------------- OS: darwin Node Version: 8.3.0 diff --git a/docs/providers/kubeless/guide/deploying.md b/docs/providers/kubeless/guide/deploying.md index 7780ad2b6..1065260ca 100644 --- a/docs/providers/kubeless/guide/deploying.md +++ b/docs/providers/kubeless/guide/deploying.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/deploying) + # Kubeless - Deploying -The Serverless Framework was designed to provision your Kubeless Functions and Events. It does this via a couple of methods designed for different types of deployments. +The Serverless Framework was designed to provision your Kubeless Functions and Events. It does this via a couple of methods designed for different types of deployments. ## Deploy All @@ -24,7 +26,7 @@ serverless deploy -v Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to your Kubernetes cluster. -**Note:** You can specify a different configuration file name with the the `--config` option. +**Note:** You can specify a different configuration file name with the the `--config` option. ### How It Works @@ -75,7 +77,6 @@ rs/hello-699783077 1 1 1 2m Kubeless will create a [Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) for your function and a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) for each event. - ## Deploy Function This deployment method updates or deploys a single function. It performs the platform API call to deploy your package without the other resources. It is much faster than redeploying your whole service each time. diff --git a/docs/providers/kubeless/guide/events.md b/docs/providers/kubeless/guide/events.md index 4f5a42c06..e33ac78a2 100644 --- a/docs/providers/kubeless/guide/events.md +++ b/docs/providers/kubeless/guide/events.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/events) + # Kubeless - Events diff --git a/docs/providers/kubeless/guide/functions.md b/docs/providers/kubeless/guide/functions.md index d71927557..34e420c88 100644 --- a/docs/providers/kubeless/guide/functions.md +++ b/docs/providers/kubeless/guide/functions.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/functions) + # Kubeless - Functions -If you are using Kubeless as a provider, all *functions* inside the service are Kubernetes Function.v1.k8s.io objects. +If you are using Kubeless as a provider, all _functions_ inside the service are Kubernetes Function.v1.k8s.io objects. ## Configuration @@ -89,8 +91,7 @@ You can specify an array of functions, which is useful if you separate your func ```yml # serverless.yml -... - +--- functions: - ${file(./foo-functions.yml)} - ${file(./bar-functions.yml)} @@ -120,9 +121,9 @@ Please see the following repository for sample projects using those runtimes: For installing dependencies the standard dependency file should be placed in the function folder: - - For Python functions, it will use the file `requirements.txt` - - For Nodejs functions, `dependencies` in the `package.json` file will be installed - - For Ruby functions, it will use the file `Gemfile.rb` +- For Python functions, it will use the file `requirements.txt` +- For Nodejs functions, `dependencies` in the `package.json` file will be installed +- For Ruby functions, it will use the file `Gemfile.rb` If one of the above files is found, the depencies will be installed using a [`Init Container`](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/). @@ -169,6 +170,7 @@ functions: environment: TABLE_NAME: tableName2 ``` + ## Secrets Kubernetes [secrets](https://kubernetes.io/docs/concepts/configuration/secret/) can be linked to your serverless function by mounting the file or defining environment variables. These can be configured as such in `serverless.yml`. @@ -179,6 +181,7 @@ Given the kubernetes secret named `secret1` created by: We can access it like so: **Mounting** + ```yml service: service-name provider: @@ -194,10 +197,10 @@ functions: handler: handler.users secrets: - secret1 - ``` **Environment Variables** + ```yml service: service-name provider: @@ -215,15 +218,14 @@ functions: # Note that the secret cannot have any `/`'s in the name handler: handler.users environment: - # The environment variable `PROD_USER_PASSWORD` would be set to "happy" + # The environment variable `PROD_USER_PASSWORD` would be set to "happy" - name: PROD_USER_PASSWORD valueFrom: - secretKeyRef: - - name: secret1 - key: password + secretKeyRef: + - name: secret1 + key: password ``` - ## Labels Using the `labels` configuration makes it possible to add `key` / `value` labels to your functions. diff --git a/docs/providers/kubeless/guide/installation.md b/docs/providers/kubeless/guide/installation.md index 483a87516..08655a3b7 100644 --- a/docs/providers/kubeless/guide/installation.md +++ b/docs/providers/kubeless/guide/installation.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/installation) + # Kubeless - Installation @@ -49,4 +51,3 @@ To see which version of serverless you have installed run: ```bash serverless --version ``` - diff --git a/docs/providers/kubeless/guide/intro.md b/docs/providers/kubeless/guide/intro.md index c056a2f6a..708abcb8f 100644 --- a/docs/providers/kubeless/guide/intro.md +++ b/docs/providers/kubeless/guide/intro.md @@ -7,16 +7,19 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/intro) + # Kubeless - Introduction -The Serverless Framework helps you develop and deploy serverless applications using Kubeless. It's a CLI that offers structure, automation and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). +The Serverless Framework helps you develop and deploy serverless applications using Kubeless. It's a CLI that offers structure, automation and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). The Serverless Framework is different than other application frameworks because: -* It manages your code as well as your infrastructure -* It supports multiple languages (Node.js, Python, Ruby) + +- It manages your code as well as your infrastructure +- It supports multiple languages (Node.js, Python, Ruby) ## Core Concepts @@ -24,25 +27,25 @@ Here are the Serverless Framework's main concepts and how they pertain to Kubele ### Functions -A Function is a [Kubeless Function](http://kubeless.io/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: +A Function is a [Kubeless Function](http://kubeless.io/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: -* *Saving a user to the database* -* *Processing a file in a database* -* *Performing a scheduled task* (To be added in newer versions) +- _Saving a user to the database_ +- _Processing a file in a database_ +- _Performing a scheduled task_ (To be added in newer versions) -You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. +You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. ### Events -Anything that triggers an Kubeless Event to execute is regarded by the Framework as an **Event**. Events are platform events on Kubeless such as: +Anything that triggers an Kubeless Event to execute is regarded by the Framework as an **Event**. Events are platform events on Kubeless such as: -* *An API Gateway HTTP endpoint (e.g., for a REST API)* -* *A Kafka queue message (e.g., a message)* -* *A scheduled timer (e.g., run every 5 minutes)* (To be added in newer versions) +- _An API Gateway HTTP endpoint (e.g., for a REST API)_ +- _A Kafka queue message (e.g., a message)_ +- _A scheduled timer (e.g., run every 5 minutes)_ (To be added in newer versions) ### Services -A **Service** is the Serverless Framework's unit of organization (not to be confused with [Kubernetes Services](https://kubernetes.io/docs/concepts/services-networking/service/). You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the Events that trigger them, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Serverless Framework's unit of organization (not to be confused with [Kubernetes Services](https://kubernetes.io/docs/concepts/services-networking/service/). You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the Events that trigger them, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml # serverless.yml @@ -53,7 +56,7 @@ functions: # Your "Functions" usersCreate: handler: hello.hello # The code to call as a response to the event events: # The "Events" that trigger this function - - http: + - http: path: /hello ``` diff --git a/docs/providers/kubeless/guide/packaging.md b/docs/providers/kubeless/guide/packaging.md index 2b788756c..b138cebc8 100644 --- a/docs/providers/kubeless/guide/packaging.md +++ b/docs/providers/kubeless/guide/packaging.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/packaging) + # Kubeless - Packaging @@ -51,7 +53,7 @@ previously excluded files and directories. Exclude all node_modules but then re-include a specific modules (in this case node-fetch) using `exclude` exclusively -``` yml +```yml package: exclude: - node_modules/** @@ -60,7 +62,7 @@ package: Exclude all files but `handler.js` using `exclude` and `include` -``` yml +```yml package: exclude: - src/** diff --git a/docs/providers/kubeless/guide/quick-start.md b/docs/providers/kubeless/guide/quick-start.md index 7af1071f5..2fea9ac08 100644 --- a/docs/providers/kubeless/guide/quick-start.md +++ b/docs/providers/kubeless/guide/quick-start.md @@ -12,8 +12,8 @@ layout: Doc 1. Node.js `v6.5.0` or later. 2. Serverless CLI `v1.20` or later. You can run -`npm install -g serverless` to install it. -3. Install Kubeless & Dependencies(./installation.md). + `npm install -g serverless` to install it. +3. Install Kubeless & Dependencies(./installation.md). ## Create a new service @@ -32,36 +32,37 @@ $ npm install 1. **Deploy the Service** - Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. +Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. - ```bash - serverless deploy -v - ``` +```bash +serverless deploy -v +``` 2. **Deploy the Function** - Use this to quickly upload and overwrite your function code, allowing you to develop faster. +Use this to quickly upload and overwrite your function code, allowing you to develop faster. - ```bash - serverless deploy function -f capitalize - ``` +```bash +serverless deploy function -f capitalize +``` 3. **Invoke the Function** - Invokes the Function and returns results. +Invokes the Function and returns results. - ```bash - $ serverless invoke --function capitalize --data '"WELCOME TO KUBELESS!"' -l - # results +```bash +$ serverless invoke --function capitalize --data '"WELCOME TO KUBELESS!"' -l +# results "Welcome To Kubeless!" - ``` +``` 4. **Fetch the Function Logs** - Open up a separate tab in your console and stream all logs for a specific Function using this command. - ```bash - serverless logs -f capitalize -t - ``` +Open up a separate tab in your console and stream all logs for a specific Function using this command. + +```bash +serverless logs -f capitalize -t +``` ## Cleanup diff --git a/docs/providers/kubeless/guide/services.md b/docs/providers/kubeless/guide/services.md index a34b0d2fe..81095ed0b 100644 --- a/docs/providers/kubeless/guide/services.md +++ b/docs/providers/kubeless/guide/services.md @@ -7,25 +7,27 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/services) + # Kubeless - Services -A `service` in the Serverless Framework is like a project ((not to be confused with [Kubernetes Services](https://kubernetes.io/docs/concepts/services-networking/service/). It's where you define your Kubeless Functions and the `events` that trigger them, all in a file called `serverless.yml`. +A `service` in the Serverless Framework is like a project ((not to be confused with [Kubernetes Services](https://kubernetes.io/docs/concepts/services-networking/service/). It's where you define your Kubeless Functions and the `events` that trigger them, all in a file called `serverless.yml`. To get started building your first Serverless Framework project, create a `service`. ## Organization -In the beginning of an application, many people use a single Service to define all of the Functions and Events. This is what we recommend in the beginning. +In the beginning of an application, many people use a single Service to define all of the Functions and Events. This is what we recommend in the beginning. ```bash myService/ serverless.yml # Contains all functions and infrastructure resources ``` -However, as your application grows, you can break it out into multiple services. A lot of people organize their services by workflows or data models, and group the functions related to those workflows and data models together in the service. +However, as your application grows, you can break it out into multiple services. A lot of people organize their services by workflows or data models, and group the functions related to those workflows and data models together in the service. ```bash users/ @@ -35,11 +37,12 @@ posts/ comments/ serverless.yml # Contains 4 functions that do Comments CRUD operations and the Comments database ``` + This makes sense since related functions usually use common infrastructure resources, and you want to keep those functions and resources together as a single unit of deployment, for better organization and separation of concerns. ## Creation -To create a service, use the `create` command. You must also pass in a runtime (e.g., node.js, python etc.) you would like to write the service in. You can also pass in a path to create a directory and auto-name your service: +To create a service, use the `create` command. You must also pass in a runtime (e.g., node.js, python etc.) you would like to write the service in. You can also pass in a path to create a directory and auto-name your service: ```bash # Create service with the Python template in the folder ./new-project @@ -48,14 +51,15 @@ $ serverless create --template kubeless-python --path new-project Here are the available runtimes for Kubeless using the Serverless plugin: -* kubeless-python -* kubeless-nodejs +- kubeless-python +- kubeless-nodejs Check out the [create command docs](../cli-reference/create) for all the details and options. ## Contents You'll see the following files in your working directory: + - `serverless.yml` - `handler.py` - `package.json` @@ -66,11 +70,11 @@ Each `service` configuration is managed in the `serverless.yml` file. The main r - Declare a Serverless service - Define one or more functions in the service - - Define the provider the service will be deployed to (in our case, kubeless) - - Define any custom plugins to be used (in our case, we will need to use the serverless-kubeless plugin) - - Define events that trigger each function to execute (e.g. HTTP requests) - - Allow events listed in the `events` section to automatically create the resources required for the event upon deployment - - Allow flexible configuration using Serverless Variables + - Define the provider the service will be deployed to (in our case, kubeless) + - Define any custom plugins to be used (in our case, we will need to use the serverless-kubeless plugin) + - Define events that trigger each function to execute (e.g. HTTP requests) + - Allow events listed in the `events` section to automatically create the resources required for the event upon deployment + - Allow flexible configuration using Serverless Variables You can see the name of the service, the provider configuration and the first function inside the `functions` definition which points to the `handler.py` file. Any further service configuration will be done in this file. @@ -112,7 +116,7 @@ To deploy a service, use the `deploy` command: serverless deploy ``` -Check out the [deployment guide](https://serverless.com/framework/docs/providers/kubeless/guide/deploying/) to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy) for all the details and options. +Check out the [deployment guide](https://serverless.com/framework/docs/providers/kubeless/guide/deploying/) to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy) for all the details and options. ## Removal @@ -164,4 +168,4 @@ provider: runtime: python2.7 … -``` \ No newline at end of file +``` diff --git a/docs/providers/kubeless/guide/workflow.md b/docs/providers/kubeless/guide/workflow.md index 8d78f10d8..5653e82e0 100644 --- a/docs/providers/kubeless/guide/workflow.md +++ b/docs/providers/kubeless/guide/workflow.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/guide/workflow) + # Kubeless - Workflow @@ -24,43 +26,55 @@ Intro. Quick recommendations and tips for various processes. 6. Write tests to run locally. ### Larger Projects -* Break your application/project into multiple Serverless Services. -* Model your Serverless Services around Data Models or Workflows. -* Keep the Functions and Resources in your Serverless Services to a minimum. + +- Break your application/project into multiple Serverless Services. +- Model your Serverless Services around Data Models or Workflows. +- Keep the Functions and Resources in your Serverless Services to a minimum. ## Cheat Sheet + A handy list of commands to use when developing with the Serverless Framework. ##### Create A Service: + Creates a new Service ``` serverless create -p [SERVICE NAME] -t kubeless-python ``` + ``` serverless create -p [SERVICE NAME] -t kubeless-nodejs ``` ##### Deploy All + Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. + ``` serverless deploy ``` ##### Deploy Function + Use this to quickly overwrite your Kubeless Functinos, allowing you to develop faster. + ``` serverless deploy function -f [FUNCTION NAME] ``` ##### Invoke Function + Invokes an Kubeless Function and returns logs. + ``` serverless invoke function -f [FUNCTION NAME] -l ``` ##### Streaming Logs + Open up a separate tab in your console and stream all logs for a specific Function using this command. + ``` serverless logs -f [FUNCTION NAME] ``` diff --git a/docs/providers/openwhisk/README.md b/docs/providers/openwhisk/README.md index f6da72ed7..7c025fd5c 100644 --- a/docs/providers/openwhisk/README.md +++ b/docs/providers/openwhisk/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/) + # Apache OpenWhisk Provider Documentation diff --git a/docs/providers/openwhisk/cli-reference/README.md b/docs/providers/openwhisk/cli-reference/README.md index e0452d707..b2c87c74f 100644 --- a/docs/providers/openwhisk/cli-reference/README.md +++ b/docs/providers/openwhisk/cli-reference/README.md @@ -5,12 +5,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/) + # Serverless Apache OpenWhisk CLI Reference -Welcome to the Serverless Apache OpenWhisk CLI Reference! Please select a section on the left to get started. +Welcome to the Serverless Apache OpenWhisk CLI Reference! Please select a section on the left to get started. **Note:** Before continuing [Apache OpenWhisk system credentials](../guide/credentials.md) are required for using the CLI. diff --git a/docs/providers/openwhisk/cli-reference/config-credentials.md b/docs/providers/openwhisk/cli-reference/config-credentials.md index 27c1d0890..4413ae9dd 100644 --- a/docs/providers/openwhisk/cli-reference/config-credentials.md +++ b/docs/providers/openwhisk/cli-reference/config-credentials.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/config-credentials) + # OpenWhisk - Config Credentials diff --git a/docs/providers/openwhisk/cli-reference/create.md b/docs/providers/openwhisk/cli-reference/create.md index f0bb2a4cb..e6ce3777b 100644 --- a/docs/providers/openwhisk/cli-reference/create.md +++ b/docs/providers/openwhisk/cli-reference/create.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/create) + # OpenWhisk - Create @@ -27,6 +29,7 @@ serverless create --template openwhisk-nodejs --path myService ``` ## Options + - `--template` or `-t` The name of one of the available templates. **Required if --template-url and --template-path are not present**. - `--template-url` or `-u` The name of one of the available templates. **Required if --template and --template-path are not present**. - `--template-path` The local path of your template. **Required if --template and --template-url are not present**. @@ -34,6 +37,7 @@ serverless create --template openwhisk-nodejs --path myService - `--name` or `-n` the name of the service in `serverless.yml`. ## Provided lifecycle events + - `create:create` ## Available Templates diff --git a/docs/providers/openwhisk/cli-reference/deploy-function.md b/docs/providers/openwhisk/cli-reference/deploy-function.md index bcbcbd584..d16c58def 100644 --- a/docs/providers/openwhisk/cli-reference/deploy-function.md +++ b/docs/providers/openwhisk/cli-reference/deploy-function.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/deploy-function) + # OpenWhisk - Deploy Function -The `sls deploy function` command deploys an individual function. This command simply compiles a deployment package with a single function handler. This is a much faster way of deploying changes in code. +The `sls deploy function` command deploys an individual function. This command simply compiles a deployment package with a single function handler. This is a much faster way of deploying changes in code. ```bash serverless deploy function -f functionName @@ -22,4 +24,5 @@ serverless deploy function -f functionName properties such as environment variables and events will **not** be deployed. ## Options + - `--function` or `-f` The name of the function which should be deployed diff --git a/docs/providers/openwhisk/cli-reference/deploy.md b/docs/providers/openwhisk/cli-reference/deploy.md index 52be99c56..aa0753e45 100644 --- a/docs/providers/openwhisk/cli-reference/deploy.md +++ b/docs/providers/openwhisk/cli-reference/deploy.md @@ -7,18 +7,21 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/deploy) + # OpenWhisk - Deploy -The `sls deploy` command deploys your entire service via the Apache OpenWhisk platform API. Run this command when you have made service changes (i.e., you edited `serverless.yml`). Use `serverless deploy function -f myFunction` when you have made code changes and you want to quickly upload your updated code to Apache OpenWhisk. +The `sls deploy` command deploys your entire service via the Apache OpenWhisk platform API. Run this command when you have made service changes (i.e., you edited `serverless.yml`). Use `serverless deploy function -f myFunction` when you have made code changes and you want to quickly upload your updated code to Apache OpenWhisk. ```bash serverless deploy ``` ## Options + - `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--noDeploy` or `-n` Skips the deployment steps and leaves artifacts in the `.serverless` directory - `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. @@ -40,6 +43,7 @@ This is the simplest deployment usage possible. With this command Serverless wil OpenWhisk platform endpoints. ## Provided lifecycle events + - `deploy:cleanup` - `deploy:initialize` - `deploy:setupProviderConfiguration` diff --git a/docs/providers/openwhisk/cli-reference/info.md b/docs/providers/openwhisk/cli-reference/info.md index f9312c96c..a06b68f50 100644 --- a/docs/providers/openwhisk/cli-reference/info.md +++ b/docs/providers/openwhisk/cli-reference/info.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/info) + # OpenWhisk - Info @@ -19,9 +21,11 @@ serverless info ``` ## Options + - `--verbose` or `-v` Shows displays any Stack Output. ## Provided lifecycle events + - `info:info` ## Examples diff --git a/docs/providers/openwhisk/cli-reference/install.md b/docs/providers/openwhisk/cli-reference/install.md index a92475c13..a43a74fe9 100644 --- a/docs/providers/openwhisk/cli-reference/install.md +++ b/docs/providers/openwhisk/cli-reference/install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/install) + # OpenWhisk - Install @@ -19,10 +21,12 @@ serverless install --url https://github.com/some/service ``` ## Options + - `--url` or `-u` The services GitHub URL. **Required**. - `--name` or `-n` Name for the service. ## Provided lifecycle events + - `install:install` ## Examples diff --git a/docs/providers/openwhisk/cli-reference/invoke-local.md b/docs/providers/openwhisk/cli-reference/invoke-local.md index 4bb1ab52d..d393521c0 100644 --- a/docs/providers/openwhisk/cli-reference/invoke-local.md +++ b/docs/providers/openwhisk/cli-reference/invoke-local.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/invoke-local) + # OpenWhisk - Invoke Local @@ -18,13 +20,14 @@ This runs your code locally by emulating the Apache OpenWhisk environment. Pleas serverless invoke local --function functionName ``` -__*Please note that only the JavaScript and Python runtimes are supported with this command.*__ +**_Please note that only the JavaScript and Python runtimes are supported with this command._** ## Options - `--function` or `-f` The name of the function in your service that you want to invoke locally. **Required**. - `--path` or `-p` The path to a json file holding input data to be passed to the invoked function. This path is relative to the root directory of the service. The json file should have event and context properties to hold your mocked event and context data. - `--data` or `-d` String data to be passed as an event to your function. Keep in mind that if you pass both `--path` and `--data`, the data included in the `--path` file will overwrite the data you passed with the `--data` flag. + * `--env` or `-e` String representing an environment variable to set when invoking your function, in the form `=`. Can be repeated for more than one environment variable. ## Examples diff --git a/docs/providers/openwhisk/cli-reference/invoke.md b/docs/providers/openwhisk/cli-reference/invoke.md index b007ad4f6..8435db20a 100644 --- a/docs/providers/openwhisk/cli-reference/invoke.md +++ b/docs/providers/openwhisk/cli-reference/invoke.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/invoke) + # OpenWhisk - Invoke @@ -19,6 +21,7 @@ serverless invoke [local] --function functionName ``` ## Options + - `--function` or `-f` The name of the function in your service that you want to invoke. **Required**. - `--data` or `-d` String data to be passed as an event to your function. By default data is read from standard input. - `--path` or `-p` The path to a json file with input data to be passed to the invoked function. This path is relative to the root directory of the service. @@ -26,6 +29,7 @@ serverless invoke [local] --function functionName - `--log` or `-l` If set to `true` and invocation type is `RequestResponse`, it will output logging data of the invocation. Default is `false`. ## Provided lifecycle events + - `invoke:invoke` # Invoke Local @@ -37,9 +41,10 @@ serverless invoke local --function functionName ``` ## Options + - `--function` or `-f` The name of the function in your service that you want to invoke locally. **Required**. - `--path` or `-p` The path to a json file holding input data to be passed to the invoked function. This path is relative to the -root directory of the service. The json file should have event and context properties to hold your mocked event and context data. + root directory of the service. The json file should have event and context properties to hold your mocked event and context data. - `--data` or `-d` String data to be passed as an event to your function. Keep in mind that if you pass both `--path` and `--data`, the data included in the `--path` file will overwrite the data you passed with the `--data` flag. ## Examples diff --git a/docs/providers/openwhisk/cli-reference/login.md b/docs/providers/openwhisk/cli-reference/login.md index 366bae169..6963da5a3 100644 --- a/docs/providers/openwhisk/cli-reference/login.md +++ b/docs/providers/openwhisk/cli-reference/login.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/login) + # Login diff --git a/docs/providers/openwhisk/cli-reference/logs.md b/docs/providers/openwhisk/cli-reference/logs.md index 48966c72f..59b37ae74 100644 --- a/docs/providers/openwhisk/cli-reference/logs.md +++ b/docs/providers/openwhisk/cli-reference/logs.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/logs) + # OpenWhisk - Logs @@ -56,11 +58,13 @@ serverless logs -f hello ```bash serverless logs -f hello --startTime 5h ``` + This will fetch the logs that happened in the past 5 hours. ```bash serverless logs -f hello --startTime 1469694264 ``` + This will fetch the logs that happened starting at epoch `1469694264`. ```bash @@ -72,4 +76,5 @@ Serverless will tail the platform log output and print new log messages coming i ```bash serverless logs -f hello --filter serverless ``` + This will fetch only the logs that contain the string `serverless` diff --git a/docs/providers/openwhisk/cli-reference/plugin-install.md b/docs/providers/openwhisk/cli-reference/plugin-install.md index 7dd6983ab..e2c0d8b40 100644 --- a/docs/providers/openwhisk/cli-reference/plugin-install.md +++ b/docs/providers/openwhisk/cli-reference/plugin-install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/plugin-install) + # Plugin Install @@ -22,9 +24,11 @@ serverless plugin install --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:install:install` ## Examples diff --git a/docs/providers/openwhisk/cli-reference/plugin-list.md b/docs/providers/openwhisk/cli-reference/plugin-list.md index cd9e53ce6..05733a1ab 100644 --- a/docs/providers/openwhisk/cli-reference/plugin-list.md +++ b/docs/providers/openwhisk/cli-reference/plugin-list.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/plugin-list) + # Plugin List @@ -19,7 +21,9 @@ serverless plugin list ``` ## Options -- *None* + +- _None_ ## Provided lifecycle events + - `plugin:list:list` diff --git a/docs/providers/openwhisk/cli-reference/plugin-search.md b/docs/providers/openwhisk/cli-reference/plugin-search.md index 6a62b91b9..cc1e8a4c3 100644 --- a/docs/providers/openwhisk/cli-reference/plugin-search.md +++ b/docs/providers/openwhisk/cli-reference/plugin-search.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/plugin-search) + # Plugin Search @@ -19,9 +21,11 @@ serverless plugin search --query query ``` ## Options + - `--query` or `-q` The query you want to use for your search. **Required**. ## Provided lifecycle events + - `plugin:search:search` ## Examples diff --git a/docs/providers/openwhisk/cli-reference/plugin-uninstall.md b/docs/providers/openwhisk/cli-reference/plugin-uninstall.md index ebd58731a..62418afce 100644 --- a/docs/providers/openwhisk/cli-reference/plugin-uninstall.md +++ b/docs/providers/openwhisk/cli-reference/plugin-uninstall.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/plugin-uninstall) + # Plugin Uninstall @@ -19,9 +21,11 @@ serverless plugin uninstall --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:uninstall:uninstall` ## Examples diff --git a/docs/providers/openwhisk/cli-reference/print.md b/docs/providers/openwhisk/cli-reference/print.md index b48882404..f8aade890 100644 --- a/docs/providers/openwhisk/cli-reference/print.md +++ b/docs/providers/openwhisk/cli-reference/print.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/print) + # Print @@ -42,13 +44,13 @@ custom: functions: hello: - handler: handler.hello - events: - - schedule: ${self:custom.globalSchedule} + handler: handler.hello + events: + - schedule: ${self:custom.globalSchedule} world: - handler: handler.world - events: - - schedule: ${self:custom.globalSchedule} + handler: handler.world + events: + - schedule: ${self:custom.globalSchedule} ``` Using `sls print` will resolve the variables in the `schedule` blocks. diff --git a/docs/providers/openwhisk/cli-reference/remove.md b/docs/providers/openwhisk/cli-reference/remove.md index f531ce51e..fd2f7c58a 100644 --- a/docs/providers/openwhisk/cli-reference/remove.md +++ b/docs/providers/openwhisk/cli-reference/remove.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/remove) + # OpenWhisk - Remove @@ -19,6 +21,7 @@ serverless remove ``` ## Provided lifecycle events + - `remove:remove` ## Examples diff --git a/docs/providers/openwhisk/cli-reference/slstats.md b/docs/providers/openwhisk/cli-reference/slstats.md index e07997b14..e7d7b4509 100644 --- a/docs/providers/openwhisk/cli-reference/slstats.md +++ b/docs/providers/openwhisk/cli-reference/slstats.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/slstats) + # Serverless Statistics @@ -19,10 +21,12 @@ serverless slstats --enable ``` ## Options + - `--enable` or `-e`. - `--disable` or `-d` ## Provided lifecycle events + - `slstats:slstats` ## Examples diff --git a/docs/providers/openwhisk/events/README.md b/docs/providers/openwhisk/events/README.md index e63b685cc..d8f397c22 100644 --- a/docs/providers/openwhisk/events/README.md +++ b/docs/providers/openwhisk/events/README.md @@ -1,11 +1,13 @@ + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/events/) + # Serverless Apache OpenWhisk Events diff --git a/docs/providers/openwhisk/events/apigateway.md b/docs/providers/openwhisk/events/apigateway.md index e448ceb01..78b32c41d 100644 --- a/docs/providers/openwhisk/events/apigateway.md +++ b/docs/providers/openwhisk/events/apigateway.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/events/apigateway) + # API Gateway @@ -144,8 +146,8 @@ functions: This feature comes with the following restrictions: -- *Path parameters are only supported when `resp` is configured as`http`.* -- *Individual path parameter values are not included as separate event parameters. Users have to manually parse values from the full `__ow_path` value.* +- _Path parameters are only supported when `resp` is configured as`http`._ +- _Individual path parameter values are not included as separate event parameters. Users have to manually parse values from the full `__ow_path` value._ ### Security diff --git a/docs/providers/openwhisk/events/cloudant.md b/docs/providers/openwhisk/events/cloudant.md index 2f8cccc3e..918cfabda 100644 --- a/docs/providers/openwhisk/events/cloudant.md +++ b/docs/providers/openwhisk/events/cloudant.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/events/cloudant) + # Cloudant -This event allows you to connect functions to [IBM Cloudant](https://cloudant.com/), a NoSQL database-as-a-service based upon [Apache CouchDB](http://couchdb.apache.org/). Functions are invoked for each database modification that occurs. +This event allows you to connect functions to [IBM Cloudant](https://cloudant.com/), a NoSQL database-as-a-service based upon [Apache CouchDB](http://couchdb.apache.org/). Functions are invoked for each database modification that occurs. This event utilise the trigger feed provided by the [Cloudant package](https://github.com/openwhisk/openwhisk-package-cloudant). @@ -24,7 +26,7 @@ This event utilise the trigger feed provided by the [Cloudant package](https://g /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1 ``` -## Configuration +## Configuration Users need to pass the database credentials and the database name to listen to changes on when defining the event. @@ -37,13 +39,12 @@ Developers only need to add the database to listen to for each event. ```yaml # serverless.yaml functions: - index: - handler: users.main - events: - - cloudant: - package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1 - db: db_name - + index: + handler: users.main + events: + - cloudant: + package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1 + db: db_name ``` The configuration will create a trigger called `${serviceName}_${fnName}_cloudant_${db}` and a rule called `${serviceName}_${fnName}_cloudant_${db}_rule` to bind the function to the database update events. @@ -57,14 +58,14 @@ Authentication credentials for the Cloudant event source can be defined explicit ```yaml # serverless.yaml functions: - index: - handler: users.main - events: - - cloudant: - host: xxx-yyy-zzz-bluemix.cloudant.com - username: USERNAME - password: PASSWORD - db: db_name + index: + handler: users.main + events: + - cloudant: + host: xxx-yyy-zzz-bluemix.cloudant.com + username: USERNAME + password: PASSWORD + db: db_name ``` ### Adding Optional Parameters @@ -73,7 +74,7 @@ The following optional feed parameters are also supported: - `max` - Maximum number of triggers to fire. Defaults to infinite. - `filter` - Filter function defined on a design document. -- `query` - Optional query parameters for the filter function. +- `query` - Optional query parameters for the filter function. ```yaml # serverless.yaml @@ -81,10 +82,10 @@ functions: index: handler: users.main events: - - cloudant: + - cloudant: ... - max: 10000 - query: + max: 10000 + query: status: new filter: mailbox/by_status ``` @@ -99,15 +100,15 @@ functions: index: handler: users.main events: - - cloudant: + - cloudant: package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1 db: my_db trigger: db_events - rule: connect_index_to_db + rule: connect_index_to_db another: handler: users.another events: - - trigger: db_events + - trigger: db_events ``` ## Event Details @@ -122,12 +123,12 @@ The JSON representation of the trigger event is as follows: ```json { - "id": "6ca436c44074c4c2aa6a40c9a188b348", - "seq": "2-g1AAAAL9aJyV-GJCaEuqx4-BktQkYp_dmIfC", - "changes": [ - { - "rev": "2-da3f80848a480379486fb4a2ad98fa16" - } - ] + "id": "6ca436c44074c4c2aa6a40c9a188b348", + "seq": "2-g1AAAAL9aJyV-GJCaEuqx4-BktQkYp_dmIfC", + "changes": [ + { + "rev": "2-da3f80848a480379486fb4a2ad98fa16" + } + ] } ``` diff --git a/docs/providers/openwhisk/events/messagehub.md b/docs/providers/openwhisk/events/messagehub.md index 833660d07..7afa116b9 100644 --- a/docs/providers/openwhisk/events/messagehub.md +++ b/docs/providers/openwhisk/events/messagehub.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/events/messagehub) + # Message Hub @@ -24,7 +26,7 @@ This event utilise the trigger feed provided by the [Message Hub package](https: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1 ``` -## Configuration +## Configuration Users need to pass the message hub credentials and the kafka topic to listen for messages on when defining the event. @@ -37,16 +39,15 @@ Developers only need to add the kafka topic to listen for messages on with each ```yaml # serverless.yaml functions: - index: - handler: users.main - events: - - message_hub: - package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1 - topic: my_kafka_topic - + index: + handler: users.main + events: + - message_hub: + package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1 + topic: my_kafka_topic ``` -*Optional parameters `json`, `binary_key`, `binary_value` are also supported.* +_Optional parameters `json`, `binary_key`, `binary_value` are also supported._ The configuration will create a trigger called `${serviceName}_${fnName}_messagehub_${db}` and a rule called `${serviceName}_${fnName}_messagehub_${db}_rule` to bind the function to the database update events. @@ -59,18 +60,18 @@ Authentication credentials for the Message Hub event source can be defined expli ```yaml # serverless.yaml functions: - index: - handler: users.main - events: - - message_hub: - topic: my_kafka_topic - brokers: afka01-prod01.messagehub.services.us-south.bluemix.net:9093 - user: USERNAME - password: PASSWORD - admin_url: https://kafka-admin-prod01.messagehub.services.us-south.bluemix.net:443 - json: true - binary_key: true - binary_value: true + index: + handler: users.main + events: + - message_hub: + topic: my_kafka_topic + brokers: afka01-prod01.messagehub.services.us-south.bluemix.net:9093 + user: USERNAME + password: PASSWORD + admin_url: https://kafka-admin-prod01.messagehub.services.us-south.bluemix.net:443 + json: true + binary_key: true + binary_value: true ``` `topic`, `brokers`, `user`, `password` and `admin_url` are mandatory parameters. @@ -85,34 +86,35 @@ functions: index: handler: users.main events: - - message_hub: + - message_hub: package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1 topic: my_kafka_topic trigger: log_events - rule: connect_index_to_kafka + rule: connect_index_to_kafka another: handler: users.another events: - - trigger: log_events + - trigger: log_events ``` ## Event Details -The payload of that trigger event will contain a `messages` field which is an array of messages that have been posted since the last time your trigger fired. +The payload of that trigger event will contain a `messages` field which is an array of messages that have been posted since the last time your trigger fired. The JSON representation of a sample event is as follows: ```json { - "messages": [ - { - "partition": 0, - "key": "U29tZSBrZXk=", - "offset": 421760, - "topic": "mytopic", - "value": "Some value" - } - ] + "messages": [ + { + "partition": 0, + "key": "U29tZSBrZXk=", + "offset": 421760, + "topic": "mytopic", + "value": "Some value" + } + ] } ``` + For more details on the exact semantics of the message properties, please see the [trigger feed documentation](https://github.com/openwhisk/openwhisk-package-kafka). diff --git a/docs/providers/openwhisk/events/schedule.md b/docs/providers/openwhisk/events/schedule.md index 86f2690fe..7edad0ba3 100644 --- a/docs/providers/openwhisk/events/schedule.md +++ b/docs/providers/openwhisk/events/schedule.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/events/schedule) + # Schedule @@ -35,8 +37,7 @@ functions: - schedule: cron(* * * * *) // run every minute ``` -This automatically generates a new trigger (``${service}_crawl_schedule_trigger`) -and rule (`${service}_crawl_schedule_rule`) during deployment. +This automatically generates a new trigger (``\${service}\_crawl_schedule_trigger`) and rule (`\${service}\_crawl_schedule_rule`) during deployment. ### Customise Parameters diff --git a/docs/providers/openwhisk/events/triggers.md b/docs/providers/openwhisk/events/triggers.md index f7e465b31..6c97a0c89 100644 --- a/docs/providers/openwhisk/events/triggers.md +++ b/docs/providers/openwhisk/events/triggers.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/events/streams) + # Triggers @@ -30,6 +32,7 @@ functions: events: - trigger: my_trigger ``` + This configuration will create a trigger called `servicename-my_trigger` with an active rule binding `my_function` to this event stream. ## Customising Rules @@ -42,8 +45,8 @@ functions: handler: index.main events: - trigger: - name: "my_trigger" - rule: "rule_name" + name: 'my_trigger' + rule: 'rule_name' ``` ## Customising Triggers diff --git a/docs/providers/openwhisk/examples/README.md b/docs/providers/openwhisk/examples/README.md index efb151111..25c9bb174 100644 --- a/docs/providers/openwhisk/examples/README.md +++ b/docs/providers/openwhisk/examples/README.md @@ -5,19 +5,21 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/examples/) + # Serverless Apache OpenWhisk Examples Have an example? Submit a PR or [open an issue](https://github.com/serverless/examples/issues). ⚡️ -| Example | Runtime | -| :--------------------------------------- | :------ | -| [OpenWhisk Node Simple](https://serverless.com/examples/openwhisk-node-simple/)
    Boilerplate project repository for OpenWhisk provider with Serverless Framework. | nodeJS | -| [OpenWhisk Python Simple](https://serverless.com/examples/openwhisk-python-simple/)
    Boilerplate project repository for OpenWhisk provider with Serverless Framework. | python | -| [OpenWhisk PHP Simple](https://serverless.com/examples/openwhisk-php-simple/)
    Boilerplate project repository for OpenWhisk provider with Serverless Framework. | php | -| [OpenWhisk Ruby Simple](https://serverless.com/examples/openwhisk-ruby-simple/)
    Boilerplate project repository for OpenWhisk provider with Serverless Framework. | ruby | -| [OpenWhisk Swift Simple](https://serverless.com/examples/openwhisk-swift-simple/)
    Boilerplate project repository for OpenWhisk provider with Serverless Framework. | swift | +| Example | Runtime | +| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | +| [OpenWhisk Node Simple](https://serverless.com/examples/openwhisk-node-simple/)
    Boilerplate project repository for OpenWhisk provider with Serverless Framework. | nodeJS | +| [OpenWhisk Python Simple](https://serverless.com/examples/openwhisk-python-simple/)
    Boilerplate project repository for OpenWhisk provider with Serverless Framework. | python | +| [OpenWhisk PHP Simple](https://serverless.com/examples/openwhisk-php-simple/)
    Boilerplate project repository for OpenWhisk provider with Serverless Framework. | php | +| [OpenWhisk Ruby Simple](https://serverless.com/examples/openwhisk-ruby-simple/)
    Boilerplate project repository for OpenWhisk provider with Serverless Framework. | ruby | +| [OpenWhisk Swift Simple](https://serverless.com/examples/openwhisk-swift-simple/)
    Boilerplate project repository for OpenWhisk provider with Serverless Framework. | swift | If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://forum.serverless.com/) diff --git a/docs/providers/openwhisk/examples/hello-world/README.md b/docs/providers/openwhisk/examples/hello-world/README.md index 81a38b238..ba5ef113a 100644 --- a/docs/providers/openwhisk/examples/hello-world/README.md +++ b/docs/providers/openwhisk/examples/hello-world/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/examples/hello-world/) + # Hello World Serverless Example 🌍 @@ -15,10 +17,10 @@ Welcome to the Hello World example. Pick your language of choice: -* [JavaScript](./node) -* [PHP](./php) -* [Python](./python) -* [Ruby](./ruby) -* [Swift](./swift) +- [JavaScript](./node) +- [PHP](./php) +- [Python](./python) +- [Ruby](./ruby) +- [Swift](./swift) [View all examples](https://www.serverless.com/framework/docs/providers/openwhisk/examples/) diff --git a/docs/providers/openwhisk/examples/hello-world/node/README.md b/docs/providers/openwhisk/examples/hello-world/node/README.md index c2d1ab8ce..4b532e83b 100644 --- a/docs/providers/openwhisk/examples/hello-world/node/README.md +++ b/docs/providers/openwhisk/examples/hello-world/node/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/examples/hello-world/node/) + # Hello World Node.js Example @@ -14,15 +16,19 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). ## 1. Create a service -`serverless create --template openwhisk-nodejs --path myService` or `sls create --template openwhisk-nodejs --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +`serverless create --template openwhisk-nodejs --path myService` or `sls create --template openwhisk-nodejs --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. ## 2. Install Provider Plugin -`npm install ` in the service directory. + +`npm install` in the service directory. ## 3. Deploy + `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ## 4. Invoke deployed function + `serverless invoke --function helloWorld` or `serverless invoke -f helloWorld` `-f` is shorthand for `--function` diff --git a/docs/providers/openwhisk/examples/hello-world/node/handler.js b/docs/providers/openwhisk/examples/hello-world/node/handler.js index 98edcb3ea..0687ec854 100644 --- a/docs/providers/openwhisk/examples/hello-world/node/handler.js +++ b/docs/providers/openwhisk/examples/hello-world/node/handler.js @@ -1,7 +1,7 @@ 'use strict'; // Your function handler -module.exports.helloWorldHandler = function (params) { +module.exports.helloWorldHandler = function(params) { const name = params.name || 'World'; return { payload: `Hello, ${name}!` }; }; diff --git a/docs/providers/openwhisk/examples/hello-world/node/serverless.yml b/docs/providers/openwhisk/examples/hello-world/node/serverless.yml index 3b24c663b..85806026b 100644 --- a/docs/providers/openwhisk/examples/hello-world/node/serverless.yml +++ b/docs/providers/openwhisk/examples/hello-world/node/serverless.yml @@ -10,6 +10,6 @@ functions: events: - http: GET hello -# remember to run npm install to download the provider plugin. +# remember to run npm install to download the provider plugin. plugins: - - serverless-openwhisk + - serverless-openwhisk diff --git a/docs/providers/openwhisk/examples/hello-world/php/README.md b/docs/providers/openwhisk/examples/hello-world/php/README.md index 944b0e6e3..d9f9cf8da 100644 --- a/docs/providers/openwhisk/examples/hello-world/php/README.md +++ b/docs/providers/openwhisk/examples/hello-world/php/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/examples/hello-world/php/) + # Hello World PHP Example @@ -14,15 +16,19 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). ## 1. Create a service -`serverless create --template openwhisk-php --path myService` or `sls create --template openwhisk-php --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +`serverless create --template openwhisk-php --path myService` or `sls create --template openwhisk-php --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. ## 2. Install Provider Plugin + Run `npm install` in the service directory. ## 3. Deploy + `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ## 4. Invoke deployed function + `serverless invoke --function helloWorld` or `serverless invoke -f helloWorld` `-f` is shorthand for `--function` diff --git a/docs/providers/openwhisk/examples/hello-world/php/serverless.yml b/docs/providers/openwhisk/examples/hello-world/php/serverless.yml index c81933e48..817c49472 100644 --- a/docs/providers/openwhisk/examples/hello-world/php/serverless.yml +++ b/docs/providers/openwhisk/examples/hello-world/php/serverless.yml @@ -11,6 +11,6 @@ functions: events: - http: GET hello -# remember to run npm install to download the provider plugin. +# remember to run npm install to download the provider plugin. plugins: - - serverless-openwhisk + - serverless-openwhisk diff --git a/docs/providers/openwhisk/examples/hello-world/python/README.md b/docs/providers/openwhisk/examples/hello-world/python/README.md index dd09ceed7..a835598d5 100644 --- a/docs/providers/openwhisk/examples/hello-world/python/README.md +++ b/docs/providers/openwhisk/examples/hello-world/python/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/examples/hello-world/python/) + # Hello World Python Example @@ -14,15 +16,19 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). ## 1. Create a service -`serverless create --template openwhisk-python --path myService` or `sls create --template openwhisk-python --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +`serverless create --template openwhisk-python --path myService` or `sls create --template openwhisk-python --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. ## 2. Install Provider Plugin + `npm install` in the service directory. ## 3. Deploy + `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ## 4. Invoke deployed function + `serverless invoke --function helloWorld` or `serverless invoke -f helloWorld` `-f` is shorthand for `--function` diff --git a/docs/providers/openwhisk/examples/hello-world/python/serverless.yml b/docs/providers/openwhisk/examples/hello-world/python/serverless.yml index 23dbe7a93..589deb408 100644 --- a/docs/providers/openwhisk/examples/hello-world/python/serverless.yml +++ b/docs/providers/openwhisk/examples/hello-world/python/serverless.yml @@ -11,6 +11,6 @@ functions: events: - http: GET hello -# remember to run npm install to download the provider plugin. +# remember to run npm install to download the provider plugin. plugins: - - serverless-openwhisk + - serverless-openwhisk diff --git a/docs/providers/openwhisk/examples/hello-world/ruby/README.md b/docs/providers/openwhisk/examples/hello-world/ruby/README.md index 24a6ad805..0986f31e4 100644 --- a/docs/providers/openwhisk/examples/hello-world/ruby/README.md +++ b/docs/providers/openwhisk/examples/hello-world/ruby/README.md @@ -15,17 +15,17 @@ This is a template Ruby service for the OpenWhisk platform. Before you can deplo Before you can deploy your service to OpenWhisk, you need to have an account registered with the platform. -- *Want to run the platform locally?* Please read the project's [*Quick Start*](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. -- *Want to use a hosted provider?* Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). +- _Want to run the platform locally?_ Please read the project's [_Quick Start_](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. +- _Want to use a hosted provider?_ Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). Account credentials for OpenWhisk can be provided through a configuration file or environment variables. This plugin requires the API endpoint, namespace and authentication credentials. -**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. +**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. **Do you want to use environment variables for credentials?** Use the following environment variables to be pass in account credentials. These values override anything extracted from the configuration file. -- *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` -- *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy +- _OW_APIHOST_ - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` +- _OW_AUTH_ - Authentication key, e.g. `xxxxxx:yyyyy ### Have you installed the provider plugin? @@ -45,8 +45,6 @@ Use the `serverless` command to deploy your service. The sample `handler.js` fil serverless deploy ``` - - ### Issues / Feedback / Feature Requests? If you have any issues, comments or want to see new features, please file an issue in the project repository: diff --git a/docs/providers/openwhisk/examples/hello-world/swift/README.md b/docs/providers/openwhisk/examples/hello-world/swift/README.md index 55904219f..04b43bb41 100644 --- a/docs/providers/openwhisk/examples/hello-world/swift/README.md +++ b/docs/providers/openwhisk/examples/hello-world/swift/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/examples/hello-world/swift/) + # Hello World Swift Example @@ -14,15 +16,19 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). ## 1. Create a service -`serverless create --template openwhisk-swift --path myService` or `sls create --template openwhisk-swift --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +`serverless create --template openwhisk-swift --path myService` or `sls create --template openwhisk-swift --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. ## 2. Install Provider Plugin + `npm install` in the service directory. ## 3. Deploy + `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ## 4. Invoke deployed function + `serverless invoke --function helloWorld` or `serverless invoke -f helloWorld` `-f` is shorthand for `--function` diff --git a/docs/providers/openwhisk/examples/hello-world/swift/serverless.yml b/docs/providers/openwhisk/examples/hello-world/swift/serverless.yml index 2a8f2a56c..a0c372c96 100644 --- a/docs/providers/openwhisk/examples/hello-world/swift/serverless.yml +++ b/docs/providers/openwhisk/examples/hello-world/swift/serverless.yml @@ -11,6 +11,6 @@ functions: events: - http: GET hello -# remember to run npm install to download the provider plugin. +# remember to run npm install to download the provider plugin. plugins: - - serverless-openwhisk + - serverless-openwhisk diff --git a/docs/providers/openwhisk/guide/README.md b/docs/providers/openwhisk/guide/README.md index 8861c09b7..e7015357b 100644 --- a/docs/providers/openwhisk/guide/README.md +++ b/docs/providers/openwhisk/guide/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/) + # Serverless Apache OpenWhisk Guide diff --git a/docs/providers/openwhisk/guide/credentials.md b/docs/providers/openwhisk/guide/credentials.md index 02abd7dc6..5fa8d579a 100644 --- a/docs/providers/openwhisk/guide/credentials.md +++ b/docs/providers/openwhisk/guide/credentials.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/credentials) + # OpenWhisk - Credentials @@ -26,23 +28,23 @@ Here's how to get started… - Sign up for a free account @ [IBM Cloud](https://console.bluemix.net/) -IBM Cloud comes with a [lite account](https://console.bluemix.net/registration/) that does not need credit card details to register. Lite accounts provide free access to certain platform services and do not expire after a limited time period. +IBM Cloud comes with a [lite account](https://console.bluemix.net/registration/) that does not need credit card details to register. Lite accounts provide free access to certain platform services and do not expire after a limited time period. **All IBM Cloud users get access to the [Free Tier for IBM Cloud Functions](https://console.ng.bluemix.net/openwhisk/learn/pricing). This includes 400,000 GB-seconds of serverless function compute time per month.** -Additional execution time is charged at $0.000017 per GB-second of execution, rounded to the nearest 100ms. +Additional execution time is charged at \$0.000017 per GB-second of execution, rounded to the nearest 100ms. ### Install the IBM Cloud CLI Following the [instructions on this page](https://console.bluemix.net/docs/cli/index.html#overview) to download and install the IBM Cloud CLI. -*On Linux, you can run this command:* +_On Linux, you can run this command:_ ``` curl -fsSL https://clis.ng.bluemix.net/install/linux | sh ``` -*On OS X, you can run this command:* +_On OS X, you can run this command:_ ``` curl -fsSL https://clis.ng.bluemix.net/install/osx | sh @@ -78,7 +80,7 @@ ibmcloud wsk property get --auth #### Regions -Cloud Functions is available with the following regions US-South (`api.ng.bluemix.net`), London (`api.eu-gb.bluemix.net`), Frankfurt (` api.eu-de.bluemix.net`). Use the appropriate [API endpoint](https://console.bluemix.net/docs/overview/ibm-cloud.html#ov_intro_reg) to target Cloud Functions in that region. +Cloud Functions is available with the following regions US-South (`api.ng.bluemix.net`), London (`api.eu-gb.bluemix.net`), Frankfurt (`api.eu-de.bluemix.net`). Use the appropriate [API endpoint](https://console.bluemix.net/docs/overview/ibm-cloud.html#ov_intro_reg) to target Cloud Functions in that region. #### Organisations and Spaces @@ -86,7 +88,7 @@ Organisations and spaces for your account can be viewed on this page: [https://c Accounts normally have a default organisation using the account email address. Default space name is usually `dev`. -*After running the login command, authentication credentials will be stored in the `.wskprops` file under your home directory.* +_After running the login command, authentication credentials will be stored in the `.wskprops` file under your home directory._ ## Register with OpenWhisk platform (Self-Hosted) @@ -108,15 +110,14 @@ cd openwhisk/tools/vagrant This platform will now be running inside a virtual machine at the following IP address: `192.168.33.13` -**Please note:** *If you are using a self-hosted platform, the `ignore_certs` property in `serverless.yaml` needs to be `true`. This allows the client to be used against local deployments of OpenWhisk with a self-signed certificate.* +**Please note:** _If you are using a self-hosted platform, the `ignore_certs` property in `serverless.yaml` needs to be `true`. This allows the client to be used against local deployments of OpenWhisk with a self-signed certificate._ ```yaml service: testing provider: name: openwhisk ignore_certs: true -functions: - ... +functions: ... ``` ### Access Account Credentials @@ -177,7 +178,7 @@ Credentials are stored in `~/.wskprops`, which you can edit directly if needed. ##### Edit file manually -The following configuration values should be stored in a new file (`.wskprops`) in your home directory. Replace the `PLATFORM_API_HOST`, `USER_AUTH_KEY` and (optionally) `ACCESS_TOKEN` values will the credentials from above. +The following configuration values should be stored in a new file (`.wskprops`) in your home directory. Replace the `PLATFORM_API_HOST`, `USER_AUTH_KEY` and (optionally) `ACCESS_TOKEN` values will the credentials from above. ``` APIHOST=PLATFORM_API_HOST diff --git a/docs/providers/openwhisk/guide/deploying.md b/docs/providers/openwhisk/guide/deploying.md index d403c679e..c286e00a2 100644 --- a/docs/providers/openwhisk/guide/deploying.md +++ b/docs/providers/openwhisk/guide/deploying.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/deploying) + # OpenWhisk - Deploying -The Serverless Framework was designed to provision your Apache OpenWhisk Functions, Triggers and Rules safely and quickly. It does this via a couple of methods designed for different types of deployments. +The Serverless Framework was designed to provision your Apache OpenWhisk Functions, Triggers and Rules safely and quickly. It does this via a couple of methods designed for different types of deployments. ## Deploy All @@ -24,23 +26,23 @@ serverless deploy Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to Apache OpenWhisk. -**Note:** You can specify a different configuration file name with the the `--config` option. +**Note:** You can specify a different configuration file name with the the `--config` option. ### How It Works The Serverless Framework translates all syntax in `serverless.yml` to [platform API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/openwhisk/openwhisk/master/core/controller/src/main/resources/whiskswagger.json) calls to provision your Actions, Triggers, Rules and APIs. -* Provider plugin parses `serverless.yml` configuration and translates to OpenWhisk resources. -* The code of your Functions is then packaged into zip files. -* Resources are deployed in the following order: *Functions, Function Sequences, API Routes, Triggers, Feeds, Rules.* -* Resources stages are deployed sequentially due to potential dependencies between the stages. -* Resources within a stage are deployed in parallel. -* Stages without any resources defined will be skipped. +- Provider plugin parses `serverless.yml` configuration and translates to OpenWhisk resources. +- The code of your Functions is then packaged into zip files. +- Resources are deployed in the following order: _Functions, Function Sequences, API Routes, Triggers, Feeds, Rules._ +- Resources stages are deployed sequentially due to potential dependencies between the stages. +- Resources within a stage are deployed in parallel. +- Stages without any resources defined will be skipped. ### Tips -* Use this in your CI/CD systems, as it is the safest method of deployment. -* Apache OpenWhisk has a [maximum action artifact](http://bit.ly/2vQIC9V) size of 48MB. This might be an issue if you are using lots of NPM packages. JavaScript build tools like webpack can help to minify your code and save space. +- Use this in your CI/CD systems, as it is the safest method of deployment. +- Apache OpenWhisk has a [maximum action artifact](http://bit.ly/2vQIC9V) size of 48MB. This might be an issue if you are using lots of NPM packages. JavaScript build tools like webpack can help to minify your code and save space. Check out the [deploy command docs](../cli-reference/deploy.md) for all details and options. @@ -54,13 +56,13 @@ serverless deploy function --function myFunction ### How It Works -* The Framework packages up the targeted Apache OpenWhisk Action into a zip file. -* That zip file is deployed to Apache OpenWhisk using the platform API. +- The Framework packages up the targeted Apache OpenWhisk Action into a zip file. +- That zip file is deployed to Apache OpenWhisk using the platform API. ### Tips -* Use this when you are developing and want to test on Apache OpenWhisk because it's much faster. -* During development, people will often run this command several times, as opposed to `serverless deploy` which is only run when larger infrastructure provisioning is required. +- Use this when you are developing and want to test on Apache OpenWhisk because it's much faster. +- During development, people will often run this command several times, as opposed to `serverless deploy` which is only run when larger infrastructure provisioning is required. Check out the [deploy command docs](../cli-reference/deploy.md) for all details and options. diff --git a/docs/providers/openwhisk/guide/events.md b/docs/providers/openwhisk/guide/events.md index b2ad809d1..33b86866d 100644 --- a/docs/providers/openwhisk/guide/events.md +++ b/docs/providers/openwhisk/guide/events.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/events) + # OpenWhisk - Events @@ -45,12 +47,12 @@ functions: events: # All events associated with this function - http: GET /users/create - http: POST /users/update - - trigger: "custom trigger" + - trigger: 'custom trigger' ``` ## Types -The Serverless Framework supports all of the Apache OpenWhisk events and more. Instead of listing them here, we've put them in a separate section, since they have a lot of configurations and functionality. [Check out the events section for more information.](../events) +The Serverless Framework supports all of the Apache OpenWhisk events and more. Instead of listing them here, we've put them in a separate section, since they have a lot of configurations and functionality. [Check out the events section for more information.](../events) ## Deploying diff --git a/docs/providers/openwhisk/guide/functions.md b/docs/providers/openwhisk/guide/functions.md index 263057f8c..8c38f3595 100644 --- a/docs/providers/openwhisk/guide/functions.md +++ b/docs/providers/openwhisk/guide/functions.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/functions) + # OpenWhisk - Functions -If you are using OpenWhisk as a provider, all *functions* inside the service are OpenWhisk Actions. +If you are using OpenWhisk as a provider, all _functions_ inside the service are OpenWhisk Actions. ## Configuration @@ -43,7 +45,7 @@ The `handler` property points to the file and module containing the code you wan ```javascript // handler.js -exports.handler = function(params) {} +exports.handler = function(params) {}; ``` You can add as many functions as you want within this property. @@ -104,8 +106,7 @@ You can specify an array of functions, which is useful if you separate your func ```yml # serverless.yml -... - +--- functions: - ${file(./foo-functions.yml)} - ${file(./bar-functions.yml)} @@ -123,7 +124,7 @@ deleteFoo: OpenWhisk provides a concept called "packages" to manage related actions. Packages can contain multiple actions under a common identifier in a namespace. Configuration values needed by all actions in a package can be set as default properties on the package, rather than individually on each action. -*Packages are identified using the following format:* `/namespaceName/packageName/actionName`. +_Packages are identified using the following format:_ `/namespaceName/packageName/actionName`. ### Implicit Packages @@ -133,10 +134,10 @@ Functions can be assigned to packages by setting the function `name` with a pack functions: foo: handler: handler.foo - name: "myPackage/foo" + name: 'myPackage/foo' bar: handler: handler.bar - name: "myPackage/bar" + name: 'myPackage/bar' ``` In this example, two new actions (`foo` & `bar`) will be created using the `myPackage` package. @@ -151,7 +152,7 @@ Packages can also be defined explicitly to set shared configuration parameters. functions: foo: handler: handler.foo - name: "myPackage/foo" + name: 'myPackage/foo' resources: packages: @@ -161,11 +162,11 @@ resources: hello: world ``` -*Explicit packages support the following properties: `name`, `parameters`, `annotations` and `shared`.* +_Explicit packages support the following properties: `name`, `parameters`, `annotations` and `shared`._ ## Binding Services (IBM Cloud Functions) -***This feature requires the [IBM Cloud CLI](https://console.bluemix.net/docs/cli/reference/bluemix_cli/download_cli.html#download_install) and [IBM Cloud Functions plugin](https://console.bluemix.net/openwhisk/learn/cli) to be installed.*** +**_This feature requires the [IBM Cloud CLI](https://console.bluemix.net/docs/cli/reference/bluemix_cli/download_cli.html#download_install) and [IBM Cloud Functions plugin](https://console.bluemix.net/openwhisk/learn/cli) to be installed._** IBM Cloud Functions supports [automatic binding of service credentials](https://console.bluemix.net/docs/openwhisk/binding_services.html#binding_services) to actions using the CLI. @@ -176,7 +177,7 @@ This feature is also available through the `serverless.yaml` file using the `bin ```yaml functions: my_function: - handler: file_name.handler + handler: file_name.handler bind: - service: name: cloud-object-storage @@ -186,10 +187,10 @@ functions: The `service` configuration supports the following properties. - `name`: identifier for the cloud service -- `instance`: instance name for service (*optional*) -- `key`: key name for instance and service (*optional*) +- `instance`: instance name for service (_optional_) +- `key`: key name for instance and service (_optional_) -*If the `instance` or `key` properties are missing, the first available instance and key found will be used.* +_If the `instance` or `key` properties are missing, the first available instance and key found will be used._ Binding services removes the need to manually create default parameters for service keys from platform services. @@ -206,6 +207,7 @@ resources: name: cloud-object-storage instance: my-cos-storage ``` + ## Runtimes The OpenWhisk provider plugin supports the following runtimes. diff --git a/docs/providers/openwhisk/guide/installation.md b/docs/providers/openwhisk/guide/installation.md index c7f1cb0b5..861f2c6e9 100644 --- a/docs/providers/openwhisk/guide/installation.md +++ b/docs/providers/openwhisk/guide/installation.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/installation) + # OpenWhisk - Installation diff --git a/docs/providers/openwhisk/guide/intro.md b/docs/providers/openwhisk/guide/intro.md index c37b6e3f6..fd6b4b951 100644 --- a/docs/providers/openwhisk/guide/intro.md +++ b/docs/providers/openwhisk/guide/intro.md @@ -7,16 +7,19 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/intro) + # OpenWhisk - Introduction -The Serverless Framework helps you develop and deploy serverless applications using Apache OpenWhisk. It's a CLI that offers structure, automation and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). +The Serverless Framework helps you develop and deploy serverless applications using Apache OpenWhisk. It's a CLI that offers structure, automation and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). The Serverless Framework is different than other application frameworks because: -* It manages your code as well as your infrastructure -* It supports multiple languages (Node.js, Python, PHP, Ruby, Swift, Java, and more) + +- It manages your code as well as your infrastructure +- It supports multiple languages (Node.js, Python, PHP, Ruby, Swift, Java, and more) ## Core Concepts @@ -24,30 +27,30 @@ Here are the Framework's main concepts and how they pertain to Apache OpenWhisk ### Functions -A Function is an [Apache OpenWhisk Action](http://bit.ly/2wMfe3s). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: +A Function is an [Apache OpenWhisk Action](http://bit.ly/2wMfe3s). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: -* *Saving a user to the database* -* *Processing a file in a database* -* *Performing a scheduled task* +- _Saving a user to the database_ +- _Processing a file in a database_ +- _Performing a scheduled task_ -You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. +You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. ### Events -Anything that triggers an Apache OpenWhisk Action to execute is regarded by the Framework as an **Event**. Events are platform events on Apache OpenWhisk such as: +Anything that triggers an Apache OpenWhisk Action to execute is regarded by the Framework as an **Event**. Events are platform events on Apache OpenWhisk such as: -* *An API Gateway HTTP endpoint (e.g., for a REST API)* -* *A NoSQL database update (e.g., for a user profile)* -* *A scheduled timer (e.g., run every 5 minutes)* -* *A Kafka queue message (e.g., a message)* -* *A Webhook fires (e.g., Github project update)* -* *And more...* +- _An API Gateway HTTP endpoint (e.g., for a REST API)_ +- _A NoSQL database update (e.g., for a user profile)_ +- _A scheduled timer (e.g., run every 5 minutes)_ +- _A Kafka queue message (e.g., a message)_ +- _A Webhook fires (e.g., Github project update)_ +- _And more..._ When you define an event for your Apache OpenWhisk Action in the Serverless Framework, the Framework will automatically translate this into [Triggers and Rules](http://bit.ly/2xQmFE8) needed for that event and configure your functions to listen to it. ### Services -A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml # serverless.yml @@ -62,11 +65,12 @@ functions: # Your "Functions" events: - http: delete /users/delete ``` + When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` (or the file specified with the `--config` option) is deployed at once. ### Plugins -You can overwrite or extend the functionality of the Framework using **Plugins**. Every `serverless.yml` can contain a `plugins:` property, which features multiple plugins. +You can overwrite or extend the functionality of the Framework using **Plugins**. Every `serverless.yml` can contain a `plugins:` property, which features multiple plugins. ```yml # serverless.yml diff --git a/docs/providers/openwhisk/guide/packaging.md b/docs/providers/openwhisk/guide/packaging.md index 0ad78da8a..35825add7 100644 --- a/docs/providers/openwhisk/guide/packaging.md +++ b/docs/providers/openwhisk/guide/packaging.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/packaging) + # OpenWhisk - Packaging @@ -47,7 +49,7 @@ previously excluded files and directories. Exclude all node_modules but then re-include a specific modules (in this case node-fetch) using `exclude` exclusively -``` yml +```yml package: exclude: - node_modules/** @@ -56,7 +58,7 @@ package: Exclude all files but `handler.js` using `exclude` and `include` -``` yml +```yml package: exclude: - src/** diff --git a/docs/providers/openwhisk/guide/plugins.md b/docs/providers/openwhisk/guide/plugins.md index c0d27405f..a0b769921 100644 --- a/docs/providers/openwhisk/guide/plugins.md +++ b/docs/providers/openwhisk/guide/plugins.md @@ -7,19 +7,21 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/plugins) + # OpenWhisk - Plugins -A Plugin is custom Javascript code that creates new or extends existing commands within the Serverless Framework. The Serverless Framework is merely a group of Plugins that are provided in the core. If you or your organization have a specific workflow, install a pre-written Plugin or write a plugin to customize the Framework to your needs. External Plugins are written exactly the same way as the core Plugins. +A Plugin is custom Javascript code that creates new or extends existing commands within the Serverless Framework. The Serverless Framework is merely a group of Plugins that are provided in the core. If you or your organization have a specific workflow, install a pre-written Plugin or write a plugin to customize the Framework to your needs. External Plugins are written exactly the same way as the core Plugins. - [How to create serverless plugins - Part 1](https://serverless.com/blog/writing-serverless-plugins/) - [How to create serverless plugins - Part 2](https://serverless.com/blog/writing-serverless-plugins-2/) ## Installing Plugins -External Plugins are added on a per service basis and are not applied globally. Make sure you are in your Service's root directory, then install the corresponding Plugin with the help of NPM: +External Plugins are added on a per service basis and are not applied globally. Make sure you are in your Service's root directory, then install the corresponding Plugin with the help of NPM: ``` npm install --save custom-serverless-plugin @@ -33,9 +35,11 @@ We need to tell Serverless that we want to use the plugin inside our service. We plugins: - custom-serverless-plugin ``` + The `plugins` section supports two formats: Array object: + ```yml plugins: - plugin1 @@ -43,6 +47,7 @@ plugins: ``` Enhanced plugins object: + ```yml plugins: localPath: './custom_serverless_plugins' @@ -66,18 +71,21 @@ custom: If you are working on a plugin or have a plugin that is just designed for one project they can be loaded from the local folder. Local plugins can be added in the `plugins` array in `serverless.yml`. By default local plugins can be added to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. + ```yml plugins: - custom-serverless-plugin ``` Local plugins folder can be changed by enhancing `plugins` object: + ```yml plugins: localPath: './custom_serverless_plugins' modules: - custom-serverless-plugin ``` + The `custom-serverless-plugin` will be loaded from the `custom_serverless_plugins` directory at the root of your service. If the `localPath` is not provided or empty `.serverless_plugins` directory will be taken as the `localPath`. The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `localPath` folder (`.serverless_plugins` by default). @@ -102,15 +110,15 @@ In this case `plugin1` is loaded before `plugin2`. #### Plugin -Code which defines *Commands*, any *Events* within a *Command*, and any *Hooks* assigned to an *Lifecycle Event*. +Code which defines _Commands_, any _Events_ within a _Command_, and any _Hooks_ assigned to an _Lifecycle Event_. -* Command // CLI configuration, commands, subcommands, options - * LifecycleEvent(s) // Events that happen sequentially when the command is run - * Hook(s) // Code that runs when a Lifecycle Event happens during a Command +- Command // CLI configuration, commands, subcommands, options + - LifecycleEvent(s) // Events that happen sequentially when the command is run + - Hook(s) // Code that runs when a Lifecycle Event happens during a Command #### Command -A CLI *Command* that can be called by a user, e.g. `serverless deploy`. A Command has no logic, but simply defines the CLI configuration (e.g. command, subcommands, parameters) and the *Lifecycle Events* for the command. Every command defines its own lifecycle events. +A CLI _Command_ that can be called by a user, e.g. `serverless deploy`. A Command has no logic, but simply defines the CLI configuration (e.g. command, subcommands, parameters) and the _Lifecycle Events_ for the command. Every command defines its own lifecycle events. ```javascript 'use strict'; @@ -119,10 +127,7 @@ class MyPlugin { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ] + lifecycleEvents: ['resources', 'functions'], }, }; } @@ -133,7 +138,7 @@ module.exports = MyPlugin; #### Lifecycle Events -Events that fire sequentially during a Command. The above example list two Events. However, for each Event, and additional `before` and `after` event is created. Therefore, six Events exist in the above example: +Events that fire sequentially during a Command. The above example list two Events. However, for each Event, and additional `before` and `after` event is created. Therefore, six Events exist in the above example: - `before:deploy:resources` - `deploy:resources` @@ -155,17 +160,14 @@ class Deploy { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ] + lifecycleEvents: ['resources', 'functions'], }, }; this.hooks = { 'before:deploy:resources': this.beforeDeployResources, 'deploy:resources': this.deployResources, - 'after:deploy:functions': this.afterDeployFunctions + 'after:deploy:functions': this.afterDeployFunctions, }; } @@ -196,20 +198,14 @@ class MyPlugin { constructor() { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - 'functions' - ], + lifecycleEvents: ['resources', 'functions'], commands: { function: { - lifecycleEvents: [ - 'package', - 'deploy' - ], + lifecycleEvents: ['package', 'deploy'], }, }, }, - } + }; } } @@ -226,7 +222,7 @@ Option Shortcuts are passed in with a single dash (`-`) like this: `serverless f The `options` object will be passed in as the second parameter to the constructor of your plugin. -In it, you can optionally add a `shortcut` property, as well as a `required` property. The Framework will return an error if a `required` Option is not included. +In it, you can optionally add a `shortcut` property, as well as a `required` property. The Framework will return an error if a `required` Option is not included. **Note:** At this time, the Serverless Framework does not use parameters. @@ -240,22 +236,20 @@ class Deploy { this.commands = { deploy: { - lifecycleEvents: [ - 'functions' - ], + lifecycleEvents: ['functions'], options: { function: { usage: 'Specify the function you want to deploy (e.g. "--function myFunction")', shortcut: 'f', - required: true - } - } + required: true, + }, + }, }, }; this.hooks = { - 'deploy:functions': this.deployFunction.bind(this) - } + 'deploy:functions': this.deployFunction.bind(this), + }; } deployFunction() { @@ -287,21 +281,19 @@ class ProviderDeploy { this.commands = { deploy: { - lifecycleEvents: [ - 'functions' - ], + lifecycleEvents: ['functions'], options: { function: { usage: 'Specify the function you want to deploy (e.g. "--function myFunction")', - required: true - } - } + required: true, + }, + }, }, }; this.hooks = { - 'deploy:functions': this.deployFunction.bind(this) - } + 'deploy:functions': this.deployFunction.bind(this), + }; } deployFunction() { @@ -328,15 +320,13 @@ class MyPlugin { this.commands = { log: { - lifecycleEvents: [ - 'serverless' - ], + lifecycleEvents: ['serverless'], }, }; this.hooks = { - 'log:serverless': this.logServerless.bind(this) - } + 'log:serverless': this.logServerless.bind(this), + }; } logServerless() { diff --git a/docs/providers/openwhisk/guide/quick-start.md b/docs/providers/openwhisk/guide/quick-start.md index a62bf6419..953a9ce98 100644 --- a/docs/providers/openwhisk/guide/quick-start.md +++ b/docs/providers/openwhisk/guide/quick-start.md @@ -12,11 +12,11 @@ layout: Doc 1. Node.js `v6.5.0` or later. 2. Serverless CLI `v1.9.0` or later. You can run -`npm install -g serverless` to install it. + `npm install -g serverless` to install it. 3. An IBM Bluemix account. If you don't already have one, you can sign up for an [account](https://console.bluemix.net/registration/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). 4. **Set-up your [Provider Credentials](./credentials.md)**. 5. Install Framework & Dependencies -*Due to an [outstanding issue](https://github.com/serverless/serverless/issues/2895) with provider plugins, the [OpenWhisk provider](https://github.com/serverless/serverless-openwhisk) must be installed as a global module.* + _Due to an [outstanding issue](https://github.com/serverless/serverless/issues/2895) with provider plugins, the [OpenWhisk provider](https://github.com/serverless/serverless-openwhisk) must be installed as a global module._ ```bash $ npm install --global serverless serverless-openwhisk @@ -34,6 +34,7 @@ $ cd my-service # Install npm dependencies $ npm install ``` + **Using a self-hosted version of the platform?** Ensure you set the `ignore_certs` option in the `serverless.yaml` prior to deployment. @@ -48,44 +49,45 @@ provider: 1. **Deploy the Service** - Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. +Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. - ```bash - serverless deploy -v - ``` +```bash +serverless deploy -v +``` 2. **Deploy the Function** - Use this to quickly upload and overwrite your function code, allowing you to develop faster. +Use this to quickly upload and overwrite your function code, allowing you to develop faster. - ```bash - serverless deploy function -f hello - ``` +```bash +serverless deploy function -f hello +``` 3. **Invoke the Function** - Invokes the Function and returns results. +Invokes the Function and returns results. - ```bash - serverless invoke --function hello - # results - { - "payload": "Hello, World!" - } +```bash +serverless invoke --function hello +# results +{ + "payload": "Hello, World!" +} - serverless invoke --function hello --data '{"name": "OpenWhisk"}' - #results - { - "payload": "Hello, OpenWhisk!" - } - ``` +serverless invoke --function hello --data '{"name": "OpenWhisk"}' +#results +{ + "payload": "Hello, OpenWhisk!" +} +``` 4. **Fetch the Function Logs** - Open up a separate tab in your console and stream all logs for a specific Function using this command. - ```bash - serverless logs -f hello -t - ``` +Open up a separate tab in your console and stream all logs for a specific Function using this command. + +```bash +serverless logs -f hello -t +``` ## Cleanup diff --git a/docs/providers/openwhisk/guide/serverless.yml.md b/docs/providers/openwhisk/guide/serverless.yml.md index 6ce224581..a399c698c 100644 --- a/docs/providers/openwhisk/guide/serverless.yml.md +++ b/docs/providers/openwhisk/guide/serverless.yml.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/serverless.yml) + # OpenWhisk - serverless.yml Reference @@ -19,7 +21,7 @@ Here is a list of all available properties in `serverless.yml` when the provider service: myService -frameworkVersion: ">=1.0.0 <2.0.0" +frameworkVersion: '>=1.0.0 <2.0.0' provider: name: openwhisk @@ -43,11 +45,11 @@ functions: overwrite: false # Can we overwrite deployed function? namespace: 'custom' # use custom namespace, defaults to '_' annotations: - parameter_name: value + parameter_name: value parameters: parameter_name: value events: # The Events that trigger this Function - # This creates an API Gateway HTTP endpoint which can be used to trigger this function. Learn more in "events/apigateway" + # This creates an API Gateway HTTP endpoint which can be used to trigger this function. Learn more in "events/apigateway" - http: METHOD /path/to/url - trigger: my_trigger # bind function to trigger event - trigger: diff --git a/docs/providers/openwhisk/guide/services.md b/docs/providers/openwhisk/guide/services.md index 901933c1b..21dec67a0 100644 --- a/docs/providers/openwhisk/guide/services.md +++ b/docs/providers/openwhisk/guide/services.md @@ -7,25 +7,27 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/services) + # OpenWhisk - Services -A `service` is like a project. It's where you define your Apache OpenWhisk Functions, the `events` that trigger them and any `resources` they require, all in a file called `serverless.yml`. +A `service` is like a project. It's where you define your Apache OpenWhisk Functions, the `events` that trigger them and any `resources` they require, all in a file called `serverless.yml`. To get started building your first Serverless Framework project, create a `service`. ## Organization -In the beginning of an application, many people use a single Service to define all of the Functions, Events and Resources for that project. This is what we recommend in the beginning. +In the beginning of an application, many people use a single Service to define all of the Functions, Events and Resources for that project. This is what we recommend in the beginning. ```bash myService/ serverless.yml # Contains all functions and infrastructure resources ``` -However, as your application grows, you can break it out into multiple services. A lot of people organize their services by workflows or data models, and group the functions related to those workflows and data models together in the service. +However, as your application grows, you can break it out into multiple services. A lot of people organize their services by workflows or data models, and group the functions related to those workflows and data models together in the service. ```bash users/ @@ -35,11 +37,12 @@ posts/ comments/ serverless.yml # Contains 4 functions that do Comments CRUD operations and the Comments database ``` + This makes sense since related functions usually use common infrastructure resources, and you want to keep those functions and resources together as a single unit of deployment, for better organization and separation of concerns. ## Creation -To create a service, use the `create` command. You must also pass in a runtime (e.g., node.js, python etc.) you would like to write the service in. You can also pass in a path to create a directory and auto-name your service: +To create a service, use the `create` command. You must also pass in a runtime (e.g., node.js, python etc.) you would like to write the service in. You can also pass in a path to create a directory and auto-name your service: ```bash # Create service with nodeJS template in the folder ./myService @@ -48,17 +51,18 @@ serverless create --template openwhisk-nodejs --path myService Here are the available runtimes for Apache OpenWhisk: -* openwhisk-nodejs -* openwhisk-php -* openwhisk-python -* openwhisk-ruby -* openwhisk-swift +- openwhisk-nodejs +- openwhisk-php +- openwhisk-python +- openwhisk-ruby +- openwhisk-swift Check out the [create command docs](../cli-reference/create) for all the details and options. ## Contents You'll see the following files in your working directory: + - `serverless.yml` - `handler.js` @@ -68,11 +72,11 @@ Each `service` configuration is managed in the `serverless.yml` file. The main r - Declare a Serverless service - Define one or more functions in the service - - Define the provider the service will be deployed to (and the runtime if provided) - - Define any custom plugins to be used - - Define events that trigger each function to execute (e.g. HTTP requests) - - Allow events listed in the `events` section to automatically create the resources required for the event upon deployment - - Allow flexible configuration using Serverless Variables + - Define the provider the service will be deployed to (and the runtime if provided) + - Define any custom plugins to be used + - Define events that trigger each function to execute (e.g. HTTP requests) + - Allow events listed in the `events` section to automatically create the resources required for the event upon deployment + - Allow flexible configuration using Serverless Variables You can see the name of the service, the provider configuration and the first function inside the `functions` definition which points to the `handler.js` file. Any further service configuration will be done in this file. @@ -93,7 +97,7 @@ functions: - http: post /users/create usersDelete: # A Function handler: users.delete - events: # The Events that trigger this Function + events: # The Events that trigger this Function - http: delete /users/delete ``` @@ -123,7 +127,7 @@ Then use the `deploy` command: serverless deploy ``` -Check out the [deployment guide](https://serverless.com/framework/docs/providers/openwhisk/guide/deploying/) to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy) for all the details and options. +Check out the [deployment guide](https://serverless.com/framework/docs/providers/openwhisk/guide/deploying/) to learn more about deployments and how they work. Or, check out the [deploy command docs](../cli-reference/deploy) for all the details and options. ## Removal @@ -181,7 +185,6 @@ provider: … ``` - ## Installing Serverless in an existing service If you already have a Serverless service, and would prefer to lock down the framework version using `package.json`, then you can install Serverless as follows: @@ -196,11 +199,13 @@ npm install serverless --save-dev To execute the locally installed Serverless executable you have to reference the binary out of the node modules directory. Example: + ``` node node_modules/.bin/serverless deploy ``` Or with npx (bundled with npm >= 5.2.0) + ``` npx serverless deploy ``` diff --git a/docs/providers/openwhisk/guide/testing.md b/docs/providers/openwhisk/guide/testing.md index 128cb3316..8a676456c 100644 --- a/docs/providers/openwhisk/guide/testing.md +++ b/docs/providers/openwhisk/guide/testing.md @@ -7,22 +7,24 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/testing) + # OpenWhisk - Testing -While the Serverless Architecture introduces a lot of simplicity when it comes to serving business logic, some of its characteristics present challenges for testing. They are: +While the Serverless Architecture introduces a lot of simplicity when it comes to serving business logic, some of its characteristics present challenges for testing. They are: -* The Serverless Architecture is an integration of separate, distributed services, which must be tested both independently, and together. -* The Serverless Architecture is dependent on internet/cloud services, which are hard to emulate locally. -* The Serverless Architecture can feature event-driven, asynchronous workflows, which are hard to emulate entirely. +- The Serverless Architecture is an integration of separate, distributed services, which must be tested both independently, and together. +- The Serverless Architecture is dependent on internet/cloud services, which are hard to emulate locally. +- The Serverless Architecture can feature event-driven, asynchronous workflows, which are hard to emulate entirely. To get through these challenges, and to keep the [test pyramid](http://martinfowler.com/bliki/TestPyramid.html) in mind, keep the following principles in mind: -* Write your business logic so that it is separate from your FaaS provider (e.g., Apache OpenWhisk), to keep it provider-independent, reusable and more easily testable. -* When your business logic is written separately from the FaaS provider, you can write traditional Unit Tests to ensure it is working properly. -* Write Integration Tests to verify integrations with other services are working correctly. +- Write your business logic so that it is separate from your FaaS provider (e.g., Apache OpenWhisk), to keep it provider-independent, reusable and more easily testable. +- When your business logic is written separately from the FaaS provider, you can write traditional Unit Tests to ensure it is working properly. +- Write Integration Tests to verify integrations with other services are working correctly. ## A Poor Example @@ -32,14 +34,14 @@ Here is an example in Node.js of how to follow the practices above. The job this const db = require('db').connect(); const mailer = require('mailer'); -module.exports.saveUser = (params) => { +module.exports.saveUser = params => { return Promise((resolve, reject) => { const user = { email: params.email, - created_at: Date.now() - } + created_at: Date.now(), + }; - db.saveUser(user, function (err) { + db.saveUser(user, function(err) { if (err) { reject(err); } else { @@ -47,14 +49,14 @@ module.exports.saveUser = (params) => { resolve(); } }); - }) + }); }; ``` There are two main problems with this function: -* The business logic is not separate from the FaaS Provider. It's bounded to how Apache OpenWhisk passes incoming data (`params` object). -* Testing this function will rely on separate services. Specifically, running a database instance and a mail server. +- The business logic is not separate from the FaaS Provider. It's bounded to how Apache OpenWhisk passes incoming data (`params` object). +- Testing this function will rely on separate services. Specifically, running a database instance and a mail server. ## Writing Testable Apache OpenWhisk Functions @@ -71,10 +73,10 @@ class Users { return new Promise((resolve, reject) => { const user = { email: email, - created_at: Date.now() - } + created_at: Date.now(), + }; - this.db.saveUser(user, function (err) { + this.db.saveUser(user, function(err) { if (err) { reject(err); } else { @@ -82,7 +84,7 @@ class Users { resolve(); } }); - }) + }); } } @@ -96,13 +98,13 @@ const Users = require('users'); let users = new Users(db, mailer); -module.exports.saveUser = (params) => { +module.exports.saveUser = params => { return users.save(params.email); }; ``` -Now, the above class keeps business logic separate. Further, the code responsible for setting up dependencies, injecting them, calling business logic functions and interacting with Apache OpenWhisk is in its own file, which will be changed less often. This way, the business logic is not provider dependent, easier to re-use, and easier to test. +Now, the above class keeps business logic separate. Further, the code responsible for setting up dependencies, injecting them, calling business logic functions and interacting with Apache OpenWhisk is in its own file, which will be changed less often. This way, the business logic is not provider dependent, easier to re-use, and easier to test. -Further, this code doesn't require running any external services. Instead of a real `db` and `mailer` services, we can pass mocks and assert if `saveUser` and `sendWelcomeEmail` has been called with proper arguments. +Further, this code doesn't require running any external services. Instead of a real `db` and `mailer` services, we can pass mocks and assert if `saveUser` and `sendWelcomeEmail` has been called with proper arguments. -Unit Tests can easily be written to cover the above class. An integration test can be added by invoking the function (`serverless invoke`) with fixture email address, check if user is actually saved to DB and check if email was received to see if everything is working together. +Unit Tests can easily be written to cover the above class. An integration test can be added by invoking the function (`serverless invoke`) with fixture email address, check if user is actually saved to DB and check if email was received to see if everything is working together. diff --git a/docs/providers/openwhisk/guide/variables.md b/docs/providers/openwhisk/guide/variables.md index 0face2d85..a7615e2c5 100644 --- a/docs/providers/openwhisk/guide/variables.md +++ b/docs/providers/openwhisk/guide/variables.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/variables) + # OpenWhisk - Variables @@ -24,6 +26,7 @@ The Serverless framework provides a powerful variable system which allows you to **Note:** You can only use variables in `serverless.yml` property **values**, not property keys. So you can't use variables to generate dynamic logical IDs in the custom resources section for example. ## Reference Properties In serverless.yml + To self-reference properties in `serverless.yml`, use the `${self:someProperty}` syntax in your `serverless.yml`. This functionality is recursive, so you can go as deep in the object tree as you want. ```yml @@ -34,18 +37,19 @@ custom: functions: hello: - handler: handler.hello - events: - - schedule: ${self:custom.globalSchedule} + handler: handler.hello + events: + - schedule: ${self:custom.globalSchedule} world: - handler: handler.world - events: - - schedule: ${self:custom.globalSchedule} + handler: handler.world + events: + - schedule: ${self:custom.globalSchedule} ``` In the above example you're setting a global schedule for all functions by referencing the `globalSchedule` property in the same `serverless.yml` file. This way, you can easily change the schedule for all functions whenever you like. ## Referencing Environment Variables + To reference environment variables, use the `${env:SOME_VAR}` syntax in your `serverless.yml` configuration file. ```yml @@ -53,16 +57,17 @@ service: new-service provider: openwhisk functions: hello: - name: ${env:FUNC_PREFIX}-hello - handler: handler.hello + name: ${env:FUNC_PREFIX}-hello + handler: handler.hello world: - name: ${env:FUNC_PREFIX}-world - handler: handler.world + name: ${env:FUNC_PREFIX}-world + handler: handler.world ``` In the above example you're dynamically adding a prefix to the function names by referencing the `FUNC_PREFIX` env var. So you can easily change that prefix for all functions by changing the `FUNC_PREFIX` env var. ## Referencing CLI Options + To reference CLI options that you passed, use the `${opt:some_option}` syntax in your `serverless.yml` configuration file. ```yml @@ -70,16 +75,17 @@ service: new-service provider: openwhisk functions: hello: - name: ${opt:stage}-hello - handler: handler.hello + name: ${opt:stage}-hello + handler: handler.hello world: - name: ${opt:stage}-world - handler: handler.world + name: ${opt:stage}-world + handler: handler.world ``` In the above example, you're dynamically adding a prefix to the function names by referencing the `stage` option that you pass in the CLI when you run `serverless deploy --stage dev`. So when you deploy, the function name will always include the stage you're deploying to. ## Reference Variables in other Files + To reference variables in other YAML or JSON files, use the `${file(./myFile.yml):someProperty}` syntax in your `serverless.yml` configuration file. Here's an example: ```yml @@ -94,16 +100,16 @@ provider: openwhisk custom: ${file(./myCustomFile.yml)} # You can reference the entire file functions: hello: - handler: handler.hello - events: - - schedule: ${file(./myCustomFile.yml):globalSchedule} # Or you can reference a specific property + handler: handler.hello + events: + - schedule: ${file(./myCustomFile.yml):globalSchedule} # Or you can reference a specific property world: - handler: handler.world - events: - - schedule: ${self:custom.globalSchedule} # This would also work in this case + handler: handler.world + events: + - schedule: ${self:custom.globalSchedule} # This would also work in this case ``` -In the above example, you're referencing the entire `myCustomFile.yml` file in the `custom` property. You need to pass the path relative to your service directory. You can also request specific properties in that file as shown in the `schedule` property. It's completely recursive and you can go as deep as you want. Additionally you can request properties that contain arrays from either YAML or JSON reference files. Here's a YAML example for an events array: +In the above example, you're referencing the entire `myCustomFile.yml` file in the `custom` property. You need to pass the path relative to your service directory. You can also request specific properties in that file as shown in the `schedule` property. It's completely recursive and you can go as deep as you want. Additionally you can request properties that contain arrays from either YAML or JSON reference files. Here's a YAML example for an events array: ```yml myevents: @@ -111,15 +117,19 @@ myevents: ``` and for JSON: + ```json { - "myevents": [{ - "schedule" : "cron(0 * * * *)" - }] + "myevents": [ + { + "schedule": "cron(0 * * * *)" + } + ] } ``` In your serverless.yml, depending on the type of your source file, either have the following syntax for YAML + ```yml functions: hello: @@ -128,6 +138,7 @@ functions: ``` or for a JSON reference file use this sytax: + ```yml functions: hello: @@ -146,9 +157,9 @@ References can be either named or unnamed exports. To use the exported `someModu ```js // scheduleConfig.js module.exports.cron = () => { - // Code that generates dynamic data - return 'cron(0 * * * *)'; -} + // Code that generates dynamic data + return 'cron(0 * * * *)'; +}; ``` ```js @@ -156,9 +167,9 @@ module.exports.cron = () => { module.exports = () => { return { property1: 'some value', - property2: 'some other value' - } -} + property2: 'some other value', + }; +}; ``` ```yml @@ -170,12 +181,12 @@ custom: ${file(./config.js)} functions: hello: - handler: handler.hello - events: - - schedule: ${file(./scheduleConfig.js):cron} # Reference a specific module + handler: handler.hello + events: + - schedule: ${file(./scheduleConfig.js):cron} # Reference a specific module ``` -You can also return an object and reference a specific property. Just make sure you are returning a valid object and referencing a valid property: +You can also return an object and reference a specific property. Just make sure you are returning a valid object and referencing a valid property: ```yml # serverless.yml @@ -183,19 +194,19 @@ service: new-service provider: openwhisk functions: scheduledFunction: - handler: handler.scheduledFunction - events: - - schedule: ${file(./myCustomFile.js):schedule.hour} + handler: handler.scheduledFunction + events: + - schedule: ${file(./myCustomFile.js):schedule.hour} ``` ```js // myCustomFile.js module.exports.schedule = () => { - // Code that generates dynamic data - return { - hour: 'cron(0 * * * *)' - }; -} + // Code that generates dynamic data + return { + hour: 'cron(0 * * * *)', + }; +}; ``` ## Multiple Configuration Files @@ -210,6 +221,7 @@ resources: The corresponding resources which are defined inside the `openwhisk-resources.json` file will be resolved and loaded into the `Resources` section. ## Nesting Variable References + The Serverless variable system allows you to nest variable references within each other for ultimate flexibility. So you can reference certain variables based on other variables. Here's an example: ```yml @@ -220,12 +232,13 @@ custom: functions: hello: - handler: handler.hello + handler: handler.hello ``` In the above example, if you pass `dev` as a stage option, the framework will look for the `dev_arn` environment variable. If you pass `production`, the framework will look for `production_arn`, and so on. This allows you to creatively use multiple variables by using a certain naming pattern without having to update the values of these variables constantly. You can go as deep as you want in your nesting, and can reference variables at any level of nesting from any source (env, opt, self or file). ## Overwriting Variables + The Serverless framework gives you an intuitive way to reference multiple variables as a fallback strategy in case one of the variables is missing. This way you'll be able to use a default value from a certain source, if the variable from another source is missing. For example, if you want to reference the stage you're deploying to, but you don't want to keep on providing the `stage` option in the CLI. What you can do in `serverless.yml` is: @@ -240,7 +253,7 @@ custom: functions: hello: - handler: handler.hello + handler: handler.hello ``` What this says is to use the `stage` CLI option if it exists, if not, use the default stage (which lives in `provider.stage`). So during development you can safely deploy with `serverless deploy`, but during production you can do `serverless deploy --stage production` and the stage will be picked up for you without having to make any changes to `serverless.yml`. @@ -248,6 +261,7 @@ What this says is to use the `stage` CLI option if it exists, if not, use the de You can have as many variable references as you want, from any source you want, and each of them can be of different type and different name. ## Migrating serverless.env.yml + Previously we used the `serverless.env.yml` file to track Serverless Variables. It was a completely different system with different concepts. To migrate your variables from `serverless.env.yml`, you'll need to decide where you want to store your variables. **Using a config file:** You can still use `serverless.env.yml`, but the difference now is that you can structure the file however you want, and you'll need to reference each variable/property correctly in `serverless.yml`. For more info, you can check the file reference section above. diff --git a/docs/providers/openwhisk/guide/web-actions.md b/docs/providers/openwhisk/guide/web-actions.md index 79b640b83..c318d71bf 100644 --- a/docs/providers/openwhisk/guide/web-actions.md +++ b/docs/providers/openwhisk/guide/web-actions.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/web-actions) + # OpenWhisk - Web Actions -Functions can be turned into ["*web actions*"](http://bit.ly/2xSRbOQ) which return HTTP content without use of an API Gateway. This feature is enabled by setting an annotation (`web-export`) in the configuration file. +Functions can be turned into ["_web actions_"](http://bit.ly/2xSRbOQ) which return HTTP content without use of an API Gateway. This feature is enabled by setting an annotation (`web-export`) in the configuration file. ``` functions: @@ -28,11 +30,11 @@ Functions with this annotation can be invoked through a URL template with the fo https://{APIHOST}/api/v1/web/{USER_NAMESPACE}/{PACKAGE}/{ACTION_NAME}.{TYPE} ``` -- *APIHOST* - platform endpoint e.g. *openwhisk.ng.bluemix.net.* -- *USER_NAMESPACE* - this must be an explicit namespace and cannot use the default namespace (_). -- *PACKAGE* - action package or `default`. -- *ACTION_NAME* - default form `${servicename}-${space}-${name}`. -- *TYPE* - `.json`, `.html`, `.text` or `.http`. +- _APIHOST_ - platform endpoint e.g. _openwhisk.ng.bluemix.net._ +- _USER_NAMESPACE_ - this must be an explicit namespace and cannot use the default namespace (\_). +- _PACKAGE_ - action package or `default`. +- _ACTION_NAME_ - default form `${servicename}-${space}-${name}`. +- _TYPE_ - `.json`, `.html`, `.text` or `.http`. Return values from the function are used to construct the HTTP response. The following parameters are supported. @@ -44,12 +46,11 @@ Here is an example of returning HTML content: ```javascript function main(args) { - var msg = "you didn't tell me who you are." - if (args.name) { - msg = `hello ${args.name}!` - } - return {body: - `

    ${msg}

    `} + var msg = 'you didn't tell me who you are.'; + if (args.name) { + msg = `hello ${args.name}!`; + } + return { body: `

    ${msg}

    ` }; } ``` diff --git a/docs/providers/openwhisk/guide/workflow.md b/docs/providers/openwhisk/guide/workflow.md index a4c9dde79..307c26ea0 100644 --- a/docs/providers/openwhisk/guide/workflow.md +++ b/docs/providers/openwhisk/guide/workflow.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/guide/workflow) + # OpenWhisk - Workflow @@ -24,45 +26,59 @@ Intro. Quick recommendations and tips for various processes. 6. Write tests to run locally. ### Larger Projects -* Break your application/project into multiple Serverless Services. -* Model your Serverless Services around Data Models or Workflows. -* Keep the Functions and Resources in your Serverless Services to a minimum. + +- Break your application/project into multiple Serverless Services. +- Model your Serverless Services around Data Models or Workflows. +- Keep the Functions and Resources in your Serverless Services to a minimum. ## Cheat Sheet + A handy list of commands to use when developing with the Serverless Framework. ##### Create A Service: + Creates a new Service + ``` serverless create -p [SERVICE NAME] -t openwhisk-nodejs ``` ##### Install A Service + This is a convenience method to install a pre-made Serverless Service locally by downloading the Github repo and unzipping it. + ``` serverless install -u [GITHUB URL OF SERVICE] ``` ##### Deploy All + Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. + ``` serverless deploy ``` ##### Deploy Function + Use this to quickly overwrite your OpenWhisk Actions, allowing you to develop faster. + ``` serverless deploy function -f [FUNCTION NAME] ``` ##### Invoke Function + Invokes an OpenWhisk Action and returns logs. + ``` serverless invoke function -f [FUNCTION NAME] -l ``` ##### Streaming Logs + Open up a separate tab in your console and stream all logs for a specific Function using this command. + ``` serverless logs -f [FUNCTION NAME] ``` diff --git a/docs/providers/spotinst/README.md b/docs/providers/spotinst/README.md index 945f713c5..2774f3965 100755 --- a/docs/providers/spotinst/README.md +++ b/docs/providers/spotinst/README.md @@ -5,7 +5,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/) + # Spotinst Provider Documentation diff --git a/docs/providers/spotinst/cli-reference/README.md b/docs/providers/spotinst/cli-reference/README.md index 340391623..e704ea7ae 100755 --- a/docs/providers/spotinst/cli-reference/README.md +++ b/docs/providers/spotinst/cli-reference/README.md @@ -6,7 +6,9 @@ menuOrder: 2 --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/) + # Serverless Spotinst Functions CLI Reference diff --git a/docs/providers/spotinst/cli-reference/config-credentials.md b/docs/providers/spotinst/cli-reference/config-credentials.md index 2876d5e46..ae464e075 100755 --- a/docs/providers/spotinst/cli-reference/config-credentials.md +++ b/docs/providers/spotinst/cli-reference/config-credentials.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/config-credentials) + # Spotinst Functions - Config Credentials @@ -26,4 +28,4 @@ serverless config credentials -p spotinst -a act-92879641 -t eyJ0eXAiOiJKV1QiLCJ - `--provider` or `-p` The provider name. - `--account` or `-a` Spotinst Account ID. -- `--token` or `-t` Spotinst Access Token. \ No newline at end of file +- `--token` or `-t` Spotinst Access Token. diff --git a/docs/providers/spotinst/cli-reference/create.md b/docs/providers/spotinst/cli-reference/create.md index fa4421d7e..64a86b00e 100755 --- a/docs/providers/spotinst/cli-reference/create.md +++ b/docs/providers/spotinst/cli-reference/create.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/create) + # Spotinst Functions - Create @@ -27,6 +29,7 @@ serverless create -t spotinst-nodejs -p myService ``` ## Options + - `--template` or `-t` The name of one of the available templates. **Required if --template-url and --template-path are not present**. - `--template-url` or `-u` The name of one of the available templates. **Required if --template and --template-path are not present**. - `--template-path` The local path of your template. **Required if --template and --template-url are not present**. @@ -61,7 +64,6 @@ serverless create -t spotinst-nodejs -n my-special-service This example will generate scaffolding for a service with `Spotinst` as a provider and `nodejs` as runtime. The scaffolding will be generated in the current working directory. - ### Creating a named service in a (new) directory ```bash diff --git a/docs/providers/spotinst/cli-reference/deploy-function.md b/docs/providers/spotinst/cli-reference/deploy-function.md index 97c60bad6..6430bbb83 100644 --- a/docs/providers/spotinst/cli-reference/deploy-function.md +++ b/docs/providers/spotinst/cli-reference/deploy-function.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/deploy-function) + # Spotinst Functions - Deploy Function @@ -16,8 +18,8 @@ The `sls deploy` function command deploys an individual function. This command s ## Note: Please update your Environment ID before deploying a function - 1. [Create an Environment](https://console.spotinst.com/functions) - 2. Update your `serverless.yml` file with the Environment ID +1. [Create an Environment](https://console.spotinst.com/functions) +2. Update your `serverless.yml` file with the Environment ID ```yml service: myService @@ -25,7 +27,7 @@ service: myService provider: name: spotinst spotinst: - environment: env-8f451a5f # NOTE: Remember to add the environment ID + environment: env-8f451a5f # NOTE: Remember to add the environment ID functions: hello: @@ -56,8 +58,8 @@ serverless deploy function -f functionName **Note:** Because this command is only deploying the function code, function properties such as environment variables and events will **not** be deployed. - ## Options + - `--function` or `-f` The name of the function which should be deployed -*more options to come soon* +_more options to come soon_ diff --git a/docs/providers/spotinst/cli-reference/deploy.md b/docs/providers/spotinst/cli-reference/deploy.md index 3f5f98d36..8dc0c561d 100755 --- a/docs/providers/spotinst/cli-reference/deploy.md +++ b/docs/providers/spotinst/cli-reference/deploy.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/deploy) + # Spotinst Functions - deploy @@ -19,6 +21,7 @@ serverless deploy -v ``` ## Options + - `--config` or `-c` Path to your conifguration file, if other than `serverless.yml|.yaml|.js|.json`. - `--package` or `-p` path to a pre-packaged directory and skip packaging step. - `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. diff --git a/docs/providers/spotinst/cli-reference/info.md b/docs/providers/spotinst/cli-reference/info.md index 0f2fa8ade..807a37949 100755 --- a/docs/providers/spotinst/cli-reference/info.md +++ b/docs/providers/spotinst/cli-reference/info.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/info) + # Spotinst Functions - Info @@ -19,5 +21,6 @@ serverless info ``` ## Options + - `--function` or `-f` The name of the function which should be fetched. - `--verbose` or `-v` Shows displays any Stack Output. diff --git a/docs/providers/spotinst/cli-reference/invoke.md b/docs/providers/spotinst/cli-reference/invoke.md index 835dbe025..67cbaf246 100755 --- a/docs/providers/spotinst/cli-reference/invoke.md +++ b/docs/providers/spotinst/cli-reference/invoke.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/invoke) + # Spotinst Functions - Invoke diff --git a/docs/providers/spotinst/cli-reference/logs.md b/docs/providers/spotinst/cli-reference/logs.md index 4ec585317..203f4f581 100755 --- a/docs/providers/spotinst/cli-reference/logs.md +++ b/docs/providers/spotinst/cli-reference/logs.md @@ -7,12 +7,14 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/logs) + # Spotinst Functions - Logs -Lets you view the logs for of the specified function. +Lets you view the logs for of the specified function. ```bash serverless logs -f hello @@ -20,8 +22,8 @@ serverless logs -f hello ## Options - - `-f` the name of the function that you want to fetch the logs for **Required** - - `--startTime` a unit of time that you want to start searching the logs from. Here is a list of the supported string formats +- `-f` the name of the function that you want to fetch the logs for **Required** +- `--startTime` a unit of time that you want to start searching the logs from. Here is a list of the supported string formats ```bash 30m # since 30 mins ago @@ -37,4 +39,4 @@ serverless logs -f hello serverless logs -f hello --startTime 3h ``` -This will fetch your logs started from 3 hours ago until the current time \ No newline at end of file +This will fetch your logs started from 3 hours ago until the current time diff --git a/docs/providers/spotinst/cli-reference/plugin-install.md b/docs/providers/spotinst/cli-reference/plugin-install.md index 4da71ffdb..65356156b 100644 --- a/docs/providers/spotinst/cli-reference/plugin-install.md +++ b/docs/providers/spotinst/cli-reference/plugin-install.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/plugin-install) + # Plugin Install @@ -22,9 +24,11 @@ serverless plugin install --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:install:install` ## Examples diff --git a/docs/providers/spotinst/cli-reference/plugin-list.md b/docs/providers/spotinst/cli-reference/plugin-list.md index d706a719f..c686365f4 100644 --- a/docs/providers/spotinst/cli-reference/plugin-list.md +++ b/docs/providers/spotinst/cli-reference/plugin-list.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/plugin-list) + # Plugin List @@ -19,7 +21,9 @@ serverless plugin list ``` ## Options -- *None* + +- _None_ ## Provided lifecycle events + - `plugin:list:list` diff --git a/docs/providers/spotinst/cli-reference/plugin-search.md b/docs/providers/spotinst/cli-reference/plugin-search.md index c78e9d53d..f2b8de47f 100644 --- a/docs/providers/spotinst/cli-reference/plugin-search.md +++ b/docs/providers/spotinst/cli-reference/plugin-search.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/plugin-search) + # Plugin Search @@ -19,9 +21,11 @@ serverless plugin search --query query ``` ## Options + - `--query` or `-q` The query you want to use for your search. **Required**. ## Provided lifecycle events + - `plugin:search:search` ## Examples diff --git a/docs/providers/spotinst/cli-reference/plugin-uninstall.md b/docs/providers/spotinst/cli-reference/plugin-uninstall.md index d04239f91..f1f686ac1 100644 --- a/docs/providers/spotinst/cli-reference/plugin-uninstall.md +++ b/docs/providers/spotinst/cli-reference/plugin-uninstall.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/plugin-uninstall) + # Plugin Uninstall @@ -19,9 +21,11 @@ serverless plugin uninstall --name pluginName ``` ## Options + - `--name` or `-n` The plugins name. **Required**. ## Provided lifecycle events + - `plugin:uninstall:uninstall` ## Examples diff --git a/docs/providers/spotinst/cli-reference/remove.md b/docs/providers/spotinst/cli-reference/remove.md index 87d2ce0d1..525c5d229 100755 --- a/docs/providers/spotinst/cli-reference/remove.md +++ b/docs/providers/spotinst/cli-reference/remove.md @@ -7,16 +7,19 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/cli-reference/remove) + # Spotinst Functions - Remove -The `sls remove` command will remove the deployed service, defined in your current working directory, from the provider. +The `sls remove` command will remove the deployed service, defined in your current working directory, from the provider. ```bash serverless remove ``` ## Options + - `--verbose` or `-v` Shows all stack events during deployment. diff --git a/docs/providers/spotinst/cli-reference/stage.md b/docs/providers/spotinst/cli-reference/stage.md index 479708f51..9098828c0 100644 --- a/docs/providers/spotinst/cli-reference/stage.md +++ b/docs/providers/spotinst/cli-reference/stage.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) + # Spotinst Functions - Stage Variables @@ -15,28 +17,31 @@ layout: Doc Serverless allows you to specify different stages to deploy your project to. Changing the stage will change the environment your function is running on, which is helpful when you wish to keep production code partitioned from your development environment. Your function's stage is set to 'dev' by default. You can update the stage when deploying the function, either from the command line using the serverless framework, or by modifying the serverless.yml in your project. When utilizing this feature, remember to include a config file that holds the environment IDs associated with your stages. An example config.json would look something like this: + ```json { - "dev": "env-abcd1234", - "prod": "env-defg5678" + "dev": "env-abcd1234", + "prod": "env-defg5678" } ``` ## Through Serverless Framework + To change the stage through the serverless framework you simply need to enter the command ```bash serverless deploy --stage #{Your Stage Name} ``` + You will also need to update the environment parameter to point to the config.json: + ```yaml  spotinst: - environment: ${file(./config.json):${opt:stage, self:provider.stage, 'dev'}} + environment: ${file(./config.json):${opt:stage, self:provider.stage, 'dev'}} ``` + Note that while I am using 'dev' as the default stage, you may change this parameter to a custom default stage. - - ## Through the .yml File To change the stage in the serverless.yml file you need to add the following into the provider tag then deploy your function as usual @@ -48,5 +53,5 @@ provider: spotinst: environment: #{Your Environment ID} ``` -Be sure to also modify your environment ID when you change the stage if you are not working with a config file. +Be sure to also modify your environment ID when you change the stage if you are not working with a config file. diff --git a/docs/providers/spotinst/events/README.md b/docs/providers/spotinst/events/README.md index 82efeac47..a34f9a665 100755 --- a/docs/providers/spotinst/events/README.md +++ b/docs/providers/spotinst/events/README.md @@ -6,7 +6,9 @@ menuOrder: 3 --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/events/) + # Serverless Spotinst Functions Events diff --git a/docs/providers/spotinst/events/http.md b/docs/providers/spotinst/events/http.md index 4ac098b08..7691f2638 100755 --- a/docs/providers/spotinst/events/http.md +++ b/docs/providers/spotinst/events/http.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/events/http) + # HTTP @@ -16,4 +18,4 @@ Spotinst Functions are automatically given an HTTP endpoint when they are create `https://{app id}{environment id}.spotinst.io/{function id}` -For information on your application ID, environment ID and function ID please checkout your Spotinst Functions dashboard on the [Spotinst website](https://console.spotinst.com/#/dashboard) \ No newline at end of file +For information on your application ID, environment ID and function ID please checkout your Spotinst Functions dashboard on the [Spotinst website](https://console.spotinst.com/#/dashboard) diff --git a/docs/providers/spotinst/events/schedule.md b/docs/providers/spotinst/events/schedule.md index bf8cf48b8..b3c4e5c4d 100755 --- a/docs/providers/spotinst/events/schedule.md +++ b/docs/providers/spotinst/events/schedule.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/events/schedule) + # Schedule @@ -22,12 +24,11 @@ The following example is a function configuration in the serverless.yml file tha functions: crawl: handler: handler.crawl - cron: # Setup scheduled trigger with cron expression + cron: # Setup scheduled trigger with cron expression active: true value: '30 18 * * *' ``` - ## Active Status You also have the option to set your functions active status as either true or false @@ -40,10 +41,9 @@ This example will create and attach a schedule event for the function `crawl` wh functions: crawl: handler: handler.crawl - cron: # Setup scheduled trigger with cron expression + cron: # Setup scheduled trigger with cron expression active: false value: '* 18 * * 1' - ``` **Note** When creating a `cron` trigger the `value` is the crontab expression. For help on crontab check out the [documentation](http://www.adminschoice.com/crontab-quick-reference) diff --git a/docs/providers/spotinst/examples/README.md b/docs/providers/spotinst/examples/README.md index 0284afe3a..9e02775ff 100644 --- a/docs/providers/spotinst/examples/README.md +++ b/docs/providers/spotinst/examples/README.md @@ -7,14 +7,16 @@ menuOrder: 4 --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/) + -# Hello World Serverless Example +# Hello World Serverless Example Pick your language of choice: -* [JavaScript](./node) -* [Python](./python) -* [Ruby](./ruby) -* [Java8](./java8) +- [JavaScript](./node) +- [Python](./python) +- [Ruby](./ruby) +- [Java8](./java8) diff --git a/docs/providers/spotinst/examples/java8/README.md b/docs/providers/spotinst/examples/java8/README.md index dd9a422d3..dee01e635 100644 --- a/docs/providers/spotinst/examples/java8/README.md +++ b/docs/providers/spotinst/examples/java8/README.md @@ -6,25 +6,30 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/) + # Hello World Java8 Example -Make sure `serverless` is installed. +Make sure `serverless` is installed. ## 1. Create a service -`serverless create --template spotinst-java8 --path serviceName` `serviceName` is going to be a new directory there the Java8 template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. Also you need to copy the Service name in the serverless.yml file and paste it into the pom.xlm file under the finalName tag. Next you will have to package the project to create a .jar file. To do this run the command `mvn package`. + +`serverless create --template spotinst-java8 --path serviceName` `serviceName` is going to be a new directory there the Java8 template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. Also you need to copy the Service name in the serverless.yml file and paste it into the pom.xlm file under the finalName tag. Next you will have to package the project to create a .jar file. To do this run the command `mvn package`. ## 2. Deploy -```bash + +```bash serverless deploy -``` +``` ## 3. Invoke deployed function + ```bash serverless invoke --function hello -``` +``` In your terminal window you should see the response @@ -36,7 +41,7 @@ Congrats you have deployed and ran your Hello World function! ## Short Hand Guide -`sls` is short hand for serverless cli commands +`sls` is short hand for serverless cli commands `-f` is short hand for `--function` diff --git a/docs/providers/spotinst/examples/node/README.md b/docs/providers/spotinst/examples/node/README.md index 2f4c72ea9..1653d2f1b 100644 --- a/docs/providers/spotinst/examples/node/README.md +++ b/docs/providers/spotinst/examples/node/README.md @@ -6,25 +6,30 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/) + # Hello World JavaScript Example -Make sure `serverless` is installed. +Make sure `serverless` is installed. ## 1. Create a service -`serverless create --template spotinst-nodejs --path serviceName` `serviceName` is going to be a new directory where the JavaScript template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. + +`serverless create --template spotinst-nodejs --path serviceName` `serviceName` is going to be a new directory where the JavaScript template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. ## 2. Deploy -```bash + +```bash serverless deploy -``` +``` ## 3. Invoke deployed function + ```bash serverless invoke --function hello -``` +``` In your terminal window you should see the response @@ -36,7 +41,7 @@ Congrats you have deployed and ran your Hello World function! ## Short Hand Guide -`sls` is short hand for serverless cli commands +`sls` is short hand for serverless cli commands `-f` is short hand for `--function` diff --git a/docs/providers/spotinst/examples/python/README.md b/docs/providers/spotinst/examples/python/README.md index f744ca739..b2777fc9d 100644 --- a/docs/providers/spotinst/examples/python/README.md +++ b/docs/providers/spotinst/examples/python/README.md @@ -6,27 +6,30 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/) + # Hello World Python Example -Make sure `serverless` is installed. +Make sure `serverless` is installed. ## 1. Create a service -`serverless create --template spotinst-python --path serviceName` `serviceName` is going to be a new directory there the python template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. +`serverless create --template spotinst-python --path serviceName` `serviceName` is going to be a new directory there the python template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. ## 2. Deploy -```bash + +```bash serverless deploy -``` +``` ## 3. Invoke deployed function + ```bash serverless invoke --function hello -``` - +``` In your terminal window you should see the response @@ -38,7 +41,7 @@ Congrats you have deployed and ran your Hello World function! ## Short Hand Guide -`sls` is short hand for serverless cli commands +`sls` is short hand for serverless cli commands `-f` is short hand for `--function` diff --git a/docs/providers/spotinst/examples/ruby/README.md b/docs/providers/spotinst/examples/ruby/README.md index a4d236bfb..87436ee91 100644 --- a/docs/providers/spotinst/examples/ruby/README.md +++ b/docs/providers/spotinst/examples/ruby/README.md @@ -6,7 +6,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/) + # Hello World Ruby Example @@ -14,15 +16,17 @@ layout: Doc Make sure `serverless` is installed. ## 1. Create a service -`serverless create --template spotinst-ruby --path serviceName` `serviceName` is going to be a new directory there the Ruby template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. +`serverless create --template spotinst-ruby --path serviceName` `serviceName` is going to be a new directory there the Ruby template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. ## 2. Deploy + ```bash serverless deploy -``` +``` ## 3. Invoke deployed function + ```bash serverless invoke --function hello ``` diff --git a/docs/providers/spotinst/guide/IAM-roles.md b/docs/providers/spotinst/guide/IAM-roles.md index ba2ec0516..6f139db98 100644 --- a/docs/providers/spotinst/guide/IAM-roles.md +++ b/docs/providers/spotinst/guide/IAM-roles.md @@ -2,24 +2,29 @@ title: Serverless Framework - Spotinst Functions Guide - IAM Role Configuration menuText: IAM Role menuOrder: 11 -description: How to configure IAM roles for AWS services +description: How to configure IAM roles for AWS services layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/iam-roles) + # Spotinst Functions - IAM roles + Functions sometimes rely on outside services from Amazon such as S3, and accessing these resources often requires authorization using IAM. Spotinst Functions can be configured with the relevant permissions with the inclusion of IAM role information in the serverless.yml file. See [Amazon's documentation][amazon-docs-url] for more information on IAM roles. ## Requirements + - You will need to create an IAM role on your AWS account and attach policies with the relevant permissions. - A spotinst role will be generated and linked with your AWS role - Only one Spotinst role per function. - Multiple functions can be associated with the same Spotinst role. -## YML +## YML + ```yaml functions: example: @@ -29,15 +34,14 @@ functions: timeout: 30 access: private iamRoleConfig: - roleId: ${role-id} + roleId: ${role-id} ``` ## Parameters + - roleId: the role created on the console - ex : sfr-5ea76784 - - For more information on how to set up IAM roles, check out our documentation [here][spotinst-help-center] [amazon-docs-url]: https://aws.amazon.com/iam/?sc_channel=PS&sc_campaign=acquisition_US&sc_publisher=google&sc_medium=iam_b&sc_content=amazon_iam_e&sc_detail=amazon%20iam&sc_category=iam&sc_segment=208382128687&sc_matchtype=e&sc_country=US&s_kwcid=AL!4422!3!208382128687!e!!g!!amazon%20iam&ef_id=WoypCQAABVVgCzd0:20180220230233:s diff --git a/docs/providers/spotinst/guide/README.md b/docs/providers/spotinst/guide/README.md index 1b42239e4..f163289f7 100755 --- a/docs/providers/spotinst/guide/README.md +++ b/docs/providers/spotinst/guide/README.md @@ -6,7 +6,9 @@ menuOrder: 1 --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/) + # Serverless Spotinst Functions Guide diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md index 29dbb01ae..897d2ec40 100644 --- a/docs/providers/spotinst/guide/active-versions.md +++ b/docs/providers/spotinst/guide/active-versions.md @@ -7,19 +7,23 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/active-versions) + # Spotinst Functions - Active Versions -Every time you update your function, a new version is being created by default. Version numbers have a unique ID that starts at 0 and increments by one each update. Each function version is immutable and cannot be changed. +Every time you update your function, a new version is being created by default. Version numbers have a unique ID that starts at 0 and increments by one each update. Each function version is immutable and cannot be changed. ## Latest Version -The 'Latest' version refers to the most recent version created by the last update. Unless otherwise specified, all incoming traffic is routed to the latest version. -*Please note: the 'Latest' tag will point to a different version number each and every time you update your function.* +The 'Latest' version refers to the most recent version created by the last update. Unless otherwise specified, all incoming traffic is routed to the latest version. + +_Please note: the 'Latest' tag will point to a different version number each and every time you update your function._ Default configuration for activeVersions when a new function is created: + ```yaml activeVersions: - version: $LATEST @@ -27,11 +31,13 @@ activeVersions: ``` ## Active Version + The 'Active' version can point to more than one version of your function, including 'Latest'. This allows you to distribute your incoming traffic between multiple versions and dictate what percentage is sent to each version. For example, say you wanted to test a new version of your function to determine if it was production-ready. You could specify that 10% of the traffic be routed to that new version, and route the remaining 90% to the stable version. You can gradually route more traffic to the new version as you become more confident in its performance. ### Examples + ```yaml activeVersions: - version: $LATEST @@ -47,6 +53,7 @@ activeVersions: - version: 2 percentage: 20.0 ``` + 80% of traffic goes to the most recently published update, and 20% goes to version 2. ```yaml @@ -58,16 +65,21 @@ activeVersions: - version: 1 percentage: 25.0 ``` + Traffic is split between versions 1. 3, and 5. Changes made to your latest version will not affect production traffic. ### Configure Active Version + You can configure active versions in the serverless.yml file, but you can also use the Spotinst Console to configure the versions without deploying a new update. In the event you would like to change your 'Active' version configuration without updating your function, you can use the 'Configure Active Version' action from the console and the API + - Console: + 1. Navigate to your function 2. Click 'Actions' 3. Select 'Configure Active Version' - + - API: (update function) + ```yaml activeVersions: - version: $LATEST @@ -77,6 +89,7 @@ activeVersions: ``` ### Requirements + - The sum of all percentages must be 100% - You can set up to two decimal digits in the percentage - Changes made to the ratio using the Spotinst Console will be overwritten by the contents of activeVersions in your serverless.yml file. diff --git a/docs/providers/spotinst/guide/cors.md b/docs/providers/spotinst/guide/cors.md index 1d886d9c1..bfad0215b 100644 --- a/docs/providers/spotinst/guide/cors.md +++ b/docs/providers/spotinst/guide/cors.md @@ -7,36 +7,37 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/cors) + # Spotinst Functions - CORS -Cross-Origin Resource Sharing is a mechanism that allows restricted resources on a web page to be requested from a domain outside of the original. CORS defines a way in which a web service and server can interact to determine whether or not it is safe to allow a cross-origin request. Enabling CORS for your function allows you to specify safe domains, and enables out-of-the-box support for preflight HTTP requests (via the OPTIONS method) that will return the needed ‘access-control-*’ headers specified below. The actual HTTP request will return the ‘access-control-allow-origin’ method. + +Cross-Origin Resource Sharing is a mechanism that allows restricted resources on a web page to be requested from a domain outside of the original. CORS defines a way in which a web service and server can interact to determine whether or not it is safe to allow a cross-origin request. Enabling CORS for your function allows you to specify safe domains, and enables out-of-the-box support for preflight HTTP requests (via the OPTIONS method) that will return the needed ‘access-control-\*’ headers specified below. The actual HTTP request will return the ‘access-control-allow-origin’ method. You can enable CORS for cross-domain HTTP requests with Spotinst Functions. Add the required fields to you serverless.yml file. ### Example CORS object: + ```yml - cors: - - enabled: true - - origin: "http://foo.example" - - headers: "Content-Type,X-PINGOTHER" - - methods: "PUT,POST" +cors: + - enabled: true + - origin: 'http://foo.example' + - headers: 'Content-Type,X-PINGOTHER' + - methods: 'PUT,POST' ``` ### Parameters: - - enabled: Boolean - - Specify if CORS is enabled for the function. - - default: false - - origin: String - - Specifies a domain/origin that may access the resource. A wildcard '*' may be used to allow any origin to access the resource. - - default: '*' - - methods: String - - Comma-separated list of HTTP methods that are allowed to access the resource. This is used in response to a preflight request. - - default: 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' - - headers: String - - Comma-separated list of allowed headers. - - default: 'Content-Type,Authorization' - - - +- enabled: Boolean + - Specify if CORS is enabled for the function. + - default: false +- origin: String + - Specifies a domain/origin that may access the resource. A wildcard '\*' may be used to allow any origin to access the resource. + - default: '\*' +- methods: String + - Comma-separated list of HTTP methods that are allowed to access the resource. This is used in response to a preflight request. + - default: 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' +- headers: String + - Comma-separated list of allowed headers. + - default: 'Content-Type,Authorization' diff --git a/docs/providers/spotinst/guide/create-token.md b/docs/providers/spotinst/guide/create-token.md index 38bdd8c33..e23276bd6 100644 --- a/docs/providers/spotinst/guide/create-token.md +++ b/docs/providers/spotinst/guide/create-token.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/create-token) + # Spotinst Functions - Create Token @@ -21,6 +23,7 @@ You can generate a Permanent Token from the [Spotinst Console](https://console.s > `WARNING`: Do not share your personal access token or your application secret with anyone outside your organization. Please contact our support if you’re concerned your token has been compromised. ## Temporary Access Token + You can also generate a the temporary access token, which is only valid for 2 hours (7200 seconds). You can generate a temporary token from the [Spotinst Console](https://console.spotinst.com/#/settings/tokens/temporary). Or, using the below command: @@ -30,12 +33,14 @@ $ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'username ``` Replace the following parameters, more info can be found [here](https://console.spotinst.com/#/settings/tokens/temporary) - - `` - - `` - - `` - - `` + +- `` +- `` +- `` +- `` The request will return two tokens: + ```json { "request": { @@ -62,11 +67,9 @@ The request will return two tokens: } ``` -* *accessToken* - Use this token when making calls to Spotinst API -* *refreshToken* - Use this token in order to refresh the temporary token. This will return a new token that is valid for additional 2 hours: - +- _accessToken_ - Use this token when making calls to Spotinst API +- _refreshToken_ - Use this token in order to refresh the temporary token. This will return a new token that is valid for additional 2 hours: ```bash $ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'refresh_token=&grant_type=refresh_token&client_id=&client_secret=' https://api.spotinst.io/token ``` - diff --git a/docs/providers/spotinst/guide/credentials.md b/docs/providers/spotinst/guide/credentials.md index b21f18c9b..43a5f851d 100644 --- a/docs/providers/spotinst/guide/credentials.md +++ b/docs/providers/spotinst/guide/credentials.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) + # Spotinst Functions - Credentials @@ -16,7 +18,7 @@ The Serverless Framework needs access to your Spotinst account so that it can cr ## Configure Credentials -You will need to have your account ID number and your account token ready. Your account ID can be found on the Spotinst console and your token can be generated by following the [Create Token Guide](./create-token.md). +You will need to have your account ID number and your account token ready. Your account ID can be found on the Spotinst console and your token can be generated by following the [Create Token Guide](./create-token.md). In order to run the config credentials command from the terminal you will need to start a new Spotinst project and install the plugin. First you will need to run the create a new project using the Spotinst template. To do this run: @@ -31,7 +33,7 @@ cd new-function npm install ``` -Once this has completed you will be able to configure your credentials by running +Once this has completed you will be able to configure your credentials by running ```bash serverless config credentials -p spotinst -a {your account id} -t {your token} @@ -42,7 +44,7 @@ This will create a ~/.spotinst/credentials file the file should look like this w ```bash default: token: {your token} - account: {your account number} + account: {your account number} ``` After this is set up properly you will be able to deploy functions from your computer and monitor them on the Spotinst Console. diff --git a/docs/providers/spotinst/guide/endpoints.md b/docs/providers/spotinst/guide/endpoints.md index 004a8077e..649a55c4f 100644 --- a/docs/providers/spotinst/guide/endpoints.md +++ b/docs/providers/spotinst/guide/endpoints.md @@ -2,31 +2,32 @@ title: Serverless Framework - Spotinst Functions Guide - Endpoint Setup menuText: Endpoint Set Up menuOrder: 6 -description: How to set up an Endpoint +description: How to set up an Endpoint layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) + # Spotinst Functions - Endpoints -You are able to set your custom endpoint path in the serverless.yml file if you do not want to use the console or API. You will have to set up your environment Alias in the console but here you can set the path and method for your individual functions to be mapped to. +You are able to set your custom endpoint path in the serverless.yml file if you do not want to use the console or API. You will have to set up your environment Alias in the console but here you can set the path and method for your individual functions to be mapped to. Here is a sample function from a yml file. As you can see at the bottom of the file we have listed an endpoint with a path and method. Both of these will need to be set in order to be deployed properly ```yml - hello: - runtime: nodejs8.3 - handler: handler.main - memory: 128 - timeout: 30 - access: public - endpoint: - path: /home - method: get - +hello: + runtime: nodejs8.3 + handler: handler.main + memory: 128 + timeout: 30 + access: public + endpoint: + path: /home + method: get ``` -For more information on how to set up endpoint alias and patterns check out our documentation [here](https://help.spotinst.com/hc/en-us/articles/115005893709) \ No newline at end of file +For more information on how to set up endpoint alias and patterns check out our documentation [here](https://help.spotinst.com/hc/en-us/articles/115005893709) diff --git a/docs/providers/spotinst/guide/intro.md b/docs/providers/spotinst/guide/intro.md index 7c8b748b2..3eac3d1b0 100644 --- a/docs/providers/spotinst/guide/intro.md +++ b/docs/providers/spotinst/guide/intro.md @@ -7,7 +7,9 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/intro) + # Spotinst - Introduction @@ -21,10 +23,9 @@ Using the Serverless Framework you can develop and deploy your Spotinst Function ## Core Benefits of `Spotinst Functions` - 1. Multi-Cloud Deployments
`(50+ Locations!)` - 2. 50-80% Cost Reduction `(via Cloud Spot Prices)` - 3. Faster Execution time & Advanced Analytics - +1. Multi-Cloud Deployments
`(50+ Locations!)` +2. 50-80% Cost Reduction `(via Cloud Spot Prices)` +3. Faster Execution time & Advanced Analytics ## Core Concepts @@ -41,22 +42,21 @@ The functions underneath the environment share similar characteristics and are m For example, you can create several environments for your application's Dev and Prod functions of the same Application -![](https://s3.amazonaws.com/spotinst-public/assets/IMG/sQ7iaNHCTXnhxSe_S4LlQpQ+(1).png) +![]() ### Functions A Function is an [Spotinst Function](https://help.spotinst.com/hc/en-us/articles/115004143245-Function). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a specific job. -You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. - +You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. ### Events -Anything that triggers an [Spotinst Function](https://help.spotinst.com/hc/en-us/articles/115004143245-Function) to execute is regarded by the Framework as an **Event** (in Spotinst Functions they called Triggers). Events platform events such as: +Anything that triggers an [Spotinst Function](https://help.spotinst.com/hc/en-us/articles/115004143245-Function) to execute is regarded by the Framework as an **Event** (in Spotinst Functions they called Triggers). Events platform events such as: -* *An HTTP Trigger (e.g., for a REST API)* -* *A scheduled Cron event (e.g., run every 5 minutes)* -* *And more...* +- _An HTTP Trigger (e.g., for a REST API)_ +- _A scheduled Cron event (e.g., run every 5 minutes)_ +- _And more..._ ### Function Template @@ -64,7 +64,7 @@ Anything that triggers an [Spotinst Function](https://help.spotinst.com/hc/en-us # handler.js module.exports.main = function main (event, context, callback) { callback(null, { - statusCode: 200, + statusCode: 200, body: '{"hello":"from NodeJS8.3 function"}', headers: {"Content-Type": "application/json"} }); @@ -73,10 +73,9 @@ module.exports.main = function main (event, context, callback) { ### Services -A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: +A **Service** is the Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions, the Events that trigger them, and the Resources your Functions use, all in one file by default entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: ```yml - service: spotinst-nodejs # NOTE: update this with your service name provider: @@ -96,17 +95,15 @@ functions: # value: '* * * * *' # environmentVariables: # key: value - ``` + When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` (or the file specified with the `--config` option) is deployed at once. ### Plugins -You can overwrite or extend the functionality of the Framework using **Plugins**. Every `serverless.yml` can contain a `plugins:` property, which features multiple plugins. Please include the **serverless-spotinst-functions** as part of your plugins in the serverless.yml file. +You can overwrite or extend the functionality of the Framework using **Plugins**. Every `serverless.yml` can contain a `plugins:` property, which features multiple plugins. Please include the **serverless-spotinst-functions** as part of your plugins in the serverless.yml file. ```yml - plugins: - serverless-spotinst-functions - ``` diff --git a/docs/providers/spotinst/guide/quick-start.md b/docs/providers/spotinst/guide/quick-start.md index a23cb539e..044239058 100644 --- a/docs/providers/spotinst/guide/quick-start.md +++ b/docs/providers/spotinst/guide/quick-start.md @@ -7,15 +7,18 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://serverless.com/framework/docs/providers/spotinst/guide/quick-start/) + # Spotinst - Quick Start -Here is a quick guide on how to create a new serverless project using the Spotinst NodeJS template. For more detailed instruction please check out the other reference material provided. +Here is a quick guide on how to create a new serverless project using the Spotinst NodeJS template. For more detailed instruction please check out the other reference material provided. ## Install Serverless Framework - First you will need to have the serverless framework installed. To do this you will have to run the command: + +First you will need to have the serverless framework installed. To do this you will have to run the command: ```bash npm install -g serverless @@ -23,41 +26,43 @@ npm install -g serverless ``` ## Set Up Credentials - To set up your Spotinst Credentials you will have to have the Spotinst Serverless Plugin installed inside a new Serverless project. The first thing you will need to do is create a new template project and enter the new project directory by entering into the terminal: + +To set up your Spotinst Credentials you will have to have the Spotinst Serverless Plugin installed inside a new Serverless project. The first thing you will need to do is create a new template project and enter the new project directory by entering into the terminal: ```bash serverless create --template spotinst-nodejs --path new-service cd new-service ``` - Once you are in the project directory you will have to install the plugin but entering: +Once you are in the project directory you will have to install the plugin but entering: ```bash npm install ``` - After the installation is completed you can then configure your credentials. Before you do this you will want to have your Spotinst account ID number and Spotinst API token ready to go. Those both can be found in the Spotinst Console under settings. Once you have those you will enter: +After the installation is completed you can then configure your credentials. Before you do this you will want to have your Spotinst account ID number and Spotinst API token ready to go. Those both can be found in the Spotinst Console under settings. Once you have those you will enter: ```bash serverless config credentials --provider spotinst --token {Your Spotinst API Token} --account {Your Spotinst Account ID} ``` - To check to see that your credentials have been set up properly you can check the credentials files by entering: +To check to see that your credentials have been set up properly you can check the credentials files by entering: ```bash cat ~/.spotinst/credentials ``` - Here you should see the account ID and Token that are linked to your account. - - **Note:** Once you have set up your Spotinst Credentials you will not need to do this again for each project +Here you should see the account ID and Token that are linked to your account. - For more help please refer to the [Credentials](./credentials.md) link provided +**Note:** Once you have set up your Spotinst Credentials you will not need to do this again for each project + +For more help please refer to the [Credentials](./credentials.md) link provided ## Create a New Project From a Template - *You can skip this step if you have already done this step in configuring your credentials* - - Create a new service using the Node.js template, specifying a unique name and an optional path for your service. + +_You can skip this step if you have already done this step in configuring your credentials_ + +Create a new service using the Node.js template, specifying a unique name and an optional path for your service. ```bash serverless create --template spotinst-nodejs --path new-service @@ -65,30 +70,32 @@ cd new-service ``` ## Install Spotinst Serverless Functions Plugin - *You can skip this step if you have already done this step in configuring your credentials* - You will first need to install the Spotinst Functions plugin before you are able to deploy your function. Once this has been done you do not need to do it again for this project. +_You can skip this step if you have already done this step in configuring your credentials_ + +You will first need to install the Spotinst Functions plugin before you are able to deploy your function. Once this has been done you do not need to do it again for this project. ```bash npm install ``` ## Deploying and Updating the Function - Deploying a project is how you launch the project into production. Once it has been deployed, you will be able to see and edit it in the Spotinst Console. - Before you deploy you will need to add in the environment ID into the `serverless.yml` file. The environment ID can be found on the Spotinst console under Functions. In this menu you will be able to add applications, environments and functions. An application is able to hold many environments and environments can hold many functions. They are mostly used for organization purposes and are at your descretion to manipulate as you like. To deploy your function you will need to select an application and environment and copy/paste the environment ID into the `serverless.yml` file under the environment tag. - +Deploying a project is how you launch the project into production. Once it has been deployed, you will be able to see and edit it in the Spotinst Console. + +Before you deploy you will need to add in the environment ID into the `serverless.yml` file. The environment ID can be found on the Spotinst console under Functions. In this menu you will be able to add applications, environments and functions. An application is able to hold many environments and environments can hold many functions. They are mostly used for organization purposes and are at your descretion to manipulate as you like. To deploy your function you will need to select an application and environment and copy/paste the environment ID into the `serverless.yml` file under the environment tag. + 1. **Deploying the Service** - Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. +Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. ```bash -serverless deploy +serverless deploy ``` 2. **Deploy the Function** - Use this to quickly upload and overwrite your function code, allowing you to develop faster. +Use this to quickly upload and overwrite your function code, allowing you to develop faster. ```bash serverless deploy function -f hello @@ -96,17 +103,17 @@ serverless deploy function -f hello 3. **Updating the Function** - Use this to update your function after you have made updates that you want to push to production. - +Use this to update your function after you have made updates that you want to push to production. + ```bash -serverless deploy +serverless deploy ``` ## Invoke the Function - Invoking a function simply means to run it. There are many event triggers that will invoke a function depending on your needs. All functions are assigned a unique URL that is initially set to private in the `serverless.yml` file but if set to public can trigger an invocation from a web browser. Also you are able to set up a cron function in the `serverless.yml` file to run at regular intervals and invoke the function on a timer. If you simply want to test the function you can invoke from the console. Under the function name there is a Test tab you can select and run a test. Otherwise you are able to test from the terminal as shown below. +Invoking a function simply means to run it. There are many event triggers that will invoke a function depending on your needs. All functions are assigned a unique URL that is initially set to private in the `serverless.yml` file but if set to public can trigger an invocation from a web browser. Also you are able to set up a cron function in the `serverless.yml` file to run at regular intervals and invoke the function on a timer. If you simply want to test the function you can invoke from the console. Under the function name there is a Test tab you can select and run a test. Otherwise you are able to test from the terminal as shown below. - Invokes a Function +Invokes a Function ```bash serverless invoke -f hello diff --git a/docs/providers/spotinst/guide/serverless.yml.md b/docs/providers/spotinst/guide/serverless.yml.md index 8fe0c6e12..dcbf88846 100644 --- a/docs/providers/spotinst/guide/serverless.yml.md +++ b/docs/providers/spotinst/guide/serverless.yml.md @@ -1,5 +1,5 @@ + ### [Read this on the main serverless docs site](https://serverless.com/framework/docs/providers/spotinst/guide/serverless.yml/) + # Serverless.yml Reference @@ -36,13 +38,13 @@ provider: spotinst: environment: #{Your Environment ID} -# Here is where you will list your functions for this service. Each Function is -# required to have a name, runtime, handler, memory and timeout. The runtime is -# the language that you want to run your function with, the handler tells which -# file and function to run, memory is the amount of memory needed to run your -# function, timeout is the time the function will take to run, if it goes over -# this time it will terminate itself. Access is default set to private so if you -# want to be able to run the function by HTTPS request this needs to be set to +# Here is where you will list your functions for this service. Each Function is +# required to have a name, runtime, handler, memory and timeout. The runtime is +# the language that you want to run your function with, the handler tells which +# file and function to run, memory is the amount of memory needed to run your +# function, timeout is the time the function will take to run, if it goes over +# this time it will terminate itself. Access is default set to private so if you +# want to be able to run the function by HTTPS request this needs to be set to # public. The environment variables can be set in here or on the Spotinst console. # Once they are set you can access the variables in your handler file with # process.env['{Your Key}'] diff --git a/docs/providers/spotinst/guide/variables.md b/docs/providers/spotinst/guide/variables.md index e6e222b43..48fcc38c3 100644 --- a/docs/providers/spotinst/guide/variables.md +++ b/docs/providers/spotinst/guide/variables.md @@ -7,16 +7,18 @@ layout: Doc --> + ### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/variables) + # Spotinst Functions - Variables -There are a few ways to introduce external variables to your serverless functions in order to customize each function call based on your personal needs. +There are a few ways to introduce external variables to your serverless functions in order to customize each function call based on your personal needs. ## Environment Variables -Environment variables allow you to pass static information into your function so you wont have to upload sensitive or protected information in your production code. It also allows you to easily change these variables from the outside so you do not have to upload your code multiple times with different variables. +Environment variables allow you to pass static information into your function so you wont have to upload sensitive or protected information in your production code. It also allows you to easily change these variables from the outside so you do not have to upload your code multiple times with different variables. To enter your environment variables you will need to go into the Spotinst console find the function you want to add environment variables to. Then under the Configuration tab you will find the Environment Variables heading. Here you can enter as many variables you need all with an associated key. @@ -34,7 +36,7 @@ To access your variables in your code you just need to put `process.env['{Your K ## URL Argument Variables -URL parameters can be use when a POST request is made to the endpoint of your function. +URL parameters can be use when a POST request is made to the endpoint of your function. ### 1. Node JS diff --git a/lib/Serverless.js b/lib/Serverless.js index ca3ffd90b..e43241b11 100644 --- a/lib/Serverless.js +++ b/lib/Serverless.js @@ -32,7 +32,8 @@ class Serverless { this.pluginManager = new PluginManager(this); // use the servicePath from the options or try to find it in the CWD - configObject.servicePath = configObject.servicePath || + configObject.servicePath = + configObject.servicePath || this.utils.findServicePath(minimist(process.argv.slice(2)).config); this.config = new Config(this, configObject); @@ -51,7 +52,7 @@ class Serverless { init() { // create an instanceId (can be e.g. used when a predictable random value is needed) - this.instanceId = (new Date()).getTime().toString(); + this.instanceId = new Date().getTime().toString(); // create a new CLI instance this.cli = new this.classes.CLI(this); @@ -60,26 +61,30 @@ class Serverless { this.processedInput = this.cli.processInput(); // load config file - return this.pluginManager.loadConfigFile().then(() => { - // set the options and commands which were processed by the CLI - this.pluginManager.setCliOptions(this.processedInput.options); - this.pluginManager.setCliCommands(this.processedInput.commands); + return this.pluginManager + .loadConfigFile() + .then(() => { + // set the options and commands which were processed by the CLI + this.pluginManager.setCliOptions(this.processedInput.options); + this.pluginManager.setCliCommands(this.processedInput.commands); - // Check if update is available - updateNotifier({ pkg }).notify(); + // Check if update is available + updateNotifier({ pkg }).notify(); - return this.service.load(this.processedInput.options); - }).then(() => { - // load all plugins - this.pluginManager.loadAllPlugins(this.service.plugins); - return this.pluginManager.asyncPluginInit(); - }).then(() => { - // give the CLI the plugins and commands so that it can print out - // information such as options when the user enters --help - this.cli.setLoadedPlugins(this.pluginManager.getPlugins()); - this.cli.setLoadedCommands(this.pluginManager.getCommands()); - return this.pluginManager.updateAutocompleteCacheFile(); - }); + return this.service.load(this.processedInput.options); + }) + .then(() => { + // load all plugins + this.pluginManager.loadAllPlugins(this.service.plugins); + return this.pluginManager.asyncPluginInit(); + }) + .then(() => { + // give the CLI the plugins and commands so that it can print out + // information such as options when the user enters --help + this.cli.setLoadedPlugins(this.pluginManager.getPlugins()); + this.cli.setLoadedCommands(this.pluginManager.getCommands()); + return this.pluginManager.updateAutocompleteCacheFile(); + }); } run() { @@ -95,8 +100,7 @@ class Serverless { // populate variables after --help, otherwise help may fail to print // (https://github.com/serverless/serverless/issues/2041) - return this.variables.populateService(this.pluginManager.cliOptions) - .then(() => { + return this.variables.populateService(this.pluginManager.cliOptions).then(() => { // merge arrays after variables have been populated // (https://github.com/serverless/serverless/issues/3511) this.service.mergeArrays(); diff --git a/lib/Serverless.test.js b/lib/Serverless.test.js index 14d33e321..83a66606d 100644 --- a/lib/Serverless.test.js +++ b/lib/Serverless.test.js @@ -115,12 +115,13 @@ describe('Serverless', () => { let updateAutocompleteCacheFileStub; beforeEach(() => { - loadAllPluginsStub = sinon - .stub(serverless.pluginManager, 'loadAllPlugins').returns(); + loadAllPluginsStub = sinon.stub(serverless.pluginManager, 'loadAllPlugins').returns(); asyncPluginInitStub = sinon - .stub(serverless.pluginManager, 'asyncPluginInit').returns(BbPromise.resolve()); + .stub(serverless.pluginManager, 'asyncPluginInit') + .returns(BbPromise.resolve()); updateAutocompleteCacheFileStub = sinon - .stub(serverless.pluginManager, 'updateAutocompleteCacheFile').resolves(); + .stub(serverless.pluginManager, 'updateAutocompleteCacheFile') + .resolves(); }); afterEach(() => { @@ -129,13 +130,15 @@ describe('Serverless', () => { serverless.pluginManager.updateAutocompleteCacheFile.restore(); }); - it('should set an instanceId', () => serverless.init().then(() => { - expect(serverless.instanceId).to.match(/\d/); - })); + it('should set an instanceId', () => + serverless.init().then(() => { + expect(serverless.instanceId).to.match(/\d/); + })); - it('should create a new CLI instance', () => serverless.init().then(() => { - expect(serverless.cli).to.be.instanceof(CLI); - })); + it('should create a new CLI instance', () => + serverless.init().then(() => { + expect(serverless.cli).to.be.instanceof(CLI); + })); it('should allow a custom CLI instance', () => { class CustomCLI extends CLI {} @@ -149,11 +152,10 @@ describe('Serverless', () => { // note: we just test that the processedInput variable is set (not the content of it) // the test for the correct input is done in the CLI class test file - it('should receive the processed input form the CLI instance', () => serverless.init() - .then(() => { + it('should receive the processed input form the CLI instance', () => + serverless.init().then(() => { expect(serverless.processedInput).to.not.deep.equal({}); - }) - ); + })); it('should resolve after loading the service', () => { const SUtils = new Utils(); @@ -174,8 +176,7 @@ describe('Serverless', () => { }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); serverless.config.update({ servicePath: tmpDirPath }); serverless.pluginManager.cliOptions = { @@ -201,16 +202,11 @@ describe('Serverless', () => { serverless.cli = new CLI(serverless); serverless.processedInput = { commands: [], options: {} }; // setup default stubs - logStatStub = sinon - .stub(serverless.utils, 'logStat').resolves(); - displayHelpStub = sinon - .stub(serverless.cli, 'displayHelp').returns(false); - validateCommandStub = sinon - .stub(serverless.pluginManager, 'validateCommand').returns(); - populateServiceStub = sinon - .stub(serverless.variables, 'populateService').resolves(); - runStub = sinon - .stub(serverless.pluginManager, 'run').resolves(); + logStatStub = sinon.stub(serverless.utils, 'logStat').resolves(); + displayHelpStub = sinon.stub(serverless.cli, 'displayHelp').returns(false); + validateCommandStub = sinon.stub(serverless.pluginManager, 'validateCommand').returns(); + populateServiceStub = sinon.stub(serverless.variables, 'populateService').resolves(); + runStub = sinon.stub(serverless.pluginManager, 'run').resolves(); }); afterEach(() => { @@ -255,8 +251,7 @@ describe('Serverless', () => { expect(validateCommandStub.calledOnce).to.equal(true); expect(populateServiceStub.calledOnce).to.equal(true); expect(runStub.calledOnce).to.equal(true); - }) - ); + })); }); describe('#setProvider()', () => { diff --git a/lib/classes/CLI.js b/lib/classes/CLI.js index 53c53f699..80299ddf4 100644 --- a/lib/classes/CLI.js +++ b/lib/classes/CLI.js @@ -34,10 +34,9 @@ class CLI { inputArray = process.argv.slice(2); } - const base64Encode = (valueStr) => - new Buffer(valueStr).toString('base64'); + const base64Encode = valueStr => new Buffer(valueStr).toString('base64'); - const toBase64Helper = (value) => { + const toBase64Helper = value => { const valueStr = value.toString(); if (valueStr.startsWith('-')) { if (valueStr.indexOf('=') !== -1) { @@ -55,7 +54,7 @@ class CLI { return base64Encode(valueStr); }; - const decodedArgsHelper = (arg) => { + const decodedArgsHelper = arg => { if (_.isString(arg)) { return new Buffer(arg, 'base64').toString(); } else if (_.isArray(arg)) { @@ -100,7 +99,7 @@ class CLI { // Make "log" no-op to suppress warnings. // But preserve "consoleLog" which "print" command use to print config. - this.log = function () {}; + this.log = function() {}; } displayHelp(processedInput) { @@ -108,16 +107,20 @@ class CLI { const options = processedInput.options; // if only "version" or "v" was entered - if ((commands.length === 0 && (options.version || options.v)) || - (commands.length === 1 && (commands.indexOf('version') > -1))) { + if ( + (commands.length === 0 && (options.version || options.v)) || + (commands.length === 1 && commands.indexOf('version') > -1) + ) { this.getVersionNumber(); return true; } // if only "help" or "h" was entered - if ((commands.length === 0) || - (commands.length === 0 && (options.help || options.h)) || - (commands.length === 1 && (commands.indexOf('help') > -1))) { + if ( + commands.length === 0 || + (commands.length === 0 && (options.help || options.h)) || + (commands.length === 1 && commands.indexOf('help') > -1) + ) { if (options.verbose || options.v) { this.generateVerboseHelp(); } else { @@ -153,13 +156,14 @@ class CLI { displayCommandOptions(commandObject) { const dotsLength = 40; - const commandOptions = commandObject.configDependent ? - Object.assign({}, commandObject.options, { - config: { - usage: 'Path to serverless config file', - shortcut: 'c', - }, - }) : commandObject.options; + const commandOptions = commandObject.configDependent + ? Object.assign({}, commandObject.options, { + config: { + usage: 'Path to serverless config file', + shortcut: 'c', + }, + }) + : commandObject.options; _.forEach(commandOptions, (optionsObject, option) => { let optionsDots = _.repeat('.', dotsLength - option.length); @@ -184,8 +188,9 @@ class CLI { requiredInfo = ' (required)'; } - const thingsToLog = `${optionInfo}${shortcutInfo}${requiredInfo} ${ - chalk.dim(optionsDots)} ${optionsUsage}`; + const thingsToLog = `${optionInfo}${shortcutInfo}${requiredInfo} ${chalk.dim( + optionsDots + )} ${optionsUsage}`; this.consoleLog(chalk.yellow(thingsToLog)); }); } @@ -219,8 +224,8 @@ class CLI { this.consoleLog(chalk.yellow.underline('Plugins')); if (this.loadedPlugins.length) { - const sortedPlugins = _.sortBy(this.loadedPlugins, (plugin) => plugin.constructor.name); - this.consoleLog(sortedPlugins.map((plugin) => plugin.constructor.name).join(', ')); + const sortedPlugins = _.sortBy(this.loadedPlugins, plugin => plugin.constructor.name); + this.consoleLog(sortedPlugins.map(plugin => plugin.constructor.name).join(', ')); } else { this.consoleLog('No plugins added yet'); } @@ -234,7 +239,7 @@ class CLI { let pluginCommands = {}; // add commands to pluginCommands based on command's plugin - const addToPluginCommands = (cmd) => { + const addToPluginCommands = cmd => { const pcmd = _.clone(cmd); // remove subcommand from clone @@ -250,24 +255,27 @@ class CLI { // check for subcommands if ('commands' in cmd) { - _.forEach(cmd.commands, (d) => { + _.forEach(cmd.commands, d => { addToPluginCommands(d); }); } }; // fill up pluginCommands with commands in loadedCommands - _.forEach(this.loadedCommands, (details) => { + _.forEach(this.loadedCommands, details => { addToPluginCommands(details); }); // sort plugins alphabetically - pluginCommands = _(pluginCommands).toPairs().sortBy(0).fromPairs() + pluginCommands = _(pluginCommands) + .toPairs() + .sortBy(0) + .fromPairs() .value(); _.forEach(pluginCommands, (details, plugin) => { this.consoleLog(plugin); - _.forEach(details, (cmd) => { + _.forEach(details, cmd => { // display command usage with single(1) indent this.displayCommandUsage(cmd, cmd.key.split(':').join(' '), 1); }); @@ -281,12 +289,16 @@ class CLI { // Get all the commands using getCommands() with filtered entrypoint // commands and reduce to the required command. const allCommands = this.serverless.pluginManager.getCommands(); - const command = _.reduce(commandsArray, (currentCmd, cmd) => { - if (currentCmd.commands && cmd in currentCmd.commands) { - return currentCmd.commands[cmd]; - } - return null; - }, { commands: allCommands }); + const command = _.reduce( + commandsArray, + (currentCmd, cmd) => { + if (currentCmd.commands && cmd in currentCmd.commands) { + return currentCmd.commands[cmd]; + } + return null; + }, + { commands: allCommands } + ); // Throw error if command not found. if (!command) { diff --git a/lib/classes/CLI.test.js b/lib/classes/CLI.test.js index fa8e44148..ff927101b 100644 --- a/lib/classes/CLI.test.js +++ b/lib/classes/CLI.test.js @@ -31,7 +31,7 @@ describe('CLI', () => { }); it('should set a null inputArray when none is provided', () => - expect(new CLI(serverless).inputArray).to.be.null); + expect(new CLI(serverless).inputArray).to.be.null); it('should set the inputObject when provided', () => { cli = new CLI(serverless, ['foo', 'bar', '--baz', '-qux']); @@ -179,9 +179,7 @@ describe('CLI', () => { this.commands = { test: { usage: 'test', - lifecycleEvents: [ - 'test', - ], + lifecycleEvents: ['test'], options: { name: { usage: 'test', @@ -193,9 +191,7 @@ describe('CLI', () => { commands: { test: { usage: 'test', - lifecycleEvents: [ - 'test', - ], + lifecycleEvents: ['test'], options: { name: { usage: 'test', @@ -263,9 +259,7 @@ describe('CLI', () => { this.commands = { test: { usage: 'test', - lifecycleEvents: [ - 'test', - ], + lifecycleEvents: ['test'], options: { name: { usage: 'test', @@ -297,9 +291,7 @@ describe('CLI', () => { this.commands = { test: { usage: 'test', - lifecycleEvents: [ - 'test', - ], + lifecycleEvents: ['test'], options: { name: { usage: 'test', @@ -311,9 +303,7 @@ describe('CLI', () => { commands: { test: { usage: 'test', - lifecycleEvents: [ - 'test', - ], + lifecycleEvents: ['test'], options: { name: { usage: 'test', @@ -347,9 +337,7 @@ describe('CLI', () => { this.commands = { test: { usage: 'test', - lifecycleEvents: [ - 'test', - ], + lifecycleEvents: ['test'], options: { name: { usage: 'test', @@ -397,8 +385,7 @@ describe('CLI', () => { beforeEach(() => { cli = new CLI(serverless); - getCommandsStub = sinon.stub(cli.serverless.pluginManager, 'getCommands') - .returns(commands); + getCommandsStub = sinon.stub(cli.serverless.pluginManager, 'getCommands').returns(commands); consoleLogStub = sinon.stub(cli, 'consoleLog').returns(); displayCommandUsageStub = sinon.stub(cli, 'displayCommandUsage').returns(); displayCommandOptionsStub = sinon.stub(cli, 'displayCommandOptions').returns(); @@ -420,14 +407,9 @@ describe('CLI', () => { expect(getCommandsStub.calledOnce).to.equal(true); expect(consoleLogStub.called).to.equal(true); expect(displayCommandUsageStub.calledOnce).to.equal(true); - expect(displayCommandUsageStub.calledWithExactly( - commands.package, - 'package' - )).to.equal(true); + expect(displayCommandUsageStub.calledWithExactly(commands.package, 'package')).to.equal(true); expect(displayCommandOptionsStub.calledOnce).to.equal(true); - expect(displayCommandOptionsStub.calledWithExactly( - commands.package - )).to.equal(true); + expect(displayCommandOptionsStub.calledWithExactly(commands.package)).to.equal(true); }); it('should throw an error if the command could not be found', () => { @@ -435,8 +417,9 @@ describe('CLI', () => { cli.inputArray = commandsArray; - expect(() => { cli.generateCommandsHelp(commandsArray); }) - .to.throw(Error, 'not found'); + expect(() => { + cli.generateCommandsHelp(commandsArray); + }).to.throw(Error, 'not found'); expect(getCommandsStub.calledOnce).to.equal(true); expect(consoleLogStub.called).to.equal(false); expect(displayCommandUsageStub.calledOnce).to.equal(false); @@ -608,7 +591,7 @@ describe('CLI', () => { }); }); - describe('Integration tests', function () { + describe('Integration tests', function() { this.timeout(0); const that = this; @@ -625,8 +608,8 @@ describe('CLI', () => { // Cannot rely on shebang in severless.js to invoke script using NodeJS on Windows. const execPrefix = os.platform() === 'win32' ? 'node ' : ''; - that.serverlessExec = execPrefix + path.join(serverless.config.serverlessPath, - '..', 'bin', 'serverless'); + that.serverlessExec = + execPrefix + path.join(serverless.config.serverlessPath, '..', 'bin', 'serverless'); }); }); @@ -634,7 +617,7 @@ describe('CLI', () => { process.chdir(that.cwd); }); - it('should print general --help to stdout', (done) => { + it('should print general --help to stdout', done => { exec(`${this.serverlessExec} --help`, (err, stdout, stderr) => { if (err) { process.stdout.write(stdout); @@ -648,7 +631,7 @@ describe('CLI', () => { }); }); - it('should print command --help to stdout', (done) => { + it('should print command --help to stdout', done => { exec(`${this.serverlessExec} deploy --help`, (err, stdout, stderr) => { if (err) { process.stdout.write(stdout); @@ -663,7 +646,7 @@ describe('CLI', () => { }); }); - it('should print help --verbose to stdout', (done) => { + it('should print help --verbose to stdout', done => { exec(`${this.serverlessExec} help --verbose`, (err, stdout, stderr) => { if (err) { process.stdout.write(stdout); diff --git a/lib/classes/Config.js b/lib/classes/Config.js index d563e5aa2..f74f841c1 100644 --- a/lib/classes/Config.js +++ b/lib/classes/Config.js @@ -4,7 +4,6 @@ const _ = require('lodash'); const path = require('path'); class Config { - constructor(serverless, config) { this.serverless = serverless; this.serverlessPath = path.join(__dirname, '..'); @@ -18,4 +17,3 @@ class Config { } module.exports = Config; - diff --git a/lib/classes/Error.js b/lib/classes/Error.js index cd66e850c..4beec093a 100644 --- a/lib/classes/Error.js +++ b/lib/classes/Error.js @@ -4,7 +4,7 @@ const version = require('./../../package.json').version; // raven implementation examples https://www.npmjs.com/browse/depended/raven const errorReporter = require('../utils/sentry').raven; -const consoleLog = (message) => { +const consoleLog = message => { console.log(message); // eslint-disable-line no-console }; @@ -38,7 +38,7 @@ module.exports.ServerlessError = class ServerlessError extends Error { // Deprecated - use ServerlessError instead module.exports.SError = module.exports.ServerlessError; -module.exports.logError = (e) => { +module.exports.logError = e => { try { const errorType = e.name.replace(/([A-Z])/g, ' $1'); @@ -67,8 +67,9 @@ module.exports.logError = (e) => { consoleLog(chalk.yellow(' Get Support --------------------------------------------')); consoleLog(`${chalk.yellow(' Docs: ')}${'docs.serverless.com'}`); - consoleLog(`${chalk.yellow(' Bugs: ')}${ - 'github.com/serverless/serverless/issues'}`); + consoleLog( + `${chalk.yellow(' Bugs: ')}${'github.com/serverless/serverless/issues'}` + ); consoleLog(`${chalk.yellow(' Issues: ')}${'forum.serverless.com'}`); consoleLog(' '); @@ -84,7 +85,8 @@ module.exports.logError = (e) => { process.exit(1); } // report error to sentry. - errorReporter.captureException(e, (sendErr, eventId) => { // eslint-disable-line + errorReporter.captureException(e, (sendErr, eventId) => { + // eslint-disable-line // process.exit(1) for CI systems to correctly fail process.exit(1); }); @@ -93,10 +95,10 @@ module.exports.logError = (e) => { } }; -module.exports.logWarning = (message) => { +module.exports.logWarning = message => { writeMessage('Serverless Warning', message); }; -module.exports.logInfo = (message) => { +module.exports.logInfo = message => { writeMessage('Serverless Information', message); }; diff --git a/lib/classes/Error.test.js b/lib/classes/Error.test.js index 9ebc18b45..a8ea828c0 100644 --- a/lib/classes/Error.test.js +++ b/lib/classes/Error.test.js @@ -57,7 +57,10 @@ describe('Error', () => { beforeEach(() => { consoleLogSpy = sandbox.spy(console, 'log'); // the following is used so that the process exiting never interrupts tests - processExitStub = sandbox.stub(process, 'exit').withArgs(1).returns(); + processExitStub = sandbox + .stub(process, 'exit') + .withArgs(1) + .returns(); captureExceptionStub = sandbox.stub(errorReporter, 'captureException').yields(); }); diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 328fc27c7..0853ebdad 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -46,7 +46,7 @@ class PluginManager { loadConfigFile() { return serverlessConfigFileUtils .getServerlessConfigFile(this.serverless) - .then((serverlessConfigFile) => { + .then(serverlessConfigFile => { this.serverlessConfigFile = serverlessConfigFile; return; }); @@ -74,8 +74,7 @@ class PluginManager { } // ignore plugins that specify a different provider than the current one - if (pluginProvider - && (pluginProvider !== this.serverless.service.provider.name)) { + if (pluginProvider && pluginProvider !== this.serverless.service.provider.name) { return; } @@ -98,7 +97,7 @@ class PluginManager { } loadPlugins(plugins) { - plugins.forEach((plugin) => { + plugins.forEach(plugin => { try { const servicePath = this.serverless.config.servicePath; const pluginPath = plugin.startsWith('./') ? path.join(servicePath, plugin) : plugin; @@ -123,8 +122,7 @@ class PluginManager { if (process.env.SLS_DEBUG) { throw error; } - errorMessage = - `Serverless plugin "${plugin}" initialization errored: ${error.message}`; + errorMessage = `Serverless plugin "${plugin}" initialization errored: ${error.message}`; } if (!this.cliOptions.help) { throw new this.serverless.classes.Error(errorMessage); @@ -138,8 +136,8 @@ class PluginManager { loadCorePlugins() { const pluginsDirectoryPath = path.join(__dirname, '../plugins'); const corePlugins = this.serverless.utils - .readFileSync(path.join(pluginsDirectoryPath, 'Plugins.json')).plugins - .map((corePluginPath) => path.join(pluginsDirectoryPath, corePluginPath)); + .readFileSync(path.join(pluginsDirectoryPath, 'Plugins.json')) + .plugins.map(corePluginPath => path.join(pluginsDirectoryPath, corePluginPath)); this.loadPlugins(corePlugins); } @@ -153,8 +151,9 @@ class PluginManager { if (pluginsObject.localPath) { module.paths.unshift(pluginsObject.localPath); } - this.loadPlugins(pluginsObject.modules - .filter(name => name !== '@serverless/enterprise-plugin')); + this.loadPlugins( + pluginsObject.modules.filter(name => name !== '@serverless/enterprise-plugin') + ); } loadEnterprisePlugin() { @@ -171,23 +170,29 @@ class PluginManager { if (this.serverless.enterpriseEnabled) { const updates = updateNotifier({ pkg: sfePkgJson, interval: 1 }); if (updates.update) { - this.serverless.cli.log('An updated version of Serverless Enterprise is available. ' + - 'Please upgrade by running `npm i -g serverless`'); + this.serverless.cli.log( + 'An updated version of Serverless Enterprise is available. ' + + 'Please upgrade by running `npm i -g serverless`' + ); } } } parsePluginsObject(servicePlugs) { - let localPath = (this.serverless && this.serverless.config && - this.serverless.config.servicePath) && + let localPath = + this.serverless && + this.serverless.config && + this.serverless.config.servicePath && path.join(this.serverless.config.servicePath, '.serverless_plugins'); let modules = []; if (_.isArray(servicePlugs)) { modules = servicePlugs; } else if (servicePlugs) { - localPath = servicePlugs.localPath && - _.isString(servicePlugs.localPath) ? servicePlugs.localPath : localPath; + localPath = + servicePlugs.localPath && _.isString(servicePlugs.localPath) + ? servicePlugs.localPath + : localPath; if (_.isArray(servicePlugs.modules)) { modules = servicePlugs.modules; } @@ -199,32 +204,41 @@ class PluginManager { createCommandAlias(alias, command) { // Deny self overrides if (_.startsWith(command, alias)) { - throw new this.serverless.classes - .Error(`Command "${alias}" cannot be overriden by an alias`); + throw new this.serverless.classes.Error(`Command "${alias}" cannot be overriden by an alias`); } const splitAlias = _.split(alias, ':'); - const aliasTarget = _.reduce(splitAlias, (__, aliasPath) => { - const currentAlias = __; - if (!currentAlias[aliasPath]) { - currentAlias[aliasPath] = {}; - } - return currentAlias[aliasPath]; - }, this.aliases); + const aliasTarget = _.reduce( + splitAlias, + (__, aliasPath) => { + const currentAlias = __; + if (!currentAlias[aliasPath]) { + currentAlias[aliasPath] = {}; + } + return currentAlias[aliasPath]; + }, + this.aliases + ); // Check if the alias is already defined if (aliasTarget.command) { - throw new this.serverless.classes - .Error(`Alias "${alias}" is already defined for command ${aliasTarget.command}`); + throw new this.serverless.classes.Error( + `Alias "${alias}" is already defined for command ${aliasTarget.command}` + ); } // Check if the alias would overwrite an exiting command - if (_.reduce(splitAlias, (__, aliasPath) => { - if (!__ || !__.commands || !__.commands[aliasPath]) { - return null; - } - return __.commands[aliasPath]; - }, this)) { - throw new this.serverless.classes - .Error(`Command "${alias}" cannot be overriden by an alias`); + if ( + _.reduce( + splitAlias, + (__, aliasPath) => { + if (!__ || !__.commands || !__.commands[aliasPath]) { + return null; + } + return __.commands[aliasPath]; + }, + this + ) + ) { + throw new this.serverless.classes.Error(`Command "${alias}" cannot be overriden by an alias`); } aliasTarget.command = command; } @@ -237,17 +251,11 @@ class PluginManager { // Check if there is already an alias for the same path as the command const aliasCommand = this.getAliasCommandTarget(_.split(key, ':')); if (aliasCommand) { - throw new this.serverless.classes - .Error(`Command "${key}" cannot override an existing alias`); + throw new this.serverless.classes.Error(`Command "${key}" cannot override an existing alias`); } // Load the command const commands = _.mapValues(details.commands, (subDetails, subKey) => - this.loadCommand( - pluginName, - subDetails, - `${key}:${subKey}`, - commandIsEntryPoint - ) + this.loadCommand(pluginName, subDetails, `${key}:${subKey}`, commandIsEntryPoint) ); // Handle command aliases _.forEach(details.aliases, alias => { @@ -374,44 +382,52 @@ class PluginManager { const aliasCommandTarget = this.getAliasCommandTarget(commandsArray); const commandOrAlias = aliasCommandTarget ? _.split(aliasCommandTarget, ':') : commandsArray; - return _.reduce(commandOrAlias, (current, name, index) => { - const commandExists = name in current.commands; - const isNotContainer = commandExists && current.commands[name].type !== 'container'; - const isNotEntrypoint = commandExists && current.commands[name].type !== 'entrypoint'; - const remainingIterationsLeft = index < commandOrAlias.length - 1; + return _.reduce( + commandOrAlias, + (current, name, index) => { + const commandExists = name in current.commands; + const isNotContainer = commandExists && current.commands[name].type !== 'container'; + const isNotEntrypoint = commandExists && current.commands[name].type !== 'entrypoint'; + const remainingIterationsLeft = index < commandOrAlias.length - 1; - if (commandExists - && (isNotContainer || remainingIterationsLeft) - && (isNotEntrypoint || allowEntryPoints)) { - return current.commands[name]; - } - // if user is using a top level command properly, but sub commands are not - if (this.serverless.cli.loadedCommands[commandOrAlias[0]]) { - const errorMessage = [`"${name}" is not a valid sub command. Run "serverless `]; - for (let i = 0; commandOrAlias[i] !== name; i++) { - errorMessage.push(`${commandOrAlias[i]}`); - if (commandOrAlias[i + 1] !== name) { - errorMessage.push(' '); - } + if ( + commandExists && + (isNotContainer || remainingIterationsLeft) && + (isNotEntrypoint || allowEntryPoints) + ) { + return current.commands[name]; + } + // if user is using a top level command properly, but sub commands are not + if (this.serverless.cli.loadedCommands[commandOrAlias[0]]) { + const errorMessage = [`"${name}" is not a valid sub command. Run "serverless `]; + for (let i = 0; commandOrAlias[i] !== name; i++) { + errorMessage.push(`${commandOrAlias[i]}`); + if (commandOrAlias[i + 1] !== name) { + errorMessage.push(' '); + } + } + errorMessage.push('" to see a more helpful error message for this command.'); + throw new this.serverless.classes.Error(errorMessage.join('')); } - errorMessage.push('" to see a more helpful error message for this command.'); - throw new this.serverless.classes.Error(errorMessage.join('')); - } - // top level command isn't valid. give a suggestion - const commandName = commandOrAlias.slice(0, index + 1).join(' '); - const suggestedCommand = getCommandSuggestion(commandName, - this.serverless.cli.loadedCommands); - const errorMessage = [ - `Serverless command "${commandName}" not found. Did you mean "${suggestedCommand}"?`, - ' Run "serverless help" for a list of all available commands.', - ].join(''); - throw new this.serverless.classes.Error(errorMessage); - }, { commands: this.commands }); + // top level command isn't valid. give a suggestion + const commandName = commandOrAlias.slice(0, index + 1).join(' '); + const suggestedCommand = getCommandSuggestion( + commandName, + this.serverless.cli.loadedCommands + ); + const errorMessage = [ + `Serverless command "${commandName}" not found. Did you mean "${suggestedCommand}"?`, + ' Run "serverless help" for a list of all available commands.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + }, + { commands: this.commands } + ); } getEvents(command) { - return _.flatMap(command.lifecycleEvents, (event) => [ + return _.flatMap(command.lifecycleEvents, event => [ `before:${command.key}:${event}`, `${command.key}:${event}`, `after:${command.key}:${event}`, @@ -423,7 +439,7 @@ class PluginManager { } getHooks(events) { - return _.flatMap([].concat(events), (event) => this.hooks[event] || []); + return _.flatMap([].concat(events), event => this.hooks[event] || []); } invoke(commandsArray, allowEntryPoints) { @@ -445,13 +461,15 @@ class PluginManager { } } - return BbPromise.reduce(hooks, (__, hook) => hook.hook(), null) - .catch(TerminateHookChain, () => { - if (process.env.SLS_DEBUG) { - this.serverless.cli.log(`Terminate ${_.join(commandsArray, ':')}`); + return BbPromise.reduce(hooks, (__, hook) => hook.hook(), null).catch( + TerminateHookChain, + () => { + if (process.env.SLS_DEBUG) { + this.serverless.cli.log(`Terminate ${_.join(commandsArray, ':')}`); + } + return BbPromise.resolve(); } - return BbPromise.resolve(); - }); + ); } /** @@ -463,8 +481,7 @@ class PluginManager { if (_.isString(commandsArray)) { commands = _.split(commandsArray, ':'); } - return this.invoke(commands, true) - .then(() => { + return this.invoke(commands, true).then(() => { if (_.get(options, 'terminateLifecycleAfterExecution', false)) { return BbPromise.reject(new TerminateHookChain(commands)); } @@ -503,7 +520,7 @@ class PluginManager { validateOptions(command) { _.forEach(command.options, (value, key) => { - if (value.required && (this.cliOptions[key] === true || !(this.cliOptions[key]))) { + if (value.required && (this.cliOptions[key] === true || !this.cliOptions[key])) { let requiredThings = `the --${key} option`; if (value.shortcut) { @@ -518,10 +535,12 @@ class PluginManager { throw new this.serverless.classes.Error(errorMessage); } - if (_.isPlainObject(value.customValidation) && + if ( + _.isPlainObject(value.customValidation) && value.customValidation.regularExpression instanceof RegExp && _.isString(value.customValidation.errorMessage) && - !value.customValidation.regularExpression.test(this.cliOptions[key])) { + !value.customValidation.regularExpression.test(this.cliOptions[key]) + ) { throw new this.serverless.classes.Error(value.customValidation.errorMessage); } }); @@ -547,8 +566,10 @@ class PluginManager { .concat(Object.keys(command.commands)); }); - const serverlessConfigFileHash = crypto.createHash('sha256') - .update(JSON.stringify(this.serverlessConfigFile)).digest('hex'); + const serverlessConfigFileHash = crypto + .createHash('sha256') + .update(JSON.stringify(this.serverlessConfigFile)) + .digest('hex'); cacheFile.validationHash = serverlessConfigFileHash; const cacheFilePath = getCacheFilePath(this.serverless.config.servicePath); @@ -557,9 +578,11 @@ class PluginManager { convertShortcutsIntoOptions(command) { _.forEach(command.options, (optionObject, optionKey) => { - if (optionObject.shortcut && _.includes(Object.keys(this.cliOptions), - optionObject.shortcut)) { - Object.keys(this.cliOptions).forEach((option) => { + if ( + optionObject.shortcut && + _.includes(Object.keys(this.cliOptions), optionObject.shortcut) + ) { + Object.keys(this.cliOptions).forEach(option => { if (option === optionObject.shortcut) { this.cliOptions[optionKey] = this.cliOptions[option]; } @@ -577,7 +600,7 @@ class PluginManager { } asyncPluginInit() { - return BbPromise.map(this.plugins, plugin => (plugin.asyncInit && plugin.asyncInit())); + return BbPromise.map(this.plugins, plugin => plugin.asyncInit && plugin.asyncInit()); } } diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index bd7a0e56c..958744f87 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -32,8 +32,7 @@ describe('PluginManager', () => { let pluginManager; let serverless; - class ServicePluginMock1 { - } + class ServicePluginMock1 {} class ServicePluginMock2 {} @@ -51,9 +50,7 @@ describe('PluginManager', () => { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - ], + lifecycleEvents: ['resources'], }, }; @@ -76,9 +73,7 @@ describe('PluginManager', () => { this.commands = { deploy: { - lifecycleEvents: [ - 'resources', - ], + lifecycleEvents: ['resources'], }, }; @@ -100,10 +95,7 @@ describe('PluginManager', () => { this.commands = { deploy: { usage: 'Deploy to the default infrastructure', - lifecycleEvents: [ - 'resources', - 'functions', - ], + lifecycleEvents: ['resources', 'functions'], options: { resource: { usage: 'The resource you want to deploy (e.g. --resource db)', @@ -115,10 +107,7 @@ describe('PluginManager', () => { commands: { onpremises: { usage: 'Deploy to your On-Premises infrastructure', - lifecycleEvents: [ - 'resources', - 'functions', - ], + lifecycleEvents: ['resources', 'functions'], options: { resource: { usage: 'The resource you want to deploy (e.g. --resource db)', @@ -143,14 +132,14 @@ describe('PluginManager', () => { } functions() { - return new BbPromise((resolve) => { + return new BbPromise(resolve => { this.deployedFunctions = this.deployedFunctions + 1; return resolve(); }); } resources() { - return new BbPromise((resolve) => { + return new BbPromise(resolve => { this.deployedResources = this.deployedResources + 1; return resolve(); }); @@ -162,10 +151,7 @@ describe('PluginManager', () => { this.commands = { deploy: { usage: 'Deploy to the default infrastructure', - lifecycleEvents: [ - 'resources', - 'functions', - ], + lifecycleEvents: ['resources', 'functions'], options: { resource: { usage: 'The resource you want to deploy (e.g. --resource db)', @@ -177,10 +163,7 @@ describe('PluginManager', () => { commands: { onpremises: { usage: 'Deploy to your On-Premises infrastructure', - lifecycleEvents: [ - 'resources', - 'functions', - ], + lifecycleEvents: ['resources', 'functions'], options: { resource: { usage: 'The resource you want to deploy (e.g. --resource db)', @@ -218,10 +201,7 @@ describe('PluginManager', () => { this.commands = { deploy: { usage: 'Deploy to the default infrastructure', - lifecycleEvents: [ - 'resources', - 'functions', - ], + lifecycleEvents: ['resources', 'functions'], options: { resource: { usage: 'The resource you want to deploy (e.g. --resource db)', @@ -233,14 +213,8 @@ describe('PluginManager', () => { commands: { onpremises: { usage: 'Deploy to your On-Premises infrastructure', - lifecycleEvents: [ - 'resources', - 'functions', - ], - aliases: [ - 'on:premise', - 'premise', - ], + lifecycleEvents: ['resources', 'functions'], + aliases: ['on:premise', 'premise'], options: { resource: { usage: 'The resource you want to deploy (e.g. --resource db)', @@ -278,57 +252,36 @@ describe('PluginManager', () => { this.commands = { myep: { type: 'entrypoint', - lifecycleEvents: [ - 'initialize', - 'finalize', - ], + lifecycleEvents: ['initialize', 'finalize'], commands: { // EP, not public command because its parent is decalred as EP mysubep: { - lifecycleEvents: [ - 'initialize', - 'finalize', - ], + lifecycleEvents: ['initialize', 'finalize'], }, // EP that will spawn sub lifecycles spawnep: { - lifecycleEvents: [ - 'event1', - 'event2', - ], + lifecycleEvents: ['event1', 'event2'], }, }, }, // public command mycmd: { - lifecycleEvents: [ - 'run', - ], + lifecycleEvents: ['run'], commands: { // public subcommand mysubcmd: { - lifecycleEvents: [ - 'initialize', - 'finalize', - ], + lifecycleEvents: ['initialize', 'finalize'], }, // command that will spawn sub lifecycles spawncmd: { - lifecycleEvents: [ - 'event1', - 'event2', - ], + lifecycleEvents: ['event1', 'event2'], }, spawnep: { type: 'entrypoint', - lifecycleEvents: [ - 'event1', - 'event2', - ], + lifecycleEvents: ['event1', 'event2'], }, }, }, - }; this.hooks = { @@ -341,14 +294,14 @@ describe('PluginManager', () => { 'mycmd:run': this.run.bind(this), // Event1 spawns mysubcmd, then myep // Event2 spawns mycmd, then mysubep - 'myep:spawnep:event1': () => pluginManager.spawn(['mycmd', 'mysubcmd']) - .then(() => pluginManager.spawn(['myep'])), - 'myep:spawnep:event2': () => pluginManager.spawn(['mycmd']) - .then(() => pluginManager.spawn(['myep', 'mysubep'])), - 'mycmd:spawncmd:event1': () => pluginManager.spawn(['mycmd', 'mysubcmd']) - .then(() => pluginManager.spawn(['myep'])), - 'mycmd:spawncmd:event2': () => pluginManager.spawn(['mycmd']) - .then(() => pluginManager.spawn(['myep', 'mysubep'])), + 'myep:spawnep:event1': () => + pluginManager.spawn(['mycmd', 'mysubcmd']).then(() => pluginManager.spawn(['myep'])), + 'myep:spawnep:event2': () => + pluginManager.spawn(['mycmd']).then(() => pluginManager.spawn(['myep', 'mysubep'])), + 'mycmd:spawncmd:event1': () => + pluginManager.spawn(['mycmd', 'mysubcmd']).then(() => pluginManager.spawn(['myep'])), + 'mycmd:spawncmd:event2': () => + pluginManager.spawn(['mycmd']).then(() => pluginManager.spawn(['myep', 'mysubep'])), }; this.callResult = ''; @@ -392,10 +345,7 @@ describe('PluginManager', () => { commands: { // public command because its children of a container mysubcmd: { - lifecycleEvents: [ - 'event1', - 'event2', - ], + lifecycleEvents: ['event1', 'event2'], }, }, }, @@ -426,9 +376,13 @@ describe('PluginManager', () => { }; } - deprecated() { return; } + deprecated() { + return; + } - untouched() { return; } + untouched() { + return; + } } let restoreEnv; @@ -594,13 +548,11 @@ describe('PluginManager', () => { it('should load two plugins that happen to have the same class name', () => { function getFirst() { - return class PluginMock { - }; + return class PluginMock {}; } function getSecond() { - return class PluginMock { - }; + return class PluginMock {}; } const first = getFirst(); @@ -675,8 +627,7 @@ describe('PluginManager', () => { it('should throw an error when trying to load unknown plugin', () => { const servicePlugins = ['ServicePluginMock3', 'ServicePluginMock1']; - expect(() => pluginManager.loadPlugins(servicePlugins)) - .to.throw(serverless.classes.Error); + expect(() => pluginManager.loadPlugins(servicePlugins)).to.throw(serverless.classes.Error); }); it('should log a warning when trying to load unknown plugin with help flag', () => { @@ -686,16 +637,16 @@ describe('PluginManager', () => { pluginManager.setCliOptions({ help: true }); pluginManager.loadPlugins(servicePlugins); - expect(pluginManager.plugins - .some(plugin => plugin instanceof ServicePluginMock1)).to.equal(true); + expect(pluginManager.plugins.some(plugin => plugin instanceof ServicePluginMock1)).to.equal( + true + ); expect(consoleLogStub.calledOnce).to.equal(true); }); it('should throw an error when trying to load a broken plugin (without SLS_DEBUG)', () => { const servicePlugins = ['BrokenPluginMock']; - expect(() => pluginManager.loadPlugins(servicePlugins)) - .to.throw(serverless.classes.Error); + expect(() => pluginManager.loadPlugins(servicePlugins)).to.throw(serverless.classes.Error); }); it('should forward any errors when trying to load a broken plugin (with SLS_DEBUG)', () => { @@ -703,19 +654,18 @@ describe('PluginManager', () => { return BbPromise.try(() => { _.set(process.env, 'SLS_DEBUG', '*'); - expect(() => pluginManager.loadPlugins(servicePlugins)) - .to.throw(/Broken plugin/); + expect(() => pluginManager.loadPlugins(servicePlugins)).to.throw(/Broken plugin/); }); }); - it('should not throw error when running the plugin commands and given plugins does not exist', - () => { + it('should not throw error when running the plugin commands and given plugins does not exist', () => { const servicePlugins = ['ServicePluginMock3']; const cliCommandsMock = ['plugin']; pluginManager.setCliCommands(cliCommandsMock); - expect(() => pluginManager.loadPlugins(servicePlugins)) - .to.not.throw(serverless.classes.Error); + expect(() => pluginManager.loadPlugins(servicePlugins)).to.not.throw( + serverless.classes.Error + ); }); afterEach(() => { @@ -734,7 +684,8 @@ describe('PluginManager', () => { }); describe('#loadAllPlugins()', () => { - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + beforeEach(() => { + // eslint-disable-line prefer-arrow-callback mockRequire('ServicePluginMock1', ServicePluginMock1); mockRequire('ServicePluginMock2', ServicePluginMock2); mockRequire('@serverless/enterprise-plugin', EnterprisePluginMock); @@ -753,12 +704,15 @@ describe('PluginManager', () => { const servicePlugins = ['ServicePluginMock1', 'ServicePluginMock2']; pluginManager.loadAllPlugins(servicePlugins); - expect(pluginManager.plugins - .some(plugin => plugin instanceof ServicePluginMock1)).to.equal(true); - expect(pluginManager.plugins - .some(plugin => plugin instanceof ServicePluginMock2)).to.equal(true); - expect(pluginManager.plugins - .some(plugin => plugin instanceof EnterprisePluginMock)).to.equal(true); + expect(pluginManager.plugins.some(plugin => plugin instanceof ServicePluginMock1)).to.equal( + true + ); + expect(pluginManager.plugins.some(plugin => plugin instanceof ServicePluginMock2)).to.equal( + true + ); + expect(pluginManager.plugins.some(plugin => plugin instanceof EnterprisePluginMock)).to.equal( + true + ); // note: this test will be refactored as the Create plugin will be moved // to another directory expect(pluginManager.plugins.length).to.be.above(2); @@ -785,7 +739,8 @@ describe('PluginManager', () => { expect(pluginManager.plugins[3]).to.be.instanceof(EnterprisePluginMock); }); - afterEach(function () { // eslint-disable-line prefer-arrow-callback + afterEach(() => { + // eslint-disable-line prefer-arrow-callback mockRequire.stop('ServicePluginMock1'); mockRequire.stop('ServicePluginMock2'); mockRequire.stop('@serverless/enterprise-plugin'); @@ -801,7 +756,8 @@ describe('PluginManager', () => { }); describe('#loadServicePlugins()', () => { - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + beforeEach(() => { + // eslint-disable-line prefer-arrow-callback mockRequire('ServicePluginMock1', ServicePluginMock1); // Plugins loaded via a relative path should be required relative to the service path const servicePath = pluginManager.serverless.config.servicePath; @@ -812,10 +768,12 @@ describe('PluginManager', () => { const servicePlugins = ['ServicePluginMock1', './RelativePath/ServicePluginMock2']; pluginManager.loadServicePlugins(servicePlugins); - expect(pluginManager.plugins - .some(plugin => plugin instanceof ServicePluginMock1)).to.equal(true); - expect(pluginManager.plugins - .some(plugin => plugin instanceof ServicePluginMock2)).to.equal(true); + expect(pluginManager.plugins.some(plugin => plugin instanceof ServicePluginMock1)).to.equal( + true + ); + expect(pluginManager.plugins.some(plugin => plugin instanceof ServicePluginMock2)).to.equal( + true + ); }); it('should not error if plugins = null', () => { @@ -830,7 +788,8 @@ describe('PluginManager', () => { pluginManager.loadServicePlugins(servicePlugins); }); - afterEach(function () { // eslint-disable-line prefer-arrow-callback + afterEach(() => { + // eslint-disable-line prefer-arrow-callback mockRequire.stop('ServicePluginMock1'); mockRequire.stop('ServicePluginMock2'); }); @@ -909,10 +868,8 @@ describe('PluginManager', () => { }, }, }; - expect(pluginManager.getAliasCommandTarget(['cmd1', 'cmd2'])) - .to.equal('command1'); - expect(pluginManager.getAliasCommandTarget(['cmd1', 'cmd3', 'cmd4'])) - .to.equal('command2'); + expect(pluginManager.getAliasCommandTarget(['cmd1', 'cmd2'])).to.equal('command1'); + expect(pluginManager.getAliasCommandTarget(['cmd1', 'cmd3', 'cmd4'])).to.equal('command2'); }); it('should return undefined if alias does not exist', () => { @@ -928,31 +885,27 @@ describe('PluginManager', () => { }, }, }; - expect(pluginManager.getAliasCommandTarget(['cmd1'])) - .to.be.undefined; - expect(pluginManager.getAliasCommandTarget(['cmd1', 'cmd3'])) - .to.be.undefined; + expect(pluginManager.getAliasCommandTarget(['cmd1'])).to.be.undefined; + expect(pluginManager.getAliasCommandTarget(['cmd1', 'cmd3'])).to.be.undefined; }); }); describe('#createCommandAlias', () => { it('should create an alias for a command', () => { pluginManager.aliases = {}; - expect(pluginManager.createCommandAlias('cmd1:alias2', 'cmd2:cmd3:cmd4')) - .to.not.throw; - expect(pluginManager.createCommandAlias('cmd1:alias2:alias3', 'cmd2:cmd3:cmd5')) - .to.not.throw; - expect(pluginManager.aliases) - .to.deep.equal({ - cmd1: { - alias2: { - command: 'cmd2:cmd3:cmd4', - alias3: { - command: 'cmd2:cmd3:cmd5', - }, + expect(pluginManager.createCommandAlias('cmd1:alias2', 'cmd2:cmd3:cmd4')).to.not.throw; + expect(pluginManager.createCommandAlias('cmd1:alias2:alias3', 'cmd2:cmd3:cmd5')).to.not + .throw; + expect(pluginManager.aliases).to.deep.equal({ + cmd1: { + alias2: { + command: 'cmd2:cmd3:cmd4', + alias3: { + command: 'cmd2:cmd3:cmd5', }, }, - }); + }, + }); }); it('should fail if the alias already exists', () => { @@ -966,22 +919,25 @@ describe('PluginManager', () => { }, }, }; - expect(() => pluginManager.createCommandAlias('cmd1:alias2', 'mycmd')) - .to.throw(/Alias "cmd1:alias2" is already defined/); + expect(() => pluginManager.createCommandAlias('cmd1:alias2', 'mycmd')).to.throw( + /Alias "cmd1:alias2" is already defined/ + ); }); it('should fail if the alias overwrites a command', () => { const synchronousPluginMockInstance = new SynchronousPluginMock(); pluginManager.loadCommands(synchronousPluginMockInstance); - expect(() => pluginManager.createCommandAlias('deploy', 'mycmd')) - .to.throw(/Command "deploy" cannot be overriden/); + expect(() => pluginManager.createCommandAlias('deploy', 'mycmd')).to.throw( + /Command "deploy" cannot be overriden/ + ); }); it('should fail if the alias overwrites the very own command', () => { const synchronousPluginMockInstance = new SynchronousPluginMock(); synchronousPluginMockInstance.commands.deploy.commands.onpremises.aliases = ['deploy']; - expect(() => pluginManager.loadCommands(synchronousPluginMockInstance)) - .to.throw(/Command "deploy" cannot be overriden/); + expect(() => pluginManager.loadCommands(synchronousPluginMockInstance)).to.throw( + /Command "deploy" cannot be overriden/ + ); }); }); }); @@ -998,9 +954,7 @@ describe('PluginManager', () => { pluginManager.loadCommands({ commands: { deploy: { - lifecycleEvents: [ - 'one', - ], + lifecycleEvents: ['one'], options: { foo: {}, }, @@ -1011,24 +965,22 @@ describe('PluginManager', () => { pluginManager.loadCommands({ commands: { deploy: { - lifecycleEvents: [ - 'one', - 'two', - ], + lifecycleEvents: ['one', 'two'], options: { bar: {}, }, commands: { - fn: { - }, + fn: {}, }, }, }, }); - expect(pluginManager.commands.deploy).to.have.property('options') + expect(pluginManager.commands.deploy) + .to.have.property('options') .that.has.all.keys('foo', 'bar'); - expect(pluginManager.commands.deploy).to.have.property('lifecycleEvents') + expect(pluginManager.commands.deploy) + .to.have.property('lifecycleEvents') .that.is.an('array') .that.deep.equals(['one', 'two']); expect(pluginManager.commands.deploy.commands).to.have.property('fn'); @@ -1042,8 +994,9 @@ describe('PluginManager', () => { }; const synchronousPluginMockInstance = new SynchronousPluginMock(); - expect(() => pluginManager.loadCommands(synchronousPluginMockInstance)) - .to.throw(/Command "deploy" cannot override an existing alias/); + expect(() => pluginManager.loadCommands(synchronousPluginMockInstance)).to.throw( + /Command "deploy" cannot override an existing alias/ + ); }); it('should log the alias when SLS_DEBUG is set', () => { @@ -1076,12 +1029,13 @@ describe('PluginManager', () => { it('should replace deprecated events with the new ones', () => { pluginManager.loadHooks(deprecatedPluginInstance); - expect(pluginManager.hooks['deprecated:deprecated']) - .to.equal(undefined); - expect(pluginManager.hooks['new:new'][0].pluginName) - .to.equal('DeprecatedLifecycleEventsPluginMock'); - expect(pluginManager.hooks['untouched:untouched'][0].pluginName) - .to.equal('DeprecatedLifecycleEventsPluginMock'); + expect(pluginManager.hooks['deprecated:deprecated']).to.equal(undefined); + expect(pluginManager.hooks['new:new'][0].pluginName).to.equal( + 'DeprecatedLifecycleEventsPluginMock' + ); + expect(pluginManager.hooks['untouched:untouched'][0].pluginName).to.equal( + 'DeprecatedLifecycleEventsPluginMock' + ); expect(consoleLogStub.calledOnce).to.equal(false); }); @@ -1094,7 +1048,8 @@ describe('PluginManager', () => { }); describe('#getEvents()', () => { - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + beforeEach(() => { + // eslint-disable-line prefer-arrow-callback pluginManager.addPlugin(SynchronousPluginMock); }); @@ -1124,12 +1079,15 @@ describe('PluginManager', () => { }); describe('#getHooks()', () => { - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + beforeEach(() => { + // eslint-disable-line prefer-arrow-callback pluginManager.addPlugin(SynchronousPluginMock); }); it('should get hooks for an event with some registered', () => { - expect(pluginManager.getHooks(['deploy:functions'])).to.be.an('Array').with.length(1); + expect(pluginManager.getHooks(['deploy:functions'])) + .to.be.an('Array') + .with.length(1); }); it('should have the plugin name and function on the hook', () => { @@ -1139,16 +1097,21 @@ describe('PluginManager', () => { }); it('should not get hooks for an event that does not have any', () => { - expect(pluginManager.getHooks(['deploy:resources'])).to.be.an('Array').with.length(0); + expect(pluginManager.getHooks(['deploy:resources'])) + .to.be.an('Array') + .with.length(0); }); it('should accept a single event in place of an array', () => { - expect(pluginManager.getHooks('deploy:functions')).to.be.an('Array').with.length(1); + expect(pluginManager.getHooks('deploy:functions')) + .to.be.an('Array') + .with.length(1); }); }); describe('#getPlugins()', () => { - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + beforeEach(() => { + // eslint-disable-line prefer-arrow-callback mockRequire('ServicePluginMock1', ServicePluginMock1); mockRequire('ServicePluginMock2', ServicePluginMock2); }); @@ -1161,7 +1124,8 @@ describe('PluginManager', () => { expect(pluginManager.getPlugins()[1]).to.be.instanceof(ServicePluginMock2); }); - afterEach(function () { // eslint-disable-line prefer-arrow-callback + afterEach(() => { + // eslint-disable-line prefer-arrow-callback mockRequire.stop('ServicePluginMock1'); mockRequire.stop('ServicePluginMock2'); }); @@ -1171,29 +1135,33 @@ describe('PluginManager', () => { it('should find commands', () => { pluginManager.addPlugin(EntrypointPluginMock); - expect(() => pluginManager.validateCommand(['mycmd', 'mysubcmd'])) - .to.not.throw(serverless.classes.Error); + expect(() => pluginManager.validateCommand(['mycmd', 'mysubcmd'])).to.not.throw( + serverless.classes.Error + ); }); it('should find container children commands', () => { pluginManager.addPlugin(ContainerPluginMock); - expect(() => pluginManager.validateCommand(['mycontainer', 'mysubcmd'])) - .to.not.throw(serverless.classes.Error); + expect(() => pluginManager.validateCommand(['mycontainer', 'mysubcmd'])).to.not.throw( + serverless.classes.Error + ); }); it('should throw on entrypoints', () => { pluginManager.addPlugin(EntrypointPluginMock); - expect(() => pluginManager.validateCommand(['myep', 'mysubep'])) - .to.throw(/command ".*" not found/); + expect(() => pluginManager.validateCommand(['myep', 'mysubep'])).to.throw( + /command ".*" not found/ + ); }); it('should throw on container', () => { pluginManager.addPlugin(ContainerPluginMock); - expect(() => pluginManager.validateCommand(['mycontainer'])) - .to.throw(/command ".*" not found/); + expect(() => pluginManager.validateCommand(['mycontainer'])).to.throw( + /command ".*" not found/ + ); }); }); @@ -1283,7 +1251,9 @@ describe('PluginManager', () => { const foo = pluginManagerInstance.commands.foo; - expect(() => { pluginManager.validateServerlessConfigDependency(foo); }).to.throw(Error); + expect(() => { + pluginManager.validateServerlessConfigDependency(foo); + }).to.throw(Error); }); it('should throw an error if configDependent is true and config is an empty string', () => { @@ -1297,7 +1267,9 @@ describe('PluginManager', () => { const foo = pluginManagerInstance.commands.foo; - expect(() => { pluginManager.validateServerlessConfigDependency(foo); }).to.throw(Error); + expect(() => { + pluginManager.validateServerlessConfigDependency(foo); + }).to.throw(Error); }); it('should load if the configDependent property is true and config exists', () => { @@ -1340,8 +1312,12 @@ describe('PluginManager', () => { const foo = pluginManager.commands.foo; const bar = pluginManager.commands.bar; - expect(() => { pluginManager.validateOptions(foo); }).to.throw(Error); - expect(() => { pluginManager.validateOptions(bar); }).to.throw(Error); + expect(() => { + pluginManager.validateOptions(foo); + }).to.throw(Error); + expect(() => { + pluginManager.validateOptions(bar); + }).to.throw(Error); }); it('should throw an error if a customValidation is not met', () => { @@ -1361,7 +1337,9 @@ describe('PluginManager', () => { }; const command = pluginManager.commands.foo; - expect(() => { pluginManager.validateOptions(command); }).to.throw(Error); + expect(() => { + pluginManager.validateOptions(command); + }).to.throw(Error); }); it('should succeeds if a custom regex matches in a plain commands object', () => { @@ -1381,7 +1359,9 @@ describe('PluginManager', () => { }; const commandsArray = ['foo']; - expect(() => { pluginManager.validateOptions(commandsArray); }).to.not.throw(Error); + expect(() => { + pluginManager.validateOptions(commandsArray); + }).to.not.throw(Error); }); }); @@ -1391,7 +1371,9 @@ describe('PluginManager', () => { const commandsArray = ['foo']; - expect(() => { pluginManager.run(commandsArray); }).to.throw(Error); + expect(() => { + pluginManager.run(commandsArray); + }).to.throw(Error); }); it('should throw an error when the given command is an entrypoint', () => { @@ -1399,7 +1381,9 @@ describe('PluginManager', () => { const commandsArray = ['myep']; - expect(() => { pluginManager.run(commandsArray); }).to.throw(Error); + expect(() => { + pluginManager.run(commandsArray); + }).to.throw(Error); }); it('should throw an error when the given command is a container', () => { @@ -1407,7 +1391,9 @@ describe('PluginManager', () => { const commandsArray = ['mycontainer']; - expect(() => { pluginManager.run(commandsArray); }).to.throw(Error); + expect(() => { + pluginManager.run(commandsArray); + }).to.throw(Error); }); it('should NOT throw an error when the given command is a child of a container', () => { @@ -1415,7 +1401,9 @@ describe('PluginManager', () => { const commandsArray = ['mycontainer', 'mysubcmd']; - expect(() => { pluginManager.run(commandsArray); }).to.not.throw(Error); + expect(() => { + pluginManager.run(commandsArray); + }).to.not.throw(Error); }); it('should throw an error when the given command is a child of an entrypoint', () => { @@ -1423,7 +1411,9 @@ describe('PluginManager', () => { const commandsArray = ['mysubcmd']; - expect(() => { pluginManager.run(commandsArray); }).to.throw(Error); + expect(() => { + pluginManager.run(commandsArray); + }).to.throw(Error); }); it('should show warning if in debug mode and the given command has no hooks', () => { @@ -1453,11 +1443,7 @@ describe('PluginManager', () => { this.commands = { run: { usage: 'Pushes the current hook status on the hookStatus array', - lifecycleEvents: [ - 'beforeHookStatus', - 'midHookStatus', - 'afterHookStatus', - ], + lifecycleEvents: ['beforeHookStatus', 'midHookStatus', 'afterHookStatus'], }, }; @@ -1486,64 +1472,66 @@ describe('PluginManager', () => { pluginManager.addPlugin(CorrectHookOrderPluginMock); const commandsArray = ['run']; - return pluginManager.run(commandsArray) - .then(() => { - expect(pluginManager.plugins[0].hookStatus[0]).to.equal('before'); - expect(pluginManager.plugins[0].hookStatus[1]).to.equal('mid'); - expect(pluginManager.plugins[0].hookStatus[2]).to.equal('after'); - }); + return pluginManager.run(commandsArray).then(() => { + expect(pluginManager.plugins[0].hookStatus[0]).to.equal('before'); + expect(pluginManager.plugins[0].hookStatus[1]).to.equal('mid'); + expect(pluginManager.plugins[0].hookStatus[2]).to.equal('after'); + }); }); describe('when using a synchronous hook function', () => { - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + beforeEach(() => { + // eslint-disable-line prefer-arrow-callback pluginManager.addPlugin(SynchronousPluginMock); }); describe('when running a simple command', () => { it('should run a simple command', () => { const commandsArray = ['deploy']; - return pluginManager.run(commandsArray) - .then(() => expect(pluginManager.plugins[0].deployedFunctions) - .to.equal(1)); + return pluginManager + .run(commandsArray) + .then(() => expect(pluginManager.plugins[0].deployedFunctions).to.equal(1)); }); }); describe('when running a nested command', () => { it('should run the nested command', () => { const commandsArray = ['deploy', 'onpremises']; - return pluginManager.run(commandsArray) - .then(() => expect(pluginManager.plugins[0].deployedResources) - .to.equal(1)); + return pluginManager + .run(commandsArray) + .then(() => expect(pluginManager.plugins[0].deployedResources).to.equal(1)); }); }); }); describe('when using a promise based hook function', () => { - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + beforeEach(() => { + // eslint-disable-line prefer-arrow-callback pluginManager.addPlugin(PromisePluginMock); }); describe('when running a simple command', () => { it('should run the simple command', () => { const commandsArray = ['deploy']; - return pluginManager.run(commandsArray) - .then(() => expect(pluginManager.plugins[0].deployedFunctions) - .to.equal(1)); + return pluginManager + .run(commandsArray) + .then(() => expect(pluginManager.plugins[0].deployedFunctions).to.equal(1)); }); }); describe('when running a nested command', () => { it('should run the nested command', () => { const commandsArray = ['deploy', 'onpremises']; - return pluginManager.run(commandsArray) - .then(() => expect(pluginManager.plugins[0].deployedResources) - .to.equal(1)); + return pluginManager + .run(commandsArray) + .then(() => expect(pluginManager.plugins[0].deployedResources).to.equal(1)); }); }); }); describe('when using provider specific plugins', () => { - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + beforeEach(() => { + // eslint-disable-line prefer-arrow-callback pluginManager.serverless.service.provider.name = 'provider1'; pluginManager.addPlugin(Provider1PluginMock); @@ -1570,13 +1558,11 @@ describe('PluginManager', () => { const commandsArray = ['mycmd', 'spawncmd']; - return pluginManager.run(commandsArray) - .then(() => { - expect(pluginManager.plugins[0].callResult) - .to.equal( - '>subInitialize>subFinalize>initialize>finalize>run>subEPInitialize>subEPFinalize' - ); - }); + return pluginManager.run(commandsArray).then(() => { + expect(pluginManager.plugins[0].callResult).to.equal( + '>subInitialize>subFinalize>initialize>finalize>run>subEPInitialize>subEPFinalize' + ); + }); }); }); @@ -1598,7 +1584,8 @@ describe('PluginManager', () => { pluginManager.addPlugin(AliasPluginMock); const commands = pluginManager.getCommands(); - expect(commands).to.have.a.property('on') + expect(commands) + .to.have.a.property('on') .that.has.a.nested.property('commands.premise'); expect(commands).to.have.a.property('premise'); }); @@ -1610,9 +1597,7 @@ describe('PluginManager', () => { pluginManager.serverless.cli.loadedCommands = { create: { usage: 'Create new Serverless service', - lifecycleEvents: [ - 'create', - ], + lifecycleEvents: ['create'], options: { template: { usage: 'Template for the service. Available templates: ", "aws-nodejs", "..."', @@ -1625,10 +1610,7 @@ describe('PluginManager', () => { deploy: { usage: 'Deploy a Serverless service', configDependent: true, - lifecycleEvents: [ - 'cleanup', - 'initialize', - ], + lifecycleEvents: ['cleanup', 'initialize'], options: { conceal: { usage: 'Hide secrets from the output (e.g. API Gateway key values)', @@ -1643,11 +1625,7 @@ describe('PluginManager', () => { commands: { function: { usage: 'Deploy a single function from the service', - lifecycleEvents: [ - 'initialize', - 'packageFunction', - 'deploy', - ], + lifecycleEvents: ['initialize', 'packageFunction', 'deploy'], options: { function: { usage: 'Name of the function', @@ -1660,17 +1638,13 @@ describe('PluginManager', () => { }, list: { usage: 'List deployed version of your Serverless Service', - lifecycleEvents: [ - 'log', - ], + lifecycleEvents: ['log'], key: 'deploy:list', pluginName: 'Deploy', commands: { functions: { usage: 'List all the deployed functions and their versions', - lifecycleEvents: [ - 'log', - ], + lifecycleEvents: ['log'], key: 'deploy:list:functions', pluginName: 'Deploy', }, @@ -1680,26 +1654,30 @@ describe('PluginManager', () => { }, }; }); - it('should give a suggestion for an unknown command', (done) => { + it('should give a suggestion for an unknown command', done => { try { pluginManager.getCommand(['creet']); done('Test failed. Expected an error to be thrown'); } catch (error) { expect(error.name).to.eql('ServerlessError'); - expect(error.message).to.eql('Serverless command "creet" not found. ' + - 'Did you mean "create"? Run "serverless help" for a list of all available commands.'); + expect(error.message).to.eql( + 'Serverless command "creet" not found. ' + + 'Did you mean "create"? Run "serverless help" for a list of all available commands.' + ); done(); } }); - it('should not give a suggestion for valid top level command', (done) => { + it('should not give a suggestion for valid top level command', done => { try { pluginManager.getCommand(['deploy', 'function-misspelled']); done('Test failed. Expected an error to be thrown'); } catch (error) { expect(error.name).to.eql('ServerlessError'); - expect(error.message).to.eql('"function-misspelled" is not a valid sub command. ' - + 'Run "serverless deploy" to see a more helpful error message for this command.'); + expect(error.message).to.eql( + '"function-misspelled" is not a valid sub command. ' + + 'Run "serverless deploy" to see a more helpful error message for this command.' + ); done(); } }); @@ -1711,7 +1689,9 @@ describe('PluginManager', () => { const commandsArray = ['foo']; - expect(() => { pluginManager.spawn(commandsArray); }).to.throw(Error); + expect(() => { + pluginManager.spawn(commandsArray); + }).to.throw(Error); }); it('should show warning in debug mode and when the given command has no hooks', () => { @@ -1731,11 +1711,10 @@ describe('PluginManager', () => { const commandsArray = ['foo']; - return pluginManager.run(commandsArray) - .then(() => { - expect(consoleLogStub.called).is.equal(true); - pluginManager.serverless.cli.log.restore(); - }); + return pluginManager.run(commandsArray).then(() => { + expect(consoleLogStub.called).is.equal(true); + pluginManager.serverless.cli.log.restore(); + }); }); describe('when invoking a command', () => { @@ -1744,10 +1723,9 @@ describe('PluginManager', () => { const commandsArray = ['mycmd']; - return pluginManager.spawn(commandsArray) - .then(() => { - expect(pluginManager.plugins[0].callResult).to.equal('>run'); - }); + return pluginManager.spawn(commandsArray).then(() => { + expect(pluginManager.plugins[0].callResult).to.equal('>run'); + }); }); it('should spawn nested commands', () => { @@ -1755,10 +1733,9 @@ describe('PluginManager', () => { const commandsArray = ['mycmd', 'mysubcmd']; - return pluginManager.spawn(commandsArray) - .then(() => { - expect(pluginManager.plugins[0].callResult).to.equal('>subInitialize>subFinalize'); - }); + return pluginManager.spawn(commandsArray).then(() => { + expect(pluginManager.plugins[0].callResult).to.equal('>subInitialize>subFinalize'); + }); }); it('should terminate the hook chain if requested', () => { @@ -1782,9 +1759,7 @@ describe('PluginManager', () => { const commandsArray = ['mycontainer']; - return expect( - () => pluginManager.spawn(commandsArray) - ).to.throw(/command ".*" not found/); + return expect(() => pluginManager.spawn(commandsArray)).to.throw(/command ".*" not found/); }); it('should spawn nested commands', () => { @@ -1792,10 +1767,9 @@ describe('PluginManager', () => { const commandsArray = ['mycontainer', 'mysubcmd']; - return pluginManager.spawn(commandsArray) - .then(() => { - expect(pluginManager.plugins[0].callResult).to.equal('>mysubcmdEvent1>mysubcmdEvent2'); - }); + return pluginManager.spawn(commandsArray).then(() => { + expect(pluginManager.plugins[0].callResult).to.equal('>mysubcmdEvent1>mysubcmdEvent2'); + }); }); }); @@ -1805,10 +1779,9 @@ describe('PluginManager', () => { const commandsArray = ['myep']; - return pluginManager.spawn(commandsArray) - .then(() => { - expect(pluginManager.plugins[0].callResult).to.equal('>initialize>finalize'); - }); + return pluginManager.spawn(commandsArray).then(() => { + expect(pluginManager.plugins[0].callResult).to.equal('>initialize>finalize'); + }); }); it('should spawn nested entrypoints', () => { @@ -1816,30 +1789,26 @@ describe('PluginManager', () => { const commandsArray = ['myep', 'mysubep']; - return pluginManager.spawn(commandsArray) - .then(() => { - expect(pluginManager.plugins[0].callResult).to.equal('>subEPInitialize>subEPFinalize'); - }); + return pluginManager.spawn(commandsArray).then(() => { + expect(pluginManager.plugins[0].callResult).to.equal('>subEPInitialize>subEPFinalize'); + }); }); describe('with string formatted syntax', () => { it('should succeed', () => { pluginManager.addPlugin(EntrypointPluginMock); - return pluginManager.spawn('myep') - .then(() => { - expect(pluginManager.plugins[0].callResult).to.equal('>initialize>finalize'); - }); + return pluginManager.spawn('myep').then(() => { + expect(pluginManager.plugins[0].callResult).to.equal('>initialize>finalize'); + }); }); it('should spawn nested entrypoints', () => { pluginManager.addPlugin(EntrypointPluginMock); - return pluginManager.spawn('myep:mysubep') - .then(() => { - expect(pluginManager.plugins[0].callResult) - .to.equal('>subEPInitialize>subEPFinalize'); - }); + return pluginManager.spawn('myep:mysubep').then(() => { + expect(pluginManager.plugins[0].callResult).to.equal('>subEPInitialize>subEPFinalize'); + }); }); }); }); @@ -1849,13 +1818,11 @@ describe('PluginManager', () => { const commandsArray = ['myep', 'spawnep']; - return pluginManager.spawn(commandsArray) - .then(() => { - expect(pluginManager.plugins[0].callResult) - .to.equal( - '>subInitialize>subFinalize>initialize>finalize>run>subEPInitialize>subEPFinalize' - ); - }); + return pluginManager.spawn(commandsArray).then(() => { + expect(pluginManager.plugins[0].callResult).to.equal( + '>subInitialize>subFinalize>initialize>finalize>run>subEPInitialize>subEPFinalize' + ); + }); }); }); @@ -1863,7 +1830,8 @@ describe('PluginManager', () => { const cwd = process.cwd(); let serviceDir; let tmpDir; - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + beforeEach(() => { + // eslint-disable-line prefer-arrow-callback tmpDir = getTmpDirPath(); serviceDir = path.join(tmpDir, 'service'); fse.mkdirsSync(serviceDir); @@ -1877,7 +1845,8 @@ describe('PluginManager', () => { pluginManager.loadServicePlugins(['local-plugin']); expect(pluginManager.plugins).to.satisfy(plugins => - plugins.some(plugin => plugin.constructor.name === 'SynchronousPluginMock')); + plugins.some(plugin => plugin.constructor.name === 'SynchronousPluginMock') + ); }); it('should load plugins from custom folder', () => { @@ -1891,7 +1860,8 @@ describe('PluginManager', () => { // Had to use contructor.name because the class will be loaded via // require and the reference will not match with SynchronousPluginMock expect(pluginManager.plugins).to.satisfy(plugins => - plugins.some(plugin => plugin.constructor.name === 'SynchronousPluginMock')); + plugins.some(plugin => plugin.constructor.name === 'SynchronousPluginMock') + ); }); it('should load plugins from custom folder outside of serviceDir', () => { @@ -1905,11 +1875,13 @@ describe('PluginManager', () => { }); // Had to use contructor.name because the class will be loaded via // require and the reference will not match with SynchronousPluginMock - expect(pluginManager.plugins).to.satisfy(plugins => plugins.some(plugin => - plugin.constructor.name === 'SynchronousPluginMock')); + expect(pluginManager.plugins).to.satisfy(plugins => + plugins.some(plugin => plugin.constructor.name === 'SynchronousPluginMock') + ); }); - afterEach(function () { // eslint-disable-line prefer-arrow-callback + afterEach(() => { + // eslint-disable-line prefer-arrow-callback process.chdir(cwd); try { fse.removeSync(tmpDir); @@ -1919,7 +1891,7 @@ describe('PluginManager', () => { }); }); - describe('Plugin / CLI integration', function () { + describe('Plugin / CLI integration', function() { this.timeout(0); const cwd = process.cwd(); @@ -1927,13 +1899,15 @@ describe('PluginManager', () => { let serviceDir; let serverlessExec; - beforeEach(function () { // eslint-disable-line prefer-arrow-callback + beforeEach(() => { + // eslint-disable-line prefer-arrow-callback serverlessInstance = new Serverless(); return serverlessInstance.init().then(() => { // Cannot rely on shebang in severless.js to invoke script using NodeJS on Windows. const execPrefix = os.platform() === 'win32' ? 'node ' : ''; - serverlessExec = execPrefix + path.join(serverlessInstance.config.serverlessPath, - '..', 'bin', 'serverless'); + serverlessExec = + execPrefix + + path.join(serverlessInstance.config.serverlessPath, '..', 'bin', 'serverless'); const tmpDir = getTmpDirPath(); serviceDir = path.join(tmpDir, 'service'); fse.mkdirsSync(serviceDir); @@ -1951,10 +1925,12 @@ describe('PluginManager', () => { }); it('should expose a working integration between the CLI and the plugin system', () => { - expect(serverlessInstance.utils - .fileExistsSync(path.join(serviceDir, 'serverless.yml'))).to.equal(true); - expect(serverlessInstance.utils - .fileExistsSync(path.join(serviceDir, 'handler.js'))).to.equal(true); + expect( + serverlessInstance.utils.fileExistsSync(path.join(serviceDir, 'serverless.yml')) + ).to.equal(true); + expect(serverlessInstance.utils.fileExistsSync(path.join(serviceDir, 'handler.js'))).to.equal( + true + ); }); it('should load plugins relatively to the working directory', () => { @@ -1963,16 +1939,19 @@ describe('PluginManager', () => { installPlugin(localPluginDir, SynchronousPluginMock); installPlugin(parentPluginDir, PromisePluginMock); - fs.appendFileSync(path.join(serviceDir, 'serverless.yml'), - 'plugins:\n - local-plugin\n - parent-plugin'); + fs.appendFileSync( + path.join(serviceDir, 'serverless.yml'), + 'plugins:\n - local-plugin\n - parent-plugin' + ); const output = execSync(serverlessExec); - const stringifiedOutput = (new Buffer(output, 'base64').toString()); + const stringifiedOutput = new Buffer(output, 'base64').toString(); expect(stringifiedOutput).to.contain('SynchronousPluginMock'); expect(stringifiedOutput).to.contain('PromisePluginMock'); }); - afterEach(function () { // eslint-disable-line prefer-arrow-callback + afterEach(() => { + // eslint-disable-line prefer-arrow-callback process.chdir(cwd); try { fse.removeSync(serviceDir); diff --git a/lib/classes/PromiseTracker.js b/lib/classes/PromiseTracker.js index 9520d705a..0abaa46bb 100644 --- a/lib/classes/PromiseTracker.js +++ b/lib/classes/PromiseTracker.js @@ -18,16 +18,19 @@ class PromiseTracker { report() { const delta = Date.now() - this.startTime; const pending = this.getPending(); - logInfo([ - '##########################################################################################', - `# ${delta}: ${this.getSettled().length} of ${this.getAll().length} promises have settled`, - `# ${delta}: ${pending.length} unsettled promises:`, - ].concat( - pending.map((promise) => `# ${delta}: ${promise.waitList}`) - ).concat([ - '# This can result from latent connections but may represent a cyclic variable dependency', - '##########################################################################################', - ]).join('\n ')); + logInfo( + [ + '##########################################################################################', + `# ${delta}: ${this.getSettled().length} of ${this.getAll().length} promises have settled`, + `# ${delta}: ${pending.length} unsettled promises:`, + ] + .concat(pending.map(promise => `# ${delta}: ${promise.waitList}`)) + .concat([ + '# This can result from latent connections but may represent a cyclic variable dependency', + '##########################################################################################', + ]) + .join('\n ') + ); } stop() { clearInterval(this.interval); @@ -37,9 +40,15 @@ class PromiseTracker { const promise = prms; promise.waitList = `${variable} waited on by: ${specifier}`; promise.state = 'pending'; - promise.then( // creates a promise with the following effects but that we otherwise ignore - () => { promise.state = 'resolved'; }, - () => { promise.state = 'rejected'; }); + promise.then( + // creates a promise with the following effects but that we otherwise ignore + () => { + promise.state = 'resolved'; + }, + () => { + promise.state = 'rejected'; + } + ); this.promiseList.push(promise); this.promiseMap[variable] = promise; return promise; @@ -52,9 +61,15 @@ class PromiseTracker { promise.waitList += ` ${specifier}`; return promise; } - getPending() { return this.promiseList.filter(p => (p.state === 'pending')); } - getSettled() { return this.promiseList.filter(p => (p.state !== 'pending')); } - getAll() { return this.promiseList; } + getPending() { + return this.promiseList.filter(p => p.state === 'pending'); + } + getSettled() { + return this.promiseList.filter(p => p.state !== 'pending'); + } + getAll() { + return this.promiseList; + } } module.exports = PromiseTracker; diff --git a/lib/classes/PromiseTracker.test.js b/lib/classes/PromiseTracker.test.js index 08073d3dc..5635a6a86 100644 --- a/lib/classes/PromiseTracker.test.js +++ b/lib/classes/PromiseTracker.test.js @@ -35,14 +35,20 @@ describe('PromiseTracker', () => { }); it('reports one pending promise when one has been added', () => { let resolve; - const promise = new BbPromise((rslv) => { resolve = rslv; }); + const promise = new BbPromise(rslv => { + resolve = rslv; + }); promiseTracker.add('foo', promise, '${foo:}'); - return BbPromise.delay(1).then(() => { - const promises = promiseTracker.getPending(); - expect(promises).to.be.an.instanceof(Array); - expect(promises.length).to.equal(1); - expect(promises[0]).to.equal(promise); - }).then(() => { resolve(); }); + return BbPromise.delay(1) + .then(() => { + const promises = promiseTracker.getPending(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(1); + expect(promises[0]).to.equal(promise); + }) + .then(() => { + resolve(); + }); }); it('reports no settled promises when none have been added', () => { const promises = promiseTracker.getSettled(); diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 641f978e0..4ba46d0ce 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -49,7 +49,7 @@ class Service { return BbPromise.all([ serverlessConfigFileUtils.getServerlessConfigFilePath(this.serverless), serverlessConfigFileUtils.getServerlessConfigFile(this.serverless), - ]).then((args) => that.loadServiceFileParam(...args)); + ]).then(args => that.loadServiceFileParam(...args)); } loadServiceFileParam(serviceFilename, serverlessFileParam) { @@ -72,7 +72,9 @@ class Service { throw new ServerlessError(`"service" property is missing in ${this.serviceFilename}`); } if (_.isObject(serverlessFile.service) && !serverlessFile.service.name) { - throw new ServerlessError(`"service" is missing the "name" property in ${this.serviceFilename}`); // eslint-disable-line max-len + throw new ServerlessError( + `"service" is missing the "name" property in ${this.serviceFilename}` + ); // eslint-disable-line max-len } if (!serverlessFile.provider) { throw new ServerlessError(`"provider" property is missing in ${this.serviceFilename}`); @@ -144,8 +146,9 @@ class Service { } if (!functionObj.name) { - that.functions[functionName].name = - `${that.service}-${stageNameForFunction}-${functionName}`; + that.functions[ + functionName + ].name = `${that.service}-${stageNameForFunction}-${functionName}`; } }); } @@ -170,8 +173,9 @@ class Service { validate() { _.forEach(this.functions, (functionObj, functionName) => { if (!_.isArray(functionObj.events)) { - throw new ServerlessError(`Events for "${functionName}" must be an array,` + - ` not an ${typeof functionObj.events}`); + throw new ServerlessError( + `Events for "${functionName}" must be an array,` + ` not an ${typeof functionObj.events}` + ); } }); @@ -181,11 +185,13 @@ class Service { this.getAllFunctions().forEach(funcName => { _.forEach(this.getAllEventsInFunction(funcName), event => { if (_.has(event, 'http') && !validAPIGatewayStageNamePattern.test(stage)) { - throw new this.serverless.classes.Error([ - `Invalid stage name ${stage}:`, - 'it should contains only [-_a-zA-Z0-9] for AWS provider if http event are present', - 'according to API Gateway limitation.', - ].join(' ')); + throw new this.serverless.classes.Error( + [ + `Invalid stage name ${stage}:`, + 'it should contains only [-_a-zA-Z0-9] for AWS provider if http event are present', + 'according to API Gateway limitation.', + ].join(' ') + ); } }); }); @@ -215,7 +221,7 @@ class Service { } getAllFunctionsNames() { - return this.getAllFunctions().map((func) => this.getFunction(func).name); + return this.getAllFunctions().map(func => this.getFunction(func).name); } getFunction(functionName) { @@ -233,8 +239,7 @@ class Service { } getEventInFunction(eventName, functionName) { - const event = this.getFunction(functionName).events - .find(e => Object.keys(e)[0] === eventName); + const event = this.getFunction(functionName).events.find(e => Object.keys(e)[0] === eventName); if (event) { return event; } diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index a4d5c2629..8d599578a 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -153,8 +153,7 @@ describe('Service', () => { }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless(); serverless.processedInput = { options: {} }; @@ -208,8 +207,7 @@ describe('Service', () => { }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), YAML.dump(serverlessYml)); const serverless = new Serverless(); serverless.processedInput = { options: {} }; @@ -263,32 +261,33 @@ describe('Service', () => { }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.json'), - JSON.stringify(serverlessJSON)); + SUtils.writeFileSync( + path.join(tmpDirPath, 'serverless.json'), + JSON.stringify(serverlessJSON) + ); const serverless = new Serverless(); serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - expect(serviceInstance.service).to.be.equal('new-service'); - expect(serviceInstance.provider.name).to.deep.equal('aws'); - expect(serviceInstance.provider.variableSyntax).to.equal( - '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}}' - ); - expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); - expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); - expect(serviceInstance.resources.azure).to.deep.equal({}); - expect(serviceInstance.resources.google).to.deep.equal({}); - expect(serviceInstance.package.exclude.length).to.equal(1); - expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); - expect(serviceInstance.package.include.length).to.equal(1); - expect(serviceInstance.package.include[0]).to.equal('include-me'); - expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); - expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); - }); + return expect(serviceInstance.load()).to.eventually.be.fulfilled.then(() => { + expect(serviceInstance.service).to.be.equal('new-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.provider.variableSyntax).to.equal( + '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}}' + ); + expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); + expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); + expect(serviceInstance.resources.azure).to.deep.equal({}); + expect(serviceInstance.resources.google).to.deep.equal({}); + expect(serviceInstance.package.exclude.length).to.equal(1); + expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); + expect(serviceInstance.package.include.length).to.equal(1); + expect(serviceInstance.package.include[0]).to.equal('include-me'); + expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); + expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); + }); }); it('should load serverless.js from filesystem', () => { @@ -319,32 +318,33 @@ describe('Service', () => { }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.js'), - `module.exports = ${JSON.stringify(serverlessJSON)};`); + SUtils.writeFileSync( + path.join(tmpDirPath, 'serverless.js'), + `module.exports = ${JSON.stringify(serverlessJSON)};` + ); const serverless = new Serverless(); serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - expect(serviceInstance.service).to.be.equal('new-service'); - expect(serviceInstance.provider.name).to.deep.equal('aws'); - expect(serviceInstance.provider.variableSyntax).to.equal( - '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}}' - ); - expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); - expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); - expect(serviceInstance.resources.azure).to.deep.equal({}); - expect(serviceInstance.resources.google).to.deep.equal({}); - expect(serviceInstance.package.exclude.length).to.equal(1); - expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); - expect(serviceInstance.package.include.length).to.equal(1); - expect(serviceInstance.package.include[0]).to.equal('include-me'); - expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); - expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); - }); + return expect(serviceInstance.load()).to.eventually.be.fulfilled.then(() => { + expect(serviceInstance.service).to.be.equal('new-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.provider.variableSyntax).to.equal( + '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}}' + ); + expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); + expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); + expect(serviceInstance.resources.azure).to.deep.equal({}); + expect(serviceInstance.resources.google).to.deep.equal({}); + expect(serviceInstance.package.exclude.length).to.equal(1); + expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); + expect(serviceInstance.package.include.length).to.equal(1); + expect(serviceInstance.package.include[0]).to.equal('include-me'); + expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); + expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); + }); }); it('should load serverless.js from filesystem', () => { @@ -375,47 +375,51 @@ describe('Service', () => { }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.js'), - `module.exports = new Promise(resolve => { resolve(${JSON.stringify(serverlessJSON)}) });`); + SUtils.writeFileSync( + path.join(tmpDirPath, 'serverless.js'), + `module.exports = new Promise(resolve => { resolve(${JSON.stringify(serverlessJSON)}) });` + ); const serverless = new Serverless(); serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - expect(serviceInstance.service).to.be.equal('new-service'); - expect(serviceInstance.provider.name).to.deep.equal('aws'); - expect(serviceInstance.provider.variableSyntax).to.equal( - '\\${{([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}}' - ); - expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); - expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); - expect(serviceInstance.resources.azure).to.deep.equal({}); - expect(serviceInstance.resources.google).to.deep.equal({}); - expect(serviceInstance.package.exclude.length).to.equal(1); - expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); - expect(serviceInstance.package.include.length).to.equal(1); - expect(serviceInstance.package.include[0]).to.equal('include-me'); - expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); - expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); - }); + return expect(serviceInstance.load()).to.eventually.be.fulfilled.then(() => { + expect(serviceInstance.service).to.be.equal('new-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.provider.variableSyntax).to.equal( + '\\${{([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}}' + ); + expect(serviceInstance.plugins).to.deep.equal(['testPlugin']); + expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' }); + expect(serviceInstance.resources.azure).to.deep.equal({}); + expect(serviceInstance.resources.google).to.deep.equal({}); + expect(serviceInstance.package.exclude.length).to.equal(1); + expect(serviceInstance.package.exclude[0]).to.equal('exclude-me'); + expect(serviceInstance.package.include.length).to.equal(1); + expect(serviceInstance.package.include[0]).to.equal('include-me'); + expect(serviceInstance.package.artifact).to.equal('some/path/foo.zip'); + expect(serviceInstance.package.excludeDevDependencies).to.equal(undefined); + }); }); it('should throw error if serverless.js exports invalid config', () => { const SUtils = new Utils(); - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.js'), - 'module.exports = function config() {};'); + SUtils.writeFileSync( + path.join(tmpDirPath, 'serverless.js'), + 'module.exports = function config() {};' + ); const serverless = new Serverless(); serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); - return expect(serviceInstance.load()) - .to.be.rejectedWith('serverless.js must export plain object'); + return expect(serviceInstance.load()).to.be.rejectedWith( + 'serverless.js must export plain object' + ); }); it('should load YAML in favor of JSON', () => { @@ -446,23 +450,23 @@ describe('Service', () => { }; serverlessJSON.service = 'JSON service'; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.json'), - JSON.stringify(serverlessJSON)); + SUtils.writeFileSync( + path.join(tmpDirPath, 'serverless.json'), + JSON.stringify(serverlessJSON) + ); serverlessJSON.service = 'YAML service'; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), - YAML.dump(serverlessJSON)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), YAML.dump(serverlessJSON)); const serverless = new Serverless(); serverless.processedInput = { options: {} }; serverless.config.update({ servicePath: tmpDirPath }); serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - // YAML should have been loaded instead of JSON - expect(serviceInstance.service).to.be.equal('YAML service'); - }); + return expect(serviceInstance.load()).to.eventually.be.fulfilled.then(() => { + // YAML should have been loaded instead of JSON + expect(serviceInstance.service).to.be.equal('YAML service'); + }); }); it('should reject when the service name is missing', () => { @@ -472,15 +476,15 @@ describe('Service', () => { provider: 'aws', }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), - YAML.dump(serverlessYaml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be - .rejectedWith('"service" is missing the "name" property in'); + return expect(serviceInstance.load()).to.eventually.be.rejectedWith( + '"service" is missing the "name" property in' + ); }); it('should support service objects', () => { @@ -493,18 +497,16 @@ describe('Service', () => { provider: 'aws', }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), - YAML.dump(serverlessYaml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - expect(serviceInstance.service).to.equal('my-service'); - expect(serviceInstance.serviceObject).to.deep.equal(serverlessYaml.service); - }); + return expect(serviceInstance.load()).to.eventually.be.fulfilled.then(() => { + expect(serviceInstance.service).to.equal('my-service'); + expect(serviceInstance.serviceObject).to.deep.equal(serverlessYaml.service); + }); }); it('should support Serverless file with a non-aws provider', () => { @@ -519,26 +521,24 @@ describe('Service', () => { }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), - YAML.dump(serverlessYaml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - serviceInstance.setFunctionNames(); - const expectedFunc = { - functionA: { - name: 'customFunctionName', - events: [], - }, - }; - expect(serviceInstance.service).to.be.equal('my-service'); - expect(serviceInstance.provider.name).to.deep.equal('openwhisk'); - expect(serviceInstance.functions).to.deep.equal(expectedFunc); - }); + return expect(serviceInstance.load()).to.eventually.be.fulfilled.then(() => { + serviceInstance.setFunctionNames(); + const expectedFunc = { + functionA: { + name: 'customFunctionName', + events: [], + }, + }; + expect(serviceInstance.service).to.be.equal('my-service'); + expect(serviceInstance.provider.name).to.deep.equal('openwhisk'); + expect(serviceInstance.functions).to.deep.equal(expectedFunc); + }); }); it('should support Serverless file with a .yaml extension', () => { @@ -553,26 +553,24 @@ describe('Service', () => { }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), - YAML.dump(serverlessYaml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'), YAML.dump(serverlessYaml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - serviceInstance.setFunctionNames(); - const expectedFunc = { - functionA: { - name: 'customFunctionName', - events: [], - }, - }; - expect(serviceInstance.service).to.be.equal('my-service'); - expect(serviceInstance.provider.name).to.deep.equal('aws'); - expect(serviceInstance.functions).to.deep.equal(expectedFunc); - }); + return expect(serviceInstance.load()).to.eventually.be.fulfilled.then(() => { + serviceInstance.setFunctionNames(); + const expectedFunc = { + functionA: { + name: 'customFunctionName', + events: [], + }, + }; + expect(serviceInstance.service).to.be.equal('my-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.functions).to.deep.equal(expectedFunc); + }); }); it('should support Serverless file with a .yml extension', () => { @@ -585,26 +583,24 @@ describe('Service', () => { }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); - return expect(serviceInstance.load({ stage: 'dev' })).to.eventually.be.fulfilled - .then(() => { - serviceInstance.setFunctionNames(); - const expectedFunc = { - functionA: { - name: 'my-service-dev-functionA', - events: [], - }, - }; - expect(serviceInstance.service).to.be.equal('my-service'); - expect(serviceInstance.provider.name).to.deep.equal('aws'); - expect(serviceInstance.functions).to.deep.equal(expectedFunc); - }); + return expect(serviceInstance.load({ stage: 'dev' })).to.eventually.be.fulfilled.then(() => { + serviceInstance.setFunctionNames(); + const expectedFunc = { + functionA: { + name: 'my-service-dev-functionA', + events: [], + }, + }; + expect(serviceInstance.service).to.be.equal('my-service'); + expect(serviceInstance.provider.name).to.deep.equal('aws'); + expect(serviceInstance.functions).to.deep.equal(expectedFunc); + }); }); it('should reject if service property is missing', () => { @@ -613,15 +609,15 @@ describe('Service', () => { provider: 'aws', functions: {}, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be - .rejectedWith('"service" property is missing in serverless.yml'); + return expect(serviceInstance.load()).to.eventually.be.rejectedWith( + '"service" property is missing in serverless.yml' + ); }); it('should reject if provider property is missing', () => { @@ -630,15 +626,15 @@ describe('Service', () => { service: 'service-name', functions: {}, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be - .rejectedWith('"provider" property is missing in serverless.yml'); + return expect(serviceInstance.load()).to.eventually.be.rejectedWith( + '"provider" property is missing in serverless.yml' + ); }); it('should reject if frameworkVersion is not satisfied', () => { @@ -649,8 +645,7 @@ describe('Service', () => { provider: 'aws', functions: {}, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; @@ -658,8 +653,9 @@ describe('Service', () => { getVersion.returns('1.0.2'); serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be - .rejectedWith(/version \(1\.0\.2\).*"frameworkVersion" \(=1\.0\.0\)/); + return expect(serviceInstance.load()).to.eventually.be.rejectedWith( + /version \(1\.0\.2\).*"frameworkVersion" \(=1\.0\.0\)/ + ); }); it('should pass if frameworkVersion is satisfied', () => { @@ -670,8 +666,7 @@ describe('Service', () => { provider: 'aws', functions: {}, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; @@ -688,24 +683,22 @@ describe('Service', () => { service: 'service-name', provider: 'aws', }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; serverless.variables.service = serverless.service; serviceInstance = new Service(serverless); - return expect(serviceInstance.load()).to.eventually.be.fulfilled - .then(() => { - // populate variables in service configuration - serverless.variables.populateService(); + return expect(serviceInstance.load()).to.eventually.be.fulfilled.then(() => { + // populate variables in service configuration + serverless.variables.populateService(); - // validate the service configuration, now that variables are loaded - serviceInstance.validate(); + // validate the service configuration, now that variables are loaded + serviceInstance.validate(); - expect(serviceInstance.functions).to.deep.equal({}); - }); + expect(serviceInstance.functions).to.deep.equal({}); + }); }); }); @@ -716,7 +709,7 @@ describe('Service', () => { tmpDirPath = getTmpDirPath(); }); - it('should throw if a function\'s event is not an array or a variable', () => { + it("should throw if a function's event is not an array or a variable", () => { const SUtils = new Utils(); const serverlessYml = { service: 'service-name', @@ -727,33 +720,32 @@ describe('Service', () => { }, }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; serverless.service = new Service(serverless); - return expect(serverless.service.load()).to.eventually.be.fulfilled - .then(() => { - // validate the service configuration, now that variables are loaded - expect(() => serverless.service.validate()) - .to.throw('Events for "functionA" must be an array, not an string'); - }); + return expect(serverless.service.load()).to.eventually.be.fulfilled.then(() => { + // validate the service configuration, now that variables are loaded + expect(() => serverless.service.validate()).to.throw( + 'Events for "functionA" must be an array, not an string' + ); + }); }); describe('stage name validation', () => { function simulateRun(serverless) { return serverless.init().then(() => - serverless.variables.populateService(serverless.pluginManager.cliOptions) - .then(() => { - serverless.service.mergeArrays(); - serverless.service.setFunctionNames(serverless.processedInput.options); - })); + serverless.variables.populateService(serverless.pluginManager.cliOptions).then(() => { + serverless.service.mergeArrays(); + serverless.service.setFunctionNames(serverless.processedInput.options); + }) + ); } it(`should not throw an error if http event is absent and - stage contains only alphanumeric, underscore and hyphen`, function () { + stage contains only alphanumeric, underscore and hyphen`, function() { this.timeout(10000); // Occasionally times out with default settings const SUtils = new Utils(); const serverlessYml = { @@ -768,8 +760,7 @@ describe('Service', () => { }, }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); serverless.processedInput = { options: {} }; @@ -800,8 +791,7 @@ describe('Service', () => { }, }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { @@ -830,16 +820,18 @@ describe('Service', () => { }, }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { - expect(() => serverless.service.validate()).to.throw(serverless.classes.Error, [ - 'Invalid stage name my@stage: it should contains only [-_a-zA-Z0-9]', - 'for AWS provider if http event are present', - 'according to API Gateway limitation.', - ].join(' ')); + expect(() => serverless.service.validate()).to.throw( + serverless.classes.Error, + [ + 'Invalid stage name my@stage: it should contains only [-_a-zA-Z0-9]', + 'for AWS provider if http event are present', + 'according to API Gateway limitation.', + ].join(' ') + ); }); }); @@ -865,16 +857,18 @@ describe('Service', () => { }, }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless({ servicePath: tmpDirPath }); return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => { - expect(() => serverless.service.validate()).to.throw(serverless.classes.Error, [ - 'Invalid stage name default:stage: it should contains only [-_a-zA-Z0-9]', - 'for AWS provider if http event are present', - 'according to API Gateway limitation.', - ].join(' ')); + expect(() => serverless.service.validate()).to.throw( + serverless.classes.Error, + [ + 'Invalid stage name default:stage: it should contains only [-_a-zA-Z0-9]', + 'for AWS provider if http event are present', + 'according to API Gateway limitation.', + ].join(' ') + ); }); }); }); @@ -897,7 +891,8 @@ describe('Service', () => { Resources: { azure: {}, }, - }, { + }, + { foo: 'bar', }, ]; @@ -951,9 +946,7 @@ describe('Service', () => { const serverless = new Serverless(); const serviceInstance = new Service(serverless); - serviceInstance.resources = [ - 42, - ]; + serviceInstance.resources = [42]; expect(() => serviceInstance.mergeArrays()).to.throw(Error); }); @@ -962,9 +955,7 @@ describe('Service', () => { const serverless = new Serverless(); const serviceInstance = new Service(serverless); - serviceInstance.resources = [ - 'string', - ]; + serviceInstance.resources = ['string']; expect(() => serviceInstance.mergeArrays()).to.throw(Error); }); @@ -973,11 +964,14 @@ describe('Service', () => { const serverless = new Serverless(); const serviceInstance = new Service(serverless); - serviceInstance.functions = [{ - a: {}, - }, { - b: {}, - }]; + serviceInstance.functions = [ + { + a: {}, + }, + { + b: {}, + }, + ]; serviceInstance.mergeArrays(); @@ -1023,8 +1017,7 @@ describe('Service', () => { }, }; - SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), - YAML.dump(serverlessYml)); + SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'), YAML.dump(serverlessYml)); const serverless = new Serverless(); serverless.processedInput = { options: {} }; @@ -1159,16 +1152,19 @@ describe('Service', () => { serviceInstance = new Service(serverless); serviceInstance.functions = { create: { - events: [{ - schedule: 'rate(5 minutes)', - }], + events: [ + { + schedule: 'rate(5 minutes)', + }, + ], }, }; }); it('should return an event object based on provided function', () => { - expect(serviceInstance.getEventInFunction('schedule', 'create')) - .to.deep.equal({ schedule: 'rate(5 minutes)' }); + expect(serviceInstance.getEventInFunction('schedule', 'create')).to.deep.equal({ + schedule: 'rate(5 minutes)', + }); }); it('should throw error if function does not exist in service', () => { @@ -1190,20 +1186,25 @@ describe('Service', () => { const serviceInstance = new Service(serverless); serviceInstance.functions = { create: { - events: [{ - schedule: 'rate(5 minutes)', - }, { - bucket: 'my_bucket', - }], + events: [ + { + schedule: 'rate(5 minutes)', + }, + { + bucket: 'my_bucket', + }, + ], }, }; - expect(serviceInstance.getAllEventsInFunction('create')) - .to.deep.equal([{ + expect(serviceInstance.getAllEventsInFunction('create')).to.deep.equal([ + { schedule: 'rate(5 minutes)', - }, { + }, + { bucket: 'my_bucket', - }]); + }, + ]); }); }); }); diff --git a/lib/classes/Utils.js b/lib/classes/Utils.js index 793e46e43..73687675e 100644 --- a/lib/classes/Utils.js +++ b/lib/classes/Utils.js @@ -34,8 +34,11 @@ class Utils { } getTmpDirPath() { - const dirPath = path.join(os.tmpdir(), - 'tmpdirs-serverless', crypto.randomBytes(8).toString('hex')); + const dirPath = path.join( + os.tmpdir(), + 'tmpdirs-serverless', + crypto.randomBytes(8).toString('hex') + ); fse.ensureDirSync(dirPath); return dirPath; } @@ -103,7 +106,9 @@ class Utils { } generateShortId(length) { - return Math.random().toString(36).substr(2, length); + return Math.random() + .toString(36) + .substr(2, length); } findServicePath(customPath) { @@ -142,7 +147,7 @@ class Utils { const options = serverless.processedInput.options; const commands = serverless.processedInput.commands; - return new BbPromise((resolve) => { + return new BbPromise(resolve => { const config = configUtils.getConfig(); const userId = config.frameworkId; const trackingDisabled = config.trackingDisabled; @@ -155,7 +160,7 @@ class Utils { let serviceName = ''; if (service && service.service && service.service.name) { serviceName = service.service.name; - } else if (service && (typeof service.service === 'string')) { + } else if (service && typeof service.service === 'string') { serviceName = service.service; } @@ -163,12 +168,12 @@ class Utils { const whitelistedOptionKeys = ['help', 'disable', 'enable']; const optionKeys = Object.keys(options); - const filteredOptionKeys = optionKeys.filter((key) => - whitelistedOptionKeys.indexOf(key) !== -1 + const filteredOptionKeys = optionKeys.filter( + key => whitelistedOptionKeys.indexOf(key) !== -1 ); const filteredOptions = {}; - filteredOptionKeys.forEach((key) => { + filteredOptionKeys.forEach(key => { filteredOptions[key] = options[key]; }); @@ -177,13 +182,11 @@ class Utils { const memorySizeAndTimeoutPerFunction = []; if (numberOfFunctions) { - _.forEach(functions, (func) => { - const memorySize = Number(func.memorySize) - || Number(this.serverless.service.provider.memorySize) - || 1024; - const timeout = Number(func.timeout) - || Number(this.serverless.service.provider.timeout) - || 6; + _.forEach(functions, func => { + const memorySize = + Number(func.memorySize) || Number(this.serverless.service.provider.memorySize) || 1024; + const timeout = + Number(func.timeout) || Number(this.serverless.service.provider.timeout) || 6; const memorySizeAndTimeoutObject = { memorySize, @@ -201,11 +204,11 @@ class Utils { let hasCustomAuthorizer = false; let hasCognitoAuthorizer = false; if (numberOfFunctions) { - _.forEach(functions, (func) => { + _.forEach(functions, func => { if (func.events) { const funcEventsArray = []; - func.events.forEach((event) => { + func.events.forEach(event => { const name = Object.keys(event)[0]; funcEventsArray.push(name); @@ -221,10 +224,12 @@ class Utils { // For HTTP events, see what authorizer types are enabled if (_.has(event, 'http.authorizer')) { - if ((_.isString(event.http.authorizer) - && _.toUpper(event.http.authorizer) === 'AWS_IAM') - || (event.http.authorizer.type - && _.toUpper(event.http.authorizer.type) === 'AWS_IAM')) { + if ( + (_.isString(event.http.authorizer) && + _.toUpper(event.http.authorizer) === 'AWS_IAM') || + (event.http.authorizer.type && + _.toUpper(event.http.authorizer.type) === 'AWS_IAM') + ) { hasIAMAuthorizer = true; } // There are three ways a user can specify a Custom authorizer: @@ -234,18 +239,22 @@ class Utils { // in the authorizer object. // 3) By listing a function's ARN in the arn property of the authorizer object. - if ((_.isString(event.http.authorizer) - && _.toUpper(event.http.authorizer) !== 'AWS_IAM' - && !awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer)) - || event.http.authorizer.name - || (event.http.authorizer.arn - && awsArnRegExs.lambdaArnExpr.test(event.http.authorizer.arn))) { + if ( + (_.isString(event.http.authorizer) && + _.toUpper(event.http.authorizer) !== 'AWS_IAM' && + !awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer)) || + event.http.authorizer.name || + (event.http.authorizer.arn && + awsArnRegExs.lambdaArnExpr.test(event.http.authorizer.arn)) + ) { hasCustomAuthorizer = true; } - if ((_.isString(event.http.authorizer) - && awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer)) - || (event.http.authorizer.arn - && awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer.arn))) { + if ( + (_.isString(event.http.authorizer) && + awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer)) || + (event.http.authorizer.arn && + awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer.arn)) + ) { hasCognitoAuthorizer = true; } } @@ -258,11 +267,11 @@ class Utils { let hasCustomResourcesDefined = false; // check if configuration in resources.Resources is defined - if ((resources && resources.Resources && Object.keys(resources.Resources).length)) { + if (resources && resources.Resources && Object.keys(resources.Resources).length) { hasCustomResourcesDefined = true; } // check if configuration in resources.Outputs is defined - if ((resources && resources.Outputs && Object.keys(resources.Outputs).length)) { + if (resources && resources.Outputs && Object.keys(resources.Outputs).length) { hasCustomResourcesDefined = true; } @@ -270,8 +279,11 @@ class Utils { const defaultVariableSyntax = '\\${([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)]+?)}'; // check if the variableSyntax in the provider section is defined - if (provider && provider.variableSyntax - && provider.variableSyntax !== defaultVariableSyntax) { + if ( + provider && + provider.variableSyntax && + provider.variableSyntax !== defaultVariableSyntax + ) { hasCustomVariableSyntaxDefined = true; } @@ -283,12 +295,12 @@ class Utils { command: { name: commands.join(' '), filteredOptions, - isRunInService: (!!serverless.config.servicePath), + isRunInService: !!serverless.config.servicePath, }, service: { numberOfCustomPlugins: _.size(service.plugins), hasCustomResourcesDefined, - hasVariablesInCustomSectionDefined: (!!service.custom), + hasVariablesInCustomSectionDefined: !!service.custom, hasCustomVariableSyntaxDefined, name: serviceName, }, @@ -311,10 +323,10 @@ class Utils { userId, context, invocationId, - timestamp: (new Date()).getTime(), - timezone: (new Date()).toString().match(/([A-Z]+[+-][0-9]+)/)[1], + timestamp: new Date().getTime(), + timezone: new Date().toString().match(/([A-Z]+[+-][0-9]+)/)[1], operatingSystem: process.platform, - userAgent: (process.env.SERVERLESS_DASHBOARD) ? 'dashboard' : 'cli', + userAgent: process.env.SERVERLESS_DASHBOARD ? 'dashboard' : 'cli', serverlessVersion: serverless.version, nodeJsVersion: process.version, isDockerContainer: isDockerContainer(), @@ -338,7 +350,7 @@ class Utils { } return resolve(data); - }).then((data) => { + }).then(data => { if (data) { segment.track(data); } @@ -350,22 +362,28 @@ class Utils { const currentId = userConfig.userId; const globalConfig = configUtils.getGlobalConfig(); const username = _.get(globalConfig, `users[${currentId}].dashboard.username`); - return _.get(globalConfig, `users[${currentId}].dashboard.accessKey`, false) || - _.get(globalConfig, `users[${currentId}].dashboard.accessKeys[${username}]`, false); + return ( + _.get(globalConfig, `users[${currentId}].dashboard.accessKey`, false) || + _.get(globalConfig, `users[${currentId}].dashboard.accessKeys[${username}]`, false) + ); } isEventUsed(functions, eventName) { - return _.reduce(functions, (accum, func) => { - const events = func.events || []; - if (events.length) { - events.forEach(event => { - if (Object.keys(event)[0] === eventName) { - accum = true; // eslint-disable-line no-param-reassign - } - }); - } - return accum; - }, false); + return _.reduce( + functions, + (accum, func) => { + const events = func.events || []; + if (events.length) { + events.forEach(event => { + if (Object.keys(event)[0] === eventName) { + accum = true; // eslint-disable-line no-param-reassign + } + }); + } + return accum; + }, + false + ); } } diff --git a/lib/classes/Utils.test.js b/lib/classes/Utils.test.js index d752f70f0..bc0bbae77 100644 --- a/lib/classes/Utils.test.js +++ b/lib/classes/Utils.test.js @@ -41,7 +41,7 @@ describe('Utils', () => { expect(dir).to.equal(true); }); - it('should detect if a directory doesn\'t exist', () => { + it("should detect if a directory doesn't exist", () => { const noDir = serverless.utils.dirExistsSync(path.join(__dirname, '..', 'XYZ')); expect(noDir).to.equal(false); }); @@ -55,7 +55,7 @@ describe('Utils', () => { expect(file).to.equal(true); }); - it('should detect if a file doesn\'t exist', () => { + it("should detect if a file doesn't exist", () => { const noFile = serverless.utils.fileExistsSync(path.join(__dirname, 'XYZ.json')); expect(noFile).to.equal(false); }); @@ -65,13 +65,15 @@ describe('Utils', () => { describe('#writeFileDir()', () => { it('should create a directory for the path of the given file', () => { const tmpDirPath = getTmpDirPath(); - const rootDir = serverless.utils - .writeFileDir(path.join(tmpDirPath, 'foo', 'bar', 'somefile.js')); + const rootDir = serverless.utils.writeFileDir( + path.join(tmpDirPath, 'foo', 'bar', 'somefile.js') + ); expect(serverless.utils.dirExistsSync(path.join(rootDir, 'foo', 'bar'))).to.equal(true); // it should only create the directories and not the file - expect(serverless.utils.fileExistsSync(path.join(rootDir, 'foo', 'bar', 'somefile.js'))) - .to.equal(false); + expect( + serverless.utils.fileExistsSync(path.join(rootDir, 'foo', 'bar', 'somefile.js')) + ).to.equal(false); }); }); @@ -90,7 +92,7 @@ describe('Utils', () => { serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' }); - return expect(serverless.yamlParser.parse(tmpFilePath)).to.be.fulfilled.then((obj) => { + return expect(serverless.yamlParser.parse(tmpFilePath)).to.be.fulfilled.then(obj => { expect(obj.foo).to.equal('bar'); }); }); @@ -100,13 +102,15 @@ describe('Utils', () => { serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' }); - return expect(serverless.yamlParser.parse(tmpFilePath)).to.be.fulfilled.then((obj) => { + return expect(serverless.yamlParser.parse(tmpFilePath)).to.be.fulfilled.then(obj => { expect(obj.foo).to.equal('bar'); }); }); it('should throw error if invalid path is provided', () => { - expect(() => { serverless.utils.writeFileSync(null); }).to.throw(Error); + expect(() => { + serverless.utils.writeFileSync(null); + }).to.throw(Error); }); }); @@ -115,12 +119,13 @@ describe('Utils', () => { const tmpFilePath = getTmpFilePath('anything.json'); // note: use return when testing promises otherwise you'll have unhandled rejection errors - return expect(serverless.utils.writeFile(tmpFilePath, { foo: 'bar' })) - .to.be.fulfilled.then(() => { - const obj = serverless.utils.readFileSync(tmpFilePath); + return expect(serverless.utils.writeFile(tmpFilePath, { foo: 'bar' })).to.be.fulfilled.then( + () => { + const obj = serverless.utils.readFileSync(tmpFilePath); - expect(obj.foo).to.equal('bar'); - }); + expect(obj.foo).to.equal('bar'); + } + ); }); }); @@ -136,7 +141,9 @@ describe('Utils', () => { }); it('should throw error if invalid path is provided', () => { - expect(() => { serverless.utils.readFileSync(null); }).to.throw(Error); + expect(() => { + serverless.utils.readFileSync(null); + }).to.throw(Error); }); }); @@ -186,7 +193,7 @@ describe('Utils', () => { serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' }); // note: use return when testing promises otherwise you'll have unhandled rejection errors - return expect(serverless.utils.readFile(tmpFilePath)).to.be.fulfilled.then((obj) => { + return expect(serverless.utils.readFile(tmpFilePath)).to.be.fulfilled.then(obj => { expect(obj.foo).to.equal('bar'); }); }); @@ -450,7 +457,6 @@ describe('Utils', () => { }, }; - return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => { expect(getConfigStub.calledOnce).to.equal(true); expect(trackStub.calledOnce).to.equal(true); @@ -793,12 +799,9 @@ describe('Utils', () => { expect(data.userId).to.equal('1234wasd'); // command property - expect(data.properties.command.name) - .to.equal(''); - expect(data.properties.command - .isRunInService).to.equal(false); // false because CWD is not a service - expect(data.properties.command.filteredOptions) - .to.deep.equal({}); + expect(data.properties.command.name).to.equal(''); + expect(data.properties.command.isRunInService).to.equal(false); // false because CWD is not a service + expect(data.properties.command.filteredOptions).to.deep.equal({}); // service property expect(data.properties.service.numberOfCustomPlugins).to.equal(0); expect(data.properties.service.hasCustomResourcesDefined).to.equal(true); @@ -807,14 +810,14 @@ describe('Utils', () => { expect(data.properties.service.name).to.equal('new-service'); // functions property expect(data.properties.functions.numberOfFunctions).to.equal(2); - expect(data.properties.functions.memorySizeAndTimeoutPerFunction[0] - .memorySize).to.equal(1024); - expect(data.properties.functions.memorySizeAndTimeoutPerFunction[0] - .timeout).to.equal(6); - expect(data.properties.functions.memorySizeAndTimeoutPerFunction[1] - .memorySize).to.equal(16); - expect(data.properties.functions.memorySizeAndTimeoutPerFunction[1] - .timeout).to.equal(200); + expect(data.properties.functions.memorySizeAndTimeoutPerFunction[0].memorySize).to.equal( + 1024 + ); + expect(data.properties.functions.memorySizeAndTimeoutPerFunction[0].timeout).to.equal(6); + expect(data.properties.functions.memorySizeAndTimeoutPerFunction[1].memorySize).to.equal( + 16 + ); + expect(data.properties.functions.memorySizeAndTimeoutPerFunction[1].timeout).to.equal(200); // events property expect(data.properties.events.numberOfEvents).to.equal(3); expect(data.properties.events.numberOfEventsPerType[0].name).to.equal('http'); diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index 1d35bb717..30b685f9a 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -80,22 +80,22 @@ class Variables { { name: 'SSM', method: 'getValueFromSsm', original: this.getValueFromSsm }, ]; const dependencyMessage = (configValue, serviceName) => - `Variable dependency failure: variable '${configValue}' references service ${ - serviceName} but using that service requires a concrete value to be called.`; + `Variable dependency failure: variable '${configValue}' references service ${serviceName} but using that service requires a concrete value to be called.`; // replace and then restore the methods for obtaining values from dependent services. the // replacement naturally rejects dependencies on these services that occur during prepopulation. // prepopulation is, of course, the process of obtaining the required configuration for using // these services. - dependentServices.forEach((dependentService) => { // knock out - this[dependentService.method] = (variableString) => BbPromise.reject( - dependencyMessage(variableString, dependentService.name)); + dependentServices.forEach(dependentService => { + // knock out + this[dependentService.method] = variableString => + BbPromise.reject(dependencyMessage(variableString, dependentService.name)); }); - return func() - .finally(() => { - dependentServices.forEach((dependentService) => { // restore - this[dependentService.method] = dependentService.original; - }); + return func().finally(() => { + dependentServices.forEach(dependentService => { + // restore + this[dependentService.method] = dependentService.original; }); + }); } prepopulateService() { const provider = this.serverless.getProvider('aws'); @@ -132,7 +132,8 @@ class Variables { return this.disableDepedentServices(() => { const prepopulations = requiredConfigs.map(config => this.populateValue(config.value, true) // populate - .then(populated => _.assign(config, { populated }))); + .then(populated => _.assign(config, { populated })) + ); return this.assignProperties(provider, prepopulations); }); } @@ -158,13 +159,15 @@ class Variables { this.service.serverless = undefined; return this.initialCall(() => this.prepopulateService() - .then(() => this.populateObjectImpl(this.service) - .finally(() => { + .then(() => + this.populateObjectImpl(this.service).finally(() => { // restore this.service.serverless = this.serverless; this.service.provider.variableSyntax = variableSyntaxProperty; - })) - .then(() => this.service)); + }) + ) + .then(() => this.service) + ); } // ############ // ## OBJECT ## @@ -212,9 +215,7 @@ class Variables { } const addContext = (value, key) => this.getProperties(root, false, value, context.concat(key), results); - if ( - _.isArray(current) - ) { + if (_.isArray(current)) { _.map(current, addContext); } else if ( _.isObject(current) && @@ -242,12 +243,14 @@ class Variables { * populated values of the given terminal properties */ populateVariables(properties) { - const variables = properties.filter(property => - _.isString(property.value) && - property.value.match(this.variableSyntax)); - return _.map(variables, - variable => this.populateValue(variable.value, false) - .then(populated => _.assign({}, variable, { populated }))); + const variables = properties.filter( + property => _.isString(property.value) && property.value.match(this.variableSyntax) + ); + return _.map(variables, variable => + this.populateValue(variable.value, false).then(populated => + _.assign({}, variable, { populated }) + ) + ); } /** * Assign the populated values back to the target object @@ -256,13 +259,15 @@ class Variables { * @returns {Promise} resolving with the number of changes that were applied to the given * target */ - assignProperties(target, populations) { // eslint-disable-line class-methods-use-this - return BbPromise.all(populations) - .then((results) => results.forEach((result) => { + assignProperties(target, populations) { + // eslint-disable-line class-methods-use-this + return BbPromise.all(populations).then(results => + results.forEach(result => { if (result.value !== result.populated) { _.set(target, result.path, result.populated); } - })); + }) + ); } /** * Populate the variables in the given object. @@ -278,8 +283,9 @@ class Variables { if (populations.length === 0) { return BbPromise.resolve(objectToPopulate); } - return this.assignProperties(objectToPopulate, populations) - .then(() => this.populateObjectImpl(objectToPopulate)); + return this.assignProperties(objectToPopulate, populations).then(() => + this.populateObjectImpl(objectToPopulate) + ); } // ############## // ## PROPERTY ## @@ -291,10 +297,7 @@ class Variables { * @returns {string} The cleaned variable match */ cleanVariable(match) { - let cleaned = match.replace( - this.variableSyntax, - (context, contents) => contents.trim() - ); + let cleaned = match.replace(this.variableSyntax, (context, contents) => contents.trim()); if (!cleaned.match(/".*"|'.*'/)) { cleaned = cleaned.replace(/\s/g, ''); } @@ -331,7 +334,7 @@ class Variables { * @returns {Promise[]} Promises for the eventual populated values of the given matches */ populateMatches(matches, property) { - return _.map(matches, (match) => this.splitAndGet(match, property)); + return _.map(matches, match => this.splitAndGet(match, property)); } /** * Render the given matches and their associated results to the given value @@ -365,7 +368,7 @@ class Variables { const populations = this.populateMatches(matches, valueToPopulate); return BbPromise.all(populations) .then(results => this.renderMatches(property, matches, results)) - .then((result) => { + .then(result => { if (root && matches.length) { return this.populateValue(result, root); } @@ -407,11 +410,14 @@ class Variables { */ populateVariable(propertyParam, matchedString, valueToPopulate) { let property = propertyParam; - if (property === matchedString) { // total replacement + if (property === matchedString) { + // total replacement property = valueToPopulate; - } else if (_.isString(valueToPopulate)) { // partial replacement, string + } else if (_.isString(valueToPopulate)) { + // partial replacement, string property = replaceall(matchedString, valueToPopulate, property); - } else if (_.isNumber(valueToPopulate)) { // partial replacement, number + } else if (_.isNumber(valueToPopulate)) { + // partial replacement, number property = replaceall(matchedString, String(valueToPopulate), property); } else { const errorMessage = [ @@ -443,15 +449,17 @@ class Variables { match = this.stringRefSyntax.exec(input); } const commaReplacements = []; - const contained = commaMatch => // curry the current commaMatch - stringMatch => // check whether stringMatch containing the commaMatch - stringMatch.start < commaMatch.index && - this.overwriteSyntax.lastIndex < stringMatch.end; + const contained = ( + commaMatch // curry the current commaMatch + ) => ( + stringMatch // check whether stringMatch containing the commaMatch + ) => stringMatch.start < commaMatch.index && this.overwriteSyntax.lastIndex < stringMatch.end; match = this.overwriteSyntax.exec(input); while (match) { const matchContained = contained(match); const containedBy = stringMatches.find(matchContained); - if (!containedBy) { // if uncontained, this comma respresents a splitting location + if (!containedBy) { + // if uncontained, this comma respresents a splitting location commaReplacements.push({ start: match.index, end: this.overwriteSyntax.lastIndex, @@ -461,7 +469,7 @@ class Variables { } let prior = 0; const results = []; - commaReplacements.forEach((replacement) => { + commaReplacements.forEach(replacement => { results.push(input.slice(prior, replacement.start)); prior = replacement.end; }); @@ -480,14 +488,13 @@ class Variables { // A sentinel to rid rejected Promises, so any of resolved value can be used as fallback. const FAIL_TOKEN = {}; const variableValues = variableStrings.map(variableString => - this.getValueFromSource(variableString, propertyString) - .catch(unused => FAIL_TOKEN)); // eslint-disable-line no-unused-vars + this.getValueFromSource(variableString, propertyString).catch(unused => FAIL_TOKEN) + ); // eslint-disable-line no-unused-vars - const validValue = value => ( + const validValue = value => value !== null && typeof value !== 'undefined' && - !(typeof value === 'object' && _.isEmpty(value)) - ); + !(typeof value === 'object' && _.isEmpty(value)); return BbPromise.all(variableValues) .then(values => values.filter(v => v !== FAIL_TOKEN)) .then(values => { @@ -499,12 +506,13 @@ class Variables { const deepVariable = this.makeDeepVariable(value); deepPropertyString = deepPropertyString.replace( variableStrings[index], - this.cleanVariable(deepVariable)); + this.cleanVariable(deepVariable) + ); } }); - return deepProperties > 0 ? - BbPromise.resolve(deepPropertyString) : // return deep variable replacement of original - BbPromise.resolve(values.find(validValue));// resolve first valid value, else undefined + return deepProperties > 0 + ? BbPromise.resolve(deepPropertyString) // return deep variable replacement of original + : BbPromise.resolve(values.find(validValue)); // resolve first valid value, else undefined }); } /** @@ -559,7 +567,8 @@ class Variables { return BbPromise.resolve(valueToPopulate); } - getValueFromEnv(variableString) { // eslint-disable-line class-methods-use-this + getValueFromEnv(variableString) { + // eslint-disable-line class-methods-use-this const requestedEnvVar = variableString.split(':')[1]; let valueToPopulate; if (requestedEnvVar !== '' || '' in process.env) { @@ -570,7 +579,8 @@ class Variables { return BbPromise.resolve(valueToPopulate); } - getValueFromString(variableString) { // eslint-disable-line class-methods-use-this + getValueFromString(variableString) { + // eslint-disable-line class-methods-use-this const valueToPopulate = variableString.replace(/^['"]|['"]$/g, ''); return BbPromise.resolve(valueToPopulate); } @@ -603,7 +613,10 @@ class Variables { variable = 'self:provider.name'; } const valueToPopulate = this.service; - const deepProperties = variable.split(':')[1].split('.').filter(property => property); + const deepProperties = variable + .split(':')[1] + .split('.') + .filter(property => property); return this.getDeeperValue(deepProperties, valueToPopulate); } @@ -613,14 +626,14 @@ class Variables { .replace(this.fileRefSyntax, (match, varName) => varName.trim()) .replace('~', os.homedir()); - let referencedFileFullPath = (path.isAbsolute(referencedFileRelativePath) ? - referencedFileRelativePath : - path.join(this.serverless.config.servicePath, referencedFileRelativePath)); + let referencedFileFullPath = path.isAbsolute(referencedFileRelativePath) + ? referencedFileRelativePath + : path.join(this.serverless.config.servicePath, referencedFileRelativePath); // Get real path to handle potential symlinks (but don't fatal error) - referencedFileFullPath = fse.existsSync(referencedFileFullPath) ? - fse.realpathSync(referencedFileFullPath) : - referencedFileFullPath; + referencedFileFullPath = fse.existsSync(referencedFileFullPath) + ? fse.realpathSync(referencedFileFullPath) + : referencedFileFullPath; let fileExtension = referencedFileRelativePath.split('.'); fileExtension = fileExtension[fileExtension.length - 1]; @@ -655,12 +668,12 @@ class Variables { } valueToPopulate = returnValueFunction.call(jsFile, this.serverless); - return BbPromise.resolve(valueToPopulate).then((valueToPopulateResolved) => { + return BbPromise.resolve(valueToPopulate).then(valueToPopulateResolved => { let deepProperties = variableString.replace(matchedFileRefString, ''); deepProperties = deepProperties.slice(1).split('.'); deepProperties.splice(0, 1); - return this.getDeeperValue(deepProperties, valueToPopulateResolved) - .then((deepValueToPopulateResolved) => { + return this.getDeeperValue(deepProperties, valueToPopulateResolved).then( + deepValueToPopulateResolved => { if (typeof deepValueToPopulateResolved === 'undefined') { const errorMessage = [ 'Invalid variable syntax when referencing', @@ -670,7 +683,8 @@ class Variables { return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } return BbPromise.resolve(deepValueToPopulateResolved); - }); + } + ); }); } @@ -678,8 +692,7 @@ class Variables { if (fileExtension !== 'js') { valueToPopulate = this.serverless.utils.readFileSync(referencedFileFullPath); if (matchedFileRefString !== variableString) { - let deepProperties = variableString - .replace(matchedFileRefString, ''); + let deepProperties = variableString.replace(matchedFileRefString, ''); if (deepProperties.substring(0, 1) !== ':') { const errorMessage = [ 'Invalid variable syntax when referencing', @@ -704,12 +717,10 @@ class Variables { if (!_.isUndefined(regionSuffix)) { options.region = regionSuffix; } - return this.serverless.getProvider('aws') - .request('CloudFormation', - 'describeStacks', - { StackName: stackName }, - options) - .then((result) => { + return this.serverless + .getProvider('aws') + .request('CloudFormation', 'describeStacks', { StackName: stackName }, options) + .then(result => { const outputs = result.Stacks[0].Outputs; const output = outputs.find(x => x.OutputKey === outputLogicalId); @@ -729,16 +740,19 @@ class Variables { const groups = variableString.match(this.s3RefSyntax); const bucket = groups[1]; const key = groups[2]; - return this.serverless.getProvider('aws').request( - 'S3', - 'getObject', - { - Bucket: bucket, - Key: key, - }, - { useCache: true }) // Use request cache + return this.serverless + .getProvider('aws') + .request( + 'S3', + 'getObject', + { + Bucket: bucket, + Key: key, + }, + { useCache: true } + ) // Use request cache .then(response => BbPromise.resolve(response.Body.toString())) - .catch((err) => { + .catch(err => { const errorMessage = `Error getting value for ${variableString}. ${err.message}`; return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); }); @@ -747,15 +761,18 @@ class Variables { getValueFromSsm(variableString) { const groups = variableString.match(this.ssmRefSyntax); const param = groups[1]; - const decrypt = (groups[2] === 'true'); - return this.serverless.getProvider('aws').request( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: decrypt, - }, - { useCache: true }) // Use request cache + const decrypt = groups[2] === 'true'; + return this.serverless + .getProvider('aws') + .request( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: decrypt, + }, + { useCache: true } + ) // Use request cache .then(response => { const plainText = response.Parameter.Value; // Only if Secrets Manager. Parameter Store does not support JSON. @@ -769,7 +786,7 @@ class Variables { } return BbPromise.resolve(plainText); }) - .catch((err) => { + .catch(err => { if (err.statusCode !== 400) { return BbPromise.reject(new this.serverless.classes.Error(err.message)); } @@ -791,8 +808,9 @@ class Variables { const variable = this.getVariableFromDeep(variableString); const deepRef = variableString.replace(deepPrefixReplace, ''); let ret = this.populateValue(variable); - if (deepRef.length) { // if there is a deep reference remaining - ret = ret.then((result) => { + if (deepRef.length) { + // if there is a deep reference remaining + ret = ret.then(result => { if (_.isString(result) && result.match(this.variableSyntax)) { const deepVariable = this.makeDeepVariable(result); return BbPromise.resolve(this.appendDeepVariable(deepVariable, deepRef)); @@ -804,15 +822,13 @@ class Variables { } makeDeepVariable(variable) { - let index = this.deep.findIndex((item) => variable === item); + let index = this.deep.findIndex(item => variable === item); if (index < 0) { index = this.deep.push(variable) - 1; } const variableContainer = variable.match(this.variableSyntax)[0]; const variableString = this.cleanVariable(variableContainer).replace(/\s/g, ''); - return variableContainer - .replace(/\s/g, '') - .replace(variableString, `deep:${index}`); + return variableContainer.replace(/\s/g, '').replace(variableString, `deep:${index}`); } appendDeepVariable(variable, subProperty) { const variableString = this.cleanVariable(variable); @@ -834,22 +850,28 @@ class Variables { * will later resolve to the deeper value */ getDeeperValue(deepProperties, valueToPopulate) { - return BbPromise.reduce(deepProperties, (reducedValueParam, subProperty) => { - let reducedValue = reducedValueParam; - if (_.isString(reducedValue) && reducedValue.match(this.deepRefSyntax)) { // build mode - reducedValue = this.appendDeepVariable(reducedValue, subProperty); - } else { // get mode - if (typeof reducedValue === 'undefined') { - reducedValue = {}; - } else if (subProperty !== '' || '' in reducedValue) { - reducedValue = reducedValue[subProperty]; + return BbPromise.reduce( + deepProperties, + (reducedValueParam, subProperty) => { + let reducedValue = reducedValueParam; + if (_.isString(reducedValue) && reducedValue.match(this.deepRefSyntax)) { + // build mode + reducedValue = this.appendDeepVariable(reducedValue, subProperty); + } else { + // get mode + if (typeof reducedValue === 'undefined') { + reducedValue = {}; + } else if (subProperty !== '' || '' in reducedValue) { + reducedValue = reducedValue[subProperty]; + } + if (typeof reducedValue === 'string' && reducedValue.match(this.variableSyntax)) { + reducedValue = this.makeDeepVariable(reducedValue); + } } - if (typeof reducedValue === 'string' && reducedValue.match(this.variableSyntax)) { - reducedValue = this.makeDeepVariable(reducedValue); - } - } - return BbPromise.resolve(reducedValue); - }, valueToPopulate); + return BbPromise.resolve(reducedValue); + }, + valueToPopulate + ); } warnIfNotFound(variableString, valueToPopulate) { @@ -871,8 +893,9 @@ class Variables { varType = 'SSM parameter'; } if (!_.isUndefined(varType)) { - logWarning(`A valid ${varType} to satisfy the declaration '${ - variableString}' could not be found.`); + logWarning( + `A valid ${varType} to satisfy the declaration '${variableString}' could not be found.` + ); } } return valueToPopulate; diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 3bec38358..e41352fe0 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -31,7 +31,6 @@ chai.should(); const expect = chai.expect; - describe('Variables', () => { let serverless; let restoreEnv; @@ -64,75 +63,83 @@ describe('Variables', () => { }); describe('#populateService()', () => { - it('should remove problematic attributes bofore calling populateObjectImpl with the service', - () => { - const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService') - .returns(BbPromise.resolve()); - const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl') - .callsFake((val) => { + it('should remove problematic attributes bofore calling populateObjectImpl with the service', () => { + const prepopulateServiceStub = sinon + .stub(serverless.variables, 'prepopulateService') + .returns(BbPromise.resolve()); + const populateObjectStub = sinon + .stub(serverless.variables, 'populateObjectImpl') + .callsFake(val => { expect(val).to.equal(serverless.service); expect(val.provider.variableSyntax).to.be.undefined; expect(val.serverless).to.be.undefined; return BbPromise.resolve(); }); - return serverless.variables.populateService().should.be.fulfilled - .then().finally(() => { - prepopulateServiceStub.restore(); - populateObjectStub.restore(); - }); - }); - it('should clear caches and remaining state *before* [pre]populating service', - () => { - const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService') - .callsFake((val) => { - expect(serverless.variables.deep).to.eql([]); - expect(serverless.variables.tracker.getAll()).to.eql([]); - return BbPromise.resolve(val); - }); - const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl') - .callsFake((val) => { - expect(serverless.variables.deep).to.eql([]); - expect(serverless.variables.tracker.getAll()).to.eql([]); - return BbPromise.resolve(val); - }); - serverless.variables.deep.push('${foo:}'); - const prms = BbPromise.resolve('foo'); - serverless.variables.tracker.add('foo:', prms, '${foo:}'); - prms.state = 'resolved'; - return serverless.variables.populateService().should.be.fulfilled - .then().finally(() => { - prepopulateServiceStub.restore(); - populateObjectStub.restore(); - }); - }); - it('should clear caches and remaining *after* [pre]populating service', - () => { - const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService') - .callsFake((val) => { - serverless.variables.deep.push('${foo:}'); - const promise = BbPromise.resolve(val); - serverless.variables.tracker.add('foo:', promise, '${foo:}'); - promise.state = 'resolved'; - return BbPromise.resolve(); - }); - const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl') - .callsFake((val) => { - serverless.variables.deep.push('${bar:}'); - const promise = BbPromise.resolve(val); - serverless.variables.tracker.add('bar:', promise, '${bar:}'); - promise.state = 'resolved'; - return BbPromise.resolve(); - }); - return serverless.variables.populateService().should.be.fulfilled - .then(() => { - expect(serverless.variables.deep).to.eql([]); - expect(serverless.variables.tracker.getAll()).to.eql([]); - }) - .finally(() => { - prepopulateServiceStub.restore(); - populateObjectStub.restore(); - }); - }); + return serverless.variables + .populateService() + .should.be.fulfilled.then() + .finally(() => { + prepopulateServiceStub.restore(); + populateObjectStub.restore(); + }); + }); + it('should clear caches and remaining state *before* [pre]populating service', () => { + const prepopulateServiceStub = sinon + .stub(serverless.variables, 'prepopulateService') + .callsFake(val => { + expect(serverless.variables.deep).to.eql([]); + expect(serverless.variables.tracker.getAll()).to.eql([]); + return BbPromise.resolve(val); + }); + const populateObjectStub = sinon + .stub(serverless.variables, 'populateObjectImpl') + .callsFake(val => { + expect(serverless.variables.deep).to.eql([]); + expect(serverless.variables.tracker.getAll()).to.eql([]); + return BbPromise.resolve(val); + }); + serverless.variables.deep.push('${foo:}'); + const prms = BbPromise.resolve('foo'); + serverless.variables.tracker.add('foo:', prms, '${foo:}'); + prms.state = 'resolved'; + return serverless.variables + .populateService() + .should.be.fulfilled.then() + .finally(() => { + prepopulateServiceStub.restore(); + populateObjectStub.restore(); + }); + }); + it('should clear caches and remaining *after* [pre]populating service', () => { + const prepopulateServiceStub = sinon + .stub(serverless.variables, 'prepopulateService') + .callsFake(val => { + serverless.variables.deep.push('${foo:}'); + const promise = BbPromise.resolve(val); + serverless.variables.tracker.add('foo:', promise, '${foo:}'); + promise.state = 'resolved'; + return BbPromise.resolve(); + }); + const populateObjectStub = sinon + .stub(serverless.variables, 'populateObjectImpl') + .callsFake(val => { + serverless.variables.deep.push('${bar:}'); + const promise = BbPromise.resolve(val); + serverless.variables.tracker.add('bar:', promise, '${bar:}'); + promise.state = 'resolved'; + return BbPromise.resolve(); + }); + return serverless.variables + .populateService() + .should.be.fulfilled.then(() => { + expect(serverless.variables.deep).to.eql([]); + expect(serverless.variables.tracker.getAll()).to.eql([]); + }) + .finally(() => { + prepopulateServiceStub.restore(); + populateObjectStub.restore(); + }); + }); }); describe('fallback', () => { @@ -140,7 +147,7 @@ describe('Variables', () => { serverless.variables.service.custom = { settings: '${self:nonExistent, "fallback"}', }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { + return serverless.variables.populateService().should.be.fulfilled.then(result => { expect(result.custom).to.be.deep.eql({ settings: 'fallback', }); @@ -151,7 +158,7 @@ describe('Variables', () => { serverless.variables.service.custom = { settings: '${opt:nonExistent, "fallback"}', }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { + return serverless.variables.populateService().should.be.fulfilled.then(result => { expect(result.custom).to.be.deep.eql({ settings: 'fallback', }); @@ -162,7 +169,7 @@ describe('Variables', () => { serverless.variables.service.custom = { settings: '${env:nonExistent, "fallback"}', }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { + return serverless.variables.populateService().should.be.fulfilled.then(result => { expect(result.custom).to.be.deep.eql({ settings: 'fallback', }); @@ -178,14 +185,17 @@ describe('Variables', () => { const fileExistsStub = sinon.stub(serverless.utils, 'fileExistsSync').returns(false); const realpathSync = sinon.stub(fse, 'realpathSync').returns(`${os.homedir()}/config.yml`); - return serverless.variables.populateService().should.be.fulfilled.then((result) => { - expect(result.custom).to.be.deep.eql({ - settings: 'fallback', + return serverless.variables + .populateService() + .should.be.fulfilled.then(result => { + expect(result.custom).to.be.deep.eql({ + settings: 'fallback', + }); + }) + .finally(() => { + fileExistsStub.restore(); + realpathSync.restore(); }); - }).finally(() => { - fileExistsStub.restore(); - realpathSync.restore(); - }); }); it('should fallback if file exists but given key not found and fallback is provided', () => { @@ -200,15 +210,18 @@ describe('Variables', () => { test2: 'test2', }); - return serverless.variables.populateService().should.be.fulfilled.then((result) => { - expect(result.custom).to.be.deep.eql({ - settings: 'fallback', + return serverless.variables + .populateService() + .should.be.fulfilled.then(result => { + expect(result.custom).to.be.deep.eql({ + settings: 'fallback', + }); + }) + .finally(() => { + fileExistsStub.restore(); + realpathSync.restore(); + readFileSyncStub.restore(); }); - }).finally(() => { - fileExistsStub.restore(); - realpathSync.restore(); - readFileSyncStub.restore(); - }); }); }); @@ -217,8 +230,9 @@ describe('Variables', () => { let requestStub; beforeEach(() => { awsProvider = new AwsProvider(serverless, {}); - requestStub = sinon.stub(awsProvider, 'request').callsFake(() => - BbPromise.reject(new serverless.classes.Error('Not found.', 400))); + requestStub = sinon + .stub(awsProvider, 'request') + .callsFake(() => BbPromise.reject(new serverless.classes.Error('Not found.', 400))); }); afterEach(() => { requestStub.restore(); @@ -227,7 +241,7 @@ describe('Variables', () => { serverless.variables.service.custom = { settings: '${s3:bucket/key, "fallback"}', }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { + return serverless.variables.populateService().should.be.fulfilled.then(result => { expect(result.custom).to.be.deep.eql({ settings: 'fallback', }); @@ -238,7 +252,7 @@ describe('Variables', () => { serverless.variables.service.custom = { settings: '${cf:stack.value, "fallback"}', }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { + return serverless.variables.populateService().should.be.fulfilled.then(result => { expect(result.custom).to.be.deep.eql({ settings: 'fallback', }); @@ -249,7 +263,7 @@ describe('Variables', () => { serverless.variables.service.custom = { settings: '${ssm:/path/param, "fallback"}', }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { + return serverless.variables.populateService().should.be.fulfilled.then(result => { expect(result.custom).to.be.deep.eql({ settings: 'fallback', }); @@ -278,43 +292,48 @@ describe('Variables', () => { awsProvider = new AwsProvider(serverless, {}); populateObjectImplStub = sinon.stub(serverless.variables, 'populateObjectImpl'); populateObjectImplStub.withArgs(serverless.variables.service).returns(BbPromise.resolve()); - requestStub = sinon.stub(awsProvider, 'request').callsFake(() => - BbPromise.reject(new Error('unexpected'))); + requestStub = sinon + .stub(awsProvider, 'request') + .callsFake(() => BbPromise.reject(new Error('unexpected'))); }); afterEach(() => { populateObjectImplStub.restore(); requestStub.restore(); }); const prepopulatedProperties = [ - { name: 'region', getter: (provider) => provider.getRegion() }, - { name: 'stage', getter: (provider) => provider.getStage() }, - { name: 'profile', getter: (provider) => provider.getProfile() }, + { name: 'region', getter: provider => provider.getRegion() }, + { name: 'stage', getter: provider => provider.getStage() }, + { name: 'profile', getter: provider => provider.getProfile() }, { name: 'credentials', - getter: (provider) => provider.serverless.service.provider.credentials, + getter: provider => provider.serverless.service.provider.credentials, }, { name: 'credentials.accessKeyId', - getter: (provider) => provider.serverless.service.provider.credentials.accessKeyId, + getter: provider => provider.serverless.service.provider.credentials.accessKeyId, }, { name: 'credentials.secretAccessKey', - getter: (provider) => provider.serverless.service.provider.credentials.secretAccessKey, + getter: provider => provider.serverless.service.provider.credentials.secretAccessKey, }, { name: 'credentials.sessionToken', - getter: (provider) => provider.serverless.service.provider.credentials.sessionToken, + getter: provider => provider.serverless.service.provider.credentials.sessionToken, }, ]; describe('basic population tests', () => { - prepopulatedProperties.forEach((property) => { + prepopulatedProperties.forEach(property => { it(`should populate variables in ${property.name} values`, () => { _.set( awsProvider.serverless.service.provider, property.name, - '${self:foobar, "default"}'); - return serverless.variables.populateService().should.be.fulfilled - .then(() => expect(property.getter(awsProvider)).to.be.eql('default')); + '${self:foobar, "default"}' + ); + return serverless.variables + .populateService() + .should.be.fulfilled.then(() => + expect(property.getter(awsProvider)).to.be.eql('default') + ); }); }); }); @@ -329,7 +348,8 @@ describe('Variables', () => { dependentConfigs.forEach(config => { it(`should reject ${config.name} variables in ${property.name} values`, () => { _.set(awsProvider.serverless.service.provider, property.name, config.value); - return serverless.variables.populateService() + return serverless.variables + .populateService() .should.be.rejectedWith('Variable dependency failure'); }); it(`should reject recursively dependent ${config.name} service dependencies`, () => { @@ -337,7 +357,8 @@ describe('Variables', () => { settings: config.value, }; awsProvider.serverless.service.provider.region = '${self:custom.settings.region}'; - return serverless.variables.populateService() + return serverless.variables + .populateService() .should.be.rejectedWith('Variable dependency failure'); }); }); @@ -350,7 +371,7 @@ describe('Variables', () => { { region: '${self:foo, "foo"}', state: 'bar' }, { region: '${self:foo, "foo"}', state: '${self:bar, "bar"}' }, ]; - stateCombinations.forEach((combination) => { + stateCombinations.forEach(combination => { it('must leave the dependent services in their original state', () => { const dependentMethods = [ { name: 'getValueFromCf', original: serverless.variables.getValueFromCf }, @@ -359,12 +380,11 @@ describe('Variables', () => { ]; awsProvider.serverless.service.provider.region = combination.region; awsProvider.serverless.service.provider.state = combination.state; - return serverless.variables.populateService().should.be.fulfilled - .then(() => { - dependentMethods.forEach((method) => { - expect(serverless.variables[method.name]).to.equal(method.original); - }); + return serverless.variables.populateService().should.be.fulfilled.then(() => { + dependentMethods.forEach(method => { + expect(serverless.variables[method.name]).to.equal(method.original); }); + }); }); }); }); @@ -380,10 +400,7 @@ describe('Variables', () => { bar: 'baz', biz: 'buz', }, - b: [ - { c: 'd' }, - { e: 'f' }, - ], + b: [{ c: 'd' }, { e: 'f' }], g: date, h: regex, i: func, @@ -423,9 +440,11 @@ describe('Variables', () => { sinon.stub(serverless.variables, 'populateValue').resolves('prod'); - return serverless.variables.populateObject(object).then((populatedObject) => { - expect(populatedObject).to.deep.equal(expectedPopulatedObject); - }) + return serverless.variables + .populateObject(object) + .then(populatedObject => { + expect(populatedObject).to.deep.equal(expectedPopulatedObject); + }) .finally(() => serverless.variables.populateValue.restore()); }); @@ -438,12 +457,15 @@ describe('Variables', () => { stage: 'prod', }; expectedPopulatedObject['some.nested.key'] = 'hello'; - const populateValueStub = sinon.stub(serverless.variables, 'populateValue') - .callsFake(// eslint-disable-next-line no-template-curly-in-string - val => (val === '${opt:stage}' ? BbPromise.resolve('prod') : BbPromise.resolve(val))); - return serverless.variables.populateObject(object) + const populateValueStub = sinon.stub(serverless.variables, 'populateValue').callsFake( + // eslint-disable-next-line no-template-curly-in-string + val => (val === '${opt:stage}' ? BbPromise.resolve('prod') : BbPromise.resolve(val)) + ); + return serverless.variables + .populateObject(object) .should.become(expectedPopulatedObject) - .then().finally(() => populateValueStub.restore()); + .then() + .finally(() => populateValueStub.restore()); }); describe('significant variable usage corner cases', () => { let service; @@ -469,9 +491,11 @@ describe('Variables', () => { expected.custom = { me: expected, }; - return expect(serverless.variables.populateObject(service).then((result) => { - expect(jc.stringify(result)).to.eql(jc.stringify(expected)); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service).then(result => { + expect(jc.stringify(result)).to.eql(jc.stringify(expected)); + }) + ).to.be.fulfilled; }); it('should properly populate embedded variables', () => { service.custom = { @@ -484,9 +508,11 @@ describe('Variables', () => { val1: '0', val2: 'my value 0', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should properly populate an overwrite with a default value that is a string', () => { service.custom = { @@ -497,9 +523,11 @@ describe('Variables', () => { val0: 'my value', val1: 'string', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should properly populate an overwrite with a default value that is the string *', () => { service.custom = { @@ -510,9 +538,11 @@ describe('Variables', () => { val0: 'my value', val1: '*', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should properly populate an overwrite with a default value that is a string w/*', () => { service.custom = { @@ -523,9 +553,11 @@ describe('Variables', () => { val0: 'my value', val1: 'foo*', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should properly populate overwrites where the first value is valid', () => { service.custom = { @@ -536,18 +568,22 @@ describe('Variables', () => { val0: 'my value', val1: 'my value', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should do nothing useful on * when not wrapped in quotes', () => { service.custom = { val0: '${self:custom.*}', }; const expected = { val0: undefined }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should properly populate overwrites where the middle value is valid', () => { service.custom = { @@ -558,9 +594,11 @@ describe('Variables', () => { val0: 'my value', val1: 'my value', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should properly populate overwrites where the last value is valid', () => { service.custom = { @@ -571,9 +609,11 @@ describe('Variables', () => { val0: 'my value', val1: 'my value', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should properly populate overwrites with nested variables in the first value', () => { service.custom = { @@ -586,9 +626,11 @@ describe('Variables', () => { val1: 0, val2: 'my value', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should properly populate overwrites with nested variables in the middle value', () => { service.custom = { @@ -601,9 +643,11 @@ describe('Variables', () => { val1: 0, val2: 'my value', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should properly populate overwrites with nested variables in the last value', () => { service.custom = { @@ -616,9 +660,11 @@ describe('Variables', () => { val1: 0, val2: 'my value', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should properly replace duplicate variable declarations', () => { service.custom = { @@ -631,9 +677,11 @@ describe('Variables', () => { val1: 'my value', val2: 'my value', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); it('should recursively populate, regardless of order and duplication', () => { service.custom = { @@ -648,9 +696,11 @@ describe('Variables', () => { val0: 'my value', val2: 'my value', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; + return expect( + serverless.variables.populateObject(service.custom).then(result => { + expect(result).to.eql(expected); + }) + ).to.be.fulfilled; }); // see https://github.com/serverless/serverless/pull/4713#issuecomment-366975172 it('should handle deep references into deep variables', () => { @@ -677,8 +727,7 @@ describe('Variables', () => { SECRET: 'secret', }, }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle deep variables that reference overrides', () => { service.custom = { @@ -689,8 +738,7 @@ describe('Variables', () => { val1: 'bar', val2: 'bar', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle deep references into deep variables', () => { service.custom = { @@ -709,8 +757,7 @@ describe('Variables', () => { }, val2: 'bar', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle deep variables that reference overrides', () => { service.custom = { @@ -721,8 +768,7 @@ describe('Variables', () => { val1: 'bar', val2: 'foobar', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle referenced deep variables that reference overrides', () => { service.custom = { @@ -735,8 +781,7 @@ describe('Variables', () => { val2: 'bar', val3: 'bar', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle partial referenced deep variables that reference overrides', () => { service.custom = { @@ -749,8 +794,7 @@ describe('Variables', () => { val2: 'bar', val3: 'foobar', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle referenced contained deep variables that reference overrides', () => { service.custom = { @@ -763,8 +807,7 @@ describe('Variables', () => { val2: 'foobar', val3: 'foobar', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle multiple referenced contained deep variables referencing overrides', () => { service.custom = { @@ -779,8 +822,7 @@ describe('Variables', () => { val2: 'foo:bar', val3: 'foo:bar', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle overrides that are populated by unresolvable deep variables', () => { service.custom = { @@ -793,8 +835,7 @@ describe('Variables', () => { val1: 'foo', val2: 'fallback', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle embedded deep variable replacements in overrides', () => { service.custom = { @@ -809,8 +850,7 @@ describe('Variables', () => { val1: 'foo', val2: 'bar', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should deal with overwites that reference embedded deep references', () => { service.custom = { @@ -827,8 +867,7 @@ describe('Variables', () => { val3: 'val', val4: 'val', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should preserve whitespace in double-quote literal fallback', () => { service.custom = { @@ -837,18 +876,16 @@ describe('Variables', () => { const expected = { val0: 'rate(3 hours)', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should preserve whitespace in single-quote literal fallback', () => { service.custom = { - val0: '${self:custom.val, \'rate(1 hour)\'}', + val0: "${self:custom.val, 'rate(1 hour)'}", }; const expected = { val0: 'rate(1 hour)', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should accept whitespace in variables', () => { service.custom = { @@ -859,8 +896,7 @@ describe('Variables', () => { val: 'foobar', val0: 'foobar', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle deep variables regardless of custom variableSyntax', () => { service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)*]+?)}}'; @@ -876,8 +912,7 @@ describe('Variables', () => { my1stStage: 'DEV', my2ndStage: 'DEV', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle deep variable continuations regardless of custom variableSyntax', () => { service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)]+?)}}'; @@ -893,8 +928,7 @@ describe('Variables', () => { my1stStage: { we: 'DEV' }, my2ndStage: 'DEV', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle deep variables regardless of recursion into custom variableSyntax', () => { service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)*]+?)}}'; @@ -914,8 +948,7 @@ describe('Variables', () => { my1stStage: 'DEV', my2ndStage: 'DEV', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should handle deep variables in complex recursions of custom variableSyntax', () => { service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)*]+?)}}'; @@ -935,8 +968,7 @@ describe('Variables', () => { s2: 'I am a DEV! A DEV!', s3: 'DEV!, I am a DEV! DEV!, I am a DEV! A DEV!', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); describe('file reading cases', () => { let tmpDirPath; @@ -986,8 +1018,7 @@ module.exports = { val2: 'my-async-value-1', val0: 'my-async-value-1', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); it('should still work with a default file name in double or single quotes', () => { makeTempFile(asyncFileName, asyncContent); @@ -1003,109 +1034,98 @@ module.exports = { val3: 'my-async-value-1', val0: 'my-async-value-1', }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); + return serverless.variables.populateObject(service.custom).should.become(expected); }); - it('should populate any given variable only once regardless of ordering or reference count', - () => { - makeTempFile(asyncFileName, asyncContent); - service.custom = { - val9: '${self:custom.val7}', // eslint-disable-line no-template-curly-in-string - val7: '${self:custom.val5}', // eslint-disable-line no-template-curly-in-string - val5: '${self:custom.val3}', // eslint-disable-line no-template-curly-in-string - val3: '${self:custom.val1}', // eslint-disable-line no-template-curly-in-string - val1: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string - val2: '${self:custom.val1}', // eslint-disable-line no-template-curly-in-string - val4: '${self:custom.val3}', // eslint-disable-line no-template-curly-in-string - val6: '${self:custom.val5}', // eslint-disable-line no-template-curly-in-string - val8: '${self:custom.val7}', // eslint-disable-line no-template-curly-in-string - val0: `\${file(${asyncFileName}):str}`, - }; - const expected = { - val9: 'my-async-value-1', - val7: 'my-async-value-1', - val5: 'my-async-value-1', - val3: 'my-async-value-1', - val1: 'my-async-value-1', - val2: 'my-async-value-1', - val4: 'my-async-value-1', - val6: 'my-async-value-1', - val8: 'my-async-value-1', + it('should populate any given variable only once regardless of ordering or reference count', () => { + makeTempFile(asyncFileName, asyncContent); + service.custom = { + val9: '${self:custom.val7}', // eslint-disable-line no-template-curly-in-string + val7: '${self:custom.val5}', // eslint-disable-line no-template-curly-in-string + val5: '${self:custom.val3}', // eslint-disable-line no-template-curly-in-string + val3: '${self:custom.val1}', // eslint-disable-line no-template-curly-in-string + val1: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string + val2: '${self:custom.val1}', // eslint-disable-line no-template-curly-in-string + val4: '${self:custom.val3}', // eslint-disable-line no-template-curly-in-string + val6: '${self:custom.val5}', // eslint-disable-line no-template-curly-in-string + val8: '${self:custom.val7}', // eslint-disable-line no-template-curly-in-string + val0: `\${file(${asyncFileName}):str}`, + }; + const expected = { + val9: 'my-async-value-1', + val7: 'my-async-value-1', + val5: 'my-async-value-1', + val3: 'my-async-value-1', + val1: 'my-async-value-1', + val2: 'my-async-value-1', + val4: 'my-async-value-1', + val6: 'my-async-value-1', + val8: 'my-async-value-1', + val0: 'my-async-value-1', + }; + return serverless.variables.populateObject(service.custom).should.become(expected); + }); + it('should populate async objects with contained variables', () => { + makeTempFile(asyncFileName, asyncContent); + serverless.variables.options = { + stage: 'dev', + }; + service.custom = { + obj: `\${file(${asyncFileName}):obj}`, + }; + const expected = { + obj: { val0: 'my-async-value-1', - }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); - }); - it('should populate async objects with contained variables', - () => { - makeTempFile(asyncFileName, asyncContent); - serverless.variables.options = { - stage: 'dev', - }; - service.custom = { - obj: `\${file(${asyncFileName}):obj}`, - }; - const expected = { - obj: { - val0: 'my-async-value-1', - val1: 'dev', - }, - }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); - }); - it('should populate variables from filesnames including \'@\', e.g scoped npm packages', - () => { - const fileName = `./node_modules/@scoped-org/${asyncFileName}`; - makeTempFile( - fileName, - asyncContent - ); - service.custom = { - val0: `\${file(${fileName}):str}`, - }; - const expected = { - val0: 'my-async-value-1', - }; - return serverless.variables - .populateObject(service.custom) - .should.become(expected); - }); + val1: 'dev', + }, + }; + return serverless.variables.populateObject(service.custom).should.become(expected); + }); + it("should populate variables from filesnames including '@', e.g scoped npm packages", () => { + const fileName = `./node_modules/@scoped-org/${asyncFileName}`; + makeTempFile(fileName, asyncContent); + service.custom = { + val0: `\${file(${fileName}):str}`, + }; + const expected = { + val0: 'my-async-value-1', + }; + return serverless.variables.populateObject(service.custom).should.become(expected); + }); const selfFileName = 'self.yml'; const selfContent = `foo: baz bar: \${self:custom.self.foo} `; - it('should populate a "cyclic" reference across an unresolved dependency (issue #4687)', - () => { - makeTempFile(selfFileName, selfContent); - service.custom = { - self: `\${file(${selfFileName})}`, - }; - const expected = { - self: { - foo: 'baz', - bar: 'baz', - }, - }; - return serverless.variables.populateObject(service.custom) - .should.become(expected); - }); + it('should populate a "cyclic" reference across an unresolved dependency (issue #4687)', () => { + makeTempFile(selfFileName, selfContent); + service.custom = { + self: `\${file(${selfFileName})}`, + }; + const expected = { + self: { + foo: 'baz', + bar: 'baz', + }, + }; + return serverless.variables.populateObject(service.custom).should.become(expected); + }); const emptyFileName = 'empty.js'; const emptyContent = `'use strict'; module.exports = { func: () => ({ value: 'a value' }), } `; - it('should reject population of an attribute not exported from a file', - () => { - makeTempFile(emptyFileName, emptyContent); - service.custom = { - val: `\${file(${emptyFileName}):func.notAValue}`, - }; - return serverless.variables.populateObject(service.custom) - .should.be.rejectedWith(serverless.classes.Error, - 'Invalid variable syntax when referencing file'); - }); + it('should reject population of an attribute not exported from a file', () => { + makeTempFile(emptyFileName, emptyContent); + service.custom = { + val: `\${file(${emptyFileName}):func.notAValue}`, + }; + return serverless.variables + .populateObject(service.custom) + .should.be.rejectedWith( + serverless.classes.Error, + 'Invalid variable syntax when referencing file' + ); + }); }); }); }); @@ -1120,7 +1140,8 @@ module.exports = { const property = 'my stage is ${opt:stage, self:provider.stage}'; serverless.variables.options = { stage: 'dev' }; serverless.service.provider.stage = 'prod'; - return serverless.variables.populateProperty(property) + return serverless.variables + .populateProperty(property) .should.eventually.eql('my stage is dev'); }); @@ -1128,7 +1149,8 @@ module.exports = { // eslint-disable-next-line no-template-curly-in-string const property = "my stage is ${opt:stage, 'prod'}"; serverless.variables.options = {}; - return serverless.variables.populateProperty(property) + return serverless.variables + .populateProperty(property) .should.eventually.eql('my stage is prod'); }); @@ -1136,7 +1158,8 @@ module.exports = { // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage, "prod"}'; serverless.variables.options = {}; - return serverless.variables.populateProperty(property) + return serverless.variables + .populateProperty(property) .should.eventually.eql('my stage is prod'); }); @@ -1144,7 +1167,8 @@ module.exports = { // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage}'; serverless.variables.options = { stage: 'prod' }; - return serverless.variables.populateProperty(property) + return serverless.variables + .populateProperty(property) .should.eventually.eql('my stage is prod'); }); @@ -1158,11 +1182,13 @@ module.exports = { const param = '/some/path/to/invalidparam'; const property = `\${ssm:${param}}`; const error = new serverless.classes.Error(`Parameter ${param} not found.`, 400); - const requestStub = sinon.stub(awsProvider, 'request') + const requestStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.reject(error)); const warnIfNotFoundSpy = sinon.spy(serverless.variables, 'warnIfNotFound'); - return serverless.variables.populateProperty(property) + return serverless.variables + .populateProperty(property) .should.become(undefined) .then(() => { expect(requestStub.callCount).to.equal(1); @@ -1184,9 +1210,11 @@ module.exports = { const param = '/some/path/to/invalidparam'; const property = `\${ssm:${param}}`; const error = new serverless.classes.Error('Some random failure.', 123); - const requestStub = sinon.stub(awsProvider, 'request') + const requestStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.reject(error)); - return serverless.variables.populateProperty(property) + return serverless.variables + .populateProperty(property) .should.be.rejectedWith(serverless.classes.Error) .then(() => expect(requestStub.callCount).to.equal(1)) .finally(() => requestStub.restore()); @@ -1197,7 +1225,8 @@ module.exports = { const property = 'my stage is ${env:${opt:name}}'; process.env.TEST_VAR = 'dev'; serverless.variables.options = { name: 'TEST_VAR' }; - return serverless.variables.populateProperty(property) + return serverless.variables + .populateProperty(property) .should.eventually.eql('my stage is dev'); }); @@ -1215,7 +1244,8 @@ module.exports = { lvl5: 'A${opt:lvl6}', lvl6: 'R', }; - return serverless.variables.populateProperty(property) + return serverless.variables + .populateProperty(property) .should.eventually.eql('my stage is dev'); }); }); @@ -1226,7 +1256,8 @@ module.exports = { const matchedString = '${opt:stage}'; // eslint-disable-line no-template-curly-in-string // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage}'; - serverless.variables.populateVariable(property, matchedString, valueToPopulate) + serverless.variables + .populateVariable(property, matchedString, valueToPopulate) .should.eql('my stage is dev'); }); @@ -1235,7 +1266,8 @@ module.exports = { const matchedString = '${opt:number}'; // eslint-disable-line no-template-curly-in-string // eslint-disable-next-line no-template-curly-in-string const property = 'your account number is ${opt:number}'; - serverless.variables.populateVariable(property, matchedString, valueToPopulate) + serverless.variables + .populateVariable(property, matchedString, valueToPopulate) .should.eql('your account number is 5'); }); @@ -1243,7 +1275,8 @@ module.exports = { const valueToPopulate = 5; const matchedString = '${opt:number}'; // eslint-disable-line no-template-curly-in-string const property = '${opt:number}'; // eslint-disable-line no-template-curly-in-string - return serverless.variables.populateVariable(property, matchedString, valueToPopulate) + return serverless.variables + .populateVariable(property, matchedString, valueToPopulate) .should.equal(5); }); @@ -1253,8 +1286,8 @@ module.exports = { // eslint-disable-next-line no-template-curly-in-string const property = 'your account number is ${opt:object}'; return expect(() => - serverless.variables.populateVariable(property, matchedString, valueToPopulate)) - .to.throw(serverless.classes.Error); + serverless.variables.populateVariable(property, matchedString, valueToPopulate) + ).to.throw(serverless.classes.Error); }); }); @@ -1286,19 +1319,12 @@ module.exports = { }); it('should ignore quoted commas', () => { const input = '",", \',\', ",\', \',\'", "\',\', \',\'", \',", ","\', \'",", ","\''; - const expected = [ - '","', - '\',\'', - '",\', \',\'"', - '"\',\', \',\'"', - '\',", ","\'', - '\'",", ","\'', - ]; + const expected = ['","', "','", "\",', ','\"", "\"',', ','\"", '\',", ","\'', '\'",", ","\'']; expect(serverless.variables.splitByComma(input)).to.eql(expected); }); it('should deal with a combination of these cases', () => { - const input = ' \t\n\'a\'\t\n , \n\t"foo,bar", opt:foo, ",", \',\', "\',\', \',\'", foo\n\t '; - const expected = ['\'a\'', '"foo,bar"', 'opt:foo', '","', '\',\'', '"\',\', \',\'"', 'foo']; + const input = " \t\n'a'\t\n , \n\t\"foo,bar\", opt:foo, \",\", ',', \"',', ','\", foo\n\t "; + const expected = ["'a'", '"foo,bar"', 'opt:foo', '","', "','", "\"',', ','\"", 'foo']; expect(serverless.variables.splitByComma(input)).to.eql(expected); }); }); @@ -1314,9 +1340,9 @@ module.exports = { getValueFromSourceStub.onCall(0).resolves(undefined); getValueFromSourceStub.onCall(1).resolves(null); getValueFromSourceStub.onCall(2).resolves('variableValue'); - return serverless.variables.overwrite(['opt:stage', 'env:stage', 'self:provider.stage']) - .should.be.fulfilled - .then((valueToPopulate) => { + return serverless.variables + .overwrite(['opt:stage', 'env:stage', 'self:provider.stage']) + .should.be.fulfilled.then(valueToPopulate => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromSourceStub).to.have.been.calledThrice; }) @@ -1327,8 +1353,9 @@ module.exports = { const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves({}); getValueFromSourceStub.onCall(1).resolves('variableValue'); - return serverless.variables.overwrite(['opt:stage', 'env:stage']).should.be.fulfilled - .then((valueToPopulate) => { + return serverless.variables + .overwrite(['opt:stage', 'env:stage']) + .should.be.fulfilled.then(valueToPopulate => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromSourceStub).to.have.been.calledTwice; }) @@ -1339,16 +1366,22 @@ module.exports = { const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(0); getValueFromSourceStub.onCall(1).resolves('variableValue'); - return serverless.variables.overwrite(['opt:stage', 'env:stage']).should.become(0) - .then().finally(() => getValueFromSourceStub.restore()); + return serverless.variables + .overwrite(['opt:stage', 'env:stage']) + .should.become(0) + .then() + .finally(() => getValueFromSourceStub.restore()); }); it('should not overwrite false values', () => { const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(false); getValueFromSourceStub.onCall(1).resolves('variableValue'); - return serverless.variables.overwrite(['opt:stage', 'env:stage']).should.become(false) - .then().finally(() => getValueFromSourceStub.restore()); + return serverless.variables + .overwrite(['opt:stage', 'env:stage']) + .should.become(false) + .then() + .finally(() => getValueFromSourceStub.restore()); }); it('should skip getting values once a value has been found', () => { @@ -1356,18 +1389,23 @@ module.exports = { getValueFromSourceStub.onCall(0).resolves(undefined); getValueFromSourceStub.onCall(1).resolves('variableValue'); getValueFromSourceStub.onCall(2).resolves('variableValue2'); - return serverless.variables.overwrite(['opt:stage', 'env:stage', 'self:provider.stage']) - .should.be.fulfilled - .then(valueToPopulate => expect(valueToPopulate).to.equal('variableValue')) + return serverless.variables + .overwrite(['opt:stage', 'env:stage', 'self:provider.stage']) + .should.be.fulfilled.then(valueToPopulate => + expect(valueToPopulate).to.equal('variableValue') + ) .finally(() => getValueFromSourceStub.restore()); }); it('should properly handle string values containing commas', () => { const str = '"foo,bar"'; - const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource') + const getValueFromSourceStub = sinon + .stub(serverless.variables, 'getValueFromSource') .resolves(undefined); - return serverless.variables.overwrite(['opt:stage', str]) - .should.be.fulfilled - .then(() => expect(getValueFromSourceStub.getCall(1).args[0]).to.eql(str)) + return serverless.variables + .overwrite(['opt:stage', str]) + .should.be.fulfilled.then(() => + expect(getValueFromSourceStub.getCall(1).args[0]).to.eql(str) + ) .finally(() => getValueFromSourceStub.restore()); }); }); @@ -1384,21 +1422,29 @@ module.exports = { let getValueFromSsmStub; beforeEach(() => { - getValueFromSlsStub = sinon.stub(serverless.variables, 'getValueFromSls') + getValueFromSlsStub = sinon + .stub(serverless.variables, 'getValueFromSls') .resolves('variableValue'); - getValueFromEnvStub = sinon.stub(serverless.variables, 'getValueFromEnv') + getValueFromEnvStub = sinon + .stub(serverless.variables, 'getValueFromEnv') .resolves('variableValue'); - getValueFromOptionsStub = sinon.stub(serverless.variables, 'getValueFromOptions') + getValueFromOptionsStub = sinon + .stub(serverless.variables, 'getValueFromOptions') .resolves('variableValue'); - getValueFromSelfStub = sinon.stub(serverless.variables, 'getValueFromSelf') + getValueFromSelfStub = sinon + .stub(serverless.variables, 'getValueFromSelf') .resolves('variableValue'); - getValueFromFileStub = sinon.stub(serverless.variables, 'getValueFromFile') + getValueFromFileStub = sinon + .stub(serverless.variables, 'getValueFromFile') .resolves('variableValue'); - getValueFromCfStub = sinon.stub(serverless.variables, 'getValueFromCf') + getValueFromCfStub = sinon + .stub(serverless.variables, 'getValueFromCf') .resolves('variableValue'); - getValueFromS3Stub = sinon.stub(serverless.variables, 'getValueFromS3') + getValueFromS3Stub = sinon + .stub(serverless.variables, 'getValueFromS3') .resolves('variableValue'); - getValueFromSsmStub = sinon.stub(serverless.variables, 'getValueFromSsm') + getValueFromSsmStub = sinon + .stub(serverless.variables, 'getValueFromSsm') .resolves('variableValue'); }); @@ -1413,74 +1459,81 @@ module.exports = { serverless.variables.getValueFromSsm.restore(); }); - it('should call getValueFromSls if referencing sls var', () => serverless.variables - .getValueFromSource('sls:instanceId').should.be.fulfilled - .then((valueToPopulate) => { + it('should call getValueFromSls if referencing sls var', () => + serverless.variables + .getValueFromSource('sls:instanceId') + .should.be.fulfilled.then(valueToPopulate => { expect(valueToPopulate).to.equal(variableValue); expect(getValueFromSlsStub).to.have.been.called; expect(getValueFromSlsStub).to.have.been.calledWith('sls:instanceId'); })); - it('should call getValueFromEnv if referencing env var', () => serverless.variables - .getValueFromSource('env:TEST_VAR').should.be.fulfilled - .then((valueToPopulate) => { + it('should call getValueFromEnv if referencing env var', () => + serverless.variables + .getValueFromSource('env:TEST_VAR') + .should.be.fulfilled.then(valueToPopulate => { expect(valueToPopulate).to.equal(variableValue); expect(getValueFromEnvStub).to.have.been.called; expect(getValueFromEnvStub).to.have.been.calledWith('env:TEST_VAR'); })); - it('should call getValueFromOptions if referencing an option', () => serverless.variables - .getValueFromSource('opt:stage').should.be.fulfilled - .then((valueToPopulate) => { + it('should call getValueFromOptions if referencing an option', () => + serverless.variables + .getValueFromSource('opt:stage') + .should.be.fulfilled.then(valueToPopulate => { expect(valueToPopulate).to.equal(variableValue); expect(getValueFromOptionsStub).to.have.been.called; expect(getValueFromOptionsStub).to.have.been.calledWith('opt:stage'); })); - it('should call getValueFromSelf if referencing from self', () => serverless.variables - .getValueFromSource('self:provider').should.be.fulfilled - .then((valueToPopulate) => { + it('should call getValueFromSelf if referencing from self', () => + serverless.variables + .getValueFromSource('self:provider') + .should.be.fulfilled.then(valueToPopulate => { expect(valueToPopulate).to.equal(variableValue); expect(getValueFromSelfStub).to.have.been.called; expect(getValueFromSelfStub).to.have.been.calledWith('self:provider'); })); - it('should call getValueFromFile if referencing from another file', () => serverless.variables - .getValueFromSource('file(./config.yml)').should.be.fulfilled - .then((valueToPopulate) => { + it('should call getValueFromFile if referencing from another file', () => + serverless.variables + .getValueFromSource('file(./config.yml)') + .should.be.fulfilled.then(valueToPopulate => { expect(valueToPopulate).to.equal(variableValue); expect(getValueFromFileStub).to.have.been.called; expect(getValueFromFileStub).to.have.been.calledWith('file(./config.yml)'); })); - it('should call getValueFromCf if referencing CloudFormation Outputs', () => serverless - .variables.getValueFromSource('cf:test-stack.testOutput').should.be.fulfilled - .then((valueToPopulate) => { + it('should call getValueFromCf if referencing CloudFormation Outputs', () => + serverless.variables + .getValueFromSource('cf:test-stack.testOutput') + .should.be.fulfilled.then(valueToPopulate => { expect(valueToPopulate).to.equal(variableValue); expect(getValueFromCfStub).to.have.been.called; expect(getValueFromCfStub).to.have.been.calledWith('cf:test-stack.testOutput'); })); - it('should call getValueFromS3 if referencing variable in S3', () => serverless.variables - .getValueFromSource('s3:test-bucket/path/to/key') - .should.be.fulfilled - .then((valueToPopulate) => { + it('should call getValueFromS3 if referencing variable in S3', () => + serverless.variables + .getValueFromSource('s3:test-bucket/path/to/key') + .should.be.fulfilled.then(valueToPopulate => { expect(valueToPopulate).to.equal(variableValue); expect(getValueFromS3Stub).to.have.been.called; expect(getValueFromS3Stub).to.have.been.calledWith('s3:test-bucket/path/to/key'); })); - it('should call getValueFromSsm if referencing variable in SSM', () => serverless.variables - .getValueFromSource('ssm:/test/path/to/param') - .should.be.fulfilled - .then((valueToPopulate) => { + it('should call getValueFromSsm if referencing variable in SSM', () => + serverless.variables + .getValueFromSource('ssm:/test/path/to/param') + .should.be.fulfilled.then(valueToPopulate => { expect(valueToPopulate).to.equal(variableValue); expect(getValueFromSsmStub).to.have.been.called; expect(getValueFromSsmStub).to.have.been.calledWith('ssm:/test/path/to/param'); })); it('should reject invalid sources', () => - serverless.variables.getValueFromSource('weird:source') + serverless.variables + .getValueFromSource('weird:source') .should.be.rejectedWith(serverless.classes.Error)); describe('caching', () => { @@ -1494,15 +1547,18 @@ module.exports = { { function: 'getValueFromS3', variableString: 's3:test-bucket/path/to/ke' }, { function: 'getValueFromSsm', variableString: 'ssm:/test/path/to/param' }, ]; - sources.forEach((source) => { + sources.forEach(source => { it(`should only call ${source.function} once, returning the cached value otherwise`, () => { const getValueFunctionStub = serverless.variables[source.function]; return BbPromise.all([ - serverless.variables.getValueFromSource(source.variableString) + serverless.variables + .getValueFromSource(source.variableString) .should.become(variableValue), BbPromise.delay(100).then(() => - serverless.variables.getValueFromSource(source.variableString) - .should.become(variableValue)), + serverless.variables + .getValueFromSource(source.variableString) + .should.become(variableValue) + ), ]).then(() => { expect(getValueFunctionStub).to.have.been.calledOnce; expect(getValueFunctionStub).to.have.been.calledWith(source.variableString); @@ -1515,7 +1571,7 @@ module.exports = { describe('#getValueFromSls()', () => { it('should get variable from Serverless Framework provided variables', () => { serverless.instanceId = 12345678; - return serverless.variables.getValueFromSls('sls:instanceId').then((valueToPopulate) => { + return serverless.variables.getValueFromSls('sls:instanceId').then(valueToPopulate => { expect(valueToPopulate).to.equal(12345678); }); }); @@ -1529,7 +1585,7 @@ module.exports = { it('should allow top-level references to the environment variables hive', () => { process.env.TEST_VAR = 'someValue'; - return serverless.variables.getValueFromEnv('env:').then((valueToPopulate) => { + return serverless.variables.getValueFromEnv('env:').then(valueToPopulate => { expect(valueToPopulate.TEST_VAR).to.be.equal('someValue'); }); }); @@ -1543,7 +1599,8 @@ module.exports = { it('should allow top-level references to the options hive', () => { serverless.variables.options = { stage: 'prod' }; - return serverless.variables.getValueFromOptions('opt:') + return serverless.variables + .getValueFromOptions('opt:') .should.become(serverless.variables.options); }); }); @@ -1566,7 +1623,8 @@ module.exports = { service: 'testService', provider: serverless.service.provider, }; - return serverless.variables.getValueFromSelf('self:service.name') + return serverless.variables + .getValueFromSelf('self:service.name') .should.become('testService'); }); it('should redirect ${self:provider} to ${self:provider.name}', () => { @@ -1585,7 +1643,8 @@ module.exports = { awsKmsKeyArn: keyArn, }, }; - return serverless.variables.getValueFromSelf('self:service.awsKmsKeyArn') + return serverless.variables + .getValueFromSelf('self:service.awsKmsKeyArn') .should.become(keyArn); }); it('should handle self-references to the root of the serverless.yml file', () => { @@ -1594,7 +1653,8 @@ module.exports = { provider: 'testProvider', defaults: serverless.service.defaults, }; - return serverless.variables.getValueFromSelf('self:') + return serverless.variables + .getValueFromSelf('self:') .should.eventually.equal(serverless.variables.service); }); }); @@ -1613,8 +1673,9 @@ module.exports = { const fileExistsStub = sinon.stub(serverless.utils, 'fileExistsSync').returns(true); const realpathSync = sinon.stub(fse, 'realpathSync').returns(expectedFileName); const readFileSyncStub = sinon.stub(serverless.utils, 'readFileSync').returns(configYml); - return serverless.variables.getValueFromFile('file(~/somedir/config.yml)').should.be.fulfilled - .then((valueToPopulate) => { + return serverless.variables + .getValueFromFile('file(~/somedir/config.yml)') + .should.be.fulfilled.then(valueToPopulate => { expect(realpathSync).to.not.have.been.called; expect(fileExistsStub).to.have.been.calledWithMatch(expectedFileName); expect(readFileSyncStub).to.have.been.calledWithMatch(expectedFileName); @@ -1640,7 +1701,8 @@ module.exports = { }; SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), YAML.dump(configYml)); serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./config.yml)') + return serverless.variables + .getValueFromFile('file(./config.yml)') .should.eventually.eql(configYml); }); @@ -1649,8 +1711,9 @@ module.exports = { serverless.config.update({ servicePath: tmpDirPath }); const realpathSync = sinon.spy(fse, 'realpathSync'); const existsSync = sinon.spy(fse, 'existsSync'); - return serverless.variables.getValueFromFile('file(./non-existing.yml)').should.be.fulfilled - .then((valueToPopulate) => { + return serverless.variables + .getValueFromFile('file(./non-existing.yml)') + .should.be.fulfilled.then(valueToPopulate => { expect(realpathSync).to.not.have.been.called; expect(existsSync).to.have.been.calledOnce; expect(valueToPopulate).to.be.undefined; @@ -1666,11 +1729,10 @@ module.exports = { const tmpDirPath = getTmpDirPath(); SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), 'hello world'); serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./someFile)') - .should.become('hello world'); + return serverless.variables.getValueFromFile('file(./someFile)').should.become('hello world'); }); - it('should populate symlinks', function () { + it('should populate symlinks', function() { const SUtils = new Utils(); const tmpDirPath = getTmpDirPath(); const realFilePath = path.join(tmpDirPath, 'someFile'); @@ -1683,9 +1745,11 @@ module.exports = { throw error; } serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./refSomeFile)') + return serverless.variables + .getValueFromFile('file(./refSomeFile)') .should.become('hello world') - .then().finally(() => { + .then() + .finally(() => { fse.removeSync(realFilePath); fse.removeSync(symlinkPath); }); @@ -1696,8 +1760,7 @@ module.exports = { const tmpDirPath = getTmpDirPath(); SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), 'hello world \n'); serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./someFile)') - .should.become('hello world'); + return serverless.variables.getValueFromFile('file(./someFile)').should.become('hello world'); }); it('should populate from another file when variable is of any type', () => { @@ -1713,7 +1776,8 @@ module.exports = { }; SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), YAML.dump(configYml)); serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./config.yml):test2.sub') + return serverless.variables + .getValueFromFile('file(./config.yml):test2.sub') .should.become(configYml.test2.sub); }); @@ -1723,7 +1787,8 @@ module.exports = { const jsData = 'module.exports.hello=function(){return "hello world";};'; SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./hello.js):hello') + return serverless.variables + .getValueFromFile('file(./hello.js):hello') .should.become('hello world'); }); @@ -1733,7 +1798,8 @@ module.exports = { const jsData = 'module.exports=function(){return { hello: "hello world" };};'; SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./hello.js)') + return serverless.variables + .getValueFromFile('file(./hello.js)') .should.become({ hello: 'hello world' }); }); @@ -1743,7 +1809,8 @@ module.exports = { const jsData = 'module.exports={ hello: "hello world" };'; SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./hello.js)') + return serverless.variables + .getValueFromFile('file(./hello.js)') .should.be.rejectedWith(serverless.classes.Error); }); @@ -1756,7 +1823,8 @@ module.exports = { SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); serverless.config.update({ servicePath: tmpDirPath }); serverless.variables.loadVariableSyntax(); - return serverless.variables.getValueFromFile('file(./hello.js):hello.one.two.three') + return serverless.variables + .getValueFromFile('file(./hello.js):hello.one.two.three') .should.become('hello world'); }); @@ -1769,7 +1837,8 @@ module.exports = { SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); serverless.config.update({ servicePath: tmpDirPath }); serverless.variables.loadVariableSyntax(); - return serverless.variables.getValueFromFile('file(./hello.js):hello.one.two.three') + return serverless.variables + .getValueFromFile('file(./hello.js):hello.one.two.three') .should.become('hello world'); }); @@ -1786,7 +1855,8 @@ module.exports = { }; SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), YAML.dump(configYml)); serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./config.yml).testObj.sub') + return serverless.variables + .getValueFromFile('file(./config.yml).testObj.sub') .should.be.rejectedWith(serverless.classes.Error); }); }); @@ -1801,16 +1871,22 @@ module.exports = { serverless.setProvider('aws', awsProvider); serverless.variables.options = options; const awsResponseMock = { - Stacks: [{ - Outputs: [{ - OutputKey: 'MockExport', - OutputValue: 'MockValue', - }], - }], + Stacks: [ + { + Outputs: [ + { + OutputKey: 'MockExport', + OutputValue: 'MockValue', + }, + ], + }, + ], }; - const cfStub = sinon.stub(serverless.getProvider('aws'), 'request').callsFake( - () => BbPromise.resolve(awsResponseMock)); - return serverless.variables.getValueFromCf('cf:some-stack.MockExport') + const cfStub = sinon + .stub(serverless.getProvider('aws'), 'request') + .callsFake(() => BbPromise.resolve(awsResponseMock)); + return serverless.variables + .getValueFromCf('cf:some-stack.MockExport') .should.become('MockValue') .then(() => { expect(cfStub).to.have.been.calledOnce; @@ -1818,7 +1894,8 @@ module.exports = { 'CloudFormation', 'describeStacks', { StackName: 'some-stack' }, - { useCache: true }); + { useCache: true } + ); }) .finally(() => cfStub.restore()); }); @@ -1832,16 +1909,22 @@ module.exports = { serverless.setProvider('aws', awsProvider); serverless.variables.options = options; const awsResponseMock = { - Stacks: [{ - Outputs: [{ - OutputKey: 'MockExport', - OutputValue: 'MockValue', - }], - }], + Stacks: [ + { + Outputs: [ + { + OutputKey: 'MockExport', + OutputValue: 'MockValue', + }, + ], + }, + ], }; - const cfStub = sinon.stub(serverless.getProvider('aws'), 'request').callsFake( - () => BbPromise.resolve(awsResponseMock)); - return serverless.variables.getValueFromCf('cf.us-east-1:some-stack.MockExport') + const cfStub = sinon + .stub(serverless.getProvider('aws'), 'request') + .callsFake(() => BbPromise.resolve(awsResponseMock)); + return serverless.variables + .getValueFromCf('cf.us-east-1:some-stack.MockExport') .should.become('MockValue') .then(() => { expect(cfStub).to.have.been.calledOnce; @@ -1849,7 +1932,8 @@ module.exports = { 'CloudFormation', 'describeStacks', { StackName: 'some-stack' }, - { region: 'us-east-1', useCache: true }); + { region: 'us-east-1', useCache: true } + ); }) .finally(() => cfStub.restore()); }); @@ -1863,25 +1947,34 @@ module.exports = { serverless.setProvider('aws', awsProvider); serverless.variables.options = options; const awsResponseMock = { - Stacks: [{ - Outputs: [{ - OutputKey: 'MockExport', - OutputValue: 'MockValue', - }], - }], + Stacks: [ + { + Outputs: [ + { + OutputKey: 'MockExport', + OutputValue: 'MockValue', + }, + ], + }, + ], }; - const cfStub = sinon.stub(serverless.getProvider('aws'), 'request').callsFake( - () => BbPromise.resolve(awsResponseMock)); - return serverless.variables.getValueFromCf('cf:some-stack.DoestNotExist') - .should.be.rejectedWith(serverless.classes.Error, - /to request a non exported variable from CloudFormation/) + const cfStub = sinon + .stub(serverless.getProvider('aws'), 'request') + .callsFake(() => BbPromise.resolve(awsResponseMock)); + return serverless.variables + .getValueFromCf('cf:some-stack.DoestNotExist') + .should.be.rejectedWith( + serverless.classes.Error, + /to request a non exported variable from CloudFormation/ + ) .then(() => { expect(cfStub).to.have.been.calledOnce; expect(cfStub).to.have.been.calledWithExactly( 'CloudFormation', 'describeStacks', { StackName: 'some-stack' }, - { useCache: true }); + { useCache: true } + ); }) .finally(() => cfStub.restore()); }); @@ -1902,9 +1995,11 @@ module.exports = { const awsResponseMock = { Body: 'MockValue', }; - const s3Stub = sinon.stub(awsProvider, 'request') + const s3Stub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables.getValueFromS3('s3:some.bucket/path/to/key') + return serverless.variables + .getValueFromS3('s3:some.bucket/path/to/key') .should.become('MockValue') .then(() => { expect(s3Stub).to.have.been.calledOnce; @@ -1915,20 +2010,24 @@ module.exports = { Bucket: 'some.bucket', Key: 'path/to/key', }, - { useCache: true }); + { useCache: true } + ); }) .finally(() => s3Stub.restore()); }); it('should throw error if error getting value from S3', () => { const error = new Error('The specified bucket is not valid'); - const requestStub = sinon.stub(awsProvider, 'request') + const requestStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.reject(error)); return expect(serverless.variables.getValueFromS3('s3:some.bucket/path/to/key')) .to.be.rejectedWith( serverless.classes.Error, - 'Error getting value for s3:some.bucket/path/to/key. The specified bucket is not valid') - .then().finally(() => requestStub.restore()); + 'Error getting value for s3:some.bucket/path/to/key. The specified bucket is not valid' + ) + .then() + .finally(() => requestStub.restore()); }); }); @@ -1951,9 +2050,11 @@ module.exports = { serverless.variables.options = options; }); it('should get variable from Ssm using regular-style param', () => { - const ssmStub = sinon.stub(awsProvider, 'request') + const ssmStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables.getValueFromSsm(`ssm:${param}`) + return serverless.variables + .getValueFromSsm(`ssm:${param}`) .should.become(value) .then(() => { expect(ssmStub).to.have.been.calledOnce; @@ -1964,14 +2065,17 @@ module.exports = { Name: param, WithDecryption: false, }, - { useCache: true }); + { useCache: true } + ); }) .finally(() => ssmStub.restore()); }); it('should get variable from Ssm using path-style param', () => { - const ssmStub = sinon.stub(awsProvider, 'request') + const ssmStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables.getValueFromSsm(`ssm:${param}`) + return serverless.variables + .getValueFromSsm(`ssm:${param}`) .should.become(value) .then(() => { expect(ssmStub).to.have.been.calledOnce; @@ -1982,14 +2086,17 @@ module.exports = { Name: param, WithDecryption: false, }, - { useCache: true }); + { useCache: true } + ); }) .finally(() => ssmStub.restore()); }); it('should get encrypted variable from Ssm using extended syntax', () => { - const ssmStub = sinon.stub(awsProvider, 'request') + const ssmStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables.getValueFromSsm(`ssm:${param}~true`) + return serverless.variables + .getValueFromSsm(`ssm:${param}~true`) .should.become(value) .then(() => { expect(ssmStub).to.have.been.calledOnce; @@ -2000,14 +2107,17 @@ module.exports = { Name: param, WithDecryption: true, }, - { useCache: true }); + { useCache: true } + ); }) .finally(() => ssmStub.restore()); }); it('should get unencrypted variable from Ssm using extended syntax', () => { - const ssmStub = sinon.stub(awsProvider, 'request') + const ssmStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables.getValueFromSsm(`ssm:${param}~false`) + return serverless.variables + .getValueFromSsm(`ssm:${param}~false`) .should.become(value) .then(() => { expect(ssmStub).to.have.been.calledOnce; @@ -2018,14 +2128,17 @@ module.exports = { Name: param, WithDecryption: false, }, - { useCache: true }); + { useCache: true } + ); }) .finally(() => ssmStub.restore()); }); it('should ignore bad values for extended syntax', () => { - const ssmStub = sinon.stub(awsProvider, 'request') + const ssmStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables.getValueFromSsm(`ssm:${param}~badvalue`) + return serverless.variables + .getValueFromSsm(`ssm:${param}~badvalue`) .should.become(value) .then(() => { expect(ssmStub).to.have.been.calledOnce; @@ -2036,7 +2149,8 @@ module.exports = { Name: param, WithDecryption: false, }, - { useCache: true }); + { useCache: true } + ); }) .finally(() => ssmStub.restore()); }); @@ -2049,11 +2163,14 @@ module.exports = { Value: jsonLikeText, }, }; - const ssmStub = sinon.stub(awsProvider, 'request') + const ssmStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.resolve(awsResponse)); - return serverless.variables.getValueFromSsm(`ssm:${secretParam}~true`) + return serverless.variables + .getValueFromSsm(`ssm:${secretParam}~true`) .should.become(jsonLikeText) - .then().finally(() => ssmStub.restore()); + .then() + .finally(() => ssmStub.restore()); }); it('should parse value as json if returned value is json-like', () => { const secretParam = '/aws/reference/secretsmanager/foo-bar'; @@ -2067,11 +2184,14 @@ module.exports = { Value: jsonLikeText, }, }; - const ssmStub = sinon.stub(awsProvider, 'request') + const ssmStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.resolve(awsResponse)); - return serverless.variables.getValueFromSsm(`ssm:${secretParam}~true`) + return serverless.variables + .getValueFromSsm(`ssm:${secretParam}~true`) .should.become(json) - .then().finally(() => ssmStub.restore()); + .then() + .finally(() => ssmStub.restore()); }); it('should get value as text if returned value is NOT json-like', () => { const secretParam = '/aws/reference/secretsmanager/foo-bar'; @@ -2081,30 +2201,39 @@ module.exports = { Value: plainText, }, }; - const ssmStub = sinon.stub(awsProvider, 'request') + const ssmStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.resolve(awsResponse)); - return serverless.variables.getValueFromSsm(`ssm:${secretParam}~true`) + return serverless.variables + .getValueFromSsm(`ssm:${secretParam}~true`) .should.become(plainText) - .then().finally(() => ssmStub.restore()); + .then() + .finally(() => ssmStub.restore()); }); }); it('should return undefined if SSM parameter does not exist', () => { const error = new serverless.classes.Error(`Parameter ${param} not found.`, 400); - const requestStub = sinon.stub(awsProvider, 'request') + const requestStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.reject(error)); - return serverless.variables.getValueFromSsm(`ssm:${param}`) + return serverless.variables + .getValueFromSsm(`ssm:${param}`) .should.become(undefined) - .then().finally(() => requestStub.restore()); + .then() + .finally(() => requestStub.restore()); }); it('should reject if SSM request returns unexpected error', () => { const error = new Error( - 'User: is not authorized to perform: ssm:GetParameter on resource: '); - const requestStub = sinon.stub(awsProvider, 'request') + 'User: is not authorized to perform: ssm:GetParameter on resource: ' + ); + const requestStub = sinon + .stub(awsProvider, 'request') .callsFake(() => BbPromise.reject(error)); - return serverless.variables.getValueFromSsm(`ssm:${param}`) - .should.be.rejected - .then().finally(() => requestStub.restore()); + return serverless.variables + .getValueFromSsm(`ssm:${param}`) + .should.be.rejected.then() + .finally(() => requestStub.restore()); }); }); @@ -2119,8 +2248,9 @@ module.exports = { }, }; serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeeperValue(['custom', 'subProperty', 'deep'], - valueToPopulateMock).should.become('deepValue'); + return serverless.variables + .getDeeperValue(['custom', 'subProperty', 'deep'], valueToPopulateMock) + .should.become('deepValue'); }); it('should not throw error if referencing invalid properties', () => { const valueToPopulateMock = { @@ -2130,8 +2260,9 @@ module.exports = { }, }; serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeeperValue(['custom', 'subProperty', 'deep', 'deeper'], - valueToPopulateMock).should.eventually.deep.equal({}); + return serverless.variables + .getDeeperValue(['custom', 'subProperty', 'deep', 'deeper'], valueToPopulateMock) + .should.eventually.deep.equal({}); }); it('should return a simple deep variable when final deep value is variable', () => { serverless.variables.service = { @@ -2145,10 +2276,9 @@ module.exports = { provider: serverless.service.provider, }; serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeeperValue( - ['custom', 'subProperty', 'deep'], - serverless.variables.service - ).should.become('${deep:0}'); + return serverless.variables + .getDeeperValue(['custom', 'subProperty', 'deep'], serverless.variables.service) + .should.become('${deep:0}'); }); it('should return a deep continuation when middle deep value is variable', () => { serverless.variables.service = { @@ -2159,9 +2289,8 @@ module.exports = { provider: serverless.service.provider, }; serverless.variables.loadVariableSyntax(); - return serverless.variables.getDeeperValue( - ['custom', 'anotherVar', 'veryDeep'], - serverless.variables.service) + return serverless.variables + .getDeeperValue(['custom', 'anotherVar', 'veryDeep'], serverless.variables.service) .should.become('${deep:0.veryDeep}'); }); }); diff --git a/lib/classes/YamlParser.js b/lib/classes/YamlParser.js index 00b84a5ba..161be346c 100644 --- a/lib/classes/YamlParser.js +++ b/lib/classes/YamlParser.js @@ -5,7 +5,6 @@ const YAML = require('js-yaml'); const resolve = require('json-refs').resolveRefs; class YamlParser { - constructor(serverless) { this.serverless = serverless; } @@ -25,7 +24,7 @@ class YamlParser { }, relativeBase: parentDir, }; - return resolve(root, options).then((res) => (res.resolved)); + return resolve(root, options).then(res => res.resolved); } } diff --git a/lib/classes/YamlParser.test.js b/lib/classes/YamlParser.test.js index 17328fd07..178be9659 100644 --- a/lib/classes/YamlParser.test.js +++ b/lib/classes/YamlParser.test.js @@ -24,7 +24,8 @@ describe('YamlParser', () => { serverless.utils.writeFileSync(tmpFilePath, YAML.dump({ foo: 'bar' })); return expect(serverless.yamlParser.parse(tmpFilePath)) - .to.eventually.have.property('foo').to.equal('bar'); + .to.eventually.have.property('foo') + .to.equal('bar'); }); it('should parse a simple .yml file', () => { @@ -33,7 +34,8 @@ describe('YamlParser', () => { serverless.utils.writeFileSync(tmpFilePath, YAML.dump({ foo: 'bar' })); return expect(serverless.yamlParser.parse(tmpFilePath)) - .to.eventually.have.property('foo').to.equal('bar'); + .to.eventually.have.property('foo') + .to.equal('bar'); }); it('should parse a .yml file with JSON-REF to YAML', () => { @@ -50,7 +52,8 @@ describe('YamlParser', () => { serverless.utils.writeFileSync(path.join(tmpDirPath, 'test.yml'), testYml); return expect(serverless.yamlParser.parse(path.join(tmpDirPath, 'test.yml'))) - .to.eventually.have.nested.property('main.foo').to.equal('bar'); + .to.eventually.have.nested.property('main.foo') + .to.equal('bar'); }); it('should parse a .yml file with JSON-REF to JSON', () => { @@ -67,7 +70,8 @@ describe('YamlParser', () => { serverless.utils.writeFileSync(path.join(tmpDirPath, 'test.yml'), testYml); return expect(serverless.yamlParser.parse(path.join(tmpDirPath, 'test.yml'))) - .to.eventually.have.nested.property('main.foo').to.equal('bar'); + .to.eventually.have.nested.property('main.foo') + .to.equal('bar'); }); it('should parse a .yml file with recursive JSON-REF', () => { @@ -92,7 +96,8 @@ describe('YamlParser', () => { serverless.utils.writeFileSync(path.join(tmpDirPath, 'one.yml'), oneYml); return expect(serverless.yamlParser.parse(path.join(tmpDirPath, 'one.yml'))) - .to.eventually.have.nested.property('one.two.foo').to.equal('bar'); + .to.eventually.have.nested.property('one.two.foo') + .to.equal('bar'); }); }); }); diff --git a/lib/plugins/aws/common/index.js b/lib/plugins/aws/common/index.js index 554def94f..ad1c8b4ad 100644 --- a/lib/plugins/aws/common/index.js +++ b/lib/plugins/aws/common/index.js @@ -17,12 +17,7 @@ class AwsCommon { this.options = options; this.provider = this.serverless.getProvider('aws'); - Object.assign( - this, - validate, - cleanupTempDir, - artifacts - ); + Object.assign(this, validate, cleanupTempDir, artifacts); // Internal commands are addressed as aws:common:[:lifecycleevent] this.commands = { @@ -33,24 +28,16 @@ class AwsCommon { common: { commands: { validate: { - lifecycleEvents: [ - 'validate', - ], + lifecycleEvents: ['validate'], }, cleanupTempDir: { - lifecycleEvents: [ - 'cleanup', - ], + lifecycleEvents: ['cleanup'], }, moveArtifactsToPackage: { - lifecycleEvents: [ - 'move', - ], + lifecycleEvents: ['move'], }, moveArtifactsToTemp: { - lifecycleEvents: [ - 'move', - ], + lifecycleEvents: ['move'], }, }, }, @@ -59,17 +46,15 @@ class AwsCommon { }; this.hooks = { - 'aws:common:validate:validate': () => BbPromise.bind(this) - .then(this.validate), + 'aws:common:validate:validate': () => BbPromise.bind(this).then(this.validate), - 'aws:common:cleanupTempDir:cleanup': () => BbPromise.bind(this) - .then(this.cleanupTempDir), + 'aws:common:cleanupTempDir:cleanup': () => BbPromise.bind(this).then(this.cleanupTempDir), - 'aws:common:moveArtifactsToPackage:move': () => BbPromise.bind(this) - .then(this.moveArtifactsToPackage), + 'aws:common:moveArtifactsToPackage:move': () => + BbPromise.bind(this).then(this.moveArtifactsToPackage), - 'aws:common:moveArtifactsToTemp:move': () => BbPromise.bind(this) - .then(this.moveArtifactsToTemp), + 'aws:common:moveArtifactsToTemp:move': () => + BbPromise.bind(this).then(this.moveArtifactsToTemp), }; } } diff --git a/lib/plugins/aws/common/index.test.js b/lib/plugins/aws/common/index.test.js index 087909a10..eef7bb650 100644 --- a/lib/plugins/aws/common/index.test.js +++ b/lib/plugins/aws/common/index.test.js @@ -1,4 +1,3 @@ - 'use strict'; const AwsProvider = require('../provider/awsProvider'); @@ -33,8 +32,7 @@ describe('AwsCommon', () => { describe('hooks', () => { describe('aws:common:validate:validate', () => { it('should call validate', () => { - const validateStub = sinon - .stub(awsCommon, 'validate').resolves(); + const validateStub = sinon.stub(awsCommon, 'validate').resolves(); return awsCommon.hooks['aws:common:validate:validate']().then(() => { expect(validateStub.calledOnce).to.be.equal(true); @@ -44,8 +42,7 @@ describe('AwsCommon', () => { describe('aws:common:cleanupTempDir:cleanup', () => { it('should call cleanupTempDir', () => { - const cleanupTempDirStub = sinon - .stub(awsCommon, 'cleanupTempDir').resolves(); + const cleanupTempDirStub = sinon.stub(awsCommon, 'cleanupTempDir').resolves(); return awsCommon.hooks['aws:common:cleanupTempDir:cleanup']().then(() => { expect(cleanupTempDirStub.calledOnce).to.be.equal(true); @@ -56,7 +53,8 @@ describe('AwsCommon', () => { describe('aws:common:moveArtifactsToPackage:move', () => { it('should call cleanupTempDir', () => { const moveArtifactsToPackageStub = sinon - .stub(awsCommon, 'moveArtifactsToPackage').resolves(); + .stub(awsCommon, 'moveArtifactsToPackage') + .resolves(); return awsCommon.hooks['aws:common:moveArtifactsToPackage:move']().then(() => { expect(moveArtifactsToPackageStub.calledOnce).to.be.equal(true); @@ -66,8 +64,7 @@ describe('AwsCommon', () => { describe('aws:common:moveArtifactsToTemp:move', () => { it('should call cleanupTempDir', () => { - const moveArtifactsToTempStub = sinon - .stub(awsCommon, 'moveArtifactsToTemp').resolves(); + const moveArtifactsToTempStub = sinon.stub(awsCommon, 'moveArtifactsToTemp').resolves(); return awsCommon.hooks['aws:common:moveArtifactsToTemp:move']().then(() => { expect(moveArtifactsToTempStub.calledOnce).to.be.equal(true); @@ -83,29 +80,31 @@ describe('AwsCommon', () => { describe('aws:common:validate', () => { it('should exist', () => { - expect(awsCommon.commands) - .to.have.nested.property('aws.commands.common.commands.validate'); + expect(awsCommon.commands).to.have.nested.property('aws.commands.common.commands.validate'); }); }); describe('aws:common:cleanupTempDir', () => { it('should exist', () => { - expect(awsCommon.commands) - .to.have.nested.property('aws.commands.common.commands.cleanupTempDir'); + expect(awsCommon.commands).to.have.nested.property( + 'aws.commands.common.commands.cleanupTempDir' + ); }); }); describe('aws:common:moveArtifactsToPackage', () => { it('should exist', () => { - expect(awsCommon.commands) - .to.have.nested.property('aws.commands.common.commands.moveArtifactsToPackage'); + expect(awsCommon.commands).to.have.nested.property( + 'aws.commands.common.commands.moveArtifactsToPackage' + ); }); }); describe('aws:common:moveArtifactsToTemp', () => { it('should exist', () => { - expect(awsCommon.commands) - .to.have.nested.property('aws.commands.common.commands.moveArtifactsToTemp'); + expect(awsCommon.commands).to.have.nested.property( + 'aws.commands.common.commands.moveArtifactsToTemp' + ); }); }); }); diff --git a/lib/plugins/aws/common/lib/artifacts.js b/lib/plugins/aws/common/lib/artifacts.js index 783b43d06..ea6ec1ed3 100644 --- a/lib/plugins/aws/common/lib/artifacts.js +++ b/lib/plugins/aws/common/lib/artifacts.js @@ -7,7 +7,8 @@ const _ = require('lodash'); module.exports = { moveArtifactsToPackage() { - const packagePath = this.options.package || + const packagePath = + this.options.package || this.serverless.service.package.path || path.join(this.serverless.config.servicePath || '.', '.serverless'); @@ -29,7 +30,8 @@ module.exports = { }, moveArtifactsToTemp() { - const packagePath = this.options.package || + const packagePath = + this.options.package || this.serverless.service.package.path || path.join(this.serverless.config.servicePath || '.', '.serverless'); diff --git a/lib/plugins/aws/common/lib/cleanupTempDir.test.js b/lib/plugins/aws/common/lib/cleanupTempDir.test.js index 680f4ceba..47592dd93 100644 --- a/lib/plugins/aws/common/lib/cleanupTempDir.test.js +++ b/lib/plugins/aws/common/lib/cleanupTempDir.test.js @@ -18,25 +18,30 @@ describe('#cleanupTempDir()', () => { }); it('should remove .serverless in the service directory', () => { - const serverlessTmpDirPath = path.join(packageService.serverless.config.servicePath, - '.serverless', 'README'); - serverless.utils.writeFileSync(serverlessTmpDirPath, - 'Some README content'); + const serverlessTmpDirPath = path.join( + packageService.serverless.config.servicePath, + '.serverless', + 'README' + ); + serverless.utils.writeFileSync(serverlessTmpDirPath, 'Some README content'); return packageService.cleanupTempDir().then(() => { - expect(serverless.utils.dirExistsSync(path.join(packageService.serverless.config.servicePath, - '.serverless'))).to.equal(false); + expect( + serverless.utils.dirExistsSync( + path.join(packageService.serverless.config.servicePath, '.serverless') + ) + ).to.equal(false); }); }); - it('should resolve if servicePath is not present', (done) => { + it('should resolve if servicePath is not present', done => { delete serverless.config.servicePath; packageService.cleanupTempDir().then(() => { done(); }); }); - it('should resolve if the .serverless directory is not present', (done) => { + it('should resolve if the .serverless directory is not present', done => { packageService.cleanupTempDir().then(() => { done(); }); diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.js index 4e5906bae..2f9b27076 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.js @@ -20,9 +20,7 @@ class AwsConfigCredentials { config: { commands: { credentials: { - lifecycleEvents: [ - 'config', - ], + lifecycleEvents: ['config'], options: { key: { usage: 'Access key for the provider', @@ -49,8 +47,9 @@ class AwsConfigCredentials { }; if (!os.homedir()) { - throw new this.serverless.classes - .Error('Can\'t find home directory on your local file system.'); + throw new this.serverless.classes.Error( + "Can't find home directory on your local file system." + ); } this.credentialsFilePath = path.join(os.homedir(), '.aws', 'credentials'); @@ -58,8 +57,7 @@ class AwsConfigCredentials { fse.ensureFileSync(this.credentialsFilePath); this.hooks = { - 'config:credentials:config': () => BbPromise.bind(this) - .then(this.configureCredentials), + 'config:credentials:config': () => BbPromise.bind(this).then(this.configureCredentials), }; } @@ -113,9 +111,11 @@ class AwsConfigCredentials { } addProfile() { - this.credentials.push(`[${this.options.profile}]`, + this.credentials.push( + `[${this.options.profile}]`, `aws_access_key_id = ${this.options.key}`, - `aws_secret_access_key = ${this.options.secret}`); + `aws_secret_access_key = ${this.options.secret}` + ); } updateProfile(profileBoundries) { @@ -125,10 +125,7 @@ class AwsConfigCredentials { // Remove existing 'aws_access_key_id' and 'aws_secret_access_key' properties while (currentLine < endLine) { const line = this.credentials[currentLine]; - if ( - line.indexOf('aws_access_key_id') > -1 || - line.indexOf('aws_secret_access_key') > -1 - ) { + if (line.indexOf('aws_access_key_id') > -1 || line.indexOf('aws_secret_access_key') > -1) { this.credentials.splice(currentLine, 1); endLine--; } else { @@ -150,7 +147,8 @@ class AwsConfigCredentials { this.serverless.cli.log('Saving your AWS profile in "~/.aws/credentials"...'); - return this.serverless.utils.writeFile(this.credentialsFilePath, updatedCredsFileContent) + return this.serverless.utils + .writeFile(this.credentialsFilePath, updatedCredsFileContent) .then(() => { // set file permissions to only readable/writable by owner (equivalent to 'chmod 600') // NOTE: `chmod` doesn't behave as intended on Windows, so skip if we're on Windows. @@ -162,7 +160,8 @@ class AwsConfigCredentials { } this.serverless.cli.log( - `Success! Your AWS access keys were stored under the "${this.options.profile}" profile.`); + `Success! Your AWS access keys were stored under the "${this.options.profile}" profile.` + ); }); } @@ -176,7 +175,6 @@ class AwsConfigCredentials { return { start, end }; } - } module.exports = AwsConfigCredentials; diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index 05dd68021..28459b68a 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -57,17 +57,18 @@ describe('AwsConfigCredentials', () => { }); it('should have the lifecycle event "config" for the "credentials" sub-command', () => { - expect(awsConfigCredentials.commands.config.commands.credentials.lifecycleEvents) - .to.deep.equal(['config']); + expect( + awsConfigCredentials.commands.config.commands.credentials.lifecycleEvents + ).to.deep.equal(['config']); }); it('should have the req. options "key" and "secret" for the "credentials" sub-command', () => { // eslint-disable-next-line no-unused-expressions - expect(awsConfigCredentials.commands.config.commands.credentials.options.key.required) - .to.be.true; + expect(awsConfigCredentials.commands.config.commands.credentials.options.key.required).to.be + .true; // eslint-disable-next-line no-unused-expressions - expect(awsConfigCredentials.commands.config.commands.credentials.options.secret.required) - .to.be.true; + expect(awsConfigCredentials.commands.config.commands.credentials.options.secret.required).to + .be.true; }); it('should have a "config:credentials:config" hook', () => { @@ -76,7 +77,8 @@ describe('AwsConfigCredentials', () => { it('should run promise chain in order for "config:credentials:config" hook', () => { const awsConfigCredentialsStub = sandbox - .stub(awsConfigCredentials, 'configureCredentials').resolves(); + .stub(awsConfigCredentials, 'configureCredentials') + .resolves(); return awsConfigCredentials.hooks['config:credentials:config']().then(() => { expect(awsConfigCredentialsStub.calledOnce).to.equal(true); @@ -113,10 +115,9 @@ describe('AwsConfigCredentials', () => { it('should use the "default" profile if option is not given', () => awsConfigCredentials.configureCredentials().then(() => { expect(awsConfigCredentials.options.profile).to.equal('default'); - }) - ); + })); - it('should resolve if the provider option is not "aws"', (done) => { + it('should resolve if the provider option is not "aws"', done => { awsConfigCredentials.options.provider = 'invalid-provider'; awsConfigCredentials.configureCredentials().then(() => done()); @@ -226,8 +227,7 @@ describe('AwsConfigCredentials', () => { const expectedFilePermissions = readableByOwnerPermission | writableByOwnerPermission; expect(filePermissions).to.equal(expectedFilePermissions); - }) - ); + })); } }); diff --git a/lib/plugins/aws/deploy/index.js b/lib/plugins/aws/deploy/index.js index 46bb2cd5d..c2534760f 100644 --- a/lib/plugins/aws/deploy/index.js +++ b/lib/plugins/aws/deploy/index.js @@ -19,7 +19,8 @@ class AwsDeploy { this.options = options; this.provider = this.serverless.getProvider('aws'); this.servicePath = this.serverless.config.servicePath || ''; - this.packagePath = this.options.package || + this.packagePath = + this.options.package || this.serverless.service.package.path || path.join(this.servicePath, '.serverless'); @@ -54,9 +55,7 @@ class AwsDeploy { ], }, finalize: { - lifecycleEvents: [ - 'cleanup', - ], + lifecycleEvents: ['cleanup'], }, }, }, @@ -65,28 +64,29 @@ class AwsDeploy { }; this.hooks = { - 'before:deploy:deploy': () => BbPromise.bind(this) - .then(() => this.serverless.pluginManager.spawn('aws:common:validate')) - .then(() => { - const bucketName = this.serverless.service.provider.deploymentBucket; - if (bucketName) { - return this.existsDeploymentBucket(bucketName); - } + 'before:deploy:deploy': () => + BbPromise.bind(this) + .then(() => this.serverless.pluginManager.spawn('aws:common:validate')) + .then(() => { + const bucketName = this.serverless.service.provider.deploymentBucket; + if (bucketName) { + return this.existsDeploymentBucket(bucketName); + } - return BbPromise.resolve(); - }) - .then(() => { - if (!this.options.package && !this.serverless.service.package.path) { - return this.extendedValidate(); - } - return BbPromise.bind(this) - .then(() => this.serverless.pluginManager.spawn('aws:common:moveArtifactsToTemp')) - .then(this.extendedValidate); - }), + return BbPromise.resolve(); + }) + .then(() => { + if (!this.options.package && !this.serverless.service.package.path) { + return this.extendedValidate(); + } + return BbPromise.bind(this) + .then(() => this.serverless.pluginManager.spawn('aws:common:moveArtifactsToTemp')) + .then(this.extendedValidate); + }), // Deploy outer lifecycle - 'deploy:deploy': () => BbPromise.bind(this) - .then(() => { + 'deploy:deploy': () => + BbPromise.bind(this).then(() => { if (this.options.noDeploy) { return BbPromise.resolve(); } @@ -96,31 +96,31 @@ class AwsDeploy { 'deploy:finalize': () => this.serverless.pluginManager.spawn('aws:deploy:finalize'), // Deploy deploy inner lifecycle - 'aws:deploy:deploy:createStack': () => BbPromise.bind(this) - .then(this.createStack), + 'aws:deploy:deploy:createStack': () => BbPromise.bind(this).then(this.createStack), - 'aws:deploy:deploy:checkForChanges': () => BbPromise.bind(this) - .then(this.setBucketName) - .then(this.checkForChanges), + 'aws:deploy:deploy:checkForChanges': () => + BbPromise.bind(this) + .then(this.setBucketName) + .then(this.checkForChanges), - 'aws:deploy:deploy:uploadArtifacts': () => BbPromise.bind(this) - .then(() => { + 'aws:deploy:deploy:uploadArtifacts': () => + BbPromise.bind(this).then(() => { if (this.serverless.service.provider.shouldNotDeploy) { return BbPromise.resolve(); } return BbPromise.bind(this).then(this.uploadArtifacts); }), - 'aws:deploy:deploy:validateTemplate': () => BbPromise.bind(this) - .then(() => { + 'aws:deploy:deploy:validateTemplate': () => + BbPromise.bind(this).then(() => { if (this.serverless.service.provider.shouldNotDeploy) { return BbPromise.resolve(); } return BbPromise.bind(this).then(this.validateTemplate); }), - 'aws:deploy:deploy:updateStack': () => BbPromise.bind(this) - .then(() => { + 'aws:deploy:deploy:updateStack': () => + BbPromise.bind(this).then(() => { if (this.serverless.service.provider.shouldNotDeploy) { return BbPromise.resolve(); } @@ -128,20 +128,22 @@ class AwsDeploy { }), // Deploy finalize inner lifecycle - 'aws:deploy:finalize:cleanup': () => BbPromise.bind(this) - .then(() => { - if (this.options.noDeploy || this.serverless.service.provider.shouldNotDeploy) { + 'aws:deploy:finalize:cleanup': () => + BbPromise.bind(this) + .then(() => { + if (this.options.noDeploy || this.serverless.service.provider.shouldNotDeploy) { + return BbPromise.resolve(); + } + return this.cleanupS3Bucket(); + }) + .then(() => { + if (this.options.package || this.serverless.service.package.path) { + return BbPromise.bind(this).then(() => + this.serverless.pluginManager.spawn('aws:common:cleanupTempDir') + ); + } return BbPromise.resolve(); - } - return this.cleanupS3Bucket(); - }) - .then(() => { - if (this.options.package || this.serverless.service.package.path) { - return BbPromise.bind(this) - .then(() => this.serverless.pluginManager.spawn('aws:common:cleanupTempDir')); - } - return BbPromise.resolve(); - }), + }), }; } } diff --git a/lib/plugins/aws/deploy/index.test.js b/lib/plugins/aws/deploy/index.test.js index f8b7d324e..bcf29223d 100644 --- a/lib/plugins/aws/deploy/index.test.js +++ b/lib/plugins/aws/deploy/index.test.js @@ -87,14 +87,10 @@ describe('AwsDeploy', () => { let checkForChangesStub; beforeEach(() => { - spawnStub = sinon - .stub(serverless.pluginManager, 'spawn'); - createStackStub = sinon - .stub(awsDeploy, 'createStack').resolves(); - setBucketNameStub = sinon - .stub(awsDeploy, 'setBucketName').resolves(); - checkForChangesStub = sinon - .stub(awsDeploy, 'checkForChanges').resolves(); + spawnStub = sinon.stub(serverless.pluginManager, 'spawn'); + createStackStub = sinon.stub(awsDeploy, 'createStack').resolves(); + setBucketNameStub = sinon.stub(awsDeploy, 'setBucketName').resolves(); + checkForChangesStub = sinon.stub(awsDeploy, 'checkForChanges').resolves(); }); afterEach(() => { @@ -112,14 +108,13 @@ describe('AwsDeploy', () => { let spawnAwsCommonMoveArtifactsToTemp; beforeEach(() => { - extendedValidateStub = sinon - .stub(awsDeploy, 'extendedValidate').resolves(); - existsDeploymentBucketStub = sinon - .stub(awsDeploy, 'existsDeploymentBucket').resolves(); + extendedValidateStub = sinon.stub(awsDeploy, 'extendedValidate').resolves(); + existsDeploymentBucketStub = sinon.stub(awsDeploy, 'existsDeploymentBucket').resolves(); spawnPackageStub = spawnStub.withArgs('package').resolves(); spawnAwsCommonValidateStub = spawnStub.withArgs('aws:common:validate').resolves(); spawnAwsCommonMoveArtifactsToTemp = spawnStub - .withArgs('aws:common:moveArtifactsToTemp').resolves(); + .withArgs('aws:common:moveArtifactsToTemp') + .resolves(); }); afterEach(() => { @@ -143,10 +138,12 @@ describe('AwsDeploy', () => { return awsDeploy.hooks['before:deploy:deploy']().then(() => { expect(spawnAwsCommonValidateStub.calledOnce).to.equal(true); - expect(spawnAwsCommonMoveArtifactsToTemp.calledAfter(spawnAwsCommonValidateStub)) - .to.equal(true); - expect(extendedValidateStub.calledAfter(spawnAwsCommonMoveArtifactsToTemp)) - .to.equal(true); + expect( + spawnAwsCommonMoveArtifactsToTemp.calledAfter(spawnAwsCommonValidateStub) + ).to.equal(true); + expect(extendedValidateStub.calledAfter(spawnAwsCommonMoveArtifactsToTemp)).to.equal( + true + ); expect(spawnPackageStub.calledOnce).to.equal(false); }); }); @@ -157,10 +154,12 @@ describe('AwsDeploy', () => { return awsDeploy.hooks['before:deploy:deploy']().then(() => { expect(spawnAwsCommonValidateStub.calledOnce).to.equal(true); - expect(spawnAwsCommonMoveArtifactsToTemp.calledAfter(spawnAwsCommonValidateStub)) - .to.equal(true); - expect(extendedValidateStub.calledAfter(spawnAwsCommonMoveArtifactsToTemp)) - .to.equal(true); + expect( + spawnAwsCommonMoveArtifactsToTemp.calledAfter(spawnAwsCommonValidateStub) + ).to.equal(true); + expect(extendedValidateStub.calledAfter(spawnAwsCommonMoveArtifactsToTemp)).to.equal( + true + ); expect(spawnPackageStub.calledOnce).to.equal(false); }); }); @@ -173,12 +172,13 @@ describe('AwsDeploy', () => { return awsDeploy.hooks['before:deploy:deploy']().then(() => { expect(spawnAwsCommonValidateStub.calledOnce).to.equal(true); - expect(existsDeploymentBucketStub.calledAfter(spawnAwsCommonValidateStub)) - .to.equal(true); - expect(spawnAwsCommonMoveArtifactsToTemp.calledAfter(existsDeploymentBucketStub)) - .to.equal(true); - expect(extendedValidateStub.calledAfter(spawnAwsCommonMoveArtifactsToTemp)) - .to.equal(true); + expect(existsDeploymentBucketStub.calledAfter(spawnAwsCommonValidateStub)).to.equal(true); + expect( + spawnAwsCommonMoveArtifactsToTemp.calledAfter(existsDeploymentBucketStub) + ).to.equal(true); + expect(extendedValidateStub.calledAfter(spawnAwsCommonMoveArtifactsToTemp)).to.equal( + true + ); expect(spawnPackageStub.calledOnce).to.equal(false); }); }); @@ -200,44 +200,41 @@ describe('AwsDeploy', () => { }); }); - it('should run "aws:deploy:deploy:createStack" hook', () => awsDeploy - .hooks['aws:deploy:deploy:createStack']().then(() => { + it('should run "aws:deploy:deploy:createStack" hook', () => + awsDeploy.hooks['aws:deploy:deploy:createStack']().then(() => { expect(createStackStub.calledOnce).to.equal(true); - }) - ); + })); - it('should run "aws:deploy:deploy:checkForChanges" hook', () => awsDeploy - .hooks['aws:deploy:deploy:checkForChanges']().then(() => { + it('should run "aws:deploy:deploy:checkForChanges" hook', () => + awsDeploy.hooks['aws:deploy:deploy:checkForChanges']().then(() => { expect(setBucketNameStub.calledOnce).to.equal(true); expect(checkForChangesStub.calledAfter(setBucketNameStub)).to.equal(true); - }) - ); + })); describe('"aws:deploy:deploy:uploadArtifacts" hook', () => { let uploadArtifactsStub; beforeEach(() => { - uploadArtifactsStub = sinon - .stub(awsDeploy, 'uploadArtifacts').resolves(); + uploadArtifactsStub = sinon.stub(awsDeploy, 'uploadArtifacts').resolves(); }); afterEach(() => { awsDeploy.uploadArtifacts.restore(); }); - it('should upload the artifacts if a deployment is necessary', () => expect(awsDeploy - .hooks['aws:deploy:deploy:uploadArtifacts']()).to.be.fulfilled.then(() => { + it('should upload the artifacts if a deployment is necessary', () => + expect(awsDeploy.hooks['aws:deploy:deploy:uploadArtifacts']()).to.be.fulfilled.then(() => { expect(uploadArtifactsStub).to.have.been.calledOnce; - }) - ); + })); it('should resolve if no deployment is necessary', () => { awsDeploy.serverless.service.provider.shouldNotDeploy = true; - return expect(awsDeploy - .hooks['aws:deploy:deploy:uploadArtifacts']()).to.be.fulfilled.then(() => { + return expect(awsDeploy.hooks['aws:deploy:deploy:uploadArtifacts']()).to.be.fulfilled.then( + () => { expect(uploadArtifactsStub).to.not.have.been.called; - }); + } + ); }); }); @@ -245,27 +242,26 @@ describe('AwsDeploy', () => { let validateTemplateStub; beforeEach(() => { - validateTemplateStub = sinon - .stub(awsDeploy, 'validateTemplate').resolves(); + validateTemplateStub = sinon.stub(awsDeploy, 'validateTemplate').resolves(); }); afterEach(() => { awsDeploy.validateTemplate.restore(); }); - it('should validate the template if a deployment is necessary', () => expect(awsDeploy - .hooks['aws:deploy:deploy:validateTemplate']()).to.be.fulfilled.then(() => { + it('should validate the template if a deployment is necessary', () => + expect(awsDeploy.hooks['aws:deploy:deploy:validateTemplate']()).to.be.fulfilled.then(() => { expect(validateTemplateStub).to.have.been.calledOnce; - }) - ); + })); it('should resolve if no deployment is necessary', () => { awsDeploy.serverless.service.provider.shouldNotDeploy = true; - return expect(awsDeploy - .hooks['aws:deploy:deploy:validateTemplate']()).to.be.fulfilled.then(() => { + return expect(awsDeploy.hooks['aws:deploy:deploy:validateTemplate']()).to.be.fulfilled.then( + () => { expect(validateTemplateStub).to.not.have.been.called; - }); + } + ); }); }); @@ -273,27 +269,26 @@ describe('AwsDeploy', () => { let updateStackStub; beforeEach(() => { - updateStackStub = sinon - .stub(awsDeploy, 'updateStack').resolves(); + updateStackStub = sinon.stub(awsDeploy, 'updateStack').resolves(); }); afterEach(() => { awsDeploy.updateStack.restore(); }); - it('should update the stack if a deployment is necessary', () => expect(awsDeploy - .hooks['aws:deploy:deploy:updateStack']()).to.be.fulfilled.then(() => { + it('should update the stack if a deployment is necessary', () => + expect(awsDeploy.hooks['aws:deploy:deploy:updateStack']()).to.be.fulfilled.then(() => { expect(updateStackStub).to.have.been.calledOnce; - }) - ); + })); it('should resolve if no deployment is necessary', () => { awsDeploy.serverless.service.provider.shouldNotDeploy = true; - return expect(awsDeploy - .hooks['aws:deploy:deploy:updateStack']()).to.be.fulfilled.then(() => { + return expect(awsDeploy.hooks['aws:deploy:deploy:updateStack']()).to.be.fulfilled.then( + () => { expect(updateStackStub).to.not.have.been.called; - }); + } + ); }); }); @@ -302,9 +297,9 @@ describe('AwsDeploy', () => { let spawnAwsCommonCleanupTempDirStub; beforeEach(() => { - cleanupS3BucketStub = sinon - .stub(awsDeploy, 'cleanupS3Bucket').resolves(); - spawnAwsCommonCleanupTempDirStub = spawnStub.withArgs('aws:common:cleanupTempDir') + cleanupS3BucketStub = sinon.stub(awsDeploy, 'cleanupS3Bucket').resolves(); + spawnAwsCommonCleanupTempDirStub = spawnStub + .withArgs('aws:common:cleanupTempDir') .resolves(); }); @@ -328,8 +323,7 @@ describe('AwsDeploy', () => { return awsDeploy.hooks['aws:deploy:finalize:cleanup']().then(() => { expect(cleanupS3BucketStub.calledOnce).to.equal(true); - expect(spawnAwsCommonCleanupTempDirStub.calledAfter(cleanupS3BucketStub)) - .to.equal(true); + expect(spawnAwsCommonCleanupTempDirStub.calledAfter(cleanupS3BucketStub)).to.equal(true); }); }); @@ -339,8 +333,7 @@ describe('AwsDeploy', () => { return awsDeploy.hooks['aws:deploy:finalize:cleanup']().then(() => { expect(cleanupS3BucketStub.calledOnce).to.equal(true); - expect(spawnAwsCommonCleanupTempDirStub.calledAfter(cleanupS3BucketStub)) - .to.equal(true); + expect(spawnAwsCommonCleanupTempDirStub.calledAfter(cleanupS3BucketStub)).to.equal(true); }); }); diff --git a/lib/plugins/aws/deploy/lib/checkForChanges.js b/lib/plugins/aws/deploy/lib/checkForChanges.js index 07635ad45..ec4419c25 100644 --- a/lib/plugins/aws/deploy/lib/checkForChanges.js +++ b/lib/plugins/aws/deploy/lib/checkForChanges.js @@ -38,55 +38,56 @@ module.exports = { Prefix: `${this.provider.getDeploymentPrefix()}/${service}/${this.provider.getStage()}`, }; - return this.provider.request('S3', - 'listObjectsV2', - params - ).catch((reason) => { - if (!_.includes(reason.message, 'The specified bucket does not exist')) { - return BbPromise.reject(reason); - } - const stackName = this.provider.naming.getStackName(); - return BbPromise.reject(new this.serverless.classes.Error([ - `The serverless deployment bucket "${params.Bucket}" does not exist.`, - `Create it manually if you want to reuse the CloudFormation stack "${stackName}",`, - 'or delete the stack if it is no longer required.', - ].join(' '))); - }).then((result) => { - if (result && result.Contents && result.Contents.length) { - const objects = result.Contents; + return this.provider + .request('S3', 'listObjectsV2', params) + .catch(reason => { + if (!_.includes(reason.message, 'The specified bucket does not exist')) { + return BbPromise.reject(reason); + } + const stackName = this.provider.naming.getStackName(); + return BbPromise.reject( + new this.serverless.classes.Error( + [ + `The serverless deployment bucket "${params.Bucket}" does not exist.`, + `Create it manually if you want to reuse the CloudFormation stack "${stackName}",`, + 'or delete the stack if it is no longer required.', + ].join(' ') + ) + ); + }) + .then(result => { + if (result && result.Contents && result.Contents.length) { + const objects = result.Contents; - const ordered = _.orderBy(objects, ['Key'], ['desc']); + const ordered = _.orderBy(objects, ['Key'], ['desc']); - const firstKey = ordered[0].Key; - const directory = firstKey.substring(0, firstKey.lastIndexOf('/')); + const firstKey = ordered[0].Key; + const directory = firstKey.substring(0, firstKey.lastIndexOf('/')); - const mostRecentObjects = ordered.filter((obj) => { - const objKey = obj.Key; - const objDirectory = objKey.substring(0, objKey.lastIndexOf('/')); + const mostRecentObjects = ordered.filter(obj => { + const objKey = obj.Key; + const objDirectory = objKey.substring(0, objKey.lastIndexOf('/')); - return directory === objDirectory; - }); + return directory === objDirectory; + }); - return BbPromise.resolve(mostRecentObjects); - } + return BbPromise.resolve(mostRecentObjects); + } - return BbPromise.resolve([]); - }); + return BbPromise.resolve([]); + }); }, getObjectMetadata(objects) { if (objects && objects.length) { - const headObjectObjects = objects - .map((obj) => this.provider.request('S3', - 'headObject', - { - Bucket: this.bucketName, - Key: obj.Key, - } - )); + const headObjectObjects = objects.map(obj => + this.provider.request('S3', 'headObject', { + Bucket: this.bucketName, + Key: obj.Key, + }) + ); - return BbPromise.all(headObjectObjects) - .then((result) => result); + return BbPromise.all(headObjectObjects).then(result => result); } return BbPromise.resolve([]); @@ -94,7 +95,7 @@ module.exports = { checkIfDeploymentIsNecessary(objects) { if (objects && objects.length) { - const remoteHashes = objects.map((object) => object.Metadata.filesha256 || ''); + const remoteHashes = objects.map(object => object.Metadata.filesha256 || ''); const serverlessDirPath = path.join(this.serverless.config.servicePath, '.serverless'); @@ -108,11 +109,17 @@ module.exports = { // create hashes for all the zip files const zipFiles = globby.sync(['**.zip'], { cwd: serverlessDirPath, dot: true, silent: true }); - const zipFilePaths = zipFiles.map((zipFile) => path.join(serverlessDirPath, zipFile)); + const zipFilePaths = zipFiles.map(zipFile => path.join(serverlessDirPath, zipFile)); const readFile = BbPromise.promisify(fs.readFile); - const zipFileHashesPromises = zipFilePaths.map(zipFilePath => readFile(zipFilePath) - .then(zipFile => crypto.createHash('sha256').update(zipFile).digest('base64'))); + const zipFileHashesPromises = zipFilePaths.map(zipFilePath => + readFile(zipFilePath).then(zipFile => + crypto + .createHash('sha256') + .update(zipFile) + .digest('base64') + ) + ); return BbPromise.all(zipFileHashesPromises).then(zipFileHashes => { const localHashes = zipFileHashes; @@ -121,9 +128,7 @@ module.exports = { if (_.isEqual(remoteHashes.sort(), localHashes.sort())) { this.serverless.service.provider.shouldNotDeploy = true; - const message = [ - 'Service files not changed. Skipping deployment...', - ].join(''); + const message = ['Service files not changed. Skipping deployment...'].join(''); this.serverless.cli.log(message, 'Serverless', { color: 'orange' }); } }); @@ -141,23 +146,23 @@ module.exports = { * subscription filters of functions that a new filter was provided, by checking the * current ARN with the new one that will be generated. * See: https://git.io/fpKCM - */ + */ checkLogGroupSubscriptionFilterResourceLimitExceeded() { const region = this.provider.getRegion(); const serviceName = this.serverless.service.getServiceName(); const stage = this.provider.getStage(); const cloudWatchLogsSdk = new this.provider.sdk.CloudWatchLogs({ region }); - return this.provider.getAccountId() - .then(accountId => - Promise.all(this.serverless.service.getAllFunctions().map((functionName) => { + return this.provider.getAccountId().then(accountId => + Promise.all( + this.serverless.service.getAllFunctions().map(functionName => { const functionObj = this.serverless.service.getFunction(functionName); if (!functionObj.events) { return BbPromise.resolve(); } - const promises = functionObj.events.map((event) => { + const promises = functionObj.events.map(event => { if (!event.cloudwatchLog) { return BbPromise.resolve(); } @@ -190,7 +195,9 @@ module.exports = { }); return Promise.all(promises); - }))); + }) + ) + ); }, fixLogGroupSubscriptionFilters(params) { @@ -202,36 +209,38 @@ module.exports = { const serviceName = params.serviceName; const stage = params.stage; - return cloudWatchLogsSdk.describeSubscriptionFilters({ logGroupName }).promise() - .then((response) => { - const subscriptionFilter = response.subscriptionFilters[0]; + return ( + cloudWatchLogsSdk + .describeSubscriptionFilters({ logGroupName }) + .promise() + .then(response => { + const subscriptionFilter = response.subscriptionFilters[0]; - // log group doesn't have any subscription filters currently - if (!subscriptionFilter) { - return false; - } + // log group doesn't have any subscription filters currently + if (!subscriptionFilter) { + return false; + } - const oldDestinationArn = subscriptionFilter.destinationArn; - const filterName = subscriptionFilter.filterName; - const newDestinationArn = - `arn:aws:lambda:${region}:${accountId}:function:${serviceName}-${stage}-${functionName}`; + const oldDestinationArn = subscriptionFilter.destinationArn; + const filterName = subscriptionFilter.filterName; + const newDestinationArn = `arn:aws:lambda:${region}:${accountId}:function:${serviceName}-${stage}-${functionName}`; - // everything is fine, just return - if (oldDestinationArn === newDestinationArn) { - return false; - } + // everything is fine, just return + if (oldDestinationArn === newDestinationArn) { + return false; + } - /* + /* If the destinations functions' ARNs doesn't match, we need to delete the current subscription filter to prevent the resource limit exceeded error to happen */ - return cloudWatchLogsSdk - .deleteSubscriptionFilter({ logGroupName, filterName }).promise(); - }) - /* + return cloudWatchLogsSdk.deleteSubscriptionFilter({ logGroupName, filterName }).promise(); + }) + /* it will throw when trying to get subscription filters of a log group that was just added to the serverless.yml (therefore not created in AWS yet), we can safely ignore this error */ - .catch(() => undefined); + .catch(() => undefined) + ); }, }; diff --git a/lib/plugins/aws/deploy/lib/checkForChanges.test.js b/lib/plugins/aws/deploy/lib/checkForChanges.test.js index 7c03a593b..9cc808d38 100644 --- a/lib/plugins/aws/deploy/lib/checkForChanges.test.js +++ b/lib/plugins/aws/deploy/lib/checkForChanges.test.js @@ -44,17 +44,18 @@ describe('checkForChanges', () => { s3Key = `serverless/${serverless.service.service}/${provider.getStage()}`; awsDeploy.serverless.cli = { log: sandbox.spy() }; cryptoStub = { - createHash: function () { return this; }, // eslint-disable-line - update: function () { return this; }, // eslint-disable-line + createHash() { + return this; + }, // eslint-disable-line + update() { + return this; + }, // eslint-disable-line digest: sandbox.stub(), }; const checkForChanges = proxyquire('./checkForChanges.js', { crypto: cryptoStub, }); - Object.assign( - awsDeploy, - checkForChanges - ); + Object.assign(awsDeploy, checkForChanges); }); describe('#checkForChanges()', () => { @@ -64,14 +65,14 @@ describe('checkForChanges', () => { let checkLogGroupSubscriptionFilterResourceLimitExceededStub; beforeEach(() => { - getMostRecentObjectsStub = sandbox - .stub(awsDeploy, 'getMostRecentObjects').resolves(); - getObjectMetadataStub = sandbox - .stub(awsDeploy, 'getObjectMetadata').resolves(); + getMostRecentObjectsStub = sandbox.stub(awsDeploy, 'getMostRecentObjects').resolves(); + getObjectMetadataStub = sandbox.stub(awsDeploy, 'getObjectMetadata').resolves(); checkIfDeploymentIsNecessaryStub = sandbox - .stub(awsDeploy, 'checkIfDeploymentIsNecessary').resolves(); + .stub(awsDeploy, 'checkIfDeploymentIsNecessary') + .resolves(); checkLogGroupSubscriptionFilterResourceLimitExceededStub = sandbox - .stub(awsDeploy, 'checkLogGroupSubscriptionFilterResourceLimitExceeded').resolves(); + .stub(awsDeploy, 'checkLogGroupSubscriptionFilterResourceLimitExceeded') + .resolves(); }); afterEach(() => { @@ -81,29 +82,28 @@ describe('checkForChanges', () => { awsDeploy.checkLogGroupSubscriptionFilterResourceLimitExceeded.restore(); }); - it('should run promise chain in order', () => expect(awsDeploy.checkForChanges()) - .to.be.fulfilled.then(() => { + it('should run promise chain in order', () => + expect(awsDeploy.checkForChanges()).to.be.fulfilled.then(() => { expect(getMostRecentObjectsStub).to.have.been.calledOnce; expect(getObjectMetadataStub).to.have.been.calledAfter(getMostRecentObjectsStub); expect(checkIfDeploymentIsNecessaryStub).to.have.been.calledAfter(getObjectMetadataStub); - expect(checkLogGroupSubscriptionFilterResourceLimitExceededStub).to.have.been - .calledAfter(checkIfDeploymentIsNecessaryStub); + expect(checkLogGroupSubscriptionFilterResourceLimitExceededStub).to.have.been.calledAfter( + checkIfDeploymentIsNecessaryStub + ); expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(false); - }) - ); + })); it('should resolve if the "force" option is used', () => { awsDeploy.options.force = true; - return expect(awsDeploy.checkForChanges()) - .to.be.fulfilled.then(() => { - expect(getMostRecentObjectsStub).to.not.have.been.called; - expect(getObjectMetadataStub).to.not.have.been.called; - expect(checkIfDeploymentIsNecessaryStub).to.not.have.been.called; + return expect(awsDeploy.checkForChanges()).to.be.fulfilled.then(() => { + expect(getMostRecentObjectsStub).to.not.have.been.called; + expect(getObjectMetadataStub).to.not.have.been.called; + expect(checkIfDeploymentIsNecessaryStub).to.not.have.been.called; - expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(false); - }); + expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(false); + }); }); }); @@ -111,8 +111,7 @@ describe('checkForChanges', () => { let listObjectsV2Stub; beforeEach(() => { - listObjectsV2Stub = sandbox - .stub(awsDeploy.provider, 'request'); + listObjectsV2Stub = sandbox.stub(awsDeploy.provider, 'request'); }); afterEach(() => { @@ -122,33 +121,31 @@ describe('checkForChanges', () => { it('should resolve if no result is returned', () => { listObjectsV2Stub.resolves(); - return expect(awsDeploy.getMostRecentObjects()).to.be.fulfilled.then((result) => { - expect(listObjectsV2Stub).to.have.been.calledWithExactly( - 'S3', - 'listObjectsV2', - { - Bucket: awsDeploy.bucketName, - Prefix: 'serverless/my-service/dev', - } - ); + return expect(awsDeploy.getMostRecentObjects()).to.be.fulfilled.then(result => { + expect(listObjectsV2Stub).to.have.been.calledWithExactly('S3', 'listObjectsV2', { + Bucket: awsDeploy.bucketName, + Prefix: 'serverless/my-service/dev', + }); expect(result).to.deep.equal([]); }); }); it('should translate error if rejected due to missing bucket', () => { - listObjectsV2Stub - .rejects(new serverless.classes.Error('The specified bucket does not exist')); + listObjectsV2Stub.rejects( + new serverless.classes.Error('The specified bucket does not exist') + ); - return expect(awsDeploy.getMostRecentObjects()).to.be.rejectedWith([ - `The serverless deployment bucket "${awsDeploy.bucketName}" does not exist.`, - 'Create it manually if you want to reuse the CloudFormation stack "my-service-dev",', - 'or delete the stack if it is no longer required.', - ].join(' ')); + return expect(awsDeploy.getMostRecentObjects()).to.be.rejectedWith( + [ + `The serverless deployment bucket "${awsDeploy.bucketName}" does not exist.`, + 'Create it manually if you want to reuse the CloudFormation stack "my-service-dev",', + 'or delete the stack if it is no longer required.', + ].join(' ') + ); }); it('should throw original error if rejected not due to missing bucket', () => { - listObjectsV2Stub - .rejects(new serverless.classes.Error('Other reason')); + listObjectsV2Stub.rejects(new serverless.classes.Error('Other reason')); return expect(awsDeploy.getMostRecentObjects()).to.be.rejectedWith('Other reason'); }); @@ -159,15 +156,11 @@ describe('checkForChanges', () => { listObjectsV2Stub.resolves(serviceObjects); - return expect(awsDeploy.getMostRecentObjects()).to.be.fulfilled.then((result) => { - expect(listObjectsV2Stub).to.have.been.calledWithExactly( - 'S3', - 'listObjectsV2', - { - Bucket: awsDeploy.bucketName, - Prefix: 'serverless/my-service/dev', - } - ); + return expect(awsDeploy.getMostRecentObjects()).to.be.fulfilled.then(result => { + expect(listObjectsV2Stub).to.have.been.calledWithExactly('S3', 'listObjectsV2', { + Bucket: awsDeploy.bucketName, + Prefix: 'serverless/my-service/dev', + }); expect(result).to.deep.equal([]); }); }); @@ -184,15 +177,11 @@ describe('checkForChanges', () => { listObjectsV2Stub.resolves(serviceObjects); - return expect(awsDeploy.getMostRecentObjects()).to.be.fulfilled.then((result) => { - expect(listObjectsV2Stub).to.have.been.calledWithExactly( - 'S3', - 'listObjectsV2', - { - Bucket: awsDeploy.bucketName, - Prefix: 'serverless/my-service/dev', - } - ); + return expect(awsDeploy.getMostRecentObjects()).to.be.fulfilled.then(result => { + expect(listObjectsV2Stub).to.have.been.calledWithExactly('S3', 'listObjectsV2', { + Bucket: awsDeploy.bucketName, + Prefix: 'serverless/my-service/dev', + }); expect(result).to.deep.equal([ { Key: `${s3Key}/151224711231-2016-08-18T15:43:00/cloudformation.json` }, { Key: `${s3Key}/151224711231-2016-08-18T15:43:00/artifact.zip` }, @@ -205,25 +194,23 @@ describe('checkForChanges', () => { let headObjectStub; beforeEach(() => { - headObjectStub = sandbox - .stub(awsDeploy.provider, 'request').resolves(); + headObjectStub = sandbox.stub(awsDeploy.provider, 'request').resolves(); }); afterEach(() => { awsDeploy.provider.request.restore(); }); - it('should resolve if no input is provided', () => expect(awsDeploy.getObjectMetadata()) - .to.be.fulfilled.then((result) => { + it('should resolve if no input is provided', () => + expect(awsDeploy.getObjectMetadata()).to.be.fulfilled.then(result => { expect(headObjectStub).to.not.have.been.called; expect(result).to.deep.equal([]); - }) - ); + })); it('should resolve if no objects are provided as input', () => { const input = []; - return expect(awsDeploy.getObjectMetadata(input)).to.be.fulfilled.then((result) => { + return expect(awsDeploy.getObjectMetadata(input)).to.be.fulfilled.then(result => { expect(headObjectStub).to.not.have.been.called; expect(result).to.deep.equal([]); }); @@ -239,38 +226,22 @@ describe('checkForChanges', () => { return expect(awsDeploy.getObjectMetadata(input)).to.be.fulfilled.then(() => { expect(headObjectStub.callCount).to.equal(4); - expect(headObjectStub).to.have.been.calledWithExactly( - 'S3', - 'headObject', - { - Bucket: awsDeploy.bucketName, - Key: `${s3Key}/151224711231-2016-08-18T15:43:00/artifact.zip`, - } - ); - expect(headObjectStub).to.have.been.calledWithExactly( - 'S3', - 'headObject', - { - Bucket: awsDeploy.bucketName, - Key: `${s3Key}/151224711231-2016-08-18T15:43:00/cloudformation.json`, - } - ); - expect(headObjectStub).to.have.been.calledWithExactly( - 'S3', - 'headObject', - { - Bucket: awsDeploy.bucketName, - Key: `${s3Key}/141264711231-2016-08-18T15:42:00/artifact.zip`, - } - ); - expect(headObjectStub).to.have.been.calledWithExactly( - 'S3', - 'headObject', - { - Bucket: awsDeploy.bucketName, - Key: `${s3Key}/141264711231-2016-08-18T15:42:00/cloudformation.json`, - } - ); + expect(headObjectStub).to.have.been.calledWithExactly('S3', 'headObject', { + Bucket: awsDeploy.bucketName, + Key: `${s3Key}/151224711231-2016-08-18T15:43:00/artifact.zip`, + }); + expect(headObjectStub).to.have.been.calledWithExactly('S3', 'headObject', { + Bucket: awsDeploy.bucketName, + Key: `${s3Key}/151224711231-2016-08-18T15:43:00/cloudformation.json`, + }); + expect(headObjectStub).to.have.been.calledWithExactly('S3', 'headObject', { + Bucket: awsDeploy.bucketName, + Key: `${s3Key}/141264711231-2016-08-18T15:42:00/artifact.zip`, + }); + expect(headObjectStub).to.have.been.calledWithExactly('S3', 'headObject', { + Bucket: awsDeploy.bucketName, + Key: `${s3Key}/141264711231-2016-08-18T15:42:00/cloudformation.json`, + }); }); }); }); @@ -284,11 +255,8 @@ describe('checkForChanges', () => { normalizeCloudFormationTemplateStub = sandbox .stub(normalizeFiles, 'normalizeCloudFormationTemplate') .returns(); - globbySyncStub = sandbox - .stub(globby, 'sync'); - readFileStub = sandbox - .stub(fs, 'readFile') - .yields(null, undefined); + globbySyncStub = sandbox.stub(globby, 'sync'); + readFileStub = sandbox.stub(fs, 'readFile').yields(null, undefined); }); afterEach(() => { @@ -297,175 +265,205 @@ describe('checkForChanges', () => { fs.readFile.restore(); }); - it('should resolve if no input is provided', () => expect(awsDeploy - .checkIfDeploymentIsNecessary()).to.be.fulfilled.then(() => { + it('should resolve if no input is provided', () => + expect(awsDeploy.checkIfDeploymentIsNecessary()).to.be.fulfilled.then(() => { expect(normalizeCloudFormationTemplateStub).to.not.have.been.called; expect(globbySyncStub).to.not.have.been.called; expect(readFileStub).to.not.have.been.called; expect(awsDeploy.serverless.cli.log).to.not.have.been.called; - }) - ); + })); it('should resolve if no objects are provided as input', () => { const input = []; - return expect(awsDeploy.checkIfDeploymentIsNecessary(input)) - .to.be.fulfilled.then(() => { - expect(normalizeCloudFormationTemplateStub).to.not.have.been.called; - expect(globbySyncStub).to.not.have.been.called; - expect(readFileStub).to.not.have.been.called; - expect(awsDeploy.serverless.cli.log).to.not.have.been.called; - }); + return expect(awsDeploy.checkIfDeploymentIsNecessary(input)).to.be.fulfilled.then(() => { + expect(normalizeCloudFormationTemplateStub).to.not.have.been.called; + expect(globbySyncStub).to.not.have.been.called; + expect(readFileStub).to.not.have.been.called; + expect(awsDeploy.serverless.cli.log).to.not.have.been.called; + }); }); it('should not set a flag if there are more remote hashes', () => { globbySyncStub.returns(['my-service.zip']); - cryptoStub.createHash().update().digest.onCall(0).returns('local-hash-cf-template'); - cryptoStub.createHash().update().digest.onCall(1).returns('local-hash-zip-file-1'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('local-hash-cf-template'); + cryptoStub + .createHash() + .update() + .digest.onCall(1) + .returns('local-hash-zip-file-1'); const input = [ { Metadata: { filesha256: 'remote-hash-cf-template' } }, { Metadata: { filesha256: 'remote-hash-zip-file-1' } }, - { Metadata: { /* no filesha256 available */ } }, // will be translated to '' + { + Metadata: { + /* no filesha256 available */ + }, + }, // will be translated to '' ]; - return expect(awsDeploy.checkIfDeploymentIsNecessary(input)) - .to.be.fulfilled.then(() => { - expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; - expect(globbySyncStub).to.have.been.calledOnce; - expect(readFileStub).to.have.been.calledOnce; - expect(awsDeploy.serverless.cli.log).to.not.have.been.called; - expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly( - awsDeploy.serverless.service.provider.compiledCloudFormationTemplate - ); - expect(globbySyncStub).to.have.been.calledWithExactly( - ['**.zip'], - { - cwd: path.join(awsDeploy.serverless.config.servicePath, '.serverless'), - dot: true, - silent: true, - } - ); - expect(readFileStub).to.have.been.calledWith( - path.join('my-service/.serverless/my-service.zip') - ); - expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(undefined); + return expect(awsDeploy.checkIfDeploymentIsNecessary(input)).to.be.fulfilled.then(() => { + expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; + expect(globbySyncStub).to.have.been.calledOnce; + expect(readFileStub).to.have.been.calledOnce; + expect(awsDeploy.serverless.cli.log).to.not.have.been.called; + expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly( + awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + ); + expect(globbySyncStub).to.have.been.calledWithExactly(['**.zip'], { + cwd: path.join(awsDeploy.serverless.config.servicePath, '.serverless'), + dot: true, + silent: true, }); + expect(readFileStub).to.have.been.calledWith( + path.join('my-service/.serverless/my-service.zip') + ); + expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(undefined); + }); }); it('should not set a flag if remote and local hashes are different', () => { globbySyncStub.returns(['my-service.zip']); - cryptoStub.createHash().update().digest.onCall(0).returns('local-hash-cf-template'); - cryptoStub.createHash().update().digest.onCall(1).returns('local-hash-zip-file-1'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('local-hash-cf-template'); + cryptoStub + .createHash() + .update() + .digest.onCall(1) + .returns('local-hash-zip-file-1'); const input = [ { Metadata: { filesha256: 'remote-hash-cf-template' } }, { Metadata: { filesha256: 'remote-hash-zip-file-1' } }, ]; - return expect(awsDeploy.checkIfDeploymentIsNecessary(input)) - .to.be.fulfilled.then(() => { - expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; - expect(globbySyncStub).to.have.been.calledOnce; - expect(readFileStub).to.have.been.calledOnce; - expect(awsDeploy.serverless.cli.log).to.not.have.been.called; - expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly( - awsDeploy.serverless.service.provider.compiledCloudFormationTemplate - ); - expect(globbySyncStub).to.have.been.calledWithExactly( - ['**.zip'], - { - cwd: path.join(awsDeploy.serverless.config.servicePath, '.serverless'), - dot: true, - silent: true, - } - ); - expect(readFileStub).to.have.been.calledWith( - path.join('my-service/.serverless/my-service.zip') - ); - expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(undefined); + return expect(awsDeploy.checkIfDeploymentIsNecessary(input)).to.be.fulfilled.then(() => { + expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; + expect(globbySyncStub).to.have.been.calledOnce; + expect(readFileStub).to.have.been.calledOnce; + expect(awsDeploy.serverless.cli.log).to.not.have.been.called; + expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly( + awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + ); + expect(globbySyncStub).to.have.been.calledWithExactly(['**.zip'], { + cwd: path.join(awsDeploy.serverless.config.servicePath, '.serverless'), + dot: true, + silent: true, }); + expect(readFileStub).to.have.been.calledWith( + path.join('my-service/.serverless/my-service.zip') + ); + expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(undefined); + }); }); it('should not set a flag if remote and local hashes are the same but are duplicated', () => { globbySyncStub.returns(['func1.zip', 'func2.zip']); - cryptoStub.createHash().update().digest.onCall(0).returns('remote-hash-cf-template'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('remote-hash-cf-template'); // happens when package.individually is used - cryptoStub.createHash().update().digest.onCall(1).returns('remote-hash-zip-file-1'); - cryptoStub.createHash().update().digest.onCall(2).returns('remote-hash-zip-file-1'); + cryptoStub + .createHash() + .update() + .digest.onCall(1) + .returns('remote-hash-zip-file-1'); + cryptoStub + .createHash() + .update() + .digest.onCall(2) + .returns('remote-hash-zip-file-1'); const input = [ { Metadata: { filesha256: 'remote-hash-cf-template' } }, { Metadata: { filesha256: 'remote-hash-zip-file-1' } }, ]; - return expect(awsDeploy.checkIfDeploymentIsNecessary(input)) - .to.be.fulfilled.then(() => { - expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; - expect(globbySyncStub).to.have.been.calledOnce; - expect(readFileStub).to.have.been.calledTwice; - expect(awsDeploy.serverless.cli.log).to.not.have.been.called; - expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly( - awsDeploy.serverless.service.provider.compiledCloudFormationTemplate - ); - expect(globbySyncStub).to.have.been.calledWithExactly( - ['**.zip'], - { - cwd: path.join(awsDeploy.serverless.config.servicePath, '.serverless'), - dot: true, - silent: true, - } - ); - expect(readFileStub).to.have.been.calledWith( - path.join('my-service/.serverless/func1.zip') - ); - expect(readFileStub).to.have.been.calledWith( - path.join('my-service/.serverless/func2.zip') - ); - expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(undefined); + return expect(awsDeploy.checkIfDeploymentIsNecessary(input)).to.be.fulfilled.then(() => { + expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; + expect(globbySyncStub).to.have.been.calledOnce; + expect(readFileStub).to.have.been.calledTwice; + expect(awsDeploy.serverless.cli.log).to.not.have.been.called; + expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly( + awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + ); + expect(globbySyncStub).to.have.been.calledWithExactly(['**.zip'], { + cwd: path.join(awsDeploy.serverless.config.servicePath, '.serverless'), + dot: true, + silent: true, }); + expect(readFileStub).to.have.been.calledWith(path.join('my-service/.serverless/func1.zip')); + expect(readFileStub).to.have.been.calledWith(path.join('my-service/.serverless/func2.zip')); + expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(undefined); + }); }); it('should set a flag if the remote and local hashes are equal', () => { globbySyncStub.returns(['my-service.zip']); - cryptoStub.createHash().update().digest.onCall(0).returns('hash-cf-template'); - cryptoStub.createHash().update().digest.onCall(1).returns('hash-zip-file-1'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('hash-cf-template'); + cryptoStub + .createHash() + .update() + .digest.onCall(1) + .returns('hash-zip-file-1'); const input = [ { Metadata: { filesha256: 'hash-cf-template' } }, { Metadata: { filesha256: 'hash-zip-file-1' } }, ]; - return expect(awsDeploy.checkIfDeploymentIsNecessary(input)) - .to.be.fulfilled.then(() => { - expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; - expect(globbySyncStub).to.have.been.calledOnce; - expect(readFileStub).to.have.been.calledOnce; - expect(awsDeploy.serverless.cli.log).to.have.been.calledOnce; - expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly( - awsDeploy.serverless.service.provider.compiledCloudFormationTemplate - ); - expect(globbySyncStub).to.have.been.calledWithExactly( - ['**.zip'], - { - cwd: path.join(awsDeploy.serverless.config.servicePath, '.serverless'), - dot: true, - silent: true, - } - ); - expect(readFileStub).to.have.been.calledWith( - path.join('my-service/.serverless/my-service.zip') - ); - expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(true); + return expect(awsDeploy.checkIfDeploymentIsNecessary(input)).to.be.fulfilled.then(() => { + expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; + expect(globbySyncStub).to.have.been.calledOnce; + expect(readFileStub).to.have.been.calledOnce; + expect(awsDeploy.serverless.cli.log).to.have.been.calledOnce; + expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly( + awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + ); + expect(globbySyncStub).to.have.been.calledWithExactly(['**.zip'], { + cwd: path.join(awsDeploy.serverless.config.servicePath, '.serverless'), + dot: true, + silent: true, }); + expect(readFileStub).to.have.been.calledWith( + path.join('my-service/.serverless/my-service.zip') + ); + expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(true); + }); }); it('should set a flag if the remote and local hashes are duplicated and equal', () => { globbySyncStub.returns(['func1.zip', 'func2.zip']); - cryptoStub.createHash().update().digest.onCall(0).returns('hash-cf-template'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('hash-cf-template'); // happens when package.individually is used - cryptoStub.createHash().update().digest.onCall(1).returns('hash-zip-file-1'); - cryptoStub.createHash().update().digest.onCall(2).returns('hash-zip-file-1'); + cryptoStub + .createHash() + .update() + .digest.onCall(1) + .returns('hash-zip-file-1'); + cryptoStub + .createHash() + .update() + .digest.onCall(2) + .returns('hash-zip-file-1'); const input = [ { Metadata: { filesha256: 'hash-cf-template' } }, @@ -473,31 +471,23 @@ describe('checkForChanges', () => { { Metadata: { filesha256: 'hash-zip-file-1' } }, ]; - return expect(awsDeploy.checkIfDeploymentIsNecessary(input)) - .to.be.fulfilled.then(() => { - expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; - expect(globbySyncStub).to.have.been.calledOnce; - expect(readFileStub).to.have.been.calledTwice; - expect(awsDeploy.serverless.cli.log).to.have.been.calledOnce; - expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly( - awsDeploy.serverless.service.provider.compiledCloudFormationTemplate - ); - expect(globbySyncStub).to.have.been.calledWithExactly( - ['**.zip'], - { - cwd: path.join(awsDeploy.serverless.config.servicePath, '.serverless'), - dot: true, - silent: true, - } - ); - expect(readFileStub).to.have.been.calledWith( - path.join('my-service/.serverless/func1.zip') - ); - expect(readFileStub).to.have.been.calledWith( - path.join('my-service/.serverless/func2.zip') - ); - expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(true); + return expect(awsDeploy.checkIfDeploymentIsNecessary(input)).to.be.fulfilled.then(() => { + expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; + expect(globbySyncStub).to.have.been.calledOnce; + expect(readFileStub).to.have.been.calledTwice; + expect(awsDeploy.serverless.cli.log).to.have.been.calledOnce; + expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly( + awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + ); + expect(globbySyncStub).to.have.been.calledWithExactly(['**.zip'], { + cwd: path.join(awsDeploy.serverless.config.servicePath, '.serverless'), + dot: true, + silent: true, }); + expect(readFileStub).to.have.been.calledWith(path.join('my-service/.serverless/func1.zip')); + expect(readFileStub).to.have.been.calledWith(path.join('my-service/.serverless/func2.zip')); + expect(awsDeploy.serverless.service.provider.shouldNotDeploy).to.equal(true); + }); }); }); @@ -524,11 +514,9 @@ describe('checkForChanges', () => { provider.sdk.CloudWatchLogs = CloudWatchLogsStub; - sandbox.stub(provider, 'getAccountId') - .returns(BbPromise.resolve(accountId)); + sandbox.stub(provider, 'getAccountId').returns(BbPromise.resolve(accountId)); - sandbox.stub(awsDeploy.serverless.service, 'getServiceName') - .returns(serviceName); + sandbox.stub(awsDeploy.serverless.service, 'getServiceName').returns(serviceName); sandbox.stub(awsDeploy, 'getMostRecentObjects').resolves(); sandbox.stub(awsDeploy, 'getObjectMetadata').resolves(); @@ -542,11 +530,13 @@ describe('checkForChanges', () => { it('should not call checkLogGroup if deployment is not required', () => { awsDeploy.checkIfDeploymentIsNecessary.restore(); - sandbox.stub(awsDeploy, 'checkIfDeploymentIsNecessary') - .callsFake(() => new Promise((resolve) => { - awsDeploy.serverless.service.provider.shouldNotDeploy = true; - resolve(); - })); + sandbox.stub(awsDeploy, 'checkIfDeploymentIsNecessary').callsFake( + () => + new Promise(resolve => { + awsDeploy.serverless.service.provider.shouldNotDeploy = true; + resolve(); + }) + ); const spy = sandbox.spy(awsDeploy, 'checkLogGroupSubscriptionFilterResourceLimitExceeded'); @@ -564,9 +554,7 @@ describe('checkForChanges', () => { it('should work normally when there are functions events that are not cloudWwatchLog', () => { awsDeploy.serverless.service.functions = { first: { - events: [ - { dummyEvent: 'test' }, - ], + events: [{ dummyEvent: 'test' }], }, }; @@ -585,9 +573,7 @@ describe('checkForChanges', () => { it('should not call delete if there are no subscriptionFilters', () => { awsDeploy.serverless.service.functions = { first: { - events: [ - { cloudwatchLog: '/aws/lambda/hello1' }, - ], + events: [{ cloudwatchLog: '/aws/lambda/hello1' }], }, }; @@ -595,54 +581,51 @@ describe('checkForChanges', () => { subscriptionFilters: [], }; - return awsDeploy.checkForChanges().then(() => - expect(deleteSubscriptionFilterStub).to.not.have.been.called); + return awsDeploy + .checkForChanges() + .then(() => expect(deleteSubscriptionFilterStub).to.not.have.been.called); }); it('should not call delete if there is a subFilter and the ARNs are the same', () => { awsDeploy.serverless.service.functions = { first: { - events: [ - { cloudwatchLog: '/aws/lambda/hello1' }, - ], + events: [{ cloudwatchLog: '/aws/lambda/hello1' }], }, }; describeSubscriptionFiltersResponse = { subscriptionFilters: [ { - destinationArn: - `arn:aws:lambda:${region}:${accountId}:function:${serviceName}-dev-first`, + destinationArn: `arn:aws:lambda:${region}:${accountId}:function:${serviceName}-dev-first`, filterName: 'dummy-filter', }, ], }; - return awsDeploy.checkForChanges().then(() => - expect(deleteSubscriptionFilterStub).to.not.have.been.called); + return awsDeploy + .checkForChanges() + .then(() => expect(deleteSubscriptionFilterStub).to.not.have.been.called); }); it('should call delete if there is a subFilter but the ARNs are not the same', () => { awsDeploy.serverless.service.functions = { first: { - events: [ - { cloudwatchLog: '/aws/lambda/hello1' }, - ], + events: [{ cloudwatchLog: '/aws/lambda/hello1' }], }, }; describeSubscriptionFiltersResponse = { subscriptionFilters: [ { - destinationArn: - `arn:aws:lambda:${region}:${accountId}:function:${serviceName}-dev-not-first`, + destinationArn: `arn:aws:lambda:${region}:${accountId}:function:${serviceName}-dev-not-first`, filterName: 'dummy-filter', }, ], }; - return awsDeploy.checkForChanges().then(() => - expect(deleteSubscriptionFilterStub).to.have.been.called); + return awsDeploy + .checkForChanges() + .then(() => expect(deleteSubscriptionFilterStub).to.have.been.called); }); }); }); diff --git a/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js b/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js index 4f2bbfb2d..8de368159 100644 --- a/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js +++ b/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js @@ -12,13 +12,12 @@ module.exports = { const stage = this.provider.getStage(); const prefix = this.provider.getDeploymentPrefix(); - return this.provider.request('S3', - 'listObjectsV2', - { + return this.provider + .request('S3', 'listObjectsV2', { Bucket: this.bucketName, Prefix: `${prefix}/${service}/${stage}`, }) - .then((response) => { + .then(response => { const stacks = findAndGroupDeployments(response, prefix, service, stage); const stacksToKeep = _.takeRight(stacks, stacksToKeepCount); const stacksToRemove = _.pullAllWith(stacks, stacksToKeep, _.isEqual); @@ -36,12 +35,10 @@ module.exports = { if (objectsToRemove && objectsToRemove.length) { this.serverless.cli.log('Removing old service artifacts from S3...'); - return this.provider.request('S3', - 'deleteObjects', - { - Bucket: this.bucketName, - Delete: { Objects: objectsToRemove }, - }); + return this.provider.request('S3', 'deleteObjects', { + Bucket: this.bucketName, + Delete: { Objects: objectsToRemove }, + }); } return BbPromise.resolve(); diff --git a/lib/plugins/aws/deploy/lib/cleanupS3Bucket.test.js b/lib/plugins/aws/deploy/lib/cleanupS3Bucket.test.js index 84447eedc..abd44b8ad 100644 --- a/lib/plugins/aws/deploy/lib/cleanupS3Bucket.test.js +++ b/lib/plugins/aws/deploy/lib/cleanupS3Bucket.test.js @@ -42,19 +42,14 @@ describe('cleanupS3Bucket', () => { Contents: [], }; - const listObjectsStub = sinon - .stub(awsDeploy.provider, 'request').resolves(serviceObjects); + const listObjectsStub = sinon.stub(awsDeploy.provider, 'request').resolves(serviceObjects); return awsDeploy.getObjectsToRemove().then(() => { expect(listObjectsStub).to.have.been.calledOnce; - expect(listObjectsStub).to.have.been.calledWithExactly( - 'S3', - 'listObjectsV2', - { - Bucket: awsDeploy.bucketName, - Prefix: `${s3Key}`, - } - ); + expect(listObjectsStub).to.have.been.calledWithExactly('S3', 'listObjectsV2', { + Bucket: awsDeploy.bucketName, + Prefix: `${s3Key}`, + }); awsDeploy.provider.request.restore(); }); }); @@ -77,43 +72,38 @@ describe('cleanupS3Bucket', () => { ], }; - const listObjectsStub = sinon - .stub(awsDeploy.provider, 'request').resolves(serviceObjects); + const listObjectsStub = sinon.stub(awsDeploy.provider, 'request').resolves(serviceObjects); - return awsDeploy.getObjectsToRemove().then((objectsToRemove) => { - expect(objectsToRemove).to.not - .include( - { Key: `${s3Key}${s3Key}/141321321541-2016-08-18T11:23:02/artifact.zip` }); - expect(objectsToRemove).to.not - .include( - { Key: `${s3Key}${s3Key}/141321321541-2016-08-18T11:23:02/cloudformation.json` }); - expect(objectsToRemove).to.not - .include( - { Key: `${s3Key}${s3Key}/142003031341-2016-08-18T12:46:04/artifact.zip` }); - expect(objectsToRemove).to.not - .include( - { Key: `${s3Key}${s3Key}/142003031341-2016-08-18T12:46:04/cloudformation.json` }); - expect(objectsToRemove).to.not - .include( - { Key: `${s3Key}${s3Key}/151224711231-2016-08-18T15:42:00/artifact.zip` }); - expect(objectsToRemove).to.not - .include( - { Key: `${s3Key}${s3Key}/151224711231-2016-08-18T15:42:00/cloudformation.json` }); - expect(objectsToRemove).to.not - .include( - { Key: `${s3Key}${s3Key}/903940390431-2016-08-18T23:42:08/artifact.zip` }); - expect(objectsToRemove).to.not - .include( - { Key: `${s3Key}${s3Key}/903940390431-2016-08-18T23:42:08/cloudformation.json` }); + return awsDeploy.getObjectsToRemove().then(objectsToRemove => { + expect(objectsToRemove).to.not.include({ + Key: `${s3Key}${s3Key}/141321321541-2016-08-18T11:23:02/artifact.zip`, + }); + expect(objectsToRemove).to.not.include({ + Key: `${s3Key}${s3Key}/141321321541-2016-08-18T11:23:02/cloudformation.json`, + }); + expect(objectsToRemove).to.not.include({ + Key: `${s3Key}${s3Key}/142003031341-2016-08-18T12:46:04/artifact.zip`, + }); + expect(objectsToRemove).to.not.include({ + Key: `${s3Key}${s3Key}/142003031341-2016-08-18T12:46:04/cloudformation.json`, + }); + expect(objectsToRemove).to.not.include({ + Key: `${s3Key}${s3Key}/151224711231-2016-08-18T15:42:00/artifact.zip`, + }); + expect(objectsToRemove).to.not.include({ + Key: `${s3Key}${s3Key}/151224711231-2016-08-18T15:42:00/cloudformation.json`, + }); + expect(objectsToRemove).to.not.include({ + Key: `${s3Key}${s3Key}/903940390431-2016-08-18T23:42:08/artifact.zip`, + }); + expect(objectsToRemove).to.not.include({ + Key: `${s3Key}${s3Key}/903940390431-2016-08-18T23:42:08/cloudformation.json`, + }); expect(listObjectsStub.calledOnce).to.be.equal(true); - expect(listObjectsStub).to.have.been.calledWithExactly( - 'S3', - 'listObjectsV2', - { - Bucket: awsDeploy.bucketName, - Prefix: `${s3Key}`, - } - ); + expect(listObjectsStub).to.have.been.calledWithExactly('S3', 'listObjectsV2', { + Bucket: awsDeploy.bucketName, + Prefix: `${s3Key}`, + }); awsDeploy.provider.request.restore(); }); }); @@ -130,20 +120,15 @@ describe('cleanupS3Bucket', () => { ], }; - const listObjectsStub = sinon - .stub(awsDeploy.provider, 'request').resolves(serviceObjects); + const listObjectsStub = sinon.stub(awsDeploy.provider, 'request').resolves(serviceObjects); - return awsDeploy.getObjectsToRemove().then((objectsToRemove) => { + return awsDeploy.getObjectsToRemove().then(objectsToRemove => { expect(objectsToRemove.length).to.equal(0); expect(listObjectsStub.calledOnce).to.be.equal(true); - expect(listObjectsStub).to.have.been.calledWithExactly( - 'S3', - 'listObjectsV2', - { - Bucket: awsDeploy.bucketName, - Prefix: `${s3Key}`, - } - ); + expect(listObjectsStub).to.have.been.calledWithExactly('S3', 'listObjectsV2', { + Bucket: awsDeploy.bucketName, + Prefix: `${s3Key}`, + }); awsDeploy.provider.request.restore(); }); }); @@ -162,20 +147,15 @@ describe('cleanupS3Bucket', () => { ], }; - const listObjectsStub = sinon - .stub(awsDeploy.provider, 'request').resolves(serviceObjects); + const listObjectsStub = sinon.stub(awsDeploy.provider, 'request').resolves(serviceObjects); - return awsDeploy.getObjectsToRemove().then((objectsToRemove) => { + return awsDeploy.getObjectsToRemove().then(objectsToRemove => { expect(objectsToRemove).to.have.lengthOf(0); expect(listObjectsStub).to.have.been.calledOnce; - expect(listObjectsStub).to.have.been.calledWithExactly( - 'S3', - 'listObjectsV2', - { - Bucket: awsDeploy.bucketName, - Prefix: `${s3Key}`, - } - ); + expect(listObjectsStub).to.have.been.calledWithExactly('S3', 'listObjectsV2', { + Bucket: awsDeploy.bucketName, + Prefix: `${s3Key}`, + }); awsDeploy.provider.request.restore(); }); }); @@ -185,16 +165,14 @@ describe('cleanupS3Bucket', () => { let deleteObjectsStub; beforeEach(() => { - deleteObjectsStub = sinon - .stub(awsDeploy.provider, 'request').resolves(); + deleteObjectsStub = sinon.stub(awsDeploy.provider, 'request').resolves(); }); - it('should resolve if no service objects are found in the S3 bucket', () => awsDeploy - .removeObjects().then(() => { + it('should resolve if no service objects are found in the S3 bucket', () => + awsDeploy.removeObjects().then(() => { expect(deleteObjectsStub.calledOnce).to.be.equal(false); awsDeploy.provider.request.restore(); - }) - ); + })); it('should remove all old service files from the S3 bucket if available', () => { const objectsToRemove = [ @@ -206,16 +184,12 @@ describe('cleanupS3Bucket', () => { return awsDeploy.removeObjects(objectsToRemove).then(() => { expect(deleteObjectsStub).to.have.been.calledOnce; - expect(deleteObjectsStub).to.have.been.calledWithExactly( - 'S3', - 'deleteObjects', - { - Bucket: awsDeploy.bucketName, - Delete: { - Objects: objectsToRemove, - }, - } - ); + expect(deleteObjectsStub).to.have.been.calledWithExactly('S3', 'deleteObjects', { + Bucket: awsDeploy.bucketName, + Delete: { + Objects: objectsToRemove, + }, + }); awsDeploy.provider.request.restore(); }); }); @@ -223,15 +197,12 @@ describe('cleanupS3Bucket', () => { describe('#cleanupS3Bucket()', () => { it('should run promise chain in order', () => { - const getObjectsToRemoveStub = sinon - .stub(awsDeploy, 'getObjectsToRemove').resolves(); - const removeObjectsStub = sinon - .stub(awsDeploy, 'removeObjects').resolves(); + const getObjectsToRemoveStub = sinon.stub(awsDeploy, 'getObjectsToRemove').resolves(); + const removeObjectsStub = sinon.stub(awsDeploy, 'removeObjects').resolves(); return awsDeploy.cleanupS3Bucket().then(() => { expect(getObjectsToRemoveStub.calledOnce).to.be.equal(true); - expect(removeObjectsStub.calledAfter(getObjectsToRemoveStub)) - .to.be.equal(true); + expect(removeObjectsStub.calledAfter(getObjectsToRemoveStub)).to.be.equal(true); awsDeploy.getObjectsToRemove.restore(); awsDeploy.removeObjects.restore(); diff --git a/lib/plugins/aws/deploy/lib/createStack.js b/lib/plugins/aws/deploy/lib/createStack.js index c47b83455..b35865aab 100644 --- a/lib/plugins/aws/deploy/lib/createStack.js +++ b/lib/plugins/aws/deploy/lib/createStack.js @@ -19,17 +19,16 @@ module.exports = { const params = { StackName: stackName, OnFailure: 'DELETE', - Capabilities: [ - 'CAPABILITY_IAM', - 'CAPABILITY_NAMED_IAM', - ], + Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], Parameters: [], TemplateBody: JSON.stringify(this.serverless.service.provider.coreCloudFormationTemplate), - Tags: Object.keys(stackTags).map((key) => ({ Key: key, Value: stackTags[key] })), + Tags: Object.keys(stackTags).map(key => ({ Key: key, Value: stackTags[key] })), }; - if (this.serverless.service.provider.compiledCloudFormationTemplate && - this.serverless.service.provider.compiledCloudFormationTemplate.Transform) { + if ( + this.serverless.service.provider.compiledCloudFormationTemplate && + this.serverless.service.provider.compiledCloudFormationTemplate.Transform + ) { params.Capabilities.push('CAPABILITY_AUTO_EXPAND'); } @@ -41,11 +40,9 @@ module.exports = { params.NotificationARNs = this.serverless.service.provider.notificationArns; } - return this.provider.request( - 'CloudFormation', - 'createStack', - params - ).then((cfData) => this.monitorStack('create', cfData)); + return this.provider + .request('CloudFormation', 'createStack', params) + .then(cfData => this.monitorStack('create', cfData)); }, createStack() { @@ -55,41 +52,41 @@ module.exports = { `The stack service name "${stackName}" is not valid. `, 'A service name should only contain alphanumeric', ' (case sensitive) and hyphens. It should start', - ' with an alphabetic character and shouldn\'t', + " with an alphabetic character and shouldn't", ' exceed 128 characters.', ].join(''); throw new this.serverless.classes.Error(errorMessage); } return BbPromise.bind(this) - .then(() => this.provider.request('CloudFormation', - 'describeStacks', - { StackName: stackName } + .then(() => + this.provider + .request('CloudFormation', 'describeStacks', { StackName: stackName }) + .then(data => { + const shouldCheckStackOutput = + // check stack output only if acceleration is requested + this.provider.isS3TransferAccelerationEnabled() && + // custom deployment bucket won't generate any output (no check) + !this.serverless.service.provider.deploymentBucket; + if (shouldCheckStackOutput) { + const isAlreadyAccelerated = !!_.find(data.Stacks[0].Outputs, { + OutputKey: 'ServerlessDeploymentBucketAccelerated', + }); + if (!isAlreadyAccelerated) { + this.serverless.cli.log('Not using S3 Transfer Acceleration (1st deploy)'); + this.provider.disableTransferAccelerationForCurrentDeploy(); + } + } + return BbPromise.resolve('alreadyCreated'); + }) ) - .then((data) => { - const shouldCheckStackOutput = - // check stack output only if acceleration is requested - this.provider.isS3TransferAccelerationEnabled() && - // custom deployment bucket won't generate any output (no check) - !this.serverless.service.provider.deploymentBucket; - if (shouldCheckStackOutput) { - const isAlreadyAccelerated = !!_.find(data.Stacks[0].Outputs, - { OutputKey: 'ServerlessDeploymentBucketAccelerated' }); - if (!isAlreadyAccelerated) { - this.serverless.cli.log('Not using S3 Transfer Acceleration (1st deploy)'); - this.provider.disableTransferAccelerationForCurrentDeploy(); - } - } - return BbPromise.resolve('alreadyCreated'); - })) - .catch((e) => { + .catch(e => { if (e.message.indexOf('does not exist') > -1) { if (this.serverless.service.provider.deploymentBucket) { this.createLater = true; return BbPromise.resolve(); } - return BbPromise.bind(this) - .then(this.create); + return BbPromise.bind(this).then(this.create); } throw new this.serverless.classes.Error(e); }); diff --git a/lib/plugins/aws/deploy/lib/createStack.test.js b/lib/plugins/aws/deploy/lib/createStack.test.js index b9e33cb39..a959fdc41 100644 --- a/lib/plugins/aws/deploy/lib/createStack.test.js +++ b/lib/plugins/aws/deploy/lib/createStack.test.js @@ -33,7 +33,7 @@ describe('createStack', () => { region: 'us-east-1', }; awsDeploy = new AwsDeploy(serverless, options); - awsDeploy.serverless.service.service = `service-${(new Date()).getTime().toString()}`; + awsDeploy.serverless.service.service = `service-${new Date().getTime().toString()}`; awsDeploy.serverless.cli = new serverless.classes.CLI(); }); @@ -45,43 +45,40 @@ describe('createStack', () => { it('should include custom stack tags', () => { awsDeploy.serverless.service.provider.stackTags = { STAGE: 'overridden', tag1: 'value1' }; - const createStackStub = sandbox - .stub(awsDeploy.provider, 'request').resolves(); + const createStackStub = sandbox.stub(awsDeploy.provider, 'request').resolves(); sandbox.stub(awsDeploy, 'monitorStack').resolves(); return awsDeploy.create().then(() => { - expect(createStackStub.args[0][2].Tags) - .to.deep.equal([ - { Key: 'STAGE', Value: 'overridden' }, - { Key: 'tag1', Value: 'value1' }, - ]); + expect(createStackStub.args[0][2].Tags).to.deep.equal([ + { Key: 'STAGE', Value: 'overridden' }, + { Key: 'tag1', Value: 'value1' }, + ]); }); }); it('should add CAPABILITY_AUTO_EXPAND if a Transform directive is specified', () => { - awsDeploy.serverless.service.provider - .compiledCloudFormationTemplate = { Transform: 'MyMacro' }; + awsDeploy.serverless.service.provider.compiledCloudFormationTemplate = { + Transform: 'MyMacro', + }; - const createStackStub = sandbox - .stub(awsDeploy.provider, 'request').resolves(); + const createStackStub = sandbox.stub(awsDeploy.provider, 'request').resolves(); sandbox.stub(awsDeploy, 'monitorStack').resolves(); return awsDeploy.create().then(() => { - expect(createStackStub.args[0][2].Capabilities) - .to.contain('CAPABILITY_AUTO_EXPAND'); + expect(createStackStub.args[0][2].Capabilities).to.contain('CAPABILITY_AUTO_EXPAND'); }); }); it('should use CloudFormation service role ARN if it is specified', () => { awsDeploy.serverless.service.provider.cfnRole = 'arn:aws:iam::123456789012:role/myrole'; - const createStackStub = sandbox - .stub(awsDeploy.provider, 'request').resolves(); + const createStackStub = sandbox.stub(awsDeploy.provider, 'request').resolves(); sandbox.stub(awsDeploy, 'monitorStack').resolves(); return awsDeploy.create().then(() => { - expect(createStackStub.args[0][2].RoleARN) - .to.equal('arn:aws:iam::123456789012:role/myrole'); + expect(createStackStub.args[0][2].RoleARN).to.equal( + 'arn:aws:iam::123456789012:role/myrole' + ); }); }); @@ -89,13 +86,11 @@ describe('createStack', () => { const mytopicArn = 'arn:aws:sns::123456789012:mytopic'; awsDeploy.serverless.service.provider.notificationArns = [mytopicArn]; - const createStackStub = sandbox - .stub(awsDeploy.provider, 'request').resolves(); + const createStackStub = sandbox.stub(awsDeploy.provider, 'request').resolves(); sandbox.stub(awsDeploy, 'monitorStack').resolves(); return awsDeploy.create().then(() => { - expect(createStackStub.args[0][2].NotificationARNs) - .to.deep.equal([mytopicArn]); + expect(createStackStub.args[0][2].NotificationARNs).to.deep.equal([mytopicArn]); awsDeploy.provider.request.restore(); awsDeploy.monitorStack.restore(); }); @@ -104,8 +99,7 @@ describe('createStack', () => { describe('#createStack()', () => { it('should resolve if stack already created', () => { - const createStub = sandbox - .stub(awsDeploy, 'create').resolves(); + const createStub = sandbox.stub(awsDeploy, 'create').resolves(); sandbox.stub(awsDeploy.provider, 'request').resolves(); @@ -119,8 +113,10 @@ describe('createStack', () => { sandbox.stub(awsDeploy.provider, 'request').resolves(); awsDeploy.serverless.service.service = 'service-name'.repeat(100); - return expect(awsDeploy.createStack.bind(awsDeploy)) - .to.throw(awsDeploy.serverless.classes.Error, /not valid/); + return expect(awsDeploy.createStack.bind(awsDeploy)).to.throw( + awsDeploy.serverless.classes.Error, + /not valid/ + ); }); it('should set the createLater flag and resolve if deployment bucket is provided', () => { @@ -139,10 +135,9 @@ describe('createStack', () => { sandbox.stub(awsDeploy.provider, 'request').rejects(errorMock); - const createStub = sandbox - .stub(awsDeploy, 'create').resolves(); + const createStub = sandbox.stub(awsDeploy, 'create').resolves(); - return awsDeploy.createStack().catch((e) => { + return awsDeploy.createStack().catch(e => { expect(createStub.called).to.be.equal(false); expect(e.name).to.be.equal('ServerlessError'); expect(e.message).to.be.equal(errorMock); @@ -156,8 +151,7 @@ describe('createStack', () => { sandbox.stub(awsDeploy.provider, 'request').rejects(errorMock); - const createStub = sandbox - .stub(awsDeploy, 'create').resolves(); + const createStub = sandbox.stub(awsDeploy, 'create').resolves(); return awsDeploy.createStack().then(() => { expect(createStub.calledOnce).to.be.equal(true); @@ -166,8 +160,8 @@ describe('createStack', () => { it('should disable S3 Transfer Acceleration if missing Output', () => { const disableTransferAccelerationStub = sandbox - .stub(awsDeploy.provider, - 'disableTransferAccelerationForCurrentDeploy').resolves(); + .stub(awsDeploy.provider, 'disableTransferAccelerationForCurrentDeploy') + .resolves(); const describeStacksOutput = { Stacks: [ @@ -187,8 +181,8 @@ describe('createStack', () => { it('should not disable S3 Transfer Acceleration if custom bucket is used', () => { const disableTransferAccelerationStub = sandbox - .stub(awsDeploy.provider, - 'disableTransferAccelerationForCurrentDeploy').resolves(); + .stub(awsDeploy.provider, 'disableTransferAccelerationForCurrentDeploy') + .resolves(); const describeStacksOutput = { Stacks: [ diff --git a/lib/plugins/aws/deploy/lib/existsDeploymentBucket.js b/lib/plugins/aws/deploy/lib/existsDeploymentBucket.js index af2e96e06..32929bac8 100644 --- a/lib/plugins/aws/deploy/lib/existsDeploymentBucket.js +++ b/lib/plugins/aws/deploy/lib/existsDeploymentBucket.js @@ -5,12 +5,11 @@ const BbPromise = require('bluebird'); module.exports = { existsDeploymentBucket(bucketName) { return BbPromise.resolve() - .then(() => this.provider.request('S3', - 'getBucketLocation', - { + .then(() => + this.provider.request('S3', 'getBucketLocation', { Bucket: bucketName, - } - )) + }) + ) .then(resultParam => { const result = resultParam; if (result.LocationConstraint === '') result.LocationConstraint = 'us-east-1'; diff --git a/lib/plugins/aws/deploy/lib/existsDeploymentBucket.test.js b/lib/plugins/aws/deploy/lib/existsDeploymentBucket.test.js index a41192971..ce7a00c9e 100644 --- a/lib/plugins/aws/deploy/lib/existsDeploymentBucket.test.js +++ b/lib/plugins/aws/deploy/lib/existsDeploymentBucket.test.js @@ -32,12 +32,11 @@ describe('#existsDeploymentBucket()', () => { LocationConstraint: awsPlugin.provider.options.region, }); - return expect(awsPlugin.existsDeploymentBucket(bucketName)).to.be.fulfilled - .then(() => { - expect(awsPluginStub.args[0][0]).to.equal('S3'); - expect(awsPluginStub.args[0][1]).to.equal('getBucketLocation'); - expect(awsPluginStub.args[0][2].Bucket).to.equal(bucketName); - }); + return expect(awsPlugin.existsDeploymentBucket(bucketName)).to.be.fulfilled.then(() => { + expect(awsPluginStub.args[0][0]).to.equal('S3'); + expect(awsPluginStub.args[0][1]).to.equal('getBucketLocation'); + expect(awsPluginStub.args[0][2].Bucket).to.equal(bucketName); + }); }); it('should reject an S3 bucket that does not exist', () => { @@ -45,14 +44,13 @@ describe('#existsDeploymentBucket()', () => { const errorObj = { message: 'Access Denied' }; sinon.stub(awsPlugin.provider, 'request').throws(errorObj); - return expect(awsPlugin.existsDeploymentBucket(bucketName)).to.be.rejected - .then(err => { - expect(awsPluginStub.args[0][0]).to.equal('S3'); - expect(awsPluginStub.args[0][1]).to.equal('getBucketLocation'); - expect(awsPluginStub.args[0][2].Bucket).to.equal(bucketName); - expect(err.message).to.contain(errorObj.message); - expect(err.message).to.contain('Could not locate deployment bucket'); - }); + return expect(awsPlugin.existsDeploymentBucket(bucketName)).to.be.rejected.then(err => { + expect(awsPluginStub.args[0][0]).to.equal('S3'); + expect(awsPluginStub.args[0][1]).to.equal('getBucketLocation'); + expect(awsPluginStub.args[0][2].Bucket).to.equal(bucketName); + expect(err.message).to.contain(errorObj.message); + expect(err.message).to.contain('Could not locate deployment bucket'); + }); }); it('should reject an S3 bucket in the wrong region', () => { @@ -62,36 +60,32 @@ describe('#existsDeploymentBucket()', () => { LocationConstraint: 'us-west-1', }); - return expect(awsPlugin.existsDeploymentBucket(bucketName)).to.be.rejected - .then(err => { - expect(awsPluginStub.args[0][0]).to.equal('S3'); - expect(awsPluginStub.args[0][1]).to.equal('getBucketLocation'); - expect(awsPluginStub.args[0][2].Bucket).to.equal(bucketName); - expect(err.message).to.contain('not in the same region'); - }); + return expect(awsPlugin.existsDeploymentBucket(bucketName)).to.be.rejected.then(err => { + expect(awsPluginStub.args[0][0]).to.equal('S3'); + expect(awsPluginStub.args[0][1]).to.equal('getBucketLocation'); + expect(awsPluginStub.args[0][2].Bucket).to.equal(bucketName); + expect(err.message).to.contain('not in the same region'); + }); }); - [ - { region: 'eu-west-1', response: 'EU' }, - { region: 'us-east-1', response: '' }, - ].forEach((value) => { - it(`should handle inconsistent getBucketLocation responses for ${value.region} region`, () => { - const bucketName = 'com.serverless.deploys'; + [{ region: 'eu-west-1', response: 'EU' }, { region: 'us-east-1', response: '' }].forEach( + value => { + it(`should handle inconsistent getBucketLocation responses for ${value.region} region`, () => { + const bucketName = 'com.serverless.deploys'; - awsPlugin.provider.options.region = value.region; + awsPlugin.provider.options.region = value.region; - sinon - .stub(awsPlugin.provider, 'request').resolves({ + sinon.stub(awsPlugin.provider, 'request').resolves({ LocationConstraint: value.response, }); - awsPlugin.serverless.service.provider.deploymentBucket = bucketName; - return expect(awsPlugin.existsDeploymentBucket(bucketName)).to.be.fulfilled - .then(() => { + awsPlugin.serverless.service.provider.deploymentBucket = bucketName; + return expect(awsPlugin.existsDeploymentBucket(bucketName)).to.be.fulfilled.then(() => { expect(awsPluginStub.args[0][0]).to.equal('S3'); expect(awsPluginStub.args[0][1]).to.equal('getBucketLocation'); expect(awsPluginStub.args[0][2].Bucket).to.equal(bucketName); }); - }); - }); + }); + } + ); }); diff --git a/lib/plugins/aws/deploy/lib/extendedValidate.js b/lib/plugins/aws/deploy/lib/extendedValidate.js index 2a1bf15e3..1f2f99d4c 100644 --- a/lib/plugins/aws/deploy/lib/extendedValidate.js +++ b/lib/plugins/aws/deploy/lib/extendedValidate.js @@ -9,15 +9,17 @@ module.exports = { extendedValidate() { // Restore state const serviceStateFileName = this.provider.naming.getServiceStateFileName(); - const serviceStateFilePath = path.join(this.serverless.config.servicePath, + const serviceStateFilePath = path.join( + this.serverless.config.servicePath, '.serverless', - serviceStateFileName); + serviceStateFileName + ); if (!this.serverless.utils.fileExistsSync(serviceStateFilePath)) { - throw new this.serverless.classes - .Error(`No ${serviceStateFileName} file found in the package path you provided.`); + throw new this.serverless.classes.Error( + `No ${serviceStateFileName} file found in the package path you provided.` + ); } - const state = this.serverless - .utils.readFileSync(serviceStateFilePath); + const state = this.serverless.utils.readFileSync(serviceStateFilePath); const selfReferences = findReferences(state.service, '${self:}'); _.forEach(selfReferences, ref => _.set(state.service, ref, this.serverless.service)); @@ -26,8 +28,11 @@ module.exports = { this.serverless.service.package.artifactDirectoryName = state.package.artifactDirectoryName; // only restore the default artifact path if the user is not using a custom path if (!_.isEmpty(state.package.artifact) && this.serverless.service.artifact) { - this.serverless.service.package.artifact = path - .join(this.serverless.config.servicePath, '.serverless', state.package.artifact); + this.serverless.service.package.artifact = path.join( + this.serverless.config.servicePath, + '.serverless', + state.package.artifact + ); } // Check function's attached to API Gateway timeout @@ -41,7 +46,7 @@ module.exports = { if (Object.keys(event)[0] === 'http') { const warnMessage = [ `WARNING: Function ${functionName} has timeout of ${functionObject.timeout} `, - 'seconds, however, it\'s attached to API Gateway so it\'s automatically ', + "seconds, however, it's attached to API Gateway so it's automatically ", 'limited to 30 seconds.', ].join(''); @@ -52,8 +57,10 @@ module.exports = { }); } - if (!_.isEmpty(this.serverless.service.functions) && - this.serverless.service.package.individually) { + if ( + !_.isEmpty(this.serverless.service.functions) && + this.serverless.service.package.individually + ) { // artifact file validation (multiple function artifacts) this.serverless.service.getAllFunctions().forEach(functionName => { let artifactFileName = this.provider.naming.getFunctionArtifactName(functionName); @@ -67,8 +74,9 @@ module.exports = { } if (!this.serverless.utils.fileExistsSync(artifactFilePath)) { - throw new this.serverless.classes - .Error(`No ${artifactFileName} file found in the package path you provided.`); + throw new this.serverless.classes.Error( + `No ${artifactFileName} file found in the package path you provided.` + ); } }); } else if (!_.isEmpty(this.serverless.service.functions)) { @@ -82,8 +90,9 @@ module.exports = { artifactFilePath = path.join(this.packagePath, artifactFileName); } if (!this.serverless.utils.fileExistsSync(artifactFilePath)) { - throw new this.serverless.classes - .Error(`No ${artifactFileName} file found in the package path you provided.`); + throw new this.serverless.classes.Error( + `No ${artifactFileName} file found in the package path you provided.` + ); } } diff --git a/lib/plugins/aws/deploy/lib/extendedValidate.test.js b/lib/plugins/aws/deploy/lib/extendedValidate.test.js index c34160510..3a659602b 100644 --- a/lib/plugins/aws/deploy/lib/extendedValidate.test.js +++ b/lib/plugins/aws/deploy/lib/extendedValidate.test.js @@ -45,7 +45,7 @@ describe('extendedValidate', () => { serverless.utils.writeFileSync(serverlessYmlPath, serverlessYml); serverless.config.servicePath = tmpDirPath; awsDeploy = new AwsDeploy(serverless, options); - awsDeploy.serverless.service.service = `service-${(new Date()).getTime().toString()}`; + awsDeploy.serverless.service.service = `service-${new Date().getTime().toString()}`; awsDeploy.serverless.cli = { log: sinon.spy(), }; @@ -56,10 +56,8 @@ describe('extendedValidate', () => { let readFileSyncStub; beforeEach(() => { - fileExistsSyncStub = sinon - .stub(awsDeploy.serverless.utils, 'fileExistsSync'); - readFileSyncStub = sinon - .stub(awsDeploy.serverless.utils, 'readFileSync'); + fileExistsSyncStub = sinon.stub(awsDeploy.serverless.utils, 'fileExistsSync'); + readFileSyncStub = sinon.stub(awsDeploy.serverless.utils, 'readFileSync'); awsDeploy.serverless.service.package.individually = false; }); @@ -155,16 +153,18 @@ describe('extendedValidate', () => { }); }); - it('should warn if function\'s timeout is greater than 30 and it\'s attached to APIGW', () => { + it("should warn if function's timeout is greater than 30 and it's attached to APIGW", () => { stateFileMock.service.functions = { first: { timeout: 31, package: { artifact: 'artifact.zip', }, - events: [{ - http: {}, - }], + events: [ + { + http: {}, + }, + ], }, }; awsDeploy.serverless.service.package.individually = true; @@ -173,8 +173,8 @@ describe('extendedValidate', () => { return awsDeploy.extendedValidate().then(() => { const msg = [ - 'WARNING: Function first has timeout of 31 seconds, however, it\'s ', - 'attached to API Gateway so it\'s automatically limited to 30 seconds.', + "WARNING: Function first has timeout of 31 seconds, however, it's ", + "attached to API Gateway so it's automatically limited to 30 seconds.", ].join(''); expect(awsDeploy.serverless.cli.log.firstCall.calledWithExactly(msg)).to.be.equal(true); }); diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.js index c941f9b42..483671639 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.js @@ -44,9 +44,7 @@ module.exports = { params = setServersideEncryptionOptions(params, deploymentBucketObject); } - return this.provider.request('S3', - 'upload', - params); + return this.provider.request('S3', 'upload', params); }, uploadZipFile(artifactFilePath) { @@ -54,7 +52,10 @@ module.exports = { // TODO refactor to be async (use util function to compute checksum async) const data = fs.readFileSync(artifactFilePath); - const fileHash = crypto.createHash('sha256').update(data).digest('base64'); + const fileHash = crypto + .createHash('sha256') + .update(data) + .digest('base64'); let params = { Bucket: this.bucketName, @@ -71,9 +72,7 @@ module.exports = { params = setServersideEncryptionOptions(params, deploymentBucketObject); } - return this.provider.request('S3', - 'upload', - params); + return this.provider.request('S3', 'upload', params); }, uploadFunctionsAndLayers() { @@ -81,15 +80,17 @@ module.exports = { const functionNames = this.serverless.service.getAllFunctions(); let artifactFilePaths = _.uniq( - _.map(functionNames, (name) => { + _.map(functionNames, name => { const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(name); const functionObject = this.serverless.service.getFunction(name); functionObject.package = functionObject.package || {}; - const artifactFilePath = functionObject.package.artifact || - this.serverless.service.package.artifact; + const artifactFilePath = + functionObject.package.artifact || this.serverless.service.package.artifact; - if (!artifactFilePath || - (this.serverless.service.artifact && !functionObject.package.artifact)) { + if ( + !artifactFilePath || + (this.serverless.service.artifact && !functionObject.package.artifact) + ) { if (this.serverless.service.package.individually || functionObject.package.individually) { const artifactFileName = functionArtifactFileName; return path.join(this.packagePath, artifactFileName); @@ -102,24 +103,29 @@ module.exports = { ); const layerNames = this.serverless.service.getAllLayers(); - artifactFilePaths = artifactFilePaths.concat(_.map(layerNames, (name) => { - const layerObject = this.serverless.service.getLayer(name); + artifactFilePaths = artifactFilePaths.concat( + _.map(layerNames, name => { + const layerObject = this.serverless.service.getLayer(name); - if (layerObject.package && layerObject.package.artifact) { - return layerObject.package.artifact; - } + if (layerObject.package && layerObject.package.artifact) { + return layerObject.package.artifact; + } - return path.join(this.packagePath, this.provider.naming.getLayerArtifactName(name)); - })); + return path.join(this.packagePath, this.provider.naming.getLayerArtifactName(name)); + }) + ); - return BbPromise.map(artifactFilePaths, (artifactFilePath) => { - const stats = fs.statSync(artifactFilePath); - const fileName = path.basename(artifactFilePath); - this.serverless.cli.log( + return BbPromise.map( + artifactFilePaths, + artifactFilePath => { + const stats = fs.statSync(artifactFilePath); + const fileName = path.basename(artifactFilePath); + this.serverless.cli.log( `Uploading service ${fileName} file to S3 (${filesize(stats.size)})...` - ); - return this.uploadZipFile(artifactFilePath); - }, { concurrency: NUM_CONCURRENT_UPLOADS } + ); + return this.uploadZipFile(artifactFilePath); + }, + { concurrency: NUM_CONCURRENT_UPLOADS } ); }, }; @@ -135,7 +141,7 @@ function setServersideEncryptionOptions(putParams, deploymentBucketOptions) { const params = putParams; - encryptionFields.forEach((element) => { + encryptionFields.forEach(element => { if (deploymentBucketOptions[element[0]]) { params[element[1]] = deploymentBucketOptions[element[0]]; } diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js index bdc72583c..26699dc44 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js @@ -44,32 +44,35 @@ describe('uploadArtifacts', () => { }; awsDeploy.serverless.cli = new serverless.classes.CLI(); cryptoStub = { - createHash: function () { return this; }, // eslint-disable-line - update: function () { return this; }, // eslint-disable-line + createHash() { + return this; + }, // eslint-disable-line + update() { + return this; + }, // eslint-disable-line digest: sinon.stub(), }; const uploadArtifacts = proxyquire('./uploadArtifacts.js', { crypto: cryptoStub, }); - Object.assign( - awsDeploy, - uploadArtifacts - ); + Object.assign(awsDeploy, uploadArtifacts); }); describe('#uploadArtifacts()', () => { it('should run promise chain in order', () => { const uploadCloudFormationFileStub = sinon - .stub(awsDeploy, 'uploadCloudFormationFile').resolves(); + .stub(awsDeploy, 'uploadCloudFormationFile') + .resolves(); const uploadFunctionsAndLayersStub = sinon - .stub(awsDeploy, 'uploadFunctionsAndLayers').resolves(); + .stub(awsDeploy, 'uploadFunctionsAndLayers') + .resolves(); return awsDeploy.uploadArtifacts().then(() => { - expect(uploadCloudFormationFileStub.calledOnce) - .to.be.equal(true); + expect(uploadCloudFormationFileStub.calledOnce).to.be.equal(true); expect(uploadFunctionsAndLayersStub.calledAfter(uploadCloudFormationFileStub)).to.be.equal( - true); + true + ); awsDeploy.uploadCloudFormationFile.restore(); awsDeploy.uploadFunctionsAndLayers.restore(); @@ -85,9 +88,7 @@ describe('uploadArtifacts', () => { normalizeCloudFormationTemplateStub = sinon .stub(normalizeFiles, 'normalizeCloudFormationTemplate') .returns(); - uploadStub = sinon - .stub(awsDeploy.provider, 'request') - .resolves(); + uploadStub = sinon.stub(awsDeploy.provider, 'request').resolves(); }); afterEach(() => { @@ -96,30 +97,34 @@ describe('uploadArtifacts', () => { }); it('should upload the CloudFormation file to the S3 bucket', () => { - cryptoStub.createHash().update().digest.onCall(0).returns('local-hash-cf-template'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('local-hash-cf-template'); return awsDeploy.uploadCloudFormationFile().then(() => { expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; expect(uploadStub).to.have.been.calledOnce; - expect(uploadStub).to.have.been.calledWithExactly( - 'S3', - 'upload', - { - Bucket: awsDeploy.bucketName, - Key: `${awsDeploy.serverless.service.package - .artifactDirectoryName}/compiled-cloudformation-template.json`, - Body: JSON.stringify({ foo: 'bar' }), - ContentType: 'application/json', - Metadata: { - filesha256: 'local-hash-cf-template', - }, - }); + expect(uploadStub).to.have.been.calledWithExactly('S3', 'upload', { + Bucket: awsDeploy.bucketName, + Key: `${awsDeploy.serverless.service.package.artifactDirectoryName}/compiled-cloudformation-template.json`, + Body: JSON.stringify({ foo: 'bar' }), + ContentType: 'application/json', + Metadata: { + filesha256: 'local-hash-cf-template', + }, + }); expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly({ foo: 'bar' }); }); }); it('should upload the CloudFormation file to a bucket with SSE bucket policy', () => { - cryptoStub.createHash().update().digest.onCall(0).returns('local-hash-cf-template'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('local-hash-cf-template'); awsDeploy.serverless.service.provider.deploymentBucketObject = { serverSideEncryption: 'AES256', }; @@ -127,20 +132,16 @@ describe('uploadArtifacts', () => { return awsDeploy.uploadCloudFormationFile().then(() => { expect(normalizeCloudFormationTemplateStub).to.have.been.calledOnce; expect(uploadStub).to.have.been.calledOnce; - expect(uploadStub).to.have.been.calledWithExactly( - 'S3', - 'upload', - { - Bucket: awsDeploy.bucketName, - Key: `${awsDeploy.serverless.service.package - .artifactDirectoryName}/compiled-cloudformation-template.json`, - Body: JSON.stringify({ foo: 'bar' }), - ContentType: 'application/json', - ServerSideEncryption: 'AES256', - Metadata: { - filesha256: 'local-hash-cf-template', - }, - }); + expect(uploadStub).to.have.been.calledWithExactly('S3', 'upload', { + Bucket: awsDeploy.bucketName, + Key: `${awsDeploy.serverless.service.package.artifactDirectoryName}/compiled-cloudformation-template.json`, + Body: JSON.stringify({ foo: 'bar' }), + ContentType: 'application/json', + ServerSideEncryption: 'AES256', + Metadata: { + filesha256: 'local-hash-cf-template', + }, + }); expect(normalizeCloudFormationTemplateStub).to.have.been.calledWithExactly({ foo: 'bar' }); }); }); @@ -151,12 +152,8 @@ describe('uploadArtifacts', () => { let uploadStub; beforeEach(() => { - readFileSyncStub = sinon - .stub(fs, 'readFileSync') - .returns(); - uploadStub = sinon - .stub(awsDeploy.provider, 'request') - .resolves(); + readFileSyncStub = sinon.stub(fs, 'readFileSync').returns(); + uploadStub = sinon.stub(awsDeploy.provider, 'request').resolves(); }); afterEach(() => { @@ -169,7 +166,11 @@ describe('uploadArtifacts', () => { }); it('should upload the .zip file to the S3 bucket', () => { - cryptoStub.createHash().update().digest.onCall(0).returns('local-hash-zip-file'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('local-hash-zip-file'); const tmpDirPath = getTmpDirPath(); const artifactFilePath = path.join(tmpDirPath, 'artifact.zip'); @@ -178,24 +179,25 @@ describe('uploadArtifacts', () => { return awsDeploy.uploadZipFile(artifactFilePath).then(() => { expect(uploadStub).to.have.been.calledOnce; expect(readFileSyncStub).to.have.been.calledOnce; - expect(uploadStub).to.have.been.calledWithExactly( - 'S3', - 'upload', - { - Bucket: awsDeploy.bucketName, - Key: `${awsDeploy.serverless.service.package.artifactDirectoryName}/artifact.zip`, - Body: sinon.match.object.and(sinon.match.has('path', artifactFilePath)), - ContentType: 'application/zip', - Metadata: { - filesha256: 'local-hash-zip-file', - }, - }); + expect(uploadStub).to.have.been.calledWithExactly('S3', 'upload', { + Bucket: awsDeploy.bucketName, + Key: `${awsDeploy.serverless.service.package.artifactDirectoryName}/artifact.zip`, + Body: sinon.match.object.and(sinon.match.has('path', artifactFilePath)), + ContentType: 'application/zip', + Metadata: { + filesha256: 'local-hash-zip-file', + }, + }); expect(readFileSyncStub).to.have.been.calledWithExactly(artifactFilePath); }); }); it('should upload the .zip file to a bucket with SSE bucket policy', () => { - cryptoStub.createHash().update().digest.onCall(0).returns('local-hash-zip-file'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('local-hash-zip-file'); const tmpDirPath = getTmpDirPath(); const artifactFilePath = path.join(tmpDirPath, 'artifact.zip'); @@ -207,19 +209,16 @@ describe('uploadArtifacts', () => { return awsDeploy.uploadZipFile(artifactFilePath).then(() => { expect(uploadStub).to.have.been.calledOnce; expect(readFileSyncStub).to.have.been.calledOnce; - expect(uploadStub).to.have.been.calledWithExactly( - 'S3', - 'upload', - { - Bucket: awsDeploy.bucketName, - Key: `${awsDeploy.serverless.service.package.artifactDirectoryName}/artifact.zip`, - Body: sinon.match.object.and(sinon.match.has('path', artifactFilePath)), - ContentType: 'application/zip', - ServerSideEncryption: 'AES256', - Metadata: { - filesha256: 'local-hash-zip-file', - }, - }); + expect(uploadStub).to.have.been.calledWithExactly('S3', 'upload', { + Bucket: awsDeploy.bucketName, + Key: `${awsDeploy.serverless.service.package.artifactDirectoryName}/artifact.zip`, + Body: sinon.match.object.and(sinon.match.has('path', artifactFilePath)), + ContentType: 'application/zip', + ServerSideEncryption: 'AES256', + Metadata: { + filesha256: 'local-hash-zip-file', + }, + }); expect(readFileSyncStub).to.have.been.calledWithExactly(artifactFilePath); }); }); @@ -286,10 +285,12 @@ describe('uploadArtifacts', () => { return awsDeploy.uploadFunctionsAndLayers().then(() => { expect(uploadZipFileStub.calledTwice).to.be.equal(true); - expect(uploadZipFileStub.args[0][0]) - .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact); - expect(uploadZipFileStub.args[1][0]) - .to.be.equal(awsDeploy.serverless.service.functions.second.package.artifact); + expect(uploadZipFileStub.args[0][0]).to.be.equal( + awsDeploy.serverless.service.functions.first.package.artifact + ); + expect(uploadZipFileStub.args[1][0]).to.be.equal( + awsDeploy.serverless.service.functions.second.package.artifact + ); }); }); @@ -310,10 +311,12 @@ describe('uploadArtifacts', () => { return awsDeploy.uploadFunctionsAndLayers().then(() => { expect(uploadZipFileStub.calledTwice).to.be.equal(true); - expect(uploadZipFileStub.args[0][0]) - .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact); - expect(uploadZipFileStub.args[1][0]) - .to.be.equal(awsDeploy.serverless.service.package.artifact); + expect(uploadZipFileStub.args[0][0]).to.be.equal( + awsDeploy.serverless.service.functions.first.package.artifact + ); + expect(uploadZipFileStub.args[1][0]).to.be.equal( + awsDeploy.serverless.service.package.artifact + ); }); }); diff --git a/lib/plugins/aws/deploy/lib/validateTemplate.js b/lib/plugins/aws/deploy/lib/validateTemplate.js index 8c1626447..b11c15ff7 100644 --- a/lib/plugins/aws/deploy/lib/validateTemplate.js +++ b/lib/plugins/aws/deploy/lib/validateTemplate.js @@ -12,15 +12,10 @@ module.exports = { TemplateURL: `https://${s3Endpoint}/${bucketName}/${artifactDirectoryName}/${compiledTemplateFileName}`, }; - return this.provider.request( - 'CloudFormation', - 'validateTemplate', - params - ).catch((error) => { - const errorMessage = [ - 'The CloudFormation template is invalid:', - ` ${error.message}`, - ].join(''); + return this.provider.request('CloudFormation', 'validateTemplate', params).catch(error => { + const errorMessage = ['The CloudFormation template is invalid:', ` ${error.message}`].join( + '' + ); throw new Error(errorMessage); }); }, diff --git a/lib/plugins/aws/deploy/lib/validateTemplate.test.js b/lib/plugins/aws/deploy/lib/validateTemplate.test.js index 8d504a45b..3e83f4128 100644 --- a/lib/plugins/aws/deploy/lib/validateTemplate.test.js +++ b/lib/plugins/aws/deploy/lib/validateTemplate.test.js @@ -55,7 +55,8 @@ describe('validateTemplate', () => { 'CloudFormation', 'validateTemplate', { - TemplateURL: 'https://s3.amazonaws.com/deployment-bucket/somedir/compiled-cloudformation-template.json', + TemplateURL: + 'https://s3.amazonaws.com/deployment-bucket/somedir/compiled-cloudformation-template.json', } ); }); @@ -64,14 +65,15 @@ describe('validateTemplate', () => { it('should throw an error if the CloudFormation template is invalid', () => { validateTemplateStub.rejects({ message: 'Some error while validating' }); - return expect(awsDeploy.validateTemplate()).to.be.rejected.then((error) => { + return expect(awsDeploy.validateTemplate()).to.be.rejected.then(error => { expect(awsDeploy.serverless.cli.log).to.have.been.called; expect(validateTemplateStub).to.have.been.calledOnce; expect(validateTemplateStub).to.have.been.calledWithExactly( 'CloudFormation', 'validateTemplate', { - TemplateURL: 'https://s3.amazonaws.com/deployment-bucket/somedir/compiled-cloudformation-template.json', + TemplateURL: + 'https://s3.amazonaws.com/deployment-bucket/somedir/compiled-cloudformation-template.json', } ); expect(error.message).to.match(/is invalid: Some error while validating/); diff --git a/lib/plugins/aws/deployFunction/index.js b/lib/plugins/aws/deployFunction/index.js index cab694f3b..820435e73 100644 --- a/lib/plugins/aws/deployFunction/index.js +++ b/lib/plugins/aws/deployFunction/index.js @@ -12,7 +12,8 @@ class AwsDeployFunction { constructor(serverless, options) { this.serverless = serverless; this.options = options || {}; - this.packagePath = this.options.package || + this.packagePath = + this.options.package || this.serverless.service.package.path || path.join(this.serverless.config.servicePath || '.', '.serverless'); this.provider = this.serverless.getProvider('aws'); @@ -23,23 +24,25 @@ class AwsDeployFunction { Object.assign(this, validate); this.hooks = { - 'deploy:function:initialize': () => BbPromise.bind(this) - .then(this.validate) - .then(this.checkIfFunctionExists), + 'deploy:function:initialize': () => + BbPromise.bind(this) + .then(this.validate) + .then(this.checkIfFunctionExists), - 'deploy:function:packageFunction': () => this.serverless.pluginManager - .spawn('package:function'), + 'deploy:function:packageFunction': () => + this.serverless.pluginManager.spawn('package:function'), - 'deploy:function:deploy': () => BbPromise.bind(this) - .then(() => { - if (!this.options['update-config']) { - return this.deployFunction(); - } + 'deploy:function:deploy': () => + BbPromise.bind(this) + .then(() => { + if (!this.options['update-config']) { + return this.deployFunction(); + } - return BbPromise.resolve(); - }) - .then(this.updateFunctionConfiguration) - .then(() => this.serverless.pluginManager.spawn('aws:common:cleanupTempDir')), + return BbPromise.resolve(); + }) + .then(this.updateFunctionConfiguration) + .then(() => this.serverless.pluginManager.spawn('aws:common:cleanupTempDir')), }; } @@ -52,12 +55,9 @@ class AwsDeployFunction { FunctionName: this.options.functionObj.name, }; - return this.provider.request( - 'Lambda', - 'getFunction', - params - ) - .then((result) => { + return this.provider + .request('Lambda', 'getFunction', params) + .then(result => { this.serverless.service.provider.remoteFunctionData = result; return result; }) @@ -84,29 +84,25 @@ class AwsDeployFunction { const roleProperties = roleResource.Properties; const compiledFullRoleName = `${roleProperties.Path || '/'}${roleProperties.RoleName}`; - return this.provider.getAccountInfo().then((result) => - `arn:${result.partition}:iam::${result.accountId}:role${compiledFullRoleName}` - ); + return this.provider + .getAccountInfo() + .then( + result => `arn:${result.partition}:iam::${result.accountId}:role${compiledFullRoleName}` + ); } return BbPromise.resolve(role); } - return this.provider.request( - 'IAM', - 'getRole', - { + return this.provider + .request('IAM', 'getRole', { RoleName: role['Fn::GetAtt'][0], - } - ).then((data) => data.Arn); + }) + .then(data => data.Arn); } callUpdateFunctionConfiguration(params) { - return this.provider.request( - 'Lambda', - 'updateFunctionConfiguration', - params - ).then(() => { + return this.provider.request('Lambda', 'updateFunctionConfiguration', params).then(() => { this.serverless.cli.log(`Successfully updated function: ${this.options.function}`); }); } @@ -146,9 +142,9 @@ class AwsDeployFunction { } if ( - 'layers' in functionObj - && _.isArray(functionObj.layers) - && !_.some(functionObj.layers, _.isObject) + 'layers' in functionObj && + _.isArray(functionObj.layers) && + !_.some(functionObj.layers, _.isObject) ) { params.Layers = functionObj.layers; } @@ -170,7 +166,7 @@ class AwsDeployFunction { if (_.some(params.Environment.Variables, value => _.isObject(value))) { delete params.Environment; } else { - Object.keys(params.Environment.Variables).forEach((key) => { + Object.keys(params.Environment.Variables).forEach(key => { // taken from the bash man pages if (!key.match(/^[A-Za-z_][a-zA-Z0-9_]*$/)) { const errorMessage = 'Invalid characters in environment variable'; @@ -223,10 +219,9 @@ class AwsDeployFunction { } deployFunction() { - const artifactFileName = this.provider.naming - .getFunctionArtifactName(this.options.function); - let artifactFilePath = this.serverless.service.package.artifact || - path.join(this.packagePath, artifactFileName); + const artifactFileName = this.provider.naming.getFunctionArtifactName(this.options.function); + let artifactFilePath = + this.serverless.service.package.artifact || path.join(this.packagePath, artifactFileName); // check if an artifact is used in function package level const functionObject = this.serverless.service.getFunction(this.options.function); @@ -237,7 +232,10 @@ class AwsDeployFunction { const data = fs.readFileSync(artifactFilePath); const remoteHash = this.serverless.service.provider.remoteFunctionData.Configuration.CodeSha256; - const localHash = crypto.createHash('sha256').update(data).digest('base64'); + const localHash = crypto + .createHash('sha256') + .update(data) + .digest('base64'); if (remoteHash === localHash && !this.options.force) { this.serverless.cli.log('Code not changed. Skipping function deployment.'); @@ -254,11 +252,7 @@ class AwsDeployFunction { `Uploading function: ${this.options.function} (${filesize(stats.size)})...` ); - return this.provider.request( - 'Lambda', - 'updateFunctionCode', - params - ).then(() => { + return this.provider.request('Lambda', 'updateFunctionCode', params).then(() => { this.serverless.cli.log(`Successfully deployed function: ${this.options.function}`); }); } diff --git a/lib/plugins/aws/deployFunction/index.test.js b/lib/plugins/aws/deployFunction/index.test.js index 0066ea8a5..34471f3dd 100644 --- a/lib/plugins/aws/deployFunction/index.test.js +++ b/lib/plugins/aws/deployFunction/index.test.js @@ -52,8 +52,12 @@ describe('AwsDeployFunction', () => { return serverless.init().then(() => { serverless.setProvider('aws', new AwsProvider(serverless, options)); cryptoStub = { - createHash: function () { return this; }, // eslint-disable-line - update: function () { return this; }, // eslint-disable-line + createHash() { + return this; + }, // eslint-disable-line + update() { + return this; + }, // eslint-disable-line digest: sinon.stub(), }; AwsDeployFunction = proxyquire('./index.js', { @@ -104,13 +108,11 @@ describe('AwsDeployFunction', () => { return awsDeployFunction.checkIfFunctionExists().then(() => { expect(getFunctionStub.calledOnce).to.be.equal(true); - expect(getFunctionStub.calledWithExactly( - 'Lambda', - 'getFunction', - { + expect( + getFunctionStub.calledWithExactly('Lambda', 'getFunction', { FunctionName: 'first', - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(awsDeployFunction.serverless.service.provider.remoteFunctionData).to.deep.equal({ func: { name: 'first', @@ -153,7 +155,7 @@ describe('AwsDeployFunction', () => { it('should return unmodified ARN if ARN was provided', () => { const arn = 'arn:aws:iam::123456789012:role/role'; - return awsDeployFunction.normalizeArnRole(arn).then((result) => { + return awsDeployFunction.normalizeArnRole(arn).then(result => { expect(getAccountInfoStub).to.not.have.been.called; expect(result).to.be.equal(arn); }); @@ -162,7 +164,7 @@ describe('AwsDeployFunction', () => { it('should return compiled ARN if role name was provided', () => { const roleName = 'MyCustomRole'; - return awsDeployFunction.normalizeArnRole(roleName).then((result) => { + return awsDeployFunction.normalizeArnRole(roleName).then(result => { expect(getAccountInfoStub).to.have.been.called; expect(result).to.be.equal('arn:aws:iam::123456789012:role/role_123'); }); @@ -170,13 +172,10 @@ describe('AwsDeployFunction', () => { it('should return compiled ARN if object role was provided', () => { const roleObj = { - 'Fn::GetAtt': [ - 'role_2', - 'Arn', - ], + 'Fn::GetAtt': ['role_2', 'Arn'], }; - return awsDeployFunction.normalizeArnRole(roleObj).then((result) => { + return awsDeployFunction.normalizeArnRole(roleObj).then(result => { expect(getRoleStub.calledOnce).to.be.equal(true); expect(getAccountInfoStub).to.not.have.been.called; expect(result).to.be.equal('arn:aws:iam::123456789012:role/role_2'); @@ -215,7 +214,7 @@ describe('AwsDeployFunction', () => { awsDeployFunction.serverless.service.provider.vpc = undefined; }); - it('should update function\'s configuration', () => { + it("should update function's configuration", () => { options.functionObj = { awsKmsKeyArn: 'arn:aws:kms:us-east-1:123456789012', description: 'desc', @@ -241,32 +240,34 @@ describe('AwsDeployFunction', () => { expect(normalizeArnRoleStub.calledOnce).to.be.equal(true); expect(normalizeArnRoleStub.calledWithExactly('arn:aws:iam::123456789012:role/Admin')); expect(updateFunctionConfigurationStub.calledOnce).to.be.equal(true); - expect(updateFunctionConfigurationStub.calledWithExactly( - 'Lambda', - 'updateFunctionConfiguration', - { - DeadLetterConfig: { - TargetArn: 'arn:aws:sqs:us-east-1:123456789012:dlq', - }, - Handler: 'my_handler', - Description: 'desc', - Environment: { - Variables: { - VARIABLE: 'value', + expect( + updateFunctionConfigurationStub.calledWithExactly( + 'Lambda', + 'updateFunctionConfiguration', + { + DeadLetterConfig: { + TargetArn: 'arn:aws:sqs:us-east-1:123456789012:dlq', }, - }, - FunctionName: 'first', - KMSKeyArn: 'arn:aws:kms:us-east-1:123456789012', - MemorySize: 128, - Role: 'arn:aws:us-east-1:123456789012:role/role', - Timeout: 3, - VpcConfig: { - SecurityGroupIds: ['1'], - SubnetIds: ['2'], - }, - Layers: ['arn:aws:lambda:us-east-1:123456789012:layer:layer:1'], - } - )).to.be.equal(true); + Handler: 'my_handler', + Description: 'desc', + Environment: { + Variables: { + VARIABLE: 'value', + }, + }, + FunctionName: 'first', + KMSKeyArn: 'arn:aws:kms:us-east-1:123456789012', + MemorySize: 128, + Role: 'arn:aws:us-east-1:123456789012:role/role', + Timeout: 3, + VpcConfig: { + SecurityGroupIds: ['1'], + SubnetIds: ['2'], + }, + Layers: ['arn:aws:lambda:us-east-1:123456789012:layer:layer:1'], + } + ) + ).to.be.equal(true); }); }); @@ -281,14 +282,16 @@ describe('AwsDeployFunction', () => { return awsDeployFunction.updateFunctionConfiguration().then(() => { expect(updateFunctionConfigurationStub.calledOnce).to.be.equal(true); - expect(updateFunctionConfigurationStub.calledWithExactly( - 'Lambda', - 'updateFunctionConfiguration', - { - FunctionName: 'first', - Description: 'change', - } - )).to.be.equal(true); + expect( + updateFunctionConfigurationStub.calledWithExactly( + 'Lambda', + 'updateFunctionConfiguration', + { + FunctionName: 'first', + Description: 'change', + } + ) + ).to.be.equal(true); }); }); @@ -298,12 +301,18 @@ describe('AwsDeployFunction', () => { description: 'change', handler: 'my_handler', vpc: { - securityGroupIds: ['xxxxx', { - ref: 'myVPCRef', - }], - subnetIds: ['xxxxx', { - ref: 'myVPCRef', - }], + securityGroupIds: [ + 'xxxxx', + { + ref: 'myVPCRef', + }, + ], + subnetIds: [ + 'xxxxx', + { + ref: 'myVPCRef', + }, + ], }, environment: { myvar: 'this is my var', @@ -317,15 +326,17 @@ describe('AwsDeployFunction', () => { return awsDeployFunction.updateFunctionConfiguration().then(() => { expect(updateFunctionConfigurationStub.calledOnce).to.be.equal(true); - expect(updateFunctionConfigurationStub.calledWithExactly( - 'Lambda', - 'updateFunctionConfiguration', - { - FunctionName: 'first', - Handler: 'my_handler', - Description: 'change', - } - )).to.be.equal(true); + expect( + updateFunctionConfigurationStub.calledWithExactly( + 'Lambda', + 'updateFunctionConfiguration', + { + FunctionName: 'first', + Handler: 'my_handler', + Description: 'change', + } + ) + ).to.be.equal(true); }); }); @@ -342,8 +353,9 @@ describe('AwsDeployFunction', () => { awsDeployFunction.options = options; - return expect(awsDeployFunction.updateFunctionConfiguration()).to.be.fulfilled - .then(() => expect(updateFunctionConfigurationStub).to.not.be.called); + return expect(awsDeployFunction.updateFunctionConfiguration()).to.be.fulfilled.then( + () => expect(updateFunctionConfigurationStub).to.not.be.called + ); }); it('should fail when using invalid characters in environment variable', () => { @@ -360,37 +372,37 @@ describe('AwsDeployFunction', () => { expect(() => awsDeployFunction.updateFunctionConfiguration()).to.throw(Error); }); - it('should transform to string values when using non-string values as environment variables', - () => { - options.functionObj = { - name: 'first', - handler: 'my_handler', - description: 'change', - environment: { - COUNTER: 6, - }, - }; + it('should transform to string values when using non-string values as environment variables', () => { + options.functionObj = { + name: 'first', + handler: 'my_handler', + description: 'change', + environment: { + COUNTER: 6, + }, + }; - awsDeployFunction.options = options; - return expect(awsDeployFunction.updateFunctionConfiguration()).to.be.fulfilled - .then(() => { - expect(updateFunctionConfigurationStub.calledOnce).to.be.equal(true); - expect(updateFunctionConfigurationStub.calledWithExactly( - 'Lambda', - 'updateFunctionConfiguration', - { - FunctionName: 'first', - Handler: 'my_handler', - Description: 'change', - Environment: { - Variables: { - COUNTER: '6', - }, + awsDeployFunction.options = options; + return expect(awsDeployFunction.updateFunctionConfiguration()).to.be.fulfilled.then(() => { + expect(updateFunctionConfigurationStub.calledOnce).to.be.equal(true); + expect( + updateFunctionConfigurationStub.calledWithExactly( + 'Lambda', + 'updateFunctionConfiguration', + { + FunctionName: 'first', + Handler: 'my_handler', + Description: 'change', + Environment: { + Variables: { + COUNTER: '6', }, - } - )).to.be.equal(true); - }); + }, + } + ) + ).to.be.equal(true); }); + }); it('should inherit provider-level config', () => { options.functionObj = { @@ -413,22 +425,24 @@ describe('AwsDeployFunction', () => { expect(normalizeArnRoleStub.calledOnce).to.be.equal(true); expect(normalizeArnRoleStub.calledWithExactly('role')); expect(updateFunctionConfigurationStub.calledOnce).to.be.equal(true); - expect(updateFunctionConfigurationStub.calledWithExactly( - 'Lambda', - 'updateFunctionConfiguration', - { - FunctionName: 'first', - Handler: 'my_handler', - Description: 'change', - VpcConfig: { - SubnetIds: [1234, 12345], - SecurityGroupIds: [123, 12], - }, - Timeout: 12, - MemorySize: 512, - Role: 'arn:aws:us-east-1:123456789012:role/role', - } - )).to.be.equal(true); + expect( + updateFunctionConfigurationStub.calledWithExactly( + 'Lambda', + 'updateFunctionConfiguration', + { + FunctionName: 'first', + Handler: 'my_handler', + Description: 'change', + VpcConfig: { + SubnetIds: [1234, 12345], + SecurityGroupIds: [123, 12], + }, + Timeout: 12, + MemorySize: 512, + Role: 'arn:aws:us-east-1:123456789012:role/role', + } + ) + ).to.be.equal(true); }); }); }); @@ -444,16 +458,10 @@ describe('AwsDeployFunction', () => { awsDeployFunction.packagePath = getTmpDirPath(); artifactFilePath = path.join(awsDeployFunction.packagePath, 'first.zip'); serverless.utils.writeFileSync(artifactFilePath, 'first.zip file content'); - updateFunctionCodeStub = sinon - .stub(awsDeployFunction.provider, 'request') - .resolves(); - statSyncStub = sinon - .stub(fs, 'statSync') - .returns({ size: 1024 }); + updateFunctionCodeStub = sinon.stub(awsDeployFunction.provider, 'request').resolves(); + statSyncStub = sinon.stub(fs, 'statSync').returns({ size: 1024 }); sinon.spy(awsDeployFunction.serverless.cli, 'log'); - readFileSyncStub = sinon - .stub(fs, 'readFileSync') - .returns(); + readFileSyncStub = sinon.stub(fs, 'readFileSync').returns(); awsDeployFunction.serverless.service.provider.remoteFunctionData = { Configuration: { CodeSha256: 'remote-hash-zip-file', @@ -468,48 +476,56 @@ describe('AwsDeployFunction', () => { }); it('should deploy the function if the hashes are different', () => { - cryptoStub.createHash().update().digest.onCall(0).returns('local-hash-zip-file'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('local-hash-zip-file'); return awsDeployFunction.deployFunction().then(() => { const data = fs.readFileSync(artifactFilePath); expect(updateFunctionCodeStub.calledOnce).to.be.equal(true); expect(readFileSyncStub.called).to.equal(true); - expect(updateFunctionCodeStub.calledWithExactly( - 'Lambda', - 'updateFunctionCode', - { + expect( + updateFunctionCodeStub.calledWithExactly('Lambda', 'updateFunctionCode', { FunctionName: 'first', ZipFile: data, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(readFileSyncStub.calledWithExactly(artifactFilePath)).to.equal(true); }); }); it('should deploy the function if the hashes are same but the "force" option is used', () => { awsDeployFunction.options.force = true; - cryptoStub.createHash().update().digest.onCall(0).returns('remote-hash-zip-file'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('remote-hash-zip-file'); return awsDeployFunction.deployFunction().then(() => { const data = fs.readFileSync(artifactFilePath); expect(updateFunctionCodeStub.calledOnce).to.be.equal(true); expect(readFileSyncStub.called).to.equal(true); - expect(updateFunctionCodeStub.calledWithExactly( - 'Lambda', - 'updateFunctionCode', - { + expect( + updateFunctionCodeStub.calledWithExactly('Lambda', 'updateFunctionCode', { FunctionName: 'first', ZipFile: data, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(readFileSyncStub.calledWithExactly(artifactFilePath)).to.equal(true); }); }); it('should resolve if the hashes are the same', () => { - cryptoStub.createHash().update().digest.onCall(0).returns('remote-hash-zip-file'); + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('remote-hash-zip-file'); return awsDeployFunction.deployFunction().then(() => { const expected = 'Code not changed. Skipping function deployment.'; @@ -552,22 +568,21 @@ describe('AwsDeployFunction', () => { serverless.service.getFunction.restore(); }); - it('should read the provided artifact', () => awsDeployFunction.deployFunction().then(() => { - const data = fs.readFileSync(artifactZipFile); + it('should read the provided artifact', () => + awsDeployFunction.deployFunction().then(() => { + const data = fs.readFileSync(artifactZipFile); - expect(readFileSyncStub).to.have.been.calledWithExactly(artifactZipFile); - expect(statSyncStub).to.have.been.calledWithExactly(artifactZipFile); - expect(getFunctionStub).to.have.been.calledWithExactly('first'); - expect(updateFunctionCodeStub.calledOnce).to.equal(true); - expect(updateFunctionCodeStub.calledWithExactly( - 'Lambda', - 'updateFunctionCode', - { - FunctionName: 'first', - ZipFile: data, - } - )).to.be.equal(true); - })); + expect(readFileSyncStub).to.have.been.calledWithExactly(artifactZipFile); + expect(statSyncStub).to.have.been.calledWithExactly(artifactZipFile); + expect(getFunctionStub).to.have.been.calledWithExactly('first'); + expect(updateFunctionCodeStub.calledOnce).to.equal(true); + expect( + updateFunctionCodeStub.calledWithExactly('Lambda', 'updateFunctionCode', { + FunctionName: 'first', + ZipFile: data, + }) + ).to.be.equal(true); + })); }); }); }); diff --git a/lib/plugins/aws/deployList/index.js b/lib/plugins/aws/deployList/index.js index 81f651854..4465b7caa 100644 --- a/lib/plugins/aws/deployList/index.js +++ b/lib/plugins/aws/deployList/index.js @@ -12,23 +12,17 @@ class AwsDeployList { this.options = options || {}; this.provider = this.serverless.getProvider('aws'); - Object.assign( - this, - validate, - setBucketName - ); + Object.assign(this, validate, setBucketName); this.hooks = { - 'before:deploy:list:log': () => BbPromise.bind(this) - .then(this.validate), - 'before:deploy:list:functions:log': () => BbPromise.bind(this) - .then(this.validate), + 'before:deploy:list:log': () => BbPromise.bind(this).then(this.validate), + 'before:deploy:list:functions:log': () => BbPromise.bind(this).then(this.validate), - 'deploy:list:log': () => BbPromise.bind(this) - .then(this.setBucketName) - .then(this.listDeployments), - 'deploy:list:functions:log': () => BbPromise.bind(this) - .then(this.listFunctions), + 'deploy:list:log': () => + BbPromise.bind(this) + .then(this.setBucketName) + .then(this.listDeployments), + 'deploy:list:functions:log': () => BbPromise.bind(this).then(this.listFunctions), }; } @@ -37,40 +31,39 @@ class AwsDeployList { const stage = this.provider.getStage(); const prefix = this.provider.getDeploymentPrefix(); - return this.provider.request('S3', - 'listObjectsV2', - { + return this.provider + .request('S3', 'listObjectsV2', { Bucket: this.bucketName, Prefix: `${prefix}/${service}/${stage}`, - } - ) - .then((response) => { - const directoryRegex = new RegExp('(.+)-(.+-.+-.+)'); - const deployments = findAndGroupDeployments(response, prefix, service, stage); + }) + .then(response => { + const directoryRegex = new RegExp('(.+)-(.+-.+-.+)'); + const deployments = findAndGroupDeployments(response, prefix, service, stage); - if (deployments.length === 0) { - this.serverless.cli.log('Couldn\'t find any existing deployments.'); - this.serverless.cli.log('Please verify that stage and region are correct.'); - return BbPromise.resolve(); - } - this.serverless.cli.log('Listing deployments:'); - deployments.forEach((deployment) => { - this.serverless.cli.log('-------------'); - const match = deployment[0].directory.match(directoryRegex); - this.serverless.cli.log(`Timestamp: ${match[1]}`); - this.serverless.cli.log(`Datetime: ${match[2]}`); - this.serverless.cli.log('Files:'); - deployment.forEach((entry) => { - this.serverless.cli.log(`- ${entry.file}`); + if (deployments.length === 0) { + this.serverless.cli.log("Couldn't find any existing deployments."); + this.serverless.cli.log('Please verify that stage and region are correct.'); + return BbPromise.resolve(); + } + this.serverless.cli.log('Listing deployments:'); + deployments.forEach(deployment => { + this.serverless.cli.log('-------------'); + const match = deployment[0].directory.match(directoryRegex); + this.serverless.cli.log(`Timestamp: ${match[1]}`); + this.serverless.cli.log(`Datetime: ${match[2]}`); + this.serverless.cli.log('Files:'); + deployment.forEach(entry => { + this.serverless.cli.log(`- ${entry.file}`); + }); }); + return BbPromise.resolve(); }); - return BbPromise.resolve(); - }); } // list all functions and their versions listFunctions() { - return BbPromise.resolve().bind(this) + return BbPromise.resolve() + .bind(this) .then(this.getFunctions) .then(this.getFunctionVersions) .then(this.displayFunctions); @@ -79,27 +72,23 @@ class AwsDeployList { getFunctions() { const funcs = this.serverless.service.getAllFunctionsNames(); - return BbPromise.map(funcs, (funcName) => { + return BbPromise.map(funcs, funcName => { const params = { FunctionName: funcName, }; - return this.provider.request('Lambda', - 'getFunction', - params); - }).then((result) => _.map(result, (item) => item.Configuration)); + return this.provider.request('Lambda', 'getFunction', params); + }).then(result => _.map(result, item => item.Configuration)); } getFunctionPaginatedVersions(params, totalVersions) { - return this.provider.request('Lambda', - 'listVersionsByFunction', - params - ) - .then((response) => { + return this.provider.request('Lambda', 'listVersionsByFunction', params).then(response => { const Versions = (totalVersions || []).concat(response.Versions); if (response.NextMarker) { return this.getFunctionPaginatedVersions( - Object.assign({}, params, { Marker: response.NextMarker }), Versions); + Object.assign({}, params, { Marker: response.NextMarker }), + Versions + ); } return Promise.resolve({ Versions }); @@ -109,7 +98,7 @@ class AwsDeployList { getFunctionVersions(funcs) { const requestPromises = []; - funcs.forEach((func) => { + funcs.forEach(func => { const params = { FunctionName: func.FunctionName, }; @@ -124,7 +113,7 @@ class AwsDeployList { this.serverless.cli.log('Listing functions and their last 5 versions:'); this.serverless.cli.log('-------------'); - funcs.forEach((func) => { + funcs.forEach(func => { let message = ''; let name = func.Versions[0].FunctionName; @@ -132,8 +121,9 @@ class AwsDeployList { name = name.replace(`${this.provider.getStage()}-`, ''); message += `${name}: `; - const versions = func.Versions.map((funcEntry) => funcEntry.Version) - .slice(Math.max(0, func.Versions.length - 5)); + const versions = func.Versions.map(funcEntry => funcEntry.Version).slice( + Math.max(0, func.Versions.length - 5) + ); message += versions.join(', '); this.serverless.cli.log(message); diff --git a/lib/plugins/aws/deployList/index.test.js b/lib/plugins/aws/deployList/index.test.js index 92ea6568b..ee51ecb39 100644 --- a/lib/plugins/aws/deployList/index.test.js +++ b/lib/plugins/aws/deployList/index.test.js @@ -35,20 +35,17 @@ describe('AwsDeployList', () => { const s3Response = { Contents: [], }; - const listObjectsStub = sinon - .stub(awsDeployList.provider, 'request').resolves(s3Response); + const listObjectsStub = sinon.stub(awsDeployList.provider, 'request').resolves(s3Response); return awsDeployList.listDeployments().then(() => { expect(listObjectsStub.calledOnce).to.be.equal(true); - expect(listObjectsStub.calledWithExactly( - 'S3', - 'listObjectsV2', - { + expect( + listObjectsStub.calledWithExactly('S3', 'listObjectsV2', { Bucket: awsDeployList.bucketName, Prefix: `${s3Key}`, - } - )).to.be.equal(true); - const infoText = 'Couldn\'t find any existing deployments.'; + }) + ).to.be.equal(true); + const infoText = "Couldn't find any existing deployments."; expect(awsDeployList.serverless.cli.log.calledWithExactly(infoText)).to.be.equal(true); const verifyText = 'Please verify that stage and region are correct.'; expect(awsDeployList.serverless.cli.log.calledWithExactly(verifyText)).to.be.equal(true); @@ -66,19 +63,16 @@ describe('AwsDeployList', () => { ], }; - const listObjectsStub = sinon - .stub(awsDeployList.provider, 'request').resolves(s3Response); + const listObjectsStub = sinon.stub(awsDeployList.provider, 'request').resolves(s3Response); return awsDeployList.listDeployments().then(() => { expect(listObjectsStub.calledOnce).to.be.equal(true); - expect(listObjectsStub.calledWithExactly( - 'S3', - 'listObjectsV2', - { + expect( + listObjectsStub.calledWithExactly('S3', 'listObjectsV2', { Bucket: awsDeployList.bucketName, Prefix: `${s3Key}`, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); const infoText = 'Listing deployments:'; expect(awsDeployList.serverless.cli.log.calledWithExactly(infoText)).to.be.equal(true); const timestampOne = 'Timestamp: 113304333331'; @@ -111,13 +105,12 @@ describe('AwsDeployList', () => { awsDeployList.displayFunctions.restore(); }); - it('should run promise chain in order', () => awsDeployList - .listFunctions().then(() => { + it('should run promise chain in order', () => + awsDeployList.listFunctions().then(() => { expect(getFunctionsStub.calledOnce).to.equal(true); expect(getFunctionVersionsStub.calledAfter(getFunctionsStub)).to.equal(true); expect(displayFunctionsStub.calledAfter(getFunctionVersionsStub)).to.equal(true); - }) - ); + })); }); describe('#getFunctions()', () => { @@ -155,7 +148,7 @@ describe('AwsDeployList', () => { { FunctionName: 'listDeployments-dev-func2' }, ]; - return awsDeployList.getFunctions().then((result) => { + return awsDeployList.getFunctions().then(result => { expect(listFunctionsStub.callCount).to.equal(2); expect(result).to.deep.equal(expectedResult); }); @@ -168,16 +161,12 @@ describe('AwsDeployList', () => { .stub(awsDeployList.provider, 'request') .onFirstCall() .resolves({ - Versions: [ - { FunctionName: 'listDeployments-dev-func', Version: '1' }, - ], + Versions: [{ FunctionName: 'listDeployments-dev-func', Version: '1' }], NextMarker: '123', }) .onSecondCall() .resolves({ - Versions: [ - { FunctionName: 'listDeployments-dev-func', Version: '2' }, - ], + Versions: [{ FunctionName: 'listDeployments-dev-func', Version: '2' }], }); }); @@ -190,7 +179,7 @@ describe('AwsDeployList', () => { FunctionName: 'listDeployments-dev-func', }; - return awsDeployList.getFunctionPaginatedVersions(params).then((result) => { + return awsDeployList.getFunctionPaginatedVersions(params).then(result => { const expectedResult = { Versions: [ { FunctionName: 'listDeployments-dev-func', Version: '1' }, @@ -207,13 +196,9 @@ describe('AwsDeployList', () => { let listVersionsByFunctionStub; beforeEach(() => { - listVersionsByFunctionStub = sinon - .stub(awsDeployList.provider, 'request') - .resolves({ - Versions: [ - { FunctionName: 'listDeployments-dev-func', Version: '$LATEST' }, - ], - }); + listVersionsByFunctionStub = sinon.stub(awsDeployList.provider, 'request').resolves({ + Versions: [{ FunctionName: 'listDeployments-dev-func', Version: '$LATEST' }], + }); }); afterEach(() => { @@ -226,17 +211,13 @@ describe('AwsDeployList', () => { { FunctionName: 'listDeployments-dev-func2' }, ]; - return awsDeployList.getFunctionVersions(funcs).then((result) => { + return awsDeployList.getFunctionVersions(funcs).then(result => { const expectedResult = [ { - Versions: [ - { FunctionName: 'listDeployments-dev-func', Version: '$LATEST' }, - ], + Versions: [{ FunctionName: 'listDeployments-dev-func', Version: '$LATEST' }], }, { - Versions: [ - { FunctionName: 'listDeployments-dev-func', Version: '$LATEST' }, - ], + Versions: [{ FunctionName: 'listDeployments-dev-func', Version: '$LATEST' }], }, ]; @@ -249,9 +230,7 @@ describe('AwsDeployList', () => { describe('#displayFunctions()', () => { const funcs = [ { - Versions: [ - { FunctionName: 'listDeployments-dev-func-1', Version: '1337' }, - ], + Versions: [{ FunctionName: 'listDeployments-dev-func-1', Version: '1337' }], }, { Versions: [ @@ -269,8 +248,9 @@ describe('AwsDeployList', () => { const log = awsDeployList.serverless.cli.log; return awsDeployList.displayFunctions(funcs).then(() => { - expect(log.calledWithExactly('Listing functions and their last 5 versions:')) - .to.be.equal(true); + expect(log.calledWithExactly('Listing functions and their last 5 versions:')).to.be.equal( + true + ); expect(log.calledWithExactly('-------------')).to.be.equal(true); expect(log.calledWithExactly('func-1: 1337')).to.be.equal(true); diff --git a/lib/plugins/aws/info/display.js b/lib/plugins/aws/info/display.js index dbf915752..5b60239e2 100644 --- a/lib/plugins/aws/info/display.js +++ b/lib/plugins/aws/info/display.js @@ -32,7 +32,7 @@ module.exports = { let apiKeysMessage = `${chalk.yellow('api keys:')}`; if (info.apiKeys && info.apiKeys.length > 0) { - info.apiKeys.forEach((apiKeyInfo) => { + info.apiKeys.forEach(apiKeyInfo => { const description = apiKeyInfo.description ? ` - ${apiKeyInfo.description}` : ''; if (conceal) { apiKeysMessage += `\n ${apiKeyInfo.name}${description}`; @@ -53,10 +53,10 @@ module.exports = { let endpointsMessage = `${chalk.yellow('endpoints:')}`; if (info.endpoints && info.endpoints.length) { - _.forEach(info.endpoints, (endpoint) => { + _.forEach(info.endpoints, endpoint => { // if the endpoint is of type http(s) if (!endpoint.startsWith('wss://')) { - _.forEach(this.serverless.service.functions, (functionObject) => { + _.forEach(this.serverless.service.functions, functionObject => { functionObject.events.forEach(event => { if (event.http) { let method; @@ -69,7 +69,13 @@ module.exports = { method = event.http.split(' ')[0].toUpperCase(); path = event.http.split(' ')[1]; } - path = path !== '/' ? `/${path.split('/').filter(p => p !== '').join('/')}` : ''; + path = + path !== '/' + ? `/${path + .split('/') + .filter(p => p !== '') + .join('/')}` + : ''; endpointsMessage += `\n ${method} - ${endpoint}${path}`; } }); @@ -92,7 +98,7 @@ module.exports = { let functionsMessage = `${chalk.yellow('functions:')}`; if (info.functions && info.functions.length > 0) { - info.functions.forEach((f) => { + info.functions.forEach(f => { functionsMessage += `\n ${f.name}: ${f.deployedName}`; }); } else { @@ -108,7 +114,7 @@ module.exports = { let layersMessage = `${chalk.yellow('layers:')}`; if (info.layers && info.layers.length > 0) { - info.layers.forEach((l) => { + info.layers.forEach(l => { layersMessage += `\n ${l.name}: ${l.arn}`; }); } else { @@ -123,7 +129,7 @@ module.exports = { let message = ''; if (this.options.verbose) { message = `${chalk.yellow.underline('\nStack Outputs\n')}`; - _.forEach(this.gatheredData.outputs, (output) => { + _.forEach(this.gatheredData.outputs, output => { message += `${chalk.yellow(output.OutputKey)}: ${output.OutputValue}\n`; }); diff --git a/lib/plugins/aws/info/display.test.js b/lib/plugins/aws/info/display.test.js index 76e5bdb9d..e7f470c4e 100644 --- a/lib/plugins/aws/info/display.test.js +++ b/lib/plugins/aws/info/display.test.js @@ -69,7 +69,8 @@ describe('#display()', () => { expectedMessage += `\n${chalk.red('WARNING:')}\n`; expectedMessage += ' You have 150 resources in your service.\n'; expectedMessage += ' CloudFormation has a hard limit of 200 resources in a service.\n'; - expectedMessage += ' For advice on avoiding this limit, check out this link: http://bit.ly/2IiYB38.'; + expectedMessage += + ' For advice on avoiding this limit, check out this link: http://bit.ly/2IiYB38.'; awsInfo.gatheredData.info.resourceCount = 150; @@ -79,11 +80,13 @@ describe('#display()', () => { }); it('should display API keys if given', () => { - awsInfo.gatheredData.info.apiKeys = [{ - name: 'keyOne', - value: '1234', - description: 'keyOne description', - }]; + awsInfo.gatheredData.info.apiKeys = [ + { + name: 'keyOne', + value: '1234', + description: 'keyOne description', + }, + ]; let expectedMessage = ''; @@ -103,11 +106,13 @@ describe('#display()', () => { it('should hide API keys values when `--conceal` is given', () => { awsInfo.options.conceal = true; - awsInfo.gatheredData.info.apiKeys = [{ - name: 'keyOne', - value: '1234', - description: 'keyOne description', - }]; + awsInfo.gatheredData.info.apiKeys = [ + { + name: 'keyOne', + value: '1234', + description: 'keyOne description', + }, + ]; let expectedMessage = ''; @@ -182,7 +187,9 @@ describe('#display()', () => { }); it('should display wss endpoint if given', () => { - awsInfo.gatheredData.info.endpoints = ['wss://ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev']; + awsInfo.gatheredData.info.endpoints = [ + 'wss://ab12cd34ef.execute-api.us-east-1.amazonaws.com/dev', + ]; let expectedMessage = ''; diff --git a/lib/plugins/aws/info/getApiKeyValues.js b/lib/plugins/aws/info/getApiKeyValues.js index a95ba9e47..fdfa3f9ff 100644 --- a/lib/plugins/aws/info/getApiKeyValues.js +++ b/lib/plugins/aws/info/getApiKeyValues.js @@ -12,11 +12,11 @@ module.exports = { const apiKeyDefinitions = this.serverless.service.provider.apiKeys; const apiKeyNames = []; if (_.isArray(apiKeyDefinitions) && apiKeyDefinitions.length) { - _.forEach(apiKeyDefinitions, (definition) => { + _.forEach(apiKeyDefinitions, definition => { // different API key types are nested in separate arrays if (_.isObject(definition)) { const keyTypeName = Object.keys(definition)[0]; - _.forEach(definition[keyTypeName], (keyName) => apiKeyNames.push(keyName)); + _.forEach(definition[keyTypeName], keyName => apiKeyNames.push(keyName)); } else if (_.isString(definition)) { // plain strings are simple, non-nested API keys apiKeyNames.push(definition); @@ -26,8 +26,9 @@ module.exports = { if (apiKeyNames.length) { return this.provider - .request('CloudFormation', - 'describeStackResources', { StackName: this.provider.naming.getStackName() }) + .request('CloudFormation', 'describeStackResources', { + StackName: this.provider.naming.getStackName(), + }) .then(resources => { const apiKeys = _(resources.StackResources) .filter(resource => resource.ResourceType === 'AWS::ApiGateway::ApiKey') @@ -44,9 +45,9 @@ module.exports = { }) .then(apiKeys => { if (apiKeys && apiKeys.length) { - info.apiKeys = - _.map(apiKeys, apiKey => - _.pick(apiKey, ['name', 'value', 'description'])); + info.apiKeys = _.map(apiKeys, apiKey => + _.pick(apiKey, ['name', 'value', 'description']) + ); } return BbPromise.resolve(); }); diff --git a/lib/plugins/aws/info/getResourceCount.js b/lib/plugins/aws/info/getResourceCount.js index 632887db9..abe00ac4c 100644 --- a/lib/plugins/aws/info/getResourceCount.js +++ b/lib/plugins/aws/info/getResourceCount.js @@ -7,14 +7,13 @@ module.exports = { getResourceCount() { const stackName = this.provider.naming.getStackName(); - return this.provider.request('CloudFormation', - 'listStackResources', - { StackName: stackName }) - .then(result => { - if (!_.isEmpty(result)) { - this.gatheredData.info.resourceCount = result.StackResourceSummaries.length; - } - return BbPromise.resolve(); - }); + return this.provider + .request('CloudFormation', 'listStackResources', { StackName: stackName }) + .then(result => { + if (!_.isEmpty(result)) { + this.gatheredData.info.resourceCount = result.StackResourceSummaries.length; + } + return BbPromise.resolve(); + }); }, }; diff --git a/lib/plugins/aws/info/getResourceCount.test.js b/lib/plugins/aws/info/getResourceCount.test.js index 405742a00..2dfa0baba 100644 --- a/lib/plugins/aws/info/getResourceCount.test.js +++ b/lib/plugins/aws/info/getResourceCount.test.js @@ -37,57 +37,79 @@ describe('#getResourceCount()', () => { it('attach resourceCount to this.gatheredData after listStackResources call', () => { const listStackResourcesResponse = { ResponseMetadata: { RequestId: '81386aed-258b-11e8-b3e8-a937105b7db3' }, - StackResourceSummaries: - [{ LogicalResourceId: 'ApiGatewayDeployment1520814106863', - PhysicalResourceId: 'eoa2a2', - ResourceType: 'AWS::ApiGateway::Deployment', - LastUpdatedTimestamp: '2018-03-12T00:22:40.680Z', - ResourceStatus: 'CREATE_COMPLETE' }, - { LogicalResourceId: 'ApiGatewayMethodHelloGet', - PhysicalResourceId: 'hello-ApiGa-11R27BUE48W38', - ResourceType: 'AWS::ApiGateway::Method', - LastUpdatedTimestamp: '2018-03-12T00:22:37.478Z', - ResourceStatus: 'CREATE_COMPLETE' }, - { LogicalResourceId: 'ApiGatewayResourceHello', - PhysicalResourceId: 'az5f7l', - ResourceType: 'AWS::ApiGateway::Resource', - LastUpdatedTimestamp: '2018-03-12T00:22:22.916Z', - ResourceStatus: 'CREATE_COMPLETE' }, - { LogicalResourceId: 'ApiGatewayRestApi', - PhysicalResourceId: 'n1uk4p7kl0', - ResourceType: 'AWS::ApiGateway::RestApi', - LastUpdatedTimestamp: '2018-03-12T00:22:19.768Z', - ResourceStatus: 'CREATE_COMPLETE' }, - { LogicalResourceId: 'HelloLambdaFunction', - PhysicalResourceId: 'hello-world-2-dev-hello', - ResourceType: 'AWS::Lambda::Function', - LastUpdatedTimestamp: '2018-03-12T00:22:34.095Z', - ResourceStatus: 'CREATE_COMPLETE' }, - { LogicalResourceId: 'HelloLambdaPermissionApiGateway', - PhysicalResourceId: 'hello-world-2-dev-HelloLambdaPermissionApiGateway-18KKZXJG1DPF5', - ResourceType: 'AWS::Lambda::Permission', - LastUpdatedTimestamp: '2018-03-12T00:22:46.950Z', - ResourceStatus: 'CREATE_COMPLETE' }, - { LogicalResourceId: 'HelloLambdaVersiongZDaMtQjEhvXacHdpTLnQ61zDCdI2IWVYCbuE50pj8', - PhysicalResourceId: 'arn:aws:lambda:us-east-1:111111111:function:hello-world-2-dev-hello:2', - ResourceType: 'AWS::Lambda::Version', - LastUpdatedTimestamp: '2018-03-12T00:22:37.256Z', - ResourceStatus: 'CREATE_COMPLETE' }, - { LogicalResourceId: 'HelloLogGroup', - PhysicalResourceId: '/aws/lambda/hello-world-2-dev-hello', - ResourceType: 'AWS::Logs::LogGroup', - LastUpdatedTimestamp: '2018-03-12T00:22:20.095Z', - ResourceStatus: 'CREATE_COMPLETE' }, - { LogicalResourceId: 'IamRoleLambdaExecution', - PhysicalResourceId: 'hello-world-2-dev-us-east-1-lambdaRole', - ResourceType: 'AWS::IAM::Role', - LastUpdatedTimestamp: '2018-03-12T00:22:30.995Z', - ResourceStatus: 'CREATE_COMPLETE' }, - { LogicalResourceId: 'ServerlessDeploymentBucket', - PhysicalResourceId: 'hello-world-2-dev-serverlessdeploymentbucket-1e3l68m8zaz7i', - ResourceType: 'AWS::S3::Bucket', - LastUpdatedTimestamp: '2018-03-12T00:22:11.380Z', - ResourceStatus: 'CREATE_COMPLETE' }], + StackResourceSummaries: [ + { + LogicalResourceId: 'ApiGatewayDeployment1520814106863', + PhysicalResourceId: 'eoa2a2', + ResourceType: 'AWS::ApiGateway::Deployment', + LastUpdatedTimestamp: '2018-03-12T00:22:40.680Z', + ResourceStatus: 'CREATE_COMPLETE', + }, + { + LogicalResourceId: 'ApiGatewayMethodHelloGet', + PhysicalResourceId: 'hello-ApiGa-11R27BUE48W38', + ResourceType: 'AWS::ApiGateway::Method', + LastUpdatedTimestamp: '2018-03-12T00:22:37.478Z', + ResourceStatus: 'CREATE_COMPLETE', + }, + { + LogicalResourceId: 'ApiGatewayResourceHello', + PhysicalResourceId: 'az5f7l', + ResourceType: 'AWS::ApiGateway::Resource', + LastUpdatedTimestamp: '2018-03-12T00:22:22.916Z', + ResourceStatus: 'CREATE_COMPLETE', + }, + { + LogicalResourceId: 'ApiGatewayRestApi', + PhysicalResourceId: 'n1uk4p7kl0', + ResourceType: 'AWS::ApiGateway::RestApi', + LastUpdatedTimestamp: '2018-03-12T00:22:19.768Z', + ResourceStatus: 'CREATE_COMPLETE', + }, + { + LogicalResourceId: 'HelloLambdaFunction', + PhysicalResourceId: 'hello-world-2-dev-hello', + ResourceType: 'AWS::Lambda::Function', + LastUpdatedTimestamp: '2018-03-12T00:22:34.095Z', + ResourceStatus: 'CREATE_COMPLETE', + }, + { + LogicalResourceId: 'HelloLambdaPermissionApiGateway', + PhysicalResourceId: 'hello-world-2-dev-HelloLambdaPermissionApiGateway-18KKZXJG1DPF5', + ResourceType: 'AWS::Lambda::Permission', + LastUpdatedTimestamp: '2018-03-12T00:22:46.950Z', + ResourceStatus: 'CREATE_COMPLETE', + }, + { + LogicalResourceId: 'HelloLambdaVersiongZDaMtQjEhvXacHdpTLnQ61zDCdI2IWVYCbuE50pj8', + PhysicalResourceId: + 'arn:aws:lambda:us-east-1:111111111:function:hello-world-2-dev-hello:2', + ResourceType: 'AWS::Lambda::Version', + LastUpdatedTimestamp: '2018-03-12T00:22:37.256Z', + ResourceStatus: 'CREATE_COMPLETE', + }, + { + LogicalResourceId: 'HelloLogGroup', + PhysicalResourceId: '/aws/lambda/hello-world-2-dev-hello', + ResourceType: 'AWS::Logs::LogGroup', + LastUpdatedTimestamp: '2018-03-12T00:22:20.095Z', + ResourceStatus: 'CREATE_COMPLETE', + }, + { + LogicalResourceId: 'IamRoleLambdaExecution', + PhysicalResourceId: 'hello-world-2-dev-us-east-1-lambdaRole', + ResourceType: 'AWS::IAM::Role', + LastUpdatedTimestamp: '2018-03-12T00:22:30.995Z', + ResourceStatus: 'CREATE_COMPLETE', + }, + { + LogicalResourceId: 'ServerlessDeploymentBucket', + PhysicalResourceId: 'hello-world-2-dev-serverlessdeploymentbucket-1e3l68m8zaz7i', + ResourceType: 'AWS::S3::Bucket', + LastUpdatedTimestamp: '2018-03-12T00:22:11.380Z', + ResourceStatus: 'CREATE_COMPLETE', + }, + ], }; listStackResourcesStub.resolves(listStackResourcesResponse); @@ -106,13 +128,11 @@ describe('#getResourceCount()', () => { return expect(awsInfo.getResourceCount()).to.be.fulfilled.then(() => { expect(listStackResourcesStub.calledOnce).to.equal(true); - expect(listStackResourcesStub.calledWithExactly( - 'CloudFormation', - 'listStackResources', - { + expect( + listStackResourcesStub.calledWithExactly('CloudFormation', 'listStackResources', { StackName: awsInfo.provider.naming.getStackName(), - } - )).to.equal(true); + }) + ).to.equal(true); expect(awsInfo.gatheredData.info.resourceCount).to.equal(10); }); diff --git a/lib/plugins/aws/info/getStackInfo.js b/lib/plugins/aws/info/getStackInfo.js index 71f803fbe..4161275a8 100644 --- a/lib/plugins/aws/info/getStackInfo.js +++ b/lib/plugins/aws/info/getStackInfo.js @@ -21,55 +21,58 @@ module.exports = { const stackName = this.provider.naming.getStackName(); // Get info from CloudFormation Outputs - return this.provider.request('CloudFormation', - 'describeStacks', - { StackName: stackName }) - .then((result) => { - let outputs; + return this.provider + .request('CloudFormation', 'describeStacks', { StackName: stackName }) + .then(result => { + let outputs; - if (result) { - outputs = result.Stacks[0].Outputs; + if (result) { + outputs = result.Stacks[0].Outputs; - const serviceEndpointOutputRegex = this.provider.naming - .getServiceEndpointRegex(); + const serviceEndpointOutputRegex = this.provider.naming.getServiceEndpointRegex(); - // Outputs - this.gatheredData.outputs = outputs; + // Outputs + this.gatheredData.outputs = outputs; - // Functions - this.serverless.service.getAllFunctions().forEach((func) => { - const functionInfo = {}; - functionInfo.name = func; - functionInfo.deployedName = this.serverless.service.getFunction(func).name; - this.gatheredData.info.functions.push(functionInfo); - }); - - // Layers - this.serverless.service.getAllLayers().forEach((layer) => { - const layerInfo = {}; - layerInfo.name = layer; - const layerOutputId = this.provider.naming.getLambdaLayerOutputLogicalId(layer); - for (const output of outputs) { - if (output.OutputKey === layerOutputId) { - layerInfo.arn = output.OutputValue; - break; - } - } - this.gatheredData.info.layers.push(layerInfo); - }); - - // Endpoints - outputs.filter(x => x.OutputKey.match(serviceEndpointOutputRegex)) - .forEach(x => { - this.gatheredData.info.endpoints.push(x.OutputValue); - if (this.serverless.service.deployment && - this.serverless.service.deployment.deploymentId) { - this.serverless.service.deployment.apiId = x.OutputValue.split('//')[1].split('.')[0]; - } + // Functions + this.serverless.service.getAllFunctions().forEach(func => { + const functionInfo = {}; + functionInfo.name = func; + functionInfo.deployedName = this.serverless.service.getFunction(func).name; + this.gatheredData.info.functions.push(functionInfo); }); - } - return BbPromise.resolve(); - }); + // Layers + this.serverless.service.getAllLayers().forEach(layer => { + const layerInfo = {}; + layerInfo.name = layer; + const layerOutputId = this.provider.naming.getLambdaLayerOutputLogicalId(layer); + for (const output of outputs) { + if (output.OutputKey === layerOutputId) { + layerInfo.arn = output.OutputValue; + break; + } + } + this.gatheredData.info.layers.push(layerInfo); + }); + + // Endpoints + outputs + .filter(x => x.OutputKey.match(serviceEndpointOutputRegex)) + .forEach(x => { + this.gatheredData.info.endpoints.push(x.OutputValue); + if ( + this.serverless.service.deployment && + this.serverless.service.deployment.deploymentId + ) { + this.serverless.service.deployment.apiId = x.OutputValue.split('//')[1].split( + '.' + )[0]; + } + }); + } + + return BbPromise.resolve(); + }); }, }; diff --git a/lib/plugins/aws/info/getStackInfo.test.js b/lib/plugins/aws/info/getStackInfo.test.js index 096e71ea5..7629879c1 100644 --- a/lib/plugins/aws/info/getStackInfo.test.js +++ b/lib/plugins/aws/info/getStackInfo.test.js @@ -37,9 +37,11 @@ describe('#getStackInfo()', () => { const describeStacksResponse = { Stacks: [ { - StackId: 'arn:aws:cloudformation:us-east-1:123456789012:' + + StackId: + 'arn:aws:cloudformation:us-east-1:123456789012:' + 'stack/myteststack/466df9e0-0dff-08e3-8e2f-5088487c4896', - Description: 'AWS CloudFormation Sample Template S3_Bucket: ' + + Description: + 'AWS CloudFormation Sample Template S3_Bucket: ' + 'Sample template showing how to create a publicly accessible S3 bucket.', Tags: [], Outputs: [ @@ -126,13 +128,11 @@ describe('#getStackInfo()', () => { return awsInfo.getStackInfo().then(() => { expect(describeStacksStub.calledOnce).to.equal(true); - expect(describeStacksStub.calledWithExactly( - 'CloudFormation', - 'describeStacks', - { + expect( + describeStacksStub.calledWithExactly('CloudFormation', 'describeStacks', { StackName: awsInfo.provider.naming.getStackName(), - } - )).to.equal(true); + }) + ).to.equal(true); expect(awsInfo.gatheredData).to.deep.equal(expectedGatheredDataObj); }); @@ -158,13 +158,11 @@ describe('#getStackInfo()', () => { return awsInfo.getStackInfo().then(() => { expect(describeStacksStub.calledOnce).to.equal(true); - expect(describeStacksStub.calledWithExactly( - 'CloudFormation', - 'describeStacks', - { + expect( + describeStacksStub.calledWithExactly('CloudFormation', 'describeStacks', { StackName: awsInfo.provider.naming.getStackName(), - } - )).to.equal(true); + }) + ).to.equal(true); expect(awsInfo.gatheredData).to.deep.equal(expectedGatheredDataObj); }); diff --git a/lib/plugins/aws/info/index.js b/lib/plugins/aws/info/index.js index 09e7887ba..5f8625118 100644 --- a/lib/plugins/aws/info/index.js +++ b/lib/plugins/aws/info/index.js @@ -12,14 +12,7 @@ class AwsInfo { this.serverless = serverless; this.provider = this.serverless.getProvider('aws'); this.options = options || {}; - Object.assign( - this, - validate, - getStackInfo, - getResourceCount, - getApiKeyValues, - display - ); + Object.assign(this, validate, getStackInfo, getResourceCount, getApiKeyValues, display); this.commands = { aws: { @@ -44,39 +37,33 @@ class AwsInfo { this.hooks = { 'info:info': () => this.serverless.pluginManager.spawn('aws:info'), - 'deploy:deploy': () => BbPromise.bind(this) - .then(() => { + 'deploy:deploy': () => + BbPromise.bind(this).then(() => { if (this.options.noDeploy) { return BbPromise.resolve(); } return this.serverless.pluginManager.spawn('aws:info'); }), - 'aws:info:validate': () => BbPromise.bind(this) - .then(this.validate), + 'aws:info:validate': () => BbPromise.bind(this).then(this.validate), - 'aws:info:gatherData': () => BbPromise.bind(this) - .then(this.getStackInfo) - .then(this.getResourceCount) - .then(this.getApiKeyValues), + 'aws:info:gatherData': () => + BbPromise.bind(this) + .then(this.getStackInfo) + .then(this.getResourceCount) + .then(this.getApiKeyValues), - 'aws:info:displayServiceInfo': () => BbPromise.bind(this) - .then(this.displayServiceInfo), + 'aws:info:displayServiceInfo': () => BbPromise.bind(this).then(this.displayServiceInfo), - 'aws:info:displayApiKeys': () => BbPromise.bind(this) - .then(this.displayApiKeys), + 'aws:info:displayApiKeys': () => BbPromise.bind(this).then(this.displayApiKeys), - 'aws:info:displayEndpoints': () => BbPromise.bind(this) - .then(this.displayEndpoints), + 'aws:info:displayEndpoints': () => BbPromise.bind(this).then(this.displayEndpoints), - 'aws:info:displayFunctions': () => BbPromise.bind(this) - .then(this.displayFunctions), + 'aws:info:displayFunctions': () => BbPromise.bind(this).then(this.displayFunctions), - 'aws:info:displayLayers': () => BbPromise.bind(this) - .then(this.displayLayers), + 'aws:info:displayLayers': () => BbPromise.bind(this).then(this.displayLayers), - 'aws:info:displayStackOutputs': () => BbPromise.bind(this) - .then(this.displayStackOutputs), + 'aws:info:displayStackOutputs': () => BbPromise.bind(this).then(this.displayStackOutputs), }; } } diff --git a/lib/plugins/aws/info/index.test.js b/lib/plugins/aws/info/index.test.js index aeac8bd23..4dc56ae98 100644 --- a/lib/plugins/aws/info/index.test.js +++ b/lib/plugins/aws/info/index.test.js @@ -34,26 +34,16 @@ describe('AwsInfo', () => { // Load commands and hooks into pluginManager serverless.pluginManager.loadCommands(awsInfo); serverless.pluginManager.loadHooks(awsInfo); - validateStub = sinon - .stub(awsInfo, 'validate').resolves(); - getStackInfoStub = sinon - .stub(awsInfo, 'getStackInfo').resolves(); - getResourceCountStub = sinon - .stub(awsInfo, 'getResourceCount').resolves(); - getApiKeyValuesStub = sinon - .stub(awsInfo, 'getApiKeyValues').resolves(); - displayServiceInfoStub = sinon - .stub(awsInfo, 'displayServiceInfo').resolves(); - displayApiKeysStub = sinon - .stub(awsInfo, 'displayApiKeys').resolves(); - displayEndpointsStub = sinon - .stub(awsInfo, 'displayEndpoints').resolves(); - displayFunctionsStub = sinon - .stub(awsInfo, 'displayFunctions').resolves(); - displayLayersStub = sinon - .stub(awsInfo, 'displayLayers').resolves(); - displayStackOutputsStub = sinon - .stub(awsInfo, 'displayStackOutputs').resolves(); + validateStub = sinon.stub(awsInfo, 'validate').resolves(); + getStackInfoStub = sinon.stub(awsInfo, 'getStackInfo').resolves(); + getResourceCountStub = sinon.stub(awsInfo, 'getResourceCount').resolves(); + getApiKeyValuesStub = sinon.stub(awsInfo, 'getApiKeyValues').resolves(); + displayServiceInfoStub = sinon.stub(awsInfo, 'displayServiceInfo').resolves(); + displayApiKeysStub = sinon.stub(awsInfo, 'displayApiKeys').resolves(); + displayEndpointsStub = sinon.stub(awsInfo, 'displayEndpoints').resolves(); + displayFunctionsStub = sinon.stub(awsInfo, 'displayFunctions').resolves(); + displayLayersStub = sinon.stub(awsInfo, 'displayLayers').resolves(); + displayStackOutputsStub = sinon.stub(awsInfo, 'displayStackOutputs').resolves(); }); afterEach(() => { @@ -91,8 +81,7 @@ describe('AwsInfo', () => { expect(displayFunctionsStub.calledAfter(getApiKeyValuesStub)).to.equal(true); expect(displayLayersStub.calledAfter(getApiKeyValuesStub)).to.equal(true); expect(displayStackOutputsStub.calledAfter(getApiKeyValuesStub)).to.equal(true); - }) - ); + })); describe('when running "deploy:deploy" hook', () => { it('should run promise chain in order if no deploy is not set', () => @@ -106,8 +95,7 @@ describe('AwsInfo', () => { expect(displayFunctionsStub.calledAfter(getApiKeyValuesStub)).to.equal(true); expect(displayLayersStub.calledAfter(getApiKeyValuesStub)).to.equal(true); expect(displayStackOutputsStub.calledAfter(getApiKeyValuesStub)).to.equal(true); - }) - ); + })); }); }); }); diff --git a/lib/plugins/aws/invoke/index.js b/lib/plugins/aws/invoke/index.js index 6d3382ddb..4bfef7ed1 100644 --- a/lib/plugins/aws/invoke/index.js +++ b/lib/plugins/aws/invoke/index.js @@ -16,10 +16,11 @@ class AwsInvoke { Object.assign(this, validate); this.hooks = { - 'invoke:invoke': () => BbPromise.bind(this) - .then(this.extendedValidate) - .then(this.invoke) - .then(this.log), + 'invoke:invoke': () => + BbPromise.bind(this) + .then(this.extendedValidate) + .then(this.invoke) + .then(this.log), }; } @@ -33,9 +34,9 @@ class AwsInvoke { if (this.options.data) { return resolve(); } else if (this.options.path) { - const absolutePath = path.isAbsolute(this.options.path) ? - this.options.path : - path.join(this.serverless.config.servicePath, this.options.path); + const absolutePath = path.isAbsolute(this.options.path) + ? this.options.path + : path.join(this.serverless.config.servicePath, this.options.path); if (!this.serverless.utils.fileExistsSync(absolutePath)) { throw new this.serverless.classes.Error('The file you provided does not exist.'); } @@ -43,10 +44,12 @@ class AwsInvoke { return resolve(); } - return stdin().then(input => { - this.options.data = input; - return resolve(); - }).catch(() => resolve()); + return stdin() + .then(input => { + this.options.data = input; + return resolve(); + }) + .catch(() => resolve()); }).then(() => { try { if (!this.options.raw) { @@ -78,7 +81,7 @@ class AwsInvoke { } log(invocationReply) { - const color = !invocationReply.FunctionError ? (x => x) : chalk.red; + const color = !invocationReply.FunctionError ? x => x : chalk.red; if (invocationReply.Payload) { const response = JSON.parse(invocationReply.Payload); @@ -87,8 +90,9 @@ class AwsInvoke { } if (invocationReply.LogResult) { - this.consoleLog(chalk - .gray('--------------------------------------------------------------------')); + this.consoleLog( + chalk.gray('--------------------------------------------------------------------') + ); const logResult = new Buffer(invocationReply.LogResult, 'base64').toString(); logResult.split('\n').forEach(line => this.consoleLog(formatLambdaLogEvent(line))); } diff --git a/lib/plugins/aws/invoke/index.test.js b/lib/plugins/aws/invoke/index.test.js index 2716dca8e..08d7febeb 100644 --- a/lib/plugins/aws/invoke/index.test.js +++ b/lib/plugins/aws/invoke/index.test.js @@ -36,16 +36,13 @@ describe('AwsInvoke', () => { describe('#constructor()', () => { it('should have hooks', () => expect(awsInvoke.hooks).to.be.not.empty); - it('should set the provider variable to an instance of AwsProvider', - () => expect(awsInvoke.provider).to.be.instanceof(AwsProvider)); + it('should set the provider variable to an instance of AwsProvider', () => + expect(awsInvoke.provider).to.be.instanceof(AwsProvider)); it('should run promise chain in order', () => { - const validateStub = sinon - .stub(awsInvoke, 'extendedValidate').resolves(); - const invokeStub = sinon - .stub(awsInvoke, 'invoke').resolves(); - const logStub = sinon - .stub(awsInvoke, 'log').resolves(); + const validateStub = sinon.stub(awsInvoke, 'extendedValidate').resolves(); + const invokeStub = sinon.stub(awsInvoke, 'invoke').resolves(); + const logStub = sinon.stub(awsInvoke, 'log').resolves(); return awsInvoke.hooks['invoke:invoke']().then(() => { expect(validateStub.calledOnce).to.be.equal(true); @@ -108,38 +105,34 @@ describe('AwsInvoke', () => { it('should not throw error when there is no input data', () => { awsInvoke.options.data = undefined; - return expect(awsInvoke.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvoke.options.data).to.equal(''); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvoke.options.data).to.equal(''); + }); }); it('should keep data if it is a simple string', () => { awsInvoke.options.data = 'simple-string'; - return expect(awsInvoke.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvoke.options.data).to.equal('simple-string'); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvoke.options.data).to.equal('simple-string'); + }); }); it('should parse data if it is a json string', () => { awsInvoke.options.data = '{"key": "value"}'; - return expect(awsInvoke.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvoke.options.data).to.deep.equal({ key: 'value' }); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvoke.options.data).to.deep.equal({ key: 'value' }); + }); }); it('should skip parsing data if "raw" requested', () => { awsInvoke.options.data = '{"key": "value"}'; awsInvoke.options.raw = true; - return expect(awsInvoke.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvoke.options.data).to.deep.equal('{"key": "value"}'); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvoke.options.data).to.deep.equal('{"key": "value"}'); + }); }); it('it should parse file if relative file path is provided', () => { @@ -147,14 +140,15 @@ describe('AwsInvoke', () => { const data = { testProp: 'testValue', }; - serverless.utils.writeFileSync(path - .join(serverless.config.servicePath, 'data.json'), JSON.stringify(data)); + serverless.utils.writeFileSync( + path.join(serverless.config.servicePath, 'data.json'), + JSON.stringify(data) + ); awsInvoke.options.path = 'data.json'; - return expect(awsInvoke.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvoke.options.data).to.deep.equal(data); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvoke.options.data).to.deep.equal(data); + }); }); it('it should parse file if absolute file path is provided', () => { @@ -166,26 +160,26 @@ describe('AwsInvoke', () => { serverless.utils.writeFileSync(dataFile, JSON.stringify(data)); awsInvoke.options.path = dataFile; - return expect(awsInvoke.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvoke.options.data).to.deep.equal(data); - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvoke.options.data).to.deep.equal(data); + }); }); it('it should parse a yaml file if file path is provided', () => { serverless.config.servicePath = getTmpDirPath(); const yamlContent = 'testProp: testValue'; - serverless.utils.writeFileSync(path - .join(serverless.config.servicePath, 'data.yml'), yamlContent); + serverless.utils.writeFileSync( + path.join(serverless.config.servicePath, 'data.yml'), + yamlContent + ); awsInvoke.options.path = 'data.yml'; - return expect(awsInvoke.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvoke.options.data).to.deep.equal({ - testProp: 'testValue', - }); + return expect(awsInvoke.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvoke.options.data).to.deep.equal({ + testProp: 'testValue', }); + }); }); it('it should throw error if service path is not set', () => { @@ -197,8 +191,9 @@ describe('AwsInvoke', () => { serverless.config.servicePath = getTmpDirPath(); awsInvoke.options.path = 'some/path'; - return expect(awsInvoke.extendedValidate()) - .to.be.rejectedWith('The file you provided does not exist.'); + return expect(awsInvoke.extendedValidate()).to.be.rejectedWith( + 'The file you provided does not exist.' + ); }); it('should resolve if path is not given', () => { @@ -221,38 +216,33 @@ describe('AwsInvoke', () => { }; }); - it('should invoke with correct params', () => awsInvoke.invoke() - .then(() => { + it('should invoke with correct params', () => + awsInvoke.invoke().then(() => { expect(invokeStub.calledOnce).to.be.equal(true); - expect(invokeStub.calledWithExactly( - 'Lambda', - 'invoke', - { + expect( + invokeStub.calledWithExactly('Lambda', 'invoke', { FunctionName: 'customName', InvocationType: 'RequestResponse', LogType: 'None', Payload: new Buffer(JSON.stringify({})), - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsInvoke.provider.request.restore(); - }) - ); + })); it('should invoke and log', () => { awsInvoke.options.log = true; return awsInvoke.invoke().then(() => { expect(invokeStub.calledOnce).to.be.equal(true); - expect(invokeStub.calledWithExactly( - 'Lambda', - 'invoke', - { + expect( + invokeStub.calledWithExactly('Lambda', 'invoke', { FunctionName: 'customName', InvocationType: 'RequestResponse', LogType: 'Tail', Payload: new Buffer(JSON.stringify({})), - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsInvoke.provider.request.restore(); }); }); @@ -262,16 +252,14 @@ describe('AwsInvoke', () => { return awsInvoke.invoke().then(() => { expect(invokeStub.calledOnce).to.be.equal(true); - expect(invokeStub.calledWithExactly( - 'Lambda', - 'invoke', - { + expect( + invokeStub.calledWithExactly('Lambda', 'invoke', { FunctionName: 'customName', InvocationType: 'OtherType', LogType: 'None', Payload: new Buffer(JSON.stringify({})), - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsInvoke.provider.request.restore(); }); }); @@ -317,8 +305,8 @@ describe('AwsInvoke', () => { }; return awsInvoke.log(invocationReplyMock).catch(err => { - expect(err).to - .and.be.instanceof(Error) + expect(err) + .to.and.be.instanceof(Error) .and.have.property('message', 'Invoked function failed'); }); }); diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 582f3aaf6..f7fdcfd5e 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -27,24 +27,24 @@ class AwsInvokeLocal { Object.assign(this, validate); this.hooks = { - 'before:invoke:local:loadEnvVars': () => BbPromise.bind(this) - .then(this.extendedValidate) - .then(this.loadEnvVars), - 'invoke:local:invoke': () => BbPromise.bind(this) - .then(this.invokeLocal), + 'before:invoke:local:loadEnvVars': () => + BbPromise.bind(this) + .then(this.extendedValidate) + .then(this.loadEnvVars), + 'invoke:local:invoke': () => BbPromise.bind(this).then(this.invokeLocal), }; } getRuntime() { - return this.options.functionObj.runtime - || this.serverless.service.provider.runtime - || 'nodejs10.x'; + return ( + this.options.functionObj.runtime || this.serverless.service.provider.runtime || 'nodejs10.x' + ); } validateFile(filePath, key) { - const absolutePath = path.isAbsolute(filePath) ? - filePath : - path.join(this.serverless.config.servicePath, filePath); + const absolutePath = path.isAbsolute(filePath) + ? filePath + : path.join(this.serverless.config.servicePath, filePath); if (!this.serverless.utils.fileExistsSync(absolutePath)) { throw new this.serverless.classes.Error('The file you provided does not exist.'); } @@ -75,10 +75,12 @@ class AwsInvokeLocal { return resolve(); } - return stdin().then(input => { - this.options.data = input; - return resolve(); - }).catch(() => resolve()); + return stdin() + .then(input => { + this.options.data = input; + return resolve(); + }) + .catch(() => resolve()); }).then(() => { try { // unless asked to preserve raw input, attempt to parse any provided objects @@ -105,13 +107,15 @@ class AwsInvokeLocal { loadEnvVars() { const lambdaName = this.options.functionObj.name; - const memorySize = Number(this.options.functionObj.memorySize) - || Number(this.serverless.service.provider.memorySize) - || 1024; + const memorySize = + Number(this.options.functionObj.memorySize) || + Number(this.serverless.service.provider.memorySize) || + 1024; const lambdaDefaultEnvVars = { LANG: 'en_US.UTF-8', - LD_LIBRARY_PATH: '/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib', // eslint-disable-line max-len + LD_LIBRARY_PATH: + '/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib', // eslint-disable-line max-len LAMBDA_TASK_ROOT: '/var/task', LAMBDA_RUNTIME_DIR: '/var/runtime', AWS_REGION: this.provider.getRegion(), @@ -152,7 +156,8 @@ class AwsInvokeLocal { handlerPath, handlerName, this.options.data, - this.options.context); + this.options.context + ); } if (_.includes(['python2.7', 'python3.6', 'python3.7'], runtime)) { @@ -164,7 +169,8 @@ class AwsInvokeLocal { handlerPath, handlerName, this.options.data, - this.options.context); + this.options.context + ); } if (runtime === 'java8') { @@ -176,7 +182,8 @@ class AwsInvokeLocal { handlerName, this.serverless.service.package.artifact, this.options.data, - this.options.context); + this.options.context + ); } if (runtime === 'ruby2.5') { @@ -188,7 +195,8 @@ class AwsInvokeLocal { handlerPath, handlerName, this.options.data, - this.options.context); + this.options.context + ); } return this.invokeLocalDocker(); @@ -212,7 +220,9 @@ class AwsInvokeLocal { return new BbPromise((resolve, reject) => { const docker = spawn('docker', ['images', '-q', `lambci/lambda:${runtime}`]); let stdout = ''; - docker.stdout.on('data', (buf) => { stdout += buf.toString(); }); + docker.stdout.on('data', buf => { + stdout += buf.toString(); + }); docker.on('exit', error => (error ? reject(error) : resolve(Boolean(stdout.trim())))); }); } @@ -229,24 +239,21 @@ class AwsInvokeLocal { } getLayerPaths() { - const layers = _.mapKeys( - this.serverless.service.layers, - (value, key) => this.provider.naming.getLambdaLayerLogicalId(key) + const layers = _.mapKeys(this.serverless.service.layers, (value, key) => + this.provider.naming.getLambdaLayerLogicalId(key) ); return BbPromise.all( - (this.options.functionObj.layers || this.serverless.service.provider.layers || []) - .map(layer => { + (this.options.functionObj.layers || this.serverless.service.provider.layers || []).map( + layer => { if (layer.Ref) { return layers[layer.Ref].path; } const arnParts = layer.split(':'); const layerArn = arnParts.slice(0, -1).join(':'); const layerVersion = Number(arnParts.slice(-1)[0]); - const layerContentsPath = path.join( - '.serverless', 'layers', arnParts[6], arnParts[7]); - const layerContentsCachePath = path.join( - cachePath, 'layers', arnParts[6], arnParts[7]); + const layerContentsPath = path.join('.serverless', 'layers', arnParts[6], arnParts[7]); + const layerContentsCachePath = path.join(cachePath, 'layers', arnParts[6], arnParts[7]); if (fs.existsSync(layerContentsPath)) { return layerContentsPath; } @@ -254,17 +261,21 @@ class AwsInvokeLocal { if (!fs.existsSync(layerContentsCachePath)) { this.serverless.cli.log(`Downloading layer ${layer}...`); mkdirp.sync(path.join(layerContentsCachePath)); - downloadPromise = this.provider.request( - 'Lambda', 'getLayerVersion', { LayerName: layerArn, VersionNumber: layerVersion }) - .then(layerInfo => download( - layerInfo.Content.Location, - layerContentsCachePath, - { extract: true })); + downloadPromise = this.provider + .request('Lambda', 'getLayerVersion', { + LayerName: layerArn, + VersionNumber: layerVersion, + }) + .then(layerInfo => + download(layerInfo.Content.Location, layerContentsCachePath, { extract: true }) + ); } return downloadPromise .then(() => fse.copySync(layerContentsCachePath, layerContentsPath)) .then(() => layerContentsPath); - })); + } + ) + ); } buildDockerImage(layerPaths) { @@ -280,43 +291,60 @@ class AwsInvokeLocal { const dockerfilePath = path.join('.serverless', 'invokeLocal', 'Dockerfile'); fs.writeFileSync(dockerfilePath, dockerfile); this.serverless.cli.log('Building Docker image...'); - const docker = spawn('docker', ['build', '-t', imageName, - `${this.serverless.config.servicePath}`, '-f', dockerfilePath]); + const docker = spawn('docker', [ + 'build', + '-t', + imageName, + `${this.serverless.config.servicePath}`, + '-f', + dockerfilePath, + ]); docker.on('exit', error => (error ? reject(error) : resolve(imageName))); }); } extractArtifact() { - const artifact = _.get(this.options.functionObj, 'package.artifact', _.get( - this.serverless.service, 'package.artifact' - )); + const artifact = _.get( + this.options.functionObj, + 'package.artifact', + _.get(this.serverless.service, 'package.artifact') + ); if (!artifact) { return this.serverless.config.servicePath; } - return fs.readFileAsync(artifact) + return fs + .readFileAsync(artifact) .then(jszip.loadAsync) - .then(zip => BbPromise.all( - Object.keys(zip.files) - .map(filename => zip.files[filename].async('nodebuffer').then(fileData => { - if (filename.endsWith(path.sep)) { - return BbPromise.resolve(); - } - mkdirp.sync(path.join( - '.serverless', 'invokeLocal', 'artifact', path.dirname(filename))); - return fs.writeFileAsync(path.join( - '.serverless', 'invokeLocal', 'artifact', filename), fileData, { - mode: zip.files[filename].unixPermissions, - }); - })))) - .then(() => path.join( - this.serverless.config.servicePath, '.serverless', 'invokeLocal', 'artifact')); + .then(zip => + BbPromise.all( + Object.keys(zip.files).map(filename => + zip.files[filename].async('nodebuffer').then(fileData => { + if (filename.endsWith(path.sep)) { + return BbPromise.resolve(); + } + mkdirp.sync( + path.join('.serverless', 'invokeLocal', 'artifact', path.dirname(filename)) + ); + return fs.writeFileAsync( + path.join('.serverless', 'invokeLocal', 'artifact', filename), + fileData, + { + mode: zip.files[filename].unixPermissions, + } + ); + }) + ) + ) + ) + .then(() => + path.join(this.serverless.config.servicePath, '.serverless', 'invokeLocal', 'artifact') + ); } getEnvVarsFromOptions() { const envVarsFromOptions = {}; // Get the env vars from command line options in the form of --env KEY=value - _.concat(this.options.env || []) - .forEach(itm => { + _.concat(this.options.env || []).forEach(itm => { const splitItm = _.split(itm, '='); envVarsFromOptions[splitItm[0]] = splitItm.slice(1, splitItm.length).join('=') || ''; }); @@ -332,33 +360,37 @@ class AwsInvokeLocal { this.checkDockerImage().then(exists => (exists ? {} : this.pullDockerImage())), this.getLayerPaths().then(layerPaths => this.buildDockerImage(layerPaths)), this.extractArtifact(), - ]) - .then((results) => new BbPromise((resolve, reject) => { - const imageName = results[2]; - const artifactPath = results[3]; - const configuredEnvVars = this.getConfiguredEnvVars(); - const envVarsFromOptions = this.getEnvVarsFromOptions(); - const envVars = _.merge(configuredEnvVars, envVarsFromOptions); - const envVarsDockerArgs = _.flatMap(envVars, - (value, key) => ['--env', `${key}=${value}`] - ); - const dockerArgsFromOptions = this.getDockerArgsFromOptions(); - const dockerArgs = _.concat([ - 'run', '--rm', '-v', `${artifactPath}:/var/task`, - ], envVarsDockerArgs, dockerArgsFromOptions, [ - imageName, handler, JSON.stringify(this.options.data), - ]); - const docker = spawn('docker', dockerArgs); - docker.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - docker.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - docker.on('exit', error => (error ? reject(error) : resolve(imageName))); - })) + ]).then( + results => + new BbPromise((resolve, reject) => { + const imageName = results[2]; + const artifactPath = results[3]; + const configuredEnvVars = this.getConfiguredEnvVars(); + const envVarsFromOptions = this.getEnvVarsFromOptions(); + const envVars = _.merge(configuredEnvVars, envVarsFromOptions); + const envVarsDockerArgs = _.flatMap(envVars, (value, key) => [ + '--env', + `${key}=${value}`, + ]); + const dockerArgsFromOptions = this.getDockerArgsFromOptions(); + const dockerArgs = _.concat( + ['run', '--rm', '-v', `${artifactPath}:/var/task`], + envVarsDockerArgs, + dockerArgsFromOptions, + [imageName, handler, JSON.stringify(this.options.data)] + ); + const docker = spawn('docker', dockerArgs); + docker.stdout.on('data', buf => this.serverless.cli.consoleLog(buf.toString())); + docker.stderr.on('data', buf => this.serverless.cli.consoleLog(buf.toString())); + docker.on('exit', error => (error ? reject(error) : resolve(imageName))); + }) + ) ); } getDockerArgsFromOptions() { const dockerArgOptions = this.options['docker-arg']; - const dockerArgsFromOptions = _.flatMap(_.concat(dockerArgOptions || []), (dockerArgOption) => { + const dockerArgsFromOptions = _.flatMap(_.concat(dockerArgOptions || []), dockerArgOption => { const splitItems = dockerArgOption.split(' '); return [splitItems[0], splitItems.slice(1).join(' ')]; }); @@ -373,9 +405,10 @@ class AwsInvokeLocal { name: this.options.functionObj.name, version: 'LATEST', logGroupName: this.provider.naming.getLogGroupName(this.options.functionObj.name), - timeout: Number(this.options.functionObj.timeout) - || Number(this.serverless.service.provider.timeout) - || 6, + timeout: + Number(this.options.functionObj.timeout) || + Number(this.serverless.service.provider.timeout) || + 6, }, context ), @@ -391,11 +424,14 @@ class AwsInvokeLocal { } return new BbPromise((resolve, reject) => { - const python = spawn(runtime.split('.')[0], + const python = spawn( + runtime.split('.')[0], ['-u', path.join(__dirname, 'invoke.py'), handlerPath, handlerName], - { env: process.env }, { shell: true }); - python.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - python.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + { env: process.env }, + { shell: true } + ); + python.stdout.on('data', buf => this.serverless.cli.consoleLog(buf.toString())); + python.stderr.on('data', buf => this.serverless.cli.consoleLog(buf.toString())); python.on('close', () => resolve()); let isRejected = false; python.on('error', error => { @@ -412,43 +448,55 @@ class AwsInvokeLocal { } callJavaBridge(artifactPath, className, handlerName, input) { - return new BbPromise((resolve, reject) => fs.statAsync(artifactPath).then(() => { - const java = spawn('java', [ - `-DartifactPath=${artifactPath}`, - `-DclassName=${className}`, - `-DhandlerName=${handlerName}`, - '-jar', - path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.1.jar'), - ], { shell: true }); + return new BbPromise((resolve, reject) => + fs.statAsync(artifactPath).then( + () => { + const java = spawn( + 'java', + [ + `-DartifactPath=${artifactPath}`, + `-DclassName=${className}`, + `-DhandlerName=${handlerName}`, + '-jar', + path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.1.jar'), + ], + { shell: true } + ); - this.serverless.cli.log([ - 'In order to get human-readable output,', - ' please implement "toString()" method of your "ApiGatewayResponse" object.', - ].join('')); + this.serverless.cli.log( + [ + 'In order to get human-readable output,', + ' please implement "toString()" method of your "ApiGatewayResponse" object.', + ].join('') + ); - java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - java.on('close', () => resolve()); - let isRejected = false; - java.on('error', error => { - isRejected = true; - reject(error); - }); + java.stdout.on('data', buf => this.serverless.cli.consoleLog(buf.toString())); + java.stderr.on('data', buf => this.serverless.cli.consoleLog(buf.toString())); + java.on('close', () => resolve()); + let isRejected = false; + java.on('error', error => { + isRejected = true; + reject(error); + }); - process.nextTick(() => { - if (isRejected) return; // Runtime not available - java.stdin.write(input); - java.stdin.end(); - }); - }, () => { - throw new Error(`Artifact ${artifactPath} doesn't exists, please compile it first.`); - })); + process.nextTick(() => { + if (isRejected) return; // Runtime not available + java.stdin.write(input); + java.stdin.end(); + }); + }, + () => { + throw new Error(`Artifact ${artifactPath} doesn't exists, please compile it first.`); + } + ) + ); } invokeLocalJava(runtime, className, handlerName, artifactPath, event, customContext) { - const timeout = Number(this.options.functionObj.timeout) - || Number(this.serverless.service.provider.timeout) - || 6; + const timeout = + Number(this.options.functionObj.timeout) || + Number(this.serverless.service.provider.timeout) || + 6; const context = { name: this.options.functionObj.name, version: 'LATEST', @@ -463,33 +511,36 @@ class AwsInvokeLocal { const javaBridgePath = path.join(__dirname, 'java'); const executablePath = path.join(javaBridgePath, 'target'); - return new BbPromise((resolve, reject) => fs.statAsync(executablePath) - .then(() => this.callJavaBridge(artifactPath, className, handlerName, input)) - .then(resolve, () => { - const mvn = spawn('mvn', [ - 'package', - '-f', - path.join(javaBridgePath, 'pom.xml'), - ], { shell: true }); + return new BbPromise((resolve, reject) => + fs + .statAsync(executablePath) + .then(() => this.callJavaBridge(artifactPath, className, handlerName, input)) + .then(resolve, () => { + const mvn = spawn('mvn', ['package', '-f', path.join(javaBridgePath, 'pom.xml')], { + shell: true, + }); - this.serverless.cli - .log('Building Java bridge, first invocation might take a bit longer.'); + this.serverless.cli.log( + 'Building Java bridge, first invocation might take a bit longer.' + ); - mvn.stderr.on('data', (buf) => this.serverless.cli.consoleLog(`mvn - ${buf.toString()}`)); + mvn.stderr.on('data', buf => this.serverless.cli.consoleLog(`mvn - ${buf.toString()}`)); - mvn.on('close', () => this.callJavaBridge(artifactPath, className, handlerName, input) - .then(resolve)); - let isRejected = false; - mvn.on('error', error => { - isRejected = true; - reject(error); - }); + mvn.on('close', () => + this.callJavaBridge(artifactPath, className, handlerName, input).then(resolve) + ); + let isRejected = false; + mvn.on('error', error => { + isRejected = true; + reject(error); + }); - process.nextTick(() => { - if (isRejected) return; // Runtime not available - mvn.stdin.end(); - }); - })); + process.nextTick(() => { + if (isRejected) return; // Runtime not available + mvn.stdin.end(); + }); + }) + ); } invokeLocalRuby(runtime, handlerPath, handlerName, event, context) { @@ -499,11 +550,14 @@ class AwsInvokeLocal { }); return new BbPromise((resolve, reject) => { - const ruby = spawn(runtime, + const ruby = spawn( + runtime, [path.join(__dirname, 'invoke.rb'), handlerPath, handlerName], - { env: process.env }, { shell: true }); - ruby.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); - ruby.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + { env: process.env }, + { shell: true } + ); + ruby.stdout.on('data', buf => this.serverless.cli.consoleLog(buf.toString())); + ruby.stderr.on('data', buf => this.serverless.cli.consoleLog(buf.toString())); ruby.on('close', () => resolve()); let isRejected = false; ruby.on('error', error => { @@ -533,9 +587,8 @@ class AwsInvokeLocal { this.options.extraServicePath || '', handlerPath ); - const handlersContainer = require( // eslint-disable-line global-require - pathToHandler - ); + const handlersContainer = require(// eslint-disable-line global-require + pathToHandler); lambda = handlersContainer[handlerName]; } catch (error) { this.serverless.cli.consoleLog(chalk.red(inspect(error))); @@ -579,7 +632,7 @@ class AwsInvokeLocal { this.serverless.cli.consoleLog(JSON.stringify(result, null, 4)); } - return new BbPromise((resolve) => { + return new BbPromise(resolve => { const callback = (err, result) => { if (!hasResponded) { hasResponded = true; @@ -593,9 +646,10 @@ class AwsInvokeLocal { }; const startTime = new Date(); - const timeout = Number(this.options.functionObj.timeout) - || Number(this.serverless.service.provider.timeout) - || 6; + const timeout = + Number(this.options.functionObj.timeout) || + Number(this.serverless.service.provider.timeout) || + 6; let context = { awsRequestId: 'id', invokeid: 'id', @@ -617,7 +671,7 @@ class AwsInvokeLocal { return callback(error, result); }, getRemainingTimeInMillis() { - return Math.max((timeout * 1000) - ((new Date()).valueOf() - startTime.valueOf()), 0); + return Math.max(timeout * 1000 - (new Date().valueOf() - startTime.valueOf()), 0); }, }; @@ -627,11 +681,10 @@ class AwsInvokeLocal { const maybeThennable = lambda(event, context, callback); if (!_.isUndefined(maybeThennable)) { - return BbPromise.resolve(maybeThennable) - .then( - callback.bind(this, null), - callback.bind(this) - ); + return BbPromise.resolve(maybeThennable).then( + callback.bind(this, null), + callback.bind(this) + ); } return maybeThennable; diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 6baa78ce8..7d3577f8a 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -56,9 +56,7 @@ describe('AwsInvokeLocal', () => { expect(awsInvokeLocal.provider).to.be.instanceof(AwsProvider)); it('should run invoke:local:invoke promise chain in order', () => { - const invokeLocalStub = sinon - .stub(awsInvokeLocal, 'invokeLocal').resolves(); - + const invokeLocalStub = sinon.stub(awsInvokeLocal, 'invokeLocal').resolves(); return awsInvokeLocal.hooks['invoke:local:invoke']().then(() => { expect(invokeLocalStub.callCount).to.be.equal(1); @@ -68,11 +66,8 @@ describe('AwsInvokeLocal', () => { }); it('should run before:invoke:local:loadEnvVars promise chain in order', () => { - const validateStub = sinon - .stub(awsInvokeLocal, 'extendedValidate').resolves(); - const loadEnvVarsStub = sinon - .stub(awsInvokeLocal, 'loadEnvVars').resolves(); - + const validateStub = sinon.stub(awsInvokeLocal, 'extendedValidate').resolves(); + const loadEnvVarsStub = sinon.stub(awsInvokeLocal, 'loadEnvVars').resolves(); return awsInvokeLocal.hooks['before:invoke:local:loadEnvVars']().then(() => { expect(validateStub.callCount).to.be.equal(1); @@ -128,10 +123,9 @@ describe('AwsInvokeLocal', () => { it('should not throw error when there are no input data', () => { awsInvokeLocal.options.data = undefined; - return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvokeLocal.options.data).to.equal(''); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvokeLocal.options.data).to.equal(''); + }); }); it('it should throw error if function is not provided', () => { @@ -142,48 +136,43 @@ describe('AwsInvokeLocal', () => { it('should keep data if it is a simple string', () => { awsInvokeLocal.options.data = 'simple-string'; - return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvokeLocal.options.data).to.equal('simple-string'); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvokeLocal.options.data).to.equal('simple-string'); + }); }); it('should parse data if it is a json string', () => { awsInvokeLocal.options.data = '{"key": "value"}'; - return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvokeLocal.options.data).to.deep.equal({ key: 'value' }); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal({ key: 'value' }); + }); }); it('should skip parsing data if "raw" requested', () => { awsInvokeLocal.options.data = '{"key": "value"}'; awsInvokeLocal.options.raw = true; - return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvokeLocal.options.data).to.deep.equal('{"key": "value"}'); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal('{"key": "value"}'); + }); }); it('should parse context if it is a json string', () => { awsInvokeLocal.options.context = '{"key": "value"}'; - return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvokeLocal.options.context).to.deep.equal({ key: 'value' }); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvokeLocal.options.context).to.deep.equal({ key: 'value' }); + }); }); it('should skip parsing context if "raw" requested', () => { awsInvokeLocal.options.context = '{"key": "value"}'; awsInvokeLocal.options.raw = true; - return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvokeLocal.options.context).to.deep.equal('{"key": "value"}'); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvokeLocal.options.context).to.deep.equal('{"key": "value"}'); + }); }); it('it should parse file if relative file path is provided', () => { @@ -191,14 +180,15 @@ describe('AwsInvokeLocal', () => { const data = { testProp: 'testValue', }; - serverless.utils.writeFileSync(path - .join(serverless.config.servicePath, 'data.json'), JSON.stringify(data)); + serverless.utils.writeFileSync( + path.join(serverless.config.servicePath, 'data.json'), + JSON.stringify(data) + ); awsInvokeLocal.options.contextPath = 'data.json'; - return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvokeLocal.options.context).to.deep.equal(data); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvokeLocal.options.context).to.deep.equal(data); + }); }); it('it should parse file if absolute file path is provided', () => { @@ -213,24 +203,24 @@ describe('AwsInvokeLocal', () => { awsInvokeLocal.options.path = dataFile; awsInvokeLocal.options.contextPath = false; - return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvokeLocal.options.data).to.deep.equal(data); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal(data); + }); }); it('it should parse a yaml file if file path is provided', () => { serverless.config.servicePath = getTmpDirPath(); const yamlContent = 'event: data'; - serverless.utils.writeFileSync(path - .join(serverless.config.servicePath, 'data.yml'), yamlContent); + serverless.utils.writeFileSync( + path.join(serverless.config.servicePath, 'data.yml'), + yamlContent + ); awsInvokeLocal.options.path = 'data.yml'; - return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvokeLocal.options.data).to.deep.equal({ event: 'data' }); - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal({ event: 'data' }); + }); }); it('it should require a js file if file path is provided', () => { @@ -242,20 +232,20 @@ describe('AwsInvokeLocal', () => { '}', ].join('\n'); - serverless.utils.writeFileSync(path - .join(serverless.config.servicePath, 'data.js'), jsContent); + serverless.utils.writeFileSync( + path.join(serverless.config.servicePath, 'data.js'), + jsContent + ); awsInvokeLocal.options.path = 'data.js'; - return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled - .then(() => { - expect(awsInvokeLocal.options.data).to.deep.equal({ - headers: { 'Content-Type': 'application/json' }, - body: '[100,200]', - }); + return expect(awsInvokeLocal.extendedValidate()).to.be.fulfilled.then(() => { + expect(awsInvokeLocal.options.data).to.deep.equal({ + headers: { 'Content-Type': 'application/json' }, + body: '[100,200]', }); + }); }); - it('it should throw error if service path is not set', () => { serverless.config.servicePath = false; return expect(awsInvokeLocal.extendedValidate()).to.be.rejected; @@ -298,11 +288,10 @@ describe('AwsInvokeLocal', () => { afterEach(() => restoreEnv()); - it('it should load provider env vars', () => awsInvokeLocal - .loadEnvVars().then(() => { + it('it should load provider env vars', () => + awsInvokeLocal.loadEnvVars().then(() => { expect(process.env.providerVar).to.be.equal('providerValue'); - }) - ); + })); it('it should load provider profile env', () => { serverless.service.provider.profile = 'jdoe'; @@ -311,31 +300,30 @@ describe('AwsInvokeLocal', () => { }); }); - it('it should load function env vars', () => awsInvokeLocal - .loadEnvVars().then(() => { + it('it should load function env vars', () => + awsInvokeLocal.loadEnvVars().then(() => { expect(process.env.functionVar).to.be.equal('functionValue'); - }) - ); + })); - it('it should load default lambda env vars', () => awsInvokeLocal - .loadEnvVars().then(() => { + it('it should load default lambda env vars', () => + awsInvokeLocal.loadEnvVars().then(() => { expect(process.env.LANG).to.equal('en_US.UTF-8'); - expect(process.env.LD_LIBRARY_PATH) - .to.equal('/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib'); // eslint-disable-line max-len + expect(process.env.LD_LIBRARY_PATH).to.equal( + '/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib' + ); // eslint-disable-line max-len expect(process.env.LAMBDA_TASK_ROOT).to.equal('/var/task'); expect(process.env.LAMBDA_RUNTIME_DIR).to.equal('/var/runtime'); expect(process.env.AWS_REGION).to.equal('us-east-1'); expect(process.env.AWS_DEFAULT_REGION).to.equal('us-east-1'); expect(process.env.AWS_LAMBDA_LOG_GROUP_NAME).to.equal('/aws/lambda/serviceName-dev-hello'); - expect(process.env.AWS_LAMBDA_LOG_STREAM_NAME) - .to.equal('2016/12/02/[$LATEST]f77ff5e4026c45bda9a9ebcec6bc9cad'); + expect(process.env.AWS_LAMBDA_LOG_STREAM_NAME).to.equal( + '2016/12/02/[$LATEST]f77ff5e4026c45bda9a9ebcec6bc9cad' + ); expect(process.env.AWS_LAMBDA_FUNCTION_NAME).to.equal('serviceName-dev-hello'); expect(process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE).to.equal('1024'); expect(process.env.AWS_LAMBDA_FUNCTION_VERSION).to.equal('$LATEST'); expect(process.env.NODE_PATH).to.equal('/var/runtime:/var/task:/var/runtime/node_modules'); - }) - ); - + })); it('should fallback to service provider configuration when options are not available', () => { awsInvokeLocal.provider.options.region = null; @@ -364,16 +352,11 @@ describe('AwsInvokeLocal', () => { let invokeLocalDockerStub; beforeEach(() => { - invokeLocalNodeJsStub = - sinon.stub(awsInvokeLocal, 'invokeLocalNodeJs').resolves(); - invokeLocalPythonStub = - sinon.stub(awsInvokeLocal, 'invokeLocalPython').resolves(); - invokeLocalJavaStub = - sinon.stub(awsInvokeLocal, 'invokeLocalJava').resolves(); - invokeLocalRubyStub = - sinon.stub(awsInvokeLocal, 'invokeLocalRuby').resolves(); - invokeLocalDockerStub = - sinon.stub(awsInvokeLocal, 'invokeLocalDocker').resolves(); + invokeLocalNodeJsStub = sinon.stub(awsInvokeLocal, 'invokeLocalNodeJs').resolves(); + invokeLocalPythonStub = sinon.stub(awsInvokeLocal, 'invokeLocalPython').resolves(); + invokeLocalJavaStub = sinon.stub(awsInvokeLocal, 'invokeLocalJava').resolves(); + invokeLocalRubyStub = sinon.stub(awsInvokeLocal, 'invokeLocalRuby').resolves(); + invokeLocalDockerStub = sinon.stub(awsInvokeLocal, 'invokeLocalDocker').resolves(); awsInvokeLocal.serverless.service.service = 'new-service'; awsInvokeLocal.provider.options.stage = 'dev'; @@ -394,43 +377,32 @@ describe('AwsInvokeLocal', () => { awsInvokeLocal.invokeLocalRuby.restore(); }); - it('should call invokeLocalNodeJs when no runtime is set', () => awsInvokeLocal.invokeLocal() - .then(() => { + it('should call invokeLocalNodeJs when no runtime is set', () => + awsInvokeLocal.invokeLocal().then(() => { expect(invokeLocalNodeJsStub.calledOnce).to.be.equal(true); - expect(invokeLocalNodeJsStub.calledWithExactly( - 'handler', - 'hello', - {}, - undefined - )).to.be.equal(true); - }) - ); + expect( + invokeLocalNodeJsStub.calledWithExactly('handler', 'hello', {}, undefined) + ).to.be.equal(true); + })); it('should call invokeLocalNodeJs for any node.js runtime version', () => { awsInvokeLocal.options.functionObj.runtime = 'nodejs10.x'; return awsInvokeLocal.invokeLocal().then(() => { expect(invokeLocalNodeJsStub.calledOnce).to.be.equal(true); - expect(invokeLocalNodeJsStub.calledWithExactly( - 'handler', - 'hello', - {}, - undefined - )).to.be.equal(true); + expect( + invokeLocalNodeJsStub.calledWithExactly('handler', 'hello', {}, undefined) + ).to.be.equal(true); }); }); it('should call invokeLocalNodeJs with custom context if provided', () => { awsInvokeLocal.options.context = 'custom context'; - return awsInvokeLocal.invokeLocal() - .then(() => { - expect(invokeLocalNodeJsStub.calledOnce).to.be.equal(true); - expect(invokeLocalNodeJsStub.calledWithExactly( - 'handler', - 'hello', - {}, - 'custom context' - )).to.be.equal(true); - }); + return awsInvokeLocal.invokeLocal().then(() => { + expect(invokeLocalNodeJsStub.calledOnce).to.be.equal(true); + expect( + invokeLocalNodeJsStub.calledWithExactly('handler', 'hello', {}, 'custom context') + ).to.be.equal(true); + }); }); it('should call invokeLocalPython when python2.7 runtime is set', () => { @@ -439,30 +411,27 @@ describe('AwsInvokeLocal', () => { // NOTE: this is important so that tests on Windows won't fail const runtime = process.platform === 'win32' ? 'python.exe' : 'python2.7'; expect(invokeLocalPythonStub.calledOnce).to.be.equal(true); - expect(invokeLocalPythonStub.calledWithExactly( - runtime, - 'handler', - 'hello', - {}, - undefined - )).to.be.equal(true); + expect( + invokeLocalPythonStub.calledWithExactly(runtime, 'handler', 'hello', {}, undefined) + ).to.be.equal(true); }); }); it('should call invokeLocalJava when java8 runtime is set', () => { awsInvokeLocal.options.functionObj.runtime = 'java8'; - return awsInvokeLocal.invokeLocal() - .then(() => { - expect(invokeLocalJavaStub.calledOnce).to.be.equal(true); - expect(invokeLocalJavaStub.calledWithExactly( + return awsInvokeLocal.invokeLocal().then(() => { + expect(invokeLocalJavaStub.calledOnce).to.be.equal(true); + expect( + invokeLocalJavaStub.calledWithExactly( 'java', 'handler.hello', 'handleRequest', undefined, {}, undefined - )).to.be.equal(true); - }); + ) + ).to.be.equal(true); + }); }); it('should call invokeLocalRuby when ruby2.5 runtime is set', () => { @@ -471,13 +440,9 @@ describe('AwsInvokeLocal', () => { // NOTE: this is important so that tests on Windows won't fail const runtime = process.platform === 'win32' ? 'ruby.exe' : 'ruby'; expect(invokeLocalRubyStub.calledOnce).to.be.equal(true); - expect(invokeLocalRubyStub.calledWithExactly( - runtime, - 'handler', - 'hello', - {}, - undefined - )).to.be.equal(true); + expect( + invokeLocalRubyStub.calledWithExactly(runtime, 'handler', 'hello', {}, undefined) + ).to.be.equal(true); }); }); @@ -488,13 +453,15 @@ describe('AwsInvokeLocal', () => { // NOTE: this is important so that tests on Windows won't fail const runtime = process.platform === 'win32' ? 'ruby.exe' : 'ruby'; expect(invokeLocalRubyStub.calledOnce).to.be.equal(true); - expect(invokeLocalRubyStub.calledWithExactly( - runtime, - 'handler', - 'MyModule::MyClass.hello', - {}, - undefined - )).to.be.equal(true); + expect( + invokeLocalRubyStub.calledWithExactly( + runtime, + 'handler', + 'MyModule::MyClass.hello', + {}, + undefined + ) + ).to.be.equal(true); }); }); @@ -537,7 +504,8 @@ describe('AwsInvokeLocal', () => { it('should succeed if succeed', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs('fixture/handlerWithSuccess', 'withMessageByReturn') + return awsInvokeLocal + .invokeLocalNodeJs('fixture/handlerWithSuccess', 'withMessageByReturn') .then(() => expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"Succeed"')); }); }); @@ -566,7 +534,9 @@ describe('AwsInvokeLocal', () => { awsInvokeLocal.invokeLocalNodeJs('fixture/handlerWithSuccess', 'withMessageByLambdaProxy'); - expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('{\n "statusCode": 200,\n "headers": {\n "Content-Type": "application/json"\n },\n "body": {\n "result": true,\n "message": "Whatever"\n }\n}'); // eslint-disable-line + expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain( + '{\n "statusCode": 200,\n "headers": {\n "Content-Type": "application/json"\n },\n "body": {\n "result": true,\n "message": "Whatever"\n }\n}' + ); // eslint-disable-line }); }); @@ -608,7 +578,9 @@ describe('AwsInvokeLocal', () => { awsInvokeLocal.invokeLocalNodeJs('handlerWithSuccess', 'withMessageByLambdaProxy'); - expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('{\n "statusCode": 200,\n "headers": {\n "Content-Type": "application/json"\n },\n "body": {\n "result": true,\n "message": "Whatever"\n }\n}'); // eslint-disable-line + expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain( + '{\n "statusCode": 200,\n "headers": {\n "Content-Type": "application/json"\n },\n "body": {\n "result": true,\n "message": "Whatever"\n }\n}' + ); // eslint-disable-line }); }); @@ -656,8 +628,9 @@ describe('AwsInvokeLocal', () => { it('should throw when module loading error', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - expect(() => awsInvokeLocal.invokeLocalNodeJs('fixture/handlerWithLoadingError', - 'anyMethod')).to.throw(/Exception encountered when loading/); + expect(() => + awsInvokeLocal.invokeLocalNodeJs('fixture/handlerWithLoadingError', 'anyMethod') + ).to.throw(/Exception encountered when loading/); }); }); @@ -679,10 +652,8 @@ describe('AwsInvokeLocal', () => { describe('with return', () => { it('should exit with error exit code', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs( - 'fixture/asyncHandlerWithSuccess', - 'withError' - ) + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withError') .then(() => { expect(process.exitCode).to.be.equal(1); }); @@ -691,10 +662,8 @@ describe('AwsInvokeLocal', () => { it('should succeed if succeed', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs( - 'fixture/asyncHandlerWithSuccess', - 'withMessage' - ) + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withMessage') .then(() => { expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"Succeed"'); }); @@ -705,15 +674,16 @@ describe('AwsInvokeLocal', () => { it('success should trigger one response', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs( - 'fixture/asyncHandlerWithSuccess', - 'withMessageByDone' - ) + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withMessageByDone') .then(() => { expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"Succeed"'); - const calls = serverless.cli.consoleLog.getCalls().reduce((acc, call) => ( - _.includes(call.args[0], 'Succeed') ? [call].concat(acc) : acc - ), []); + const calls = serverless.cli.consoleLog + .getCalls() + .reduce( + (acc, call) => (_.includes(call.args[0], 'Succeed') ? [call].concat(acc) : acc), + [] + ); expect(calls.length).to.equal(1); }); }); @@ -721,10 +691,8 @@ describe('AwsInvokeLocal', () => { it('error should trigger one response', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs( - 'fixture/asyncHandlerWithSuccess', - 'withErrorByDone' - ) + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withErrorByDone') .then(() => { expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"failed"'); expect(process.exitCode).to.be.equal(1); @@ -736,15 +704,16 @@ describe('AwsInvokeLocal', () => { it('should succeed once if succeed if by callback', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs( - 'fixture/asyncHandlerWithSuccess', - 'withMessageByCallback' - ) + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withMessageByCallback') .then(() => { expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"Succeed"'); - const calls = serverless.cli.consoleLog.getCalls().reduce((acc, call) => ( - _.includes(call.args[0], 'Succeed') ? [call].concat(acc) : acc - ), []); + const calls = serverless.cli.consoleLog + .getCalls() + .reduce( + (acc, call) => (_.includes(call.args[0], 'Succeed') ? [call].concat(acc) : acc), + [] + ); expect(calls.length).to.equal(1); }); }); @@ -754,15 +723,16 @@ describe('AwsInvokeLocal', () => { it('should succeed once if succeed if by callback', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs( - 'fixture/asyncHandlerWithSuccess', - 'withMessageAndDelayByCallback' - ) + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withMessageAndDelayByCallback') .then(() => { expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"Succeed"'); - const calls = serverless.cli.consoleLog.getCalls().reduce((acc, call) => ( - _.includes(call.args[0], 'Succeed') ? [call].concat(acc) : acc - ), []); + const calls = serverless.cli.consoleLog + .getCalls() + .reduce( + (acc, call) => (_.includes(call.args[0], 'Succeed') ? [call].concat(acc) : acc), + [] + ); expect(calls.length).to.equal(1); }); }); @@ -772,12 +742,12 @@ describe('AwsInvokeLocal', () => { it('should succeed if succeed', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs( - 'fixture/asyncHandlerWithSuccess', - 'withMessageByLambdaProxy' - ) + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withMessageByLambdaProxy') .then(() => { - expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('{\n "statusCode": 200,\n "headers": {\n "Content-Type": "application/json"\n },\n "body": {\n "result": true,\n "message": "Whatever"\n }\n}'); // eslint-disable-line + expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain( + '{\n "statusCode": 200,\n "headers": {\n "Content-Type": "application/json"\n },\n "body": {\n "result": true,\n "message": "Whatever"\n }\n}' + ); // eslint-disable-line }); }); }); @@ -786,10 +756,8 @@ describe('AwsInvokeLocal', () => { it('should become lower over time', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs( - 'fixture/asyncHandlerWithSuccess', - 'withRemainingTime' - ) + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withRemainingTime') .then(() => { const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); expect(remainingTimes.start).to.be.above(remainingTimes.stop); @@ -800,10 +768,8 @@ describe('AwsInvokeLocal', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; awsInvokeLocal.serverless.service.provider.timeout = 5; - return awsInvokeLocal.invokeLocalNodeJs( - 'fixture/asyncHandlerWithSuccess', - 'withRemainingTime' - ) + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithSuccess', 'withRemainingTime') .then(() => { const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); expect(remainingTimes.start).to.match(/\d+/); @@ -828,12 +794,12 @@ describe('AwsInvokeLocal', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; awsInvokeLocal.options.extraServicePath = 'fixture'; - return awsInvokeLocal.invokeLocalNodeJs( - 'asyncHandlerWithSuccess', - 'withMessageByLambdaProxy' - ) + return awsInvokeLocal + .invokeLocalNodeJs('asyncHandlerWithSuccess', 'withMessageByLambdaProxy') .then(() => { - expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('{\n "statusCode": 200,\n "headers": {\n "Content-Type": "application/json"\n },\n "body": {\n "result": true,\n "message": "Whatever"\n }\n}'); // eslint-disable-line + expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain( + '{\n "statusCode": 200,\n "headers": {\n "Content-Type": "application/json"\n },\n "body": {\n "result": true,\n "message": "Whatever"\n }\n}' + ); // eslint-disable-line }); }); }); @@ -841,7 +807,8 @@ describe('AwsInvokeLocal', () => { it('should exit with error exit code', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs('fixture/asyncHandlerWithError', 'withError') + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithError', 'withError') .then(() => { expect(process.exitCode).to.be.equal(1); }); @@ -850,7 +817,8 @@ describe('AwsInvokeLocal', () => { it('should log Error instance when called back', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs('fixture/asyncHandlerWithError', 'withError') + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithError', 'withError') .then(() => { expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"errorMessage": "failed"'); expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"errorType": "Error"'); @@ -860,7 +828,8 @@ describe('AwsInvokeLocal', () => { it('should log error', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs('fixture/asyncHandlerWithError', 'withMessage') + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithError', 'withMessage') .then(() => { expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"errorMessage": "failed"'); }); @@ -869,7 +838,8 @@ describe('AwsInvokeLocal', () => { it('should log error when error is returned', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalNodeJs('fixture/asyncHandlerWithError', 'returnsError') + return awsInvokeLocal + .invokeLocalNodeJs('fixture/asyncHandlerWithError', 'returnsError') .then(() => { expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"errorMessage": "failed"'); }); @@ -893,7 +863,7 @@ describe('AwsInvokeLocal', () => { afterEach(afterEachCallback); describe('context.remainingTimeInMillis', () => { - it('should become lower over time', function () { + it('should become lower over time', function() { // skipping in CI for now due to handler loading issues // in the Windows machine on Travis CI if (process.env.CI) { @@ -902,18 +872,20 @@ describe('AwsInvokeLocal', () => { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalPython( - 'python2.7', - 'fixture/handler', - 'withRemainingTime').then(() => { - const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); - expect(remainingTimes.start).to.be.above(remainingTimes.stop); - }, error => { - if (error.code === 'ENOENT' && error.path === 'python2') { - skipWithNotice(this, 'Python runtime is not installed', afterEachCallback); + return awsInvokeLocal + .invokeLocalPython('python2.7', 'fixture/handler', 'withRemainingTime') + .then( + () => { + const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); + expect(remainingTimes.start).to.be.above(remainingTimes.stop); + }, + error => { + if (error.code === 'ENOENT' && error.path === 'python2') { + skipWithNotice(this, 'Python runtime is not installed', afterEachCallback); + } + throw error; } - throw error; - }); + ); }); }); }); @@ -940,40 +912,42 @@ describe('AwsInvokeLocal', () => { afterEach(afterEachCallback); describe('context.remainingTimeInMillis', () => { - it('should become lower over time', function () { + it('should become lower over time', function() { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalRuby( - 'ruby', - 'fixture/handler', - 'withRemainingTime').then(() => { + return awsInvokeLocal.invokeLocalRuby('ruby', 'fixture/handler', 'withRemainingTime').then( + () => { const remainingTimes = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); expect(remainingTimes.start).to.be.above(remainingTimes.stop); - }, error => { + }, + error => { if (error.code === 'ENOENT' && error.path === 'ruby') { skipWithNotice(this, 'Ruby runtime is not installed', afterEachCallback); } throw error; - }); + } + ); }); }); describe('calling a class method', () => { - it('should execute', function () { + it('should execute', function() { awsInvokeLocal.serverless.config.servicePath = __dirname; - return awsInvokeLocal.invokeLocalRuby( - 'ruby', - 'fixture/handler', - 'MyModule::MyClass.my_class_method').then(() => { - const result = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); - expect(result.foo).to.eq('bar'); - }, error => { - if (error.code === 'ENOENT' && error.path === 'ruby') { - skipWithNotice(this, 'Ruby runtime is not installed', afterEachCallback); + return awsInvokeLocal + .invokeLocalRuby('ruby', 'fixture/handler', 'MyModule::MyClass.my_class_method') + .then( + () => { + const result = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); + expect(result.foo).to.eq('bar'); + }, + error => { + if (error.code === 'ENOENT' && error.path === 'ruby') { + skipWithNotice(this, 'Ruby runtime is not installed', afterEachCallback); + } + throw error; } - throw error; - }); + ); }); }); }); @@ -1033,17 +1007,13 @@ describe('AwsInvokeLocal', () => { }); it('spawns java process with correct arguments', () => - awsInvokeLocalMocked.callJavaBridge( - __dirname, - 'com.serverless.Handler', - 'handleRequest', - '{}' - ).then(() => { - expect(writeChildStub.calledOnce).to.be.equal(true); - expect(endChildStub.calledOnce).to.be.equal(true); - expect(writeChildStub.calledWithExactly('{}')).to.be.equal(true); - }) - ); + awsInvokeLocalMocked + .callJavaBridge(__dirname, 'com.serverless.Handler', 'handleRequest', '{}') + .then(() => { + expect(writeChildStub.calledOnce).to.be.equal(true); + expect(endChildStub.calledOnce).to.be.equal(true); + expect(writeChildStub.calledWithExactly('{}')).to.be.equal(true); + })); }); describe('#invokeLocalJava()', () => { @@ -1071,30 +1041,27 @@ describe('AwsInvokeLocal', () => { }); it('should invoke callJavaBridge when bridge is built', () => - awsInvokeLocal.invokeLocalJava( - 'java', - 'com.serverless.Handler', - 'handleRequest', - __dirname, - {} - ).then(() => { - expect(callJavaBridgeStub.calledOnce).to.be.equal(true); - expect(callJavaBridgeStub.calledWithExactly( - __dirname, - 'com.serverless.Handler', - 'handleRequest', - JSON.stringify({ - event: {}, - context: { - name: 'hello', - version: 'LATEST', - logGroupName: '/aws/lambda/hello', - timeout: 4, - }, - }) - )).to.be.equal(true); - }) - ); + awsInvokeLocal + .invokeLocalJava('java', 'com.serverless.Handler', 'handleRequest', __dirname, {}) + .then(() => { + expect(callJavaBridgeStub.calledOnce).to.be.equal(true); + expect( + callJavaBridgeStub.calledWithExactly( + __dirname, + 'com.serverless.Handler', + 'handleRequest', + JSON.stringify({ + event: {}, + context: { + name: 'hello', + version: 'LATEST', + logGroupName: '/aws/lambda/hello', + timeout: 4, + }, + }) + ) + ).to.be.equal(true); + })); describe('when attempting to build the Java bridge', () => { let awsInvokeLocalMocked; @@ -1141,31 +1108,28 @@ describe('AwsInvokeLocal', () => { delete require.cache[require.resolve('child_process')]; }); - it('if it\'s not present yet', () => - awsInvokeLocalMocked.invokeLocalJava( - 'java', - 'com.serverless.Handler', - 'handleRequest', - __dirname, - {} - ).then(() => { - expect(callJavaBridgeMockedStub.calledOnce).to.be.equal(true); - expect(callJavaBridgeMockedStub.calledWithExactly( - __dirname, - 'com.serverless.Handler', - 'handleRequest', - JSON.stringify({ - event: {}, - context: { - name: 'hello', - version: 'LATEST', - logGroupName: '/aws/lambda/hello', - timeout: 4, - }, - }) - )).to.be.equal(true); - }) - ); + it("if it's not present yet", () => + awsInvokeLocalMocked + .invokeLocalJava('java', 'com.serverless.Handler', 'handleRequest', __dirname, {}) + .then(() => { + expect(callJavaBridgeMockedStub.calledOnce).to.be.equal(true); + expect( + callJavaBridgeMockedStub.calledWithExactly( + __dirname, + 'com.serverless.Handler', + 'handleRequest', + JSON.stringify({ + event: {}, + context: { + name: 'hello', + version: 'LATEST', + logGroupName: '/aws/lambda/hello', + timeout: 4, + }, + }) + ) + ).to.be.equal(true); + })); }); }); @@ -1211,9 +1175,9 @@ describe('AwsInvokeLocal', () => { providerVar: 'providerValue', }; awsInvokeLocalMocked.options = { - stage: 'dev', - function: 'first', - functionObj: { + 'stage': 'dev', + 'function': 'first', + 'functionObj': { handler: 'handler.hello', name: 'hello', timeout: 4, @@ -1222,17 +1186,15 @@ describe('AwsInvokeLocal', () => { functionVar: 'functionValue', }, }, - data: {}, - env: 'commandLineEnvVar=commandLineEnvVarValue', + 'data': {}, + 'env': 'commandLineEnvVar=commandLineEnvVarValue', 'docker-arg': '-p 9292:9292', }; - pluginMangerSpawnStub = sinon - .stub(serverless.pluginManager, 'spawn'); + pluginMangerSpawnStub = sinon.stub(serverless.pluginManager, 'spawn'); pluginMangerSpawnPackageStub = pluginMangerSpawnStub.withArgs('package').resolves(); }); - afterEach(() => { delete require.cache[require.resolve('./index')]; delete require.cache[require.resolve('child_process')]; @@ -1246,37 +1208,39 @@ describe('AwsInvokeLocal', () => { expect(pluginMangerSpawnPackageStub.calledOnce).to.equal(true); expect(spawnStub.getCall(0).args).to.deep.equal(['docker', ['version']]); - expect(spawnStub.getCall(1).args).to.deep.equal(['docker', - ['images', '-q', 'lambci/lambda:nodejs10.x']]); - expect(spawnStub.getCall(2).args).to.deep.equal(['docker', - ['pull', 'lambci/lambda:nodejs10.x']]); - expect(spawnStub.getCall(3).args).to.deep.equal(['docker', [ - 'build', - '-t', - 'sls-docker', - 'servicePath', - '-f', - dockerfilePath, - ]]); - expect(spawnStub.getCall(4).args).to.deep.equal(['docker', [ - 'run', - '--rm', - '-v', - 'servicePath:/var/task', - '--env', - 'providerVar=providerValue', - '--env', - 'functionVar=functionValue', - '--env', - 'commandLineEnvVar=commandLineEnvVarValue', - '-p', - '9292:9292', - 'sls-docker', - 'handler.hello', - '{}', - ]]); - }) - ); + expect(spawnStub.getCall(1).args).to.deep.equal([ + 'docker', + ['images', '-q', 'lambci/lambda:nodejs10.x'], + ]); + expect(spawnStub.getCall(2).args).to.deep.equal([ + 'docker', + ['pull', 'lambci/lambda:nodejs10.x'], + ]); + expect(spawnStub.getCall(3).args).to.deep.equal([ + 'docker', + ['build', '-t', 'sls-docker', 'servicePath', '-f', dockerfilePath], + ]); + expect(spawnStub.getCall(4).args).to.deep.equal([ + 'docker', + [ + 'run', + '--rm', + '-v', + 'servicePath:/var/task', + '--env', + 'providerVar=providerValue', + '--env', + 'functionVar=functionValue', + '--env', + 'commandLineEnvVar=commandLineEnvVarValue', + '-p', + '9292:9292', + 'sls-docker', + 'handler.hello', + '{}', + ], + ]); + })); }); describe('#getEnvVarsFromOptions', () => { diff --git a/lib/plugins/aws/lib/getServiceState.test.js b/lib/plugins/aws/lib/getServiceState.test.js index 88ef7f4e8..f7540287f 100644 --- a/lib/plugins/aws/lib/getServiceState.test.js +++ b/lib/plugins/aws/lib/getServiceState.test.js @@ -24,8 +24,7 @@ describe('#getServiceState()', () => { awsPlugin.options = options; Object.assign(awsPlugin, getServiceState); - readFileSyncStub = sinon - .stub(awsPlugin.serverless.utils, 'readFileSync').returns(); + readFileSyncStub = sinon.stub(awsPlugin.serverless.utils, 'readFileSync').returns(); }); afterEach(() => { diff --git a/lib/plugins/aws/lib/monitorStack.js b/lib/plugins/aws/lib/monitorStack.js index 5f7186d90..332741527 100644 --- a/lib/plugins/aws/lib/monitorStack.js +++ b/lib/plugins/aws/lib/monitorStack.js @@ -10,11 +10,7 @@ module.exports = { if (cfData === 'alreadyCreated') return BbPromise.bind(this).then(BbPromise.resolve()); // Monitor stack creation/update/removal - const validStatuses = [ - 'CREATE_COMPLETE', - 'UPDATE_COMPLETE', - 'DELETE_COMPLETE', - ]; + const validStatuses = ['CREATE_COMPLETE', 'UPDATE_COMPLETE', 'DELETE_COMPLETE']; const loggedEvents = []; const region = this.provider.getRegion(); const baseCfUrl = `https://${region}.console.aws.amazon.com/cloudformation/home`; @@ -30,30 +26,31 @@ module.exports = { return new BbPromise((resolve, reject) => { async.whilst( - () => (validStatuses.indexOf(stackStatus) === -1), - (callback) => { + () => validStatuses.indexOf(stackStatus) === -1, + callback => { setTimeout(() => { const params = { StackName: cfData.StackId, }; - return this.provider.request('CloudFormation', - 'describeStackEvents', - params) - .then((data) => { + return this.provider + .request('CloudFormation', 'describeStackEvents', params) + .then(data => { const stackEvents = data.StackEvents; // look through all the stack events and find the first relevant // event which is a "Stack" event and has a CREATE, UPDATE or DELETE status - const firstRelevantEvent = stackEvents.find((event) => { + const firstRelevantEvent = stackEvents.find(event => { const isStack = 'AWS::CloudFormation::Stack'; const updateIsInProgress = 'UPDATE_IN_PROGRESS'; const createIsInProgress = 'CREATE_IN_PROGRESS'; const deleteIsInProgress = 'DELETE_IN_PROGRESS'; - return event.ResourceType === isStack - && (event.ResourceStatus === updateIsInProgress - || event.ResourceStatus === createIsInProgress - || event.ResourceStatus === deleteIsInProgress); + return ( + event.ResourceType === isStack && + (event.ResourceStatus === updateIsInProgress || + event.ResourceStatus === createIsInProgress || + event.ResourceStatus === deleteIsInProgress) + ); }); // set the date some time before the first found @@ -65,21 +62,25 @@ module.exports = { } // Loop through stack events - stackEvents.reverse().forEach((event) => { - const eventInRange = (monitoredSince <= event.Timestamp); - const eventNotLogged = (loggedEvents.indexOf(event.EventId) === -1); + stackEvents.reverse().forEach(event => { + const eventInRange = monitoredSince <= event.Timestamp; + const eventNotLogged = loggedEvents.indexOf(event.EventId) === -1; let eventStatus = event.ResourceStatus || null; if (eventInRange && eventNotLogged) { // Keep track of stack status - if (event.ResourceType === 'AWS::CloudFormation::Stack' - && event.StackName === event.LogicalResourceId) { + if ( + event.ResourceType === 'AWS::CloudFormation::Stack' && + event.StackName === event.LogicalResourceId + ) { stackStatus = eventStatus; } // Keep track of first failed event - if (eventStatus - && (eventStatus.endsWith('FAILED') - || eventStatus === 'UPDATE_ROLLBACK_IN_PROGRESS') - && stackLatestError === null) { + if ( + eventStatus && + (eventStatus.endsWith('FAILED') || + eventStatus === 'UPDATE_ROLLBACK_IN_PROGRESS') && + stackLatestError === null + ) { stackLatestError = event; } // Log stack events @@ -103,11 +104,13 @@ module.exports = { } }); // Handle stack create/update/delete failures - if ((stackLatestError && !this.options.verbose) - || (stackStatus - && (stackStatus.endsWith('ROLLBACK_COMPLETE') - || stackStatus === 'DELETE_FAILED') - && this.options.verbose)) { + if ( + (stackLatestError && !this.options.verbose) || + (stackStatus && + (stackStatus.endsWith('ROLLBACK_COMPLETE') || + stackStatus === 'DELETE_FAILED') && + this.options.verbose) + ) { // empty console.log for a prettier output if (!this.options.verbose) this.serverless.cli.consoleLog(''); this.serverless.cli.log('Operation failed!'); @@ -120,7 +123,7 @@ module.exports = { // Trigger next monitoring action return callback(); }) - .catch((e) => { + .catch(e => { if (action === 'removal' && e.message.endsWith('does not exist')) { // empty console.log for a prettier output if (!this.options.verbose) this.serverless.cli.consoleLog(''); @@ -137,7 +140,8 @@ module.exports = { if (!this.options.verbose) this.serverless.cli.consoleLog(''); this.serverless.cli.log(`Stack ${action} finished...`); resolve(stackStatus); - }); + } + ); }); }, }; diff --git a/lib/plugins/aws/lib/monitorStack.test.js b/lib/plugins/aws/lib/monitorStack.test.js index 858851929..427631acb 100644 --- a/lib/plugins/aws/lib/monitorStack.test.js +++ b/lib/plugins/aws/lib/monitorStack.test.js @@ -67,15 +67,13 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(0).resolves(updateStartEvent); describeStackEventsStub.onCall(1).resolves(updateFinishedEvent); - return awsPlugin.monitorStack('create', cfDataMock, 10).then((stackStatus) => { + return awsPlugin.monitorStack('create', cfDataMock, 10).then(stackStatus => { expect(describeStackEventsStub.callCount).to.be.equal(2); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(stackStatus).to.be.equal('CREATE_COMPLETE'); awsPlugin.provider.request.restore(); }); @@ -114,15 +112,13 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(0).resolves(updateStartEvent); describeStackEventsStub.onCall(1).resolves(updateFinishedEvent); - return awsPlugin.monitorStack('update', cfDataMock, 10).then((stackStatus) => { + return awsPlugin.monitorStack('update', cfDataMock, 10).then(stackStatus => { expect(describeStackEventsStub.callCount).to.be.equal(2); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(stackStatus).to.be.equal('UPDATE_COMPLETE'); awsPlugin.provider.request.restore(); }); @@ -161,15 +157,13 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(0).resolves(updateStartEvent); describeStackEventsStub.onCall(1).resolves(updateFinishedEvent); - return awsPlugin.monitorStack('removal', cfDataMock, 10).then((stackStatus) => { + return awsPlugin.monitorStack('removal', cfDataMock, 10).then(stackStatus => { expect(describeStackEventsStub.callCount).to.be.equal(2); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(stackStatus).to.be.equal('DELETE_COMPLETE'); awsPlugin.provider.request.restore(); }); @@ -221,15 +215,13 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(1).resolves(nestedStackEvent); describeStackEventsStub.onCall(2).resolves(updateFinishedEvent); - return awsPlugin.monitorStack('create', cfDataMock, 10).then((stackStatus) => { + return awsPlugin.monitorStack('create', cfDataMock, 10).then(stackStatus => { expect(describeStackEventsStub.callCount).to.be.equal(3); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(stackStatus).to.be.equal('CREATE_COMPLETE'); awsPlugin.provider.request.restore(); }); @@ -281,15 +273,13 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(1).resolves(nestedStackEvent); describeStackEventsStub.onCall(2).resolves(updateFinishedEvent); - return awsPlugin.monitorStack('update', cfDataMock, 10).then((stackStatus) => { + return awsPlugin.monitorStack('update', cfDataMock, 10).then(stackStatus => { expect(describeStackEventsStub.callCount).to.be.equal(3); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(stackStatus).to.be.equal('UPDATE_COMPLETE'); awsPlugin.provider.request.restore(); }); @@ -341,15 +331,13 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(1).resolves(nestedStackEvent); describeStackEventsStub.onCall(2).resolves(updateFinishedEvent); - return awsPlugin.monitorStack('removal', cfDataMock, 10).then((stackStatus) => { + return awsPlugin.monitorStack('removal', cfDataMock, 10).then(stackStatus => { expect(describeStackEventsStub.callCount).to.be.equal(3); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(stackStatus).to.be.equal('DELETE_COMPLETE'); awsPlugin.provider.request.restore(); }); @@ -379,15 +367,13 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(0).resolves(updateStartEvent); describeStackEventsStub.onCall(1).rejects(stackNotFoundError); - return awsPlugin.monitorStack('removal', cfDataMock, 10).then((stackStatus) => { + return awsPlugin.monitorStack('removal', cfDataMock, 10).then(stackStatus => { expect(describeStackEventsStub.callCount).to.be.equal(2); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(stackStatus).to.be.equal('DELETE_COMPLETE'); awsPlugin.provider.request.restore(); }); @@ -453,19 +439,17 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(2).resolves(updateRollbackEvent); describeStackEventsStub.onCall(3).resolves(updateRollbackComplete); - return awsPlugin.monitorStack('update', cfDataMock, 10).catch((e) => { + return awsPlugin.monitorStack('update', cfDataMock, 10).catch(e => { let errorMessage = 'An error occurred: '; errorMessage += 'mochaS3 - Bucket already exists.'; expect(e.name).to.be.equal('ServerlessError'); expect(e.message).to.be.equal(errorMessage); expect(describeStackEventsStub.callCount).to.be.equal(4); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsPlugin.provider.request.restore(); }); }); @@ -516,13 +500,11 @@ describe('monitorStack', () => { return awsPlugin.monitorStack('update', cfDataMock, 10).then(() => { expect(describeStackEventsStub.callCount).to.be.equal(3); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsPlugin.provider.request.restore(); }); }); @@ -538,16 +520,14 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(0).rejects(failedDescribeStackEvents); - return awsPlugin.monitorStack('update', cfDataMock, 10).catch((e) => { + return awsPlugin.monitorStack('update', cfDataMock, 10).catch(e => { expect(e.message).to.be.equal('Something went wrong.'); expect(describeStackEventsStub.callCount).to.be.equal(1); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsPlugin.provider.request.restore(); }); }); @@ -608,20 +588,18 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(2).resolves(updateRollbackEvent); describeStackEventsStub.onCall(3).resolves(updateRollbackFailedEvent); - return awsPlugin.monitorStack('update', cfDataMock, 10).catch((e) => { + return awsPlugin.monitorStack('update', cfDataMock, 10).catch(e => { let errorMessage = 'An error occurred: '; errorMessage += 'mochaS3 - Bucket already exists.'; expect(e.name).to.be.equal('ServerlessError'); expect(e.message).to.be.equal(errorMessage); // callCount is 2 because Serverless will immediately exits and shows the error expect(describeStackEventsStub.callCount).to.be.equal(2); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsPlugin.provider.request.restore(); }); }); @@ -686,103 +664,102 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(2).resolves(deleteItemFailedEvent); describeStackEventsStub.onCall(3).resolves(deleteFailedEvent); - return awsPlugin.monitorStack('removal', cfDataMock, 10).catch((e) => { + return awsPlugin.monitorStack('removal', cfDataMock, 10).catch(e => { let errorMessage = 'An error occurred: '; errorMessage += 'mochaLambda - You are not authorized to perform this operation.'; expect(e.name).to.be.equal('ServerlessError'); expect(e.message).to.be.equal(errorMessage); // callCount is 2 because Serverless will immediately exits and shows the error expect(describeStackEventsStub.callCount).to.be.equal(3); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsPlugin.provider.request.restore(); }); }); - it('should throw an error if stack status is DELETE_FAILED and should output all ' + - 'stack events information with the --verbose option', () => { - awsPlugin.options.verbose = true; - const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request'); - const cfDataMock = { - StackId: 'new-service-dev', - }; - const deleteStartEvent = { - StackEvents: [ - { - EventId: '1a2b3c4d', - StackName: 'new-service-dev', - LogicalResourceId: 'new-service-dev', - ResourceType: 'AWS::CloudFormation::Stack', - Timestamp: new Date(), - ResourceStatus: 'DELETE_IN_PROGRESS', - }, - ], - }; - const deleteItemEvent = { - StackEvents: [ - { - EventId: '1e2f3g4h', - StackName: 'new-service-dev', - LogicalResourceId: 'mochaLambda', - ResourceType: 'AWS::Lambda::Function', - Timestamp: new Date(), - ResourceStatus: 'DELETE_IN_PROGRESS', - }, - ], - }; - const deleteItemFailedEvent = { - StackEvents: [ - { - EventId: '1i2j3k4l', - StackName: 'new-service-dev', - LogicalResourceId: 'mochaLambda', - ResourceType: 'AWS::Lambda::Function', - Timestamp: new Date(), - ResourceStatus: 'DELETE_FAILED', - ResourceStatusReason: 'You are not authorized to perform this operation', - }, - ], - }; - const deleteFailedEvent = { - StackEvents: [ - { - EventId: '1m2n3o4p', - StackName: 'new-service-dev', - LogicalResourceId: 'new-service-dev', - ResourceType: 'AWS::CloudFormation::Stack', - Timestamp: new Date(), - ResourceStatus: 'DELETE_FAILED', - }, - ], - }; + it( + 'should throw an error if stack status is DELETE_FAILED and should output all ' + + 'stack events information with the --verbose option', + () => { + awsPlugin.options.verbose = true; + const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request'); + const cfDataMock = { + StackId: 'new-service-dev', + }; + const deleteStartEvent = { + StackEvents: [ + { + EventId: '1a2b3c4d', + StackName: 'new-service-dev', + LogicalResourceId: 'new-service-dev', + ResourceType: 'AWS::CloudFormation::Stack', + Timestamp: new Date(), + ResourceStatus: 'DELETE_IN_PROGRESS', + }, + ], + }; + const deleteItemEvent = { + StackEvents: [ + { + EventId: '1e2f3g4h', + StackName: 'new-service-dev', + LogicalResourceId: 'mochaLambda', + ResourceType: 'AWS::Lambda::Function', + Timestamp: new Date(), + ResourceStatus: 'DELETE_IN_PROGRESS', + }, + ], + }; + const deleteItemFailedEvent = { + StackEvents: [ + { + EventId: '1i2j3k4l', + StackName: 'new-service-dev', + LogicalResourceId: 'mochaLambda', + ResourceType: 'AWS::Lambda::Function', + Timestamp: new Date(), + ResourceStatus: 'DELETE_FAILED', + ResourceStatusReason: 'You are not authorized to perform this operation', + }, + ], + }; + const deleteFailedEvent = { + StackEvents: [ + { + EventId: '1m2n3o4p', + StackName: 'new-service-dev', + LogicalResourceId: 'new-service-dev', + ResourceType: 'AWS::CloudFormation::Stack', + Timestamp: new Date(), + ResourceStatus: 'DELETE_FAILED', + }, + ], + }; - describeStackEventsStub.onCall(0).resolves(deleteStartEvent); - describeStackEventsStub.onCall(1).resolves(deleteItemEvent); - describeStackEventsStub.onCall(2).resolves(deleteItemFailedEvent); - describeStackEventsStub.onCall(3).resolves(deleteFailedEvent); + describeStackEventsStub.onCall(0).resolves(deleteStartEvent); + describeStackEventsStub.onCall(1).resolves(deleteItemEvent); + describeStackEventsStub.onCall(2).resolves(deleteItemFailedEvent); + describeStackEventsStub.onCall(3).resolves(deleteFailedEvent); - return awsPlugin.monitorStack('removal', cfDataMock, 10).catch((e) => { - let errorMessage = 'An error occurred: '; - errorMessage += 'mochaLambda - You are not authorized to perform this operation.'; - expect(e.name).to.be.equal('ServerlessError'); - expect(e.message).to.be.equal(errorMessage); - // callCount is 2 because Serverless will immediately exits and shows the error - expect(describeStackEventsStub.callCount).to.be.equal(4); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { - StackName: cfDataMock.StackId, - } - )).to.be.equal(true); - awsPlugin.provider.request.restore(); - }); - }); + return awsPlugin.monitorStack('removal', cfDataMock, 10).catch(e => { + let errorMessage = 'An error occurred: '; + errorMessage += 'mochaLambda - You are not authorized to perform this operation.'; + expect(e.name).to.be.equal('ServerlessError'); + expect(e.message).to.be.equal(errorMessage); + // callCount is 2 because Serverless will immediately exits and shows the error + expect(describeStackEventsStub.callCount).to.be.equal(4); + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { + StackName: cfDataMock.StackId, + }) + ).to.be.equal(true); + awsPlugin.provider.request.restore(); + }); + } + ); it('should record an error and fail if status is UPDATE_ROLLBACK_IN_PROGRESS', () => { const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request'); @@ -828,20 +805,18 @@ describe('monitorStack', () => { describeStackEventsStub.onCall(1).resolves(updateRollbackEvent); describeStackEventsStub.onCall(2).resolves(updateRollbackCompleteEvent); - return awsPlugin.monitorStack('update', cfDataMock, 10).catch((e) => { + return awsPlugin.monitorStack('update', cfDataMock, 10).catch(e => { let errorMessage = 'An error occurred: '; errorMessage += 'mocha - Export is in use.'; expect(e.name).to.be.equal('ServerlessError'); expect(e.message).to.be.equal(errorMessage); // callCount is 2 because Serverless will immediately exits and shows the error expect(describeStackEventsStub.callCount).to.be.equal(2); - expect(describeStackEventsStub.calledWithExactly( - 'CloudFormation', - 'describeStackEvents', - { + expect( + describeStackEventsStub.calledWithExactly('CloudFormation', 'describeStackEvents', { StackName: cfDataMock.StackId, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsPlugin.provider.request.restore(); }); }); diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index 95c2a41ed..d77336cd9 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -23,7 +23,6 @@ const _ = require('lodash'); * That is to say that they are easier to change together. */ module.exports = { - // General normalizeName(name) { return `${_.upperFirst(name)}`; @@ -46,8 +45,10 @@ module.exports = { // Stack getStackName() { - if (this.provider.serverless.service.provider.stackName && - _.isString(this.provider.serverless.service.provider.stackName)) { + if ( + this.provider.serverless.service.provider.stackName && + _.isString(this.provider.serverless.service.provider.stackName) + ) { return `${this.provider.serverless.service.provider.stackName}`; } return `${this.provider.serverless.service.service}-${this.provider.getStage()}`; @@ -100,7 +101,8 @@ module.exports = { // Policy getPolicyName() { - return { // TODO should probably have name ordered and altered as above - see AWS docs + return { + // TODO should probably have name ordered and altered as above - see AWS docs 'Fn::Join': [ '-', [ @@ -123,9 +125,7 @@ module.exports = { // Lambda getNormalizedFunctionName(functionName) { - return this.normalizeName(functionName - .replace(/-/g, 'Dash') - .replace(/_/g, 'Underscore')); + return this.normalizeName(functionName.replace(/-/g, 'Dash').replace(/_/g, 'Underscore')); }, extractLambdaNameFromArn(functionArn) { return functionArn.substring(functionArn.lastIndexOf(':') + 1); @@ -144,15 +144,19 @@ module.exports = { return `${this.getNormalizedFunctionName(layerName)}LambdaLayer`; }, getLambdaLayerPermissionLogicalId(layerName, account) { - return `${this.getNormalizedFunctionName(layerName)}${ - account.replace('*', 'Wild')}LambdaLayerPermission`; + return `${this.getNormalizedFunctionName(layerName)}${account.replace( + '*', + 'Wild' + )}LambdaLayerPermission`; }, getLambdaLogicalIdRegex() { return /LambdaFunction$/; }, getLambdaVersionLogicalId(functionName, sha) { - return `${this.getNormalizedFunctionName(functionName)}LambdaVersion${sha - .replace(/[^0-9a-z]/gi, '')}`; + return `${this.getNormalizedFunctionName(functionName)}LambdaVersion${sha.replace( + /[^0-9a-z]/gi, + '' + )}`; }, getLambdaVersionOutputLogicalId(functionName) { return `${this.getLambdaLogicalId(functionName)}QualifiedArn`; @@ -163,8 +167,10 @@ module.exports = { // Websockets API getWebsocketsApiName() { - if (this.provider.serverless.service.provider.websocketsApiName && - _.isString(this.provider.serverless.service.provider.websocketsApiName)) { + if ( + this.provider.serverless.service.provider.websocketsApiName && + _.isString(this.provider.serverless.service.provider.websocketsApiName) + ) { return `${this.provider.serverless.service.provider.websocketsApiName}`; } return `${this.provider.getStage()}-${this.provider.serverless.service.service}-websockets`; @@ -216,8 +222,10 @@ module.exports = { // API Gateway getApiGatewayName() { - if (this.provider.serverless.service.provider.apiName && - _.isString(this.provider.serverless.service.provider.apiName)) { + if ( + this.provider.serverless.service.provider.apiName && + _.isString(this.provider.serverless.service.provider.apiName) + ) { return `${this.provider.serverless.service.provider.apiName}`; } return `${this.provider.getStage()}-${this.provider.serverless.service.service}`; @@ -235,9 +243,10 @@ module.exports = { return `${this.getNormalizedAuthorizerName(functionName)}ApiGatewayAuthorizer`; }, normalizePath(resourcePath) { - return resourcePath.split('/').map( - this.normalizePathPart.bind(this) - ).join(''); + return resourcePath + .split('/') + .map(this.normalizePathPart.bind(this)) + .join(''); }, getResourceLogicalId(resourcePath) { return `ApiGatewayResource${this.normalizePath(resourcePath)}`; @@ -255,8 +264,10 @@ module.exports = { return `${this.getMethodLogicalId(resourceId, methodName)}Validator`; }, getModelLogicalId(resourceId, methodName, contentType) { - return `${this.getMethodLogicalId(resourceId, methodName)}${_.startCase( - contentType).replace(' ', '')}Model`; + return `${this.getMethodLogicalId(resourceId, methodName)}${_.startCase(contentType).replace( + ' ', + '' + )}Model`; }, getApiKeyLogicalId(apiKeyNumber, apiKeyName) { if (apiKeyName) { @@ -324,11 +335,9 @@ module.exports = { // Stream getStreamLogicalId(functionName, streamType, streamName) { - return `${ - this.getNormalizedFunctionName(functionName) - }EventSourceMapping${ - this.normalizeName(streamType) - }${this.normalizeNameToAlphaNumericOnly(streamName)}`; + return `${this.getNormalizedFunctionName(functionName)}EventSourceMapping${this.normalizeName( + streamType + )}${this.normalizeNameToAlphaNumericOnly(streamName)}`; }, // IoT @@ -336,8 +345,7 @@ module.exports = { return `${this.getNormalizedFunctionName(functionName)}IotTopicRule${iotIndex}`; }, getLambdaIotPermissionLogicalId(functionName, iotIndex) { - return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionIotTopicRule${ - iotIndex}`; + return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionIotTopicRule${iotIndex}`; }, // CloudWatch Event @@ -345,14 +353,16 @@ module.exports = { return `${functionName}CloudWatchEvent`; }, getCloudWatchEventLogicalId(functionName, cloudWatchIndex) { - return `${this - .getNormalizedFunctionName(functionName)}EventsRuleCloudWatchEvent${cloudWatchIndex}`; + return `${this.getNormalizedFunctionName( + functionName + )}EventsRuleCloudWatchEvent${cloudWatchIndex}`; }, // CloudWatch Log getCloudWatchLogLogicalId(functionName, logsIndex) { - return `${this - .getNormalizedFunctionName(functionName)}LogsSubscriptionFilterCloudWatchLog${logsIndex}`; + return `${this.getNormalizedFunctionName( + functionName + )}LogsSubscriptionFilterCloudWatchLog${logsIndex}`; }, // Cognito User Pool @@ -362,11 +372,9 @@ module.exports = { // SQS getQueueLogicalId(functionName, queueName) { - return `${ - this.getNormalizedFunctionName(functionName) - }EventSourceMappingSQS${ - this.normalizeNameToAlphaNumericOnly(queueName) - }`; + return `${this.getNormalizedFunctionName( + functionName + )}EventSourceMappingSQS${this.normalizeNameToAlphaNumericOnly(queueName)}`; }, // ALB @@ -379,45 +387,54 @@ module.exports = { // Permissions getLambdaS3PermissionLogicalId(functionName, bucketName) { - return `${this.getNormalizedFunctionName(functionName)}LambdaPermission${this - .normalizeBucketName(bucketName)}S3`; + return `${this.getNormalizedFunctionName( + functionName + )}LambdaPermission${this.normalizeBucketName(bucketName)}S3`; }, getLambdaSnsPermissionLogicalId(functionName, topicName) { - return `${this.getNormalizedFunctionName(functionName)}LambdaPermission${ - this.normalizeTopicName(topicName)}SNS`; + return `${this.getNormalizedFunctionName( + functionName + )}LambdaPermission${this.normalizeTopicName(topicName)}SNS`; }, getLambdaSnsSubscriptionLogicalId(functionName, topicName) { - return `${this.getNormalizedFunctionName(functionName)}SnsSubscription${ - this.normalizeTopicName(topicName)}`; + return `${this.getNormalizedFunctionName(functionName)}SnsSubscription${this.normalizeTopicName( + topicName + )}`; }, getLambdaSchedulePermissionLogicalId(functionName, scheduleIndex) { - return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionEventsRuleSchedule${ - scheduleIndex}`; + return `${this.getNormalizedFunctionName( + functionName + )}LambdaPermissionEventsRuleSchedule${scheduleIndex}`; }, getLambdaCloudWatchEventPermissionLogicalId(functionName, cloudWatchIndex) { - return `${this - .getNormalizedFunctionName(functionName)}LambdaPermissionEventsRuleCloudWatchEvent${ - cloudWatchIndex}`; + return `${this.getNormalizedFunctionName( + functionName + )}LambdaPermissionEventsRuleCloudWatchEvent${cloudWatchIndex}`; }, getLambdaApiGatewayPermissionLogicalId(functionName) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionApiGateway`; }, getLambdaAlexaSkillPermissionLogicalId(functionName, alexaSkillIndex) { - return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSkill${ - alexaSkillIndex || '0'}`; + return `${this.getNormalizedFunctionName( + functionName + )}LambdaPermissionAlexaSkill${alexaSkillIndex || '0'}`; }, getLambdaAlexaSmartHomePermissionLogicalId(functionName, alexaSmartHomeIndex) { - return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSmartHome${ - alexaSmartHomeIndex}`; + return `${this.getNormalizedFunctionName( + functionName + )}LambdaPermissionAlexaSmartHome${alexaSmartHomeIndex}`; }, getLambdaCloudWatchLogPermissionLogicalId(functionName) { - return `${this.getNormalizedFunctionName(functionName) - }LambdaPermissionLogsSubscriptionFilterCloudWatchLog`; + return `${this.getNormalizedFunctionName( + functionName + )}LambdaPermissionLogsSubscriptionFilterCloudWatchLog`; }, getLambdaCognitoUserPoolPermissionLogicalId(functionName, poolId, triggerSource) { - return `${this - .getNormalizedFunctionName(functionName)}LambdaPermissionCognitoUserPool${ - this.normalizeNameToAlphaNumericOnly(poolId)}TriggerSource${triggerSource}`; + return `${this.getNormalizedFunctionName( + functionName + )}LambdaPermissionCognitoUserPool${this.normalizeNameToAlphaNumericOnly( + poolId + )}TriggerSource${triggerSource}`; }, getLambdaAlbPermissionLogicalId(functionName) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlb`; diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index fbc19a249..58b9e6d01 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -35,8 +35,9 @@ describe('#naming()', () => { describe('#normalizeNameToAlphaNumericOnly()', () => { it('should strip non-alpha-numeric characters', () => { - expect(sdk.naming - .normalizeNameToAlphaNumericOnly('`!@#$%^&*()-={}|[]\\:";\'<>?,./')).to.equal(''); + expect( + sdk.naming.normalizeNameToAlphaNumericOnly('`!@#$%^&*()-={}|[]\\:";\'<>?,./') + ).to.equal(''); }); it('should apply normalizeName to the remaining characters', () => { @@ -46,58 +47,50 @@ describe('#naming()', () => { describe('#normalizePathPart()', () => { it('converts `-` to `Dash`', () => { - expect(sdk.naming.normalizePathPart( - 'a-path' - )).to.equal('ADashpath'); + expect(sdk.naming.normalizePathPart('a-path')).to.equal('ADashpath'); }); it('converts variable declarations (`${var}`) to `VariableVar`', () => { - expect(sdk.naming.normalizePathPart( - '${variable}' - )).to.equal('VariableVar'); + expect(sdk.naming.normalizePathPart('${variable}')).to.equal('VariableVar'); }); it('converts variable declarations prefixes to `VariableVarpath`', () => { - expect(sdk.naming.normalizePathPart( - '${variable}Path' - )).to.equal('VariableVarpath'); + expect(sdk.naming.normalizePathPart('${variable}Path')).to.equal('VariableVarpath'); }); it('converts variable declarations suffixes to `PathvariableVar`', () => { - expect(sdk.naming.normalizePathPart( - 'path${variable}' - )).to.equal('PathvariableVar'); + expect(sdk.naming.normalizePathPart('path${variable}')).to.equal('PathvariableVar'); }); it('converts variable declarations in center to `PathvariableVardir`', () => { - expect(sdk.naming.normalizePathPart( - 'path${variable}Dir' - )).to.equal('PathvariableVardir'); + expect(sdk.naming.normalizePathPart('path${variable}Dir')).to.equal('PathvariableVardir'); }); }); describe('#getServiceEndpointRegex()', () => { it('should match the prefix', () => { - expect(sdk.naming.getServiceEndpointRegex().test('ServiceEndpoint')) - .to.equal(true); + expect(sdk.naming.getServiceEndpointRegex().test('ServiceEndpoint')).to.equal(true); }); it('should not match a name without the prefix', () => { - expect(sdk.naming.getServiceEndpointRegex() - .test('NotThePrefixServiceEndpoint')).to.equal(false); + expect(sdk.naming.getServiceEndpointRegex().test('NotThePrefixServiceEndpoint')).to.equal( + false + ); }); it('should match a name with the prefix', () => { - expect(sdk.naming.getServiceEndpointRegex() - .test('ServiceEndpointForAService')).to.equal(true); + expect(sdk.naming.getServiceEndpointRegex().test('ServiceEndpointForAService')).to.equal( + true + ); }); }); describe('#getStackName()', () => { it('should use the service name & stage if custom stack name not provided', () => { serverless.service.service = 'myService'; - expect(sdk.naming.getStackName()).to.equal(`${serverless.service.service}-${ - sdk.naming.provider.getStage()}`); + expect(sdk.naming.getStackName()).to.equal( + `${serverless.service.service}-${sdk.naming.provider.getStage()}` + ); }); it('should use the custom stack name if provided', () => { @@ -141,14 +134,7 @@ describe('#naming()', () => { it('should use the stage and service name', () => { serverless.service.service = 'myService'; expect(sdk.naming.getPolicyName()).to.eql({ - 'Fn::Join': [ - '-', - [ - sdk.naming.provider.getStage(), - serverless.service.service, - 'lambda', - ], - ], + 'Fn::Join': ['-', [sdk.naming.provider.getStage(), serverless.service.service, 'lambda']], }); }); }); @@ -167,18 +153,15 @@ describe('#naming()', () => { describe('#getNormalizedFunctionName()', () => { it('should normalize the given functionName', () => { - expect(sdk.naming.getNormalizedFunctionName('functionName')) - .to.equal('FunctionName'); + expect(sdk.naming.getNormalizedFunctionName('functionName')).to.equal('FunctionName'); }); it('should normalize the given functionName with an underscore', () => { - expect(sdk.naming.getNormalizedFunctionName('hello_world')) - .to.equal('HelloUnderscoreworld'); + expect(sdk.naming.getNormalizedFunctionName('hello_world')).to.equal('HelloUnderscoreworld'); }); it('should normalize the given functionName with a dash', () => { - expect(sdk.naming.getNormalizedFunctionName('hello-world')) - .to.equal('HelloDashworld'); + expect(sdk.naming.getNormalizedFunctionName('hello-world')).to.equal('HelloDashworld'); }); }); @@ -198,33 +181,34 @@ describe('#naming()', () => { describe('#getLambdaLogicalId()', () => { it('should normalize the function name and add the logical suffix', () => { - expect(sdk.naming.getLambdaLogicalId('functionName')) - .to.equal('FunctionNameLambdaFunction'); + expect(sdk.naming.getLambdaLogicalId('functionName')).to.equal('FunctionNameLambdaFunction'); }); }); describe('#getLambdaLogicalIdRegex()', () => { it('should match the suffix', () => { - expect(sdk.naming.getLambdaLogicalIdRegex() - .test('LambdaFunction')).to.equal(true); + expect(sdk.naming.getLambdaLogicalIdRegex().test('LambdaFunction')).to.equal(true); }); it('should not match a name without the suffix', () => { - expect(sdk.naming.getLambdaLogicalIdRegex() - .test('LambdaFunctionNotTheSuffix')).to.equal(false); + expect(sdk.naming.getLambdaLogicalIdRegex().test('LambdaFunctionNotTheSuffix')).to.equal( + false + ); }); it('should match a name with the suffix', () => { - expect(sdk.naming.getLambdaLogicalIdRegex() - .test('AFunctionNameLambdaFunction')).to.equal(true); + expect(sdk.naming.getLambdaLogicalIdRegex().test('AFunctionNameLambdaFunction')).to.equal( + true + ); }); }); describe('#getWebsocketsApiName()', () => { it('should return the composition of stage & service name if custom name not provided', () => { serverless.service.service = 'myService'; - expect(sdk.naming.getWebsocketsApiName()) - .to.equal(`${sdk.naming.provider.getStage()}-${serverless.service.service}-websockets`); + expect(sdk.naming.getWebsocketsApiName()).to.equal( + `${sdk.naming.provider.getStage()}-${serverless.service.service}-websockets` + ); }); it('should return the custom api name if provided', () => { @@ -243,62 +227,61 @@ describe('#naming()', () => { describe('#getWebsocketsIntegrationLogicalId()', () => { it('should return the integrations logical id', () => { - expect(sdk.naming.getWebsocketsIntegrationLogicalId('myFunc')) - .to.equal('MyFuncWebsocketsIntegration'); + expect(sdk.naming.getWebsocketsIntegrationLogicalId('myFunc')).to.equal( + 'MyFuncWebsocketsIntegration' + ); }); }); describe('#getLambdaWebsocketsPermissionLogicalId()', () => { it('should return the lambda websocket permission logical id', () => { - expect(sdk.naming.getLambdaWebsocketsPermissionLogicalId('myFunc')) - .to.equal('MyFuncLambdaPermissionWebsockets'); + expect(sdk.naming.getLambdaWebsocketsPermissionLogicalId('myFunc')).to.equal( + 'MyFuncLambdaPermissionWebsockets' + ); }); }); describe('#getNormalizedWebsocketsRouteKey()', () => { it('should return a normalized version of the route key', () => { - expect(sdk.naming.getNormalizedWebsocketsRouteKey('$connect')) - .to.equal('Sconnect'); + expect(sdk.naming.getNormalizedWebsocketsRouteKey('$connect')).to.equal('Sconnect'); - expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo/bar')) - .to.equal('fooSlashbar'); + expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo/bar')).to.equal('fooSlashbar'); - expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo-bar')) - .to.equal('fooDashbar'); + expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo-bar')).to.equal('fooDashbar'); - expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo_bar')) - .to.equal('fooUnderscorebar'); + expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo_bar')).to.equal('fooUnderscorebar'); - expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo.bar')) - .to.equal('fooPeriodbar'); + expect(sdk.naming.getNormalizedWebsocketsRouteKey('foo.bar')).to.equal('fooPeriodbar'); }); }); describe('#getWebsocketsRouteLogicalId()', () => { it('should return the websockets route logical id', () => { - expect(sdk.naming.getWebsocketsRouteLogicalId('$connect')) - .to.equal('SconnectWebsocketsRoute'); + expect(sdk.naming.getWebsocketsRouteLogicalId('$connect')).to.equal( + 'SconnectWebsocketsRoute' + ); }); }); describe('#getWebsocketsDeploymentLogicalId()', () => { it('should return the websockets deployment logical id', () => { - expect(sdk.naming.getWebsocketsDeploymentLogicalId(1234)) - .to.equal('WebsocketsDeployment1234'); + expect(sdk.naming.getWebsocketsDeploymentLogicalId(1234)).to.equal( + 'WebsocketsDeployment1234' + ); }); }); describe('#getWebsocketsStageLogicalId()', () => { it('should return the websockets stage logical id', () => { - expect(sdk.naming.getWebsocketsStageLogicalId()) - .to.equal('WebsocketsDeploymentStage'); + expect(sdk.naming.getWebsocketsStageLogicalId()).to.equal('WebsocketsDeploymentStage'); }); }); describe('#getWebsocketsAuthorizerLogicalId()', () => { it('should return the websockets authorizer logical id', () => { - expect(sdk.naming.getWebsocketsAuthorizerLogicalId('auth')) - .to.equal('AuthWebsocketsAuthorizer'); + expect(sdk.naming.getWebsocketsAuthorizerLogicalId('auth')).to.equal( + 'AuthWebsocketsAuthorizer' + ); }); }); @@ -323,8 +306,9 @@ describe('#naming()', () => { describe('#getApiGatewayName()', () => { it('should return the composition of stage & service name if custom name not provided', () => { serverless.service.service = 'myService'; - expect(sdk.naming.getApiGatewayName()) - .to.equal(`${sdk.naming.provider.getStage()}-${serverless.service.service}`); + expect(sdk.naming.getApiGatewayName()).to.equal( + `${sdk.naming.provider.getStage()}-${serverless.service.service}` + ); }); it('should return the custom api name if provided', () => { @@ -337,8 +321,9 @@ describe('#naming()', () => { describe('#generateApiGatewayDeploymentLogicalId()', () => { it('should return ApiGatewayDeployment with a suffix', () => { - expect(sdk.naming.generateApiGatewayDeploymentLogicalId(1234)) - .to.equal('ApiGatewayDeployment1234'); + expect(sdk.naming.generateApiGatewayDeploymentLogicalId(1234)).to.equal( + 'ApiGatewayDeployment1234' + ); }); }); @@ -350,15 +335,15 @@ describe('#naming()', () => { describe('#getNormalizedAuthorizerName()', () => { it('normalize the authorizer name', () => { - expect(sdk.naming.getNormalizedAuthorizerName('authorizerName')) - .to.equal('AuthorizerName'); + expect(sdk.naming.getNormalizedAuthorizerName('authorizerName')).to.equal('AuthorizerName'); }); }); describe('#getAuthorizerLogicalId()', () => { it('should normalize the authorizer name and add the standard suffix', () => { - expect(sdk.naming.getAuthorizerLogicalId('authorizerName')) - .to.equal('AuthorizerNameApiGatewayAuthorizer'); + expect(sdk.naming.getAuthorizerLogicalId('authorizerName')).to.equal( + 'AuthorizerNameApiGatewayAuthorizer' + ); }); }); @@ -370,27 +355,26 @@ describe('#naming()', () => { }); describe('#normalizePath()', () => { - it('should normalize each part of the resource path and remove non-alpha-numeric characters', - () => { - expect(sdk.naming.normalizePath( - 'my/path/to/a-${var}-resource' - )).to.equal('MyPathToADashvarVarDashresource'); - }); + it('should normalize each part of the resource path and remove non-alpha-numeric characters', () => { + expect(sdk.naming.normalizePath('my/path/to/a-${var}-resource')).to.equal( + 'MyPathToADashvarVarDashresource' + ); + }); }); describe('#getResourceLogicalId()', () => { it('should normalize the resource and add the standard suffix', () => { - expect(sdk.naming.getResourceLogicalId( - 'my/path/to/a-${var}-resource' - )).to.equal('ApiGatewayResourceMyPathToADashvarVarDashresource'); + expect(sdk.naming.getResourceLogicalId('my/path/to/a-${var}-resource')).to.equal( + 'ApiGatewayResourceMyPathToADashvarVarDashresource' + ); }); }); describe('#extractResourceId()', () => { it('should extract the normalized resource name', () => { - expect(sdk.naming.extractResourceId( - 'ApiGatewayResourceMyPathToADashvarVarDashResource' - )).to.equal('MyPathToADashvarVarDashResource'); + expect( + sdk.naming.extractResourceId('ApiGatewayResourceMyPathToADashvarVarDashResource') + ).to.equal('MyPathToADashvarVarDashResource'); }); }); @@ -402,25 +386,25 @@ describe('#naming()', () => { describe('#getMethodLogicalId()', () => { it('', () => { - expect(sdk.naming.getMethodLogicalId( - 'ResourceId', 'get' - )).to.equal('ApiGatewayMethodResourceIdGet'); + expect(sdk.naming.getMethodLogicalId('ResourceId', 'get')).to.equal( + 'ApiGatewayMethodResourceIdGet' + ); }); }); describe('#getValidatorLogicalId()', () => { it('', () => { - expect(sdk.naming.getValidatorLogicalId( - 'ResourceId', 'get' - )).to.equal('ApiGatewayMethodResourceIdGetValidator'); + expect(sdk.naming.getValidatorLogicalId('ResourceId', 'get')).to.equal( + 'ApiGatewayMethodResourceIdGetValidator' + ); }); }); describe('#getModelLogicalId()', () => { it('', () => { - expect(sdk.naming.getModelLogicalId( - 'ResourceId', 'get', 'application/json' - )).to.equal('ApiGatewayMethodResourceIdGetApplicationJsonModel'); + expect(sdk.naming.getModelLogicalId('ResourceId', 'get', 'application/json')).to.equal( + 'ApiGatewayMethodResourceIdGetApplicationJsonModel' + ); }); }); @@ -436,30 +420,27 @@ describe('#naming()', () => { describe('#getApiKeyLogicalIdRegex()', () => { it('should match the prefix', () => { - expect(sdk.naming.getApiKeyLogicalIdRegex() - .test('ApiGatewayApiKey')).to.equal(true); + expect(sdk.naming.getApiKeyLogicalIdRegex().test('ApiGatewayApiKey')).to.equal(true); }); it('should not match a name without the prefix', () => { - expect(sdk.naming.getApiKeyLogicalIdRegex() - .test('NotThePrefixApiGatewayApiKey')).to.equal(false); + expect(sdk.naming.getApiKeyLogicalIdRegex().test('NotThePrefixApiGatewayApiKey')).to.equal( + false + ); }); it('should match a name with the prefix', () => { - expect(sdk.naming.getApiKeyLogicalIdRegex() - .test('ApiGatewayApiKeySuffix')).to.equal(true); + expect(sdk.naming.getApiKeyLogicalIdRegex().test('ApiGatewayApiKeySuffix')).to.equal(true); }); }); describe('#getUsagePlanLogicalId()', () => { it('should return the default ApiGateway usage plan logical id', () => { - expect(sdk.naming.getUsagePlanLogicalId()) - .to.equal('ApiGatewayUsagePlan'); + expect(sdk.naming.getUsagePlanLogicalId()).to.equal('ApiGatewayUsagePlan'); }); it('should return the named ApiGateway usage plan logical id', () => { - expect(sdk.naming.getUsagePlanLogicalId('free')) - .to.equal('ApiGatewayUsagePlanFree'); + expect(sdk.naming.getUsagePlanLogicalId('free')).to.equal('ApiGatewayUsagePlanFree'); }); }); @@ -469,8 +450,9 @@ describe('#naming()', () => { }); it('should support API Key names', () => { - expect(sdk.naming.getUsagePlanKeyLogicalId(1, 'free')) - .to.equal('ApiGatewayUsagePlanKeyFree1'); + expect(sdk.naming.getUsagePlanKeyLogicalId(1, 'free')).to.equal( + 'ApiGatewayUsagePlanKeyFree1' + ); }); }); @@ -506,8 +488,9 @@ describe('#naming()', () => { describe('#getDeploymentBucketOutputLogicalId()', () => { it('should return "ServerlessDeploymentBucketName"', () => { - expect(sdk.naming.getDeploymentBucketOutputLogicalId()) - .to.equal('ServerlessDeploymentBucketName'); + expect(sdk.naming.getDeploymentBucketOutputLogicalId()).to.equal( + 'ServerlessDeploymentBucketName' + ); }); }); @@ -543,164 +526,181 @@ describe('#naming()', () => { describe('#getScheduleLogicalId()', () => { it('should normalize the function name and add the standard suffix including the index', () => { - expect(sdk.naming.getScheduleLogicalId('functionName', 0)) - .to.equal('FunctionNameEventsRuleSchedule0'); + expect(sdk.naming.getScheduleLogicalId('functionName', 0)).to.equal( + 'FunctionNameEventsRuleSchedule0' + ); }); }); describe('#getCloudWatchEventId()', () => { it('should add the standard suffix', () => { - expect(sdk.naming.getCloudWatchEventId('functionName')) - .to.equal('functionNameCloudWatchEvent'); + expect(sdk.naming.getCloudWatchEventId('functionName')).to.equal( + 'functionNameCloudWatchEvent' + ); }); }); describe('#getCloudWatchEventLogicalId()', () => { it('should normalize the function name and add the standard suffix including the index', () => { - expect(sdk.naming.getCloudWatchEventLogicalId('functionName', 0)) - .to.equal('FunctionNameEventsRuleCloudWatchEvent0'); + expect(sdk.naming.getCloudWatchEventLogicalId('functionName', 0)).to.equal( + 'FunctionNameEventsRuleCloudWatchEvent0' + ); }); }); describe('#getCloudWatchLogLogicalId()', () => { it('should normalize the function name and add the standard suffix including the index', () => { - expect(sdk.naming.getCloudWatchLogLogicalId('functionName', 0)) - .to.equal('FunctionNameLogsSubscriptionFilterCloudWatchLog0'); + expect(sdk.naming.getCloudWatchLogLogicalId('functionName', 0)).to.equal( + 'FunctionNameLogsSubscriptionFilterCloudWatchLog0' + ); }); }); describe('#getCognitoUserPoolLogicalId()', () => { it('should normalize the user pool name and add the standard prefix', () => { - expect(sdk.naming.getCognitoUserPoolLogicalId('us-east-1_v123sDAS1')) - .to.equal('CognitoUserPoolUseast1v123sDAS1'); + expect(sdk.naming.getCognitoUserPoolLogicalId('us-east-1_v123sDAS1')).to.equal( + 'CognitoUserPoolUseast1v123sDAS1' + ); }); }); describe('#getLambdaS3PermissionLogicalId()', () => { it('should normalize the function name and add the standard suffix', () => { - expect(sdk.naming.getLambdaS3PermissionLogicalId('functionName', 'bucket')) - .to.equal('FunctionNameLambdaPermissionBucketS3'); + expect(sdk.naming.getLambdaS3PermissionLogicalId('functionName', 'bucket')).to.equal( + 'FunctionNameLambdaPermissionBucketS3' + ); }); }); describe('#getLambdaSnsPermissionLogicalId()', () => { - it('should normalize the function and topic names and add them as prefix and suffix to the ' + - 'standard permission center', () => { - expect(sdk.naming.getLambdaSnsPermissionLogicalId('functionName', 'topic')) - .to.equal('FunctionNameLambdaPermissionTopicSNS'); - }); + it( + 'should normalize the function and topic names and add them as prefix and suffix to the ' + + 'standard permission center', + () => { + expect(sdk.naming.getLambdaSnsPermissionLogicalId('functionName', 'topic')).to.equal( + 'FunctionNameLambdaPermissionTopicSNS' + ); + } + ); }); describe('#getLambdaSchedulePermissionLogicalId()', () => { - it('should normalize the function name and add the standard suffix including event index', - () => { - expect(sdk.naming.getLambdaSchedulePermissionLogicalId('functionName', 0)) - .to.equal('FunctionNameLambdaPermissionEventsRuleSchedule0'); - }); + it('should normalize the function name and add the standard suffix including event index', () => { + expect(sdk.naming.getLambdaSchedulePermissionLogicalId('functionName', 0)).to.equal( + 'FunctionNameLambdaPermissionEventsRuleSchedule0' + ); + }); }); describe('#getLambdaCloudWatchEventPermissionLogicalId()', () => { - it('should normalize the function name and add the standard suffix including event index', - () => { - expect(sdk.naming.getLambdaCloudWatchEventPermissionLogicalId('functionName', 0)) - .to.equal('FunctionNameLambdaPermissionEventsRuleCloudWatchEvent0'); - }); + it('should normalize the function name and add the standard suffix including event index', () => { + expect(sdk.naming.getLambdaCloudWatchEventPermissionLogicalId('functionName', 0)).to.equal( + 'FunctionNameLambdaPermissionEventsRuleCloudWatchEvent0' + ); + }); }); describe('#getLambdaApiGatewayPermissionLogicalId()', () => { it('should normalize the function name and append the standard suffix', () => { - expect(sdk.naming.getLambdaApiGatewayPermissionLogicalId('functionName')) - .to.equal('FunctionNameLambdaPermissionApiGateway'); + expect(sdk.naming.getLambdaApiGatewayPermissionLogicalId('functionName')).to.equal( + 'FunctionNameLambdaPermissionApiGateway' + ); }); }); describe('#getIotLogicalId()', () => { it('should normalize the function name and add the standard suffix including the index', () => { - expect(sdk.naming.getIotLogicalId('functionName', 0)) - .to.equal('FunctionNameIotTopicRule0'); + expect(sdk.naming.getIotLogicalId('functionName', 0)).to.equal('FunctionNameIotTopicRule0'); }); }); describe('#getLambdaIotPermissionLogicalId()', () => { - it('should normalize the function name and add the standard suffix including event index', - () => { - expect(sdk.naming.getLambdaIotPermissionLogicalId('functionName', 0)) - .to.equal('FunctionNameLambdaPermissionIotTopicRule0'); - }); + it('should normalize the function name and add the standard suffix including event index', () => { + expect(sdk.naming.getLambdaIotPermissionLogicalId('functionName', 0)).to.equal( + 'FunctionNameLambdaPermissionIotTopicRule0' + ); + }); }); describe('#getLambdaAlexaSkillPermissionLogicalId()', () => { - it('should normalize the function name and append the standard suffix', - () => { - expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName', 2)) - .to.equal('FunctionNameLambdaPermissionAlexaSkill2'); - }); + it('should normalize the function name and append the standard suffix', () => { + expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName', 2)).to.equal( + 'FunctionNameLambdaPermissionAlexaSkill2' + ); + }); - it('should normalize the function name and append a default suffix if not defined', - () => { - expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName')) - .to.equal('FunctionNameLambdaPermissionAlexaSkill0'); - }); + it('should normalize the function name and append a default suffix if not defined', () => { + expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName')).to.equal( + 'FunctionNameLambdaPermissionAlexaSkill0' + ); + }); }); describe('#getLambdaAlexaSmartHomePermissionLogicalId()', () => { - it('should normalize the function name and append the standard suffix', - () => { - expect(sdk.naming.getLambdaAlexaSmartHomePermissionLogicalId('functionName', 0)) - .to.equal('FunctionNameLambdaPermissionAlexaSmartHome0'); - }); + it('should normalize the function name and append the standard suffix', () => { + expect(sdk.naming.getLambdaAlexaSmartHomePermissionLogicalId('functionName', 0)).to.equal( + 'FunctionNameLambdaPermissionAlexaSmartHome0' + ); + }); }); describe('#getLambdaSnsSubscriptionLogicalId()', () => { it('should normalize the function name and append the standard suffix', () => { - expect(sdk.naming.getLambdaSnsSubscriptionLogicalId('functionName', 'topicName')) - .to.equal('FunctionNameSnsSubscriptionTopicName'); + expect(sdk.naming.getLambdaSnsSubscriptionLogicalId('functionName', 'topicName')).to.equal( + 'FunctionNameSnsSubscriptionTopicName' + ); }); }); describe('#getLambdaCloudWatchLogPermissionLogicalId()', () => { - it('should normalize the function name and add the standard suffix including event index', - () => { - expect(sdk.naming.getLambdaCloudWatchLogPermissionLogicalId('functionName')) - .to.equal('FunctionNameLambdaPermissionLogsSubscriptionFilterCloudWatchLog'); - }); + it('should normalize the function name and add the standard suffix including event index', () => { + expect(sdk.naming.getLambdaCloudWatchLogPermissionLogicalId('functionName')).to.equal( + 'FunctionNameLambdaPermissionLogsSubscriptionFilterCloudWatchLog' + ); + }); }); describe('#getLambdaCognitoUserPoolPermissionLogicalId()', () => { it('should normalize the function name and add the standard suffix', () => { - expect(sdk.naming.getLambdaCognitoUserPoolPermissionLogicalId( - 'functionName', - 'Pool1', - 'CustomMessage' - )).to.equal('FunctionNameLambdaPermissionCognitoUserPoolPool1TriggerSourceCustomMessage'); + expect( + sdk.naming.getLambdaCognitoUserPoolPermissionLogicalId( + 'functionName', + 'Pool1', + 'CustomMessage' + ) + ).to.equal('FunctionNameLambdaPermissionCognitoUserPoolPool1TriggerSourceCustomMessage'); }); describe('#getLambdaAlbPermissionLogicalId()', () => { it('should normalize the function name', () => { - expect(sdk.naming.getLambdaAlbPermissionLogicalId('functionName')) - .to.equal('FunctionNameLambdaPermissionAlb'); + expect(sdk.naming.getLambdaAlbPermissionLogicalId('functionName')).to.equal( + 'FunctionNameLambdaPermissionAlb' + ); }); }); }); describe('#getQueueLogicalId()', () => { it('should normalize the function name and add the standard suffix', () => { - expect(sdk.naming.getQueueLogicalId('functionName', 'MyQueue')) - .to.equal('FunctionNameEventSourceMappingSQSMyQueue'); + expect(sdk.naming.getQueueLogicalId('functionName', 'MyQueue')).to.equal( + 'FunctionNameEventSourceMappingSQSMyQueue' + ); }); }); describe('#getAlbTargetGroupLogicalId()', () => { it('should normalize the function name', () => { - expect(sdk.naming.getAlbTargetGroupLogicalId('functionName')) - .to.equal('FunctionNameAlbTargetGroup'); + expect(sdk.naming.getAlbTargetGroupLogicalId('functionName')).to.equal( + 'FunctionNameAlbTargetGroup' + ); }); }); describe('#getAlbListenerRuleLogicalId()', () => { it('should normalize the function name and add an index', () => { - expect(sdk.naming.getAlbListenerRuleLogicalId('functionName', 0)) - .to.equal('FunctionNameAlbListenerRule0'); + expect(sdk.naming.getAlbListenerRuleLogicalId('functionName', 0)).to.equal( + 'FunctionNameAlbListenerRule0' + ); }); }); }); diff --git a/lib/plugins/aws/lib/normalizeFiles.js b/lib/plugins/aws/lib/normalizeFiles.js index e0150cc18..a1c90634d 100644 --- a/lib/plugins/aws/lib/normalizeFiles.js +++ b/lib/plugins/aws/lib/normalizeFiles.js @@ -8,8 +8,9 @@ module.exports = { _.forEach(normalizedTemplate.Resources, (value, key) => { if (key.startsWith('ApiGatewayDeployment')) { - delete Object.assign(normalizedTemplate.Resources, - { ApiGatewayDeployment: normalizedTemplate.Resources[key] })[key]; + delete Object.assign(normalizedTemplate.Resources, { + ApiGatewayDeployment: normalizedTemplate.Resources[key], + })[key]; } if (value.Type && value.Type === 'AWS::Lambda::Function') { const newVal = value; diff --git a/lib/plugins/aws/lib/setBucketName.js b/lib/plugins/aws/lib/setBucketName.js index 8a4b9c28a..8a3296099 100644 --- a/lib/plugins/aws/lib/setBucketName.js +++ b/lib/plugins/aws/lib/setBucketName.js @@ -8,9 +8,8 @@ module.exports = { return BbPromise.resolve(this.bucketName); } - return this.provider.getServerlessDeploymentBucketName() - .then((bucketName) => { - this.bucketName = bucketName; - }); + return this.provider.getServerlessDeploymentBucketName().then(bucketName => { + this.bucketName = bucketName; + }); }, }; diff --git a/lib/plugins/aws/lib/setBucketName.test.js b/lib/plugins/aws/lib/setBucketName.test.js index 8d2f9f89e..b18b87291 100644 --- a/lib/plugins/aws/lib/setBucketName.test.js +++ b/lib/plugins/aws/lib/setBucketName.test.js @@ -26,19 +26,19 @@ describe('#setBucketName()', () => { .resolves('bucket-name'); }); - it('should store the name of the Serverless deployment bucket', () => awsDeploy - .setBucketName().then(() => { + it('should store the name of the Serverless deployment bucket', () => + awsDeploy.setBucketName().then(() => { expect(awsDeploy.bucketName).to.equal('bucket-name'); expect(getServerlessDeploymentBucketNameStub.calledOnce).to.be.equal(true); expect(getServerlessDeploymentBucketNameStub.calledWithExactly()).to.be.equal(true); awsDeploy.provider.getServerlessDeploymentBucketName.restore(); - }) - ); + })); it('should resolve if the bucketName is already set', () => { const bucketName = 'someBucket'; awsDeploy.bucketName = bucketName; - return awsDeploy.setBucketName() + return awsDeploy + .setBucketName() .then(() => expect(getServerlessDeploymentBucketNameStub.calledOnce).to.be.false) .then(() => expect(awsDeploy.bucketName).to.equal(bucketName)); }); diff --git a/lib/plugins/aws/lib/updateStack.js b/lib/plugins/aws/lib/updateStack.js index 4288b4425..2e1af627d 100644 --- a/lib/plugins/aws/lib/updateStack.js +++ b/lib/plugins/aws/lib/updateStack.js @@ -25,17 +25,16 @@ module.exports = { const params = { StackName: stackName, OnFailure: 'ROLLBACK', - Capabilities: [ - 'CAPABILITY_IAM', - 'CAPABILITY_NAMED_IAM', - ], + Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], Parameters: [], TemplateURL: templateUrl, - Tags: Object.keys(stackTags).map((key) => ({ Key: key, Value: stackTags[key] })), + Tags: Object.keys(stackTags).map(key => ({ Key: key, Value: stackTags[key] })), }; - if (this.serverless.service.provider.compiledCloudFormationTemplate && - this.serverless.service.provider.compiledCloudFormationTemplate.Transform) { + if ( + this.serverless.service.provider.compiledCloudFormationTemplate && + this.serverless.service.provider.compiledCloudFormationTemplate.Transform + ) { params.Capabilities.push('CAPABILITY_AUTO_EXPAND'); } @@ -47,10 +46,9 @@ module.exports = { params.NotificationARNs = this.serverless.service.provider.notificationArns; } - return this.provider.request('CloudFormation', - 'createStack', - params) - .then((cfData) => this.monitorStack('create', cfData)); + return this.provider + .request('CloudFormation', 'createStack', params) + .then(cfData => this.monitorStack('create', cfData)); }, update() { @@ -69,17 +67,16 @@ module.exports = { const params = { StackName: stackName, - Capabilities: [ - 'CAPABILITY_IAM', - 'CAPABILITY_NAMED_IAM', - ], + Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], Parameters: [], TemplateURL: templateUrl, - Tags: Object.keys(stackTags).map((key) => ({ Key: key, Value: stackTags[key] })), + Tags: Object.keys(stackTags).map(key => ({ Key: key, Value: stackTags[key] })), }; - if (this.serverless.service.provider.compiledCloudFormationTemplate && - this.serverless.service.provider.compiledCloudFormationTemplate.Transform) { + if ( + this.serverless.service.provider.compiledCloudFormationTemplate && + this.serverless.service.provider.compiledCloudFormationTemplate.Transform + ) { params.Capabilities.push('CAPABILITY_AUTO_EXPAND'); } @@ -92,18 +89,19 @@ module.exports = { } // Policy must have at least one statement, otherwise no updates would be possible at all - if (this.serverless.service.provider.stackPolicy && - !_.isEmpty(this.serverless.service.provider.stackPolicy)) { + if ( + this.serverless.service.provider.stackPolicy && + !_.isEmpty(this.serverless.service.provider.stackPolicy) + ) { params.StackPolicyBody = JSON.stringify({ Statement: this.serverless.service.provider.stackPolicy, }); } - return this.provider.request('CloudFormation', - 'updateStack', - params) - .then((cfData) => this.monitorStack('update', cfData)) - .catch((e) => { + return this.provider + .request('CloudFormation', 'updateStack', params) + .then(cfData => this.monitorStack('update', cfData)) + .catch(e => { if (e.message === NO_UPDATE_MESSAGE) { return BbPromise.resolve(); } @@ -112,14 +110,11 @@ module.exports = { }, updateStack() { - return BbPromise.bind(this) - .then(() => { - if (this.createLater) { - return BbPromise.bind(this) - .then(this.createFallback); - } - return BbPromise.bind(this) - .then(this.update); - }); + return BbPromise.bind(this).then(() => { + if (this.createLater) { + return BbPromise.bind(this).then(this.createFallback); + } + return BbPromise.bind(this).then(this.update); + }); }, }; diff --git a/lib/plugins/aws/lib/updateStack.test.js b/lib/plugins/aws/lib/updateStack.test.js index 1421a7355..e3c976680 100644 --- a/lib/plugins/aws/lib/updateStack.test.js +++ b/lib/plugins/aws/lib/updateStack.test.js @@ -24,7 +24,7 @@ describe('updateStack', () => { awsDeploy.deployedFunctions = [{ name: 'first', zipFileKey: 'zipFileOfFirstFunction' }]; awsDeploy.bucketName = 'deployment-bucket'; - serverless.service.service = `service-${(new Date()).getTime().toString()}`; + serverless.service.service = `service-${new Date().getTime().toString()}`; serverless.config.servicePath = tmpDirPath; awsDeploy.serverless.service.package.artifactDirectoryName = 'somedir'; awsDeploy.serverless.cli = new serverless.classes.CLI(); @@ -34,28 +34,21 @@ describe('updateStack', () => { it('should create a stack with the CF template URL', () => { const compiledTemplateFileName = 'compiled-cloudformation-template.json'; - const createStackStub = sinon - .stub(awsDeploy.provider, 'request').resolves(); + const createStackStub = sinon.stub(awsDeploy.provider, 'request').resolves(); sinon.stub(awsDeploy, 'monitorStack').resolves(); return awsDeploy.createFallback().then(() => { expect(createStackStub.calledOnce).to.be.equal(true); - expect(createStackStub.calledWithExactly( - 'CloudFormation', - 'createStack', - { + expect( + createStackStub.calledWithExactly('CloudFormation', 'createStack', { StackName: awsDeploy.provider.naming.getStackName(), OnFailure: 'ROLLBACK', - Capabilities: [ - 'CAPABILITY_IAM', - 'CAPABILITY_NAMED_IAM', - ], + Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], Parameters: [], - TemplateURL: `https://s3.amazonaws.com/${awsDeploy.bucketName}/${awsDeploy.serverless - .service.package.artifactDirectoryName}/${compiledTemplateFileName}`, + TemplateURL: `https://s3.amazonaws.com/${awsDeploy.bucketName}/${awsDeploy.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`, Tags: [{ Key: 'STAGE', Value: awsDeploy.provider.getStage() }], - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsDeploy.provider.request.restore(); awsDeploy.monitorStack.restore(); }); @@ -64,31 +57,29 @@ describe('updateStack', () => { it('should include custom stack tags', () => { awsDeploy.serverless.service.provider.stackTags = { STAGE: 'overridden', tag1: 'value1' }; - const createStackStub = sinon - .stub(awsDeploy.provider, 'request').resolves(); + const createStackStub = sinon.stub(awsDeploy.provider, 'request').resolves(); sinon.stub(awsDeploy, 'monitorStack').resolves(); return awsDeploy.createFallback().then(() => { - expect(createStackStub.args[0][2].Tags) - .to.deep.equal([ - { Key: 'STAGE', Value: 'overridden' }, - { Key: 'tag1', Value: 'value1' }, - ]); + expect(createStackStub.args[0][2].Tags).to.deep.equal([ + { Key: 'STAGE', Value: 'overridden' }, + { Key: 'tag1', Value: 'value1' }, + ]); awsDeploy.provider.request.restore(); awsDeploy.monitorStack.restore(); }); }); it('should add CAPABILITY_AUTO_EXPAND if a Transform directive is specified', () => { - awsDeploy.serverless.service.provider - .compiledCloudFormationTemplate = { Transform: 'MyMacro' }; + awsDeploy.serverless.service.provider.compiledCloudFormationTemplate = { + Transform: 'MyMacro', + }; const createStackStub = sinon.stub(awsDeploy.provider, 'request').resolves(); sinon.stub(awsDeploy, 'monitorStack').resolves(); return awsDeploy.createFallback().then(() => { - expect(createStackStub.args[0][2].Capabilities) - .to.contain('CAPABILITY_AUTO_EXPAND'); + expect(createStackStub.args[0][2].Capabilities).to.contain('CAPABILITY_AUTO_EXPAND'); awsDeploy.provider.request.restore(); awsDeploy.monitorStack.restore(); }); @@ -101,8 +92,9 @@ describe('updateStack', () => { sinon.stub(awsDeploy, 'monitorStack').resolves(); return awsDeploy.createFallback().then(() => { - expect(createStackStub.args[0][2].RoleARN) - .to.equal('arn:aws:iam::123456789012:role/myrole'); + expect(createStackStub.args[0][2].RoleARN).to.equal( + 'arn:aws:iam::123456789012:role/myrole' + ); awsDeploy.provider.request.restore(); awsDeploy.monitorStack.restore(); }); @@ -116,8 +108,7 @@ describe('updateStack', () => { sinon.stub(awsDeploy, 'monitorStack').resolves(); return awsDeploy.createFallback().then(() => { - expect(createStackStub.args[0][2].NotificationARNs) - .to.deep.equal([mytopicArn]); + expect(createStackStub.args[0][2].NotificationARNs).to.deep.equal([mytopicArn]); awsDeploy.provider.request.restore(); awsDeploy.monitorStack.restore(); }); @@ -128,8 +119,7 @@ describe('updateStack', () => { let updateStackStub; beforeEach(() => { - updateStackStub = sinon - .stub(awsDeploy.provider, 'request').resolves(); + updateStackStub = sinon.stub(awsDeploy.provider, 'request').resolves(); sinon.stub(awsDeploy, 'monitorStack').resolves(); }); @@ -138,65 +128,57 @@ describe('updateStack', () => { awsDeploy.monitorStack.restore(); }); - it('should update the stack', () => awsDeploy.update() - .then(() => { + it('should update the stack', () => + awsDeploy.update().then(() => { const compiledTemplateFileName = 'compiled-cloudformation-template.json'; expect(updateStackStub.calledOnce).to.be.equal(true); - expect(updateStackStub.calledWithExactly( - 'CloudFormation', - 'updateStack', - { + expect( + updateStackStub.calledWithExactly('CloudFormation', 'updateStack', { StackName: awsDeploy.provider.naming.getStackName(), - Capabilities: [ - 'CAPABILITY_IAM', - 'CAPABILITY_NAMED_IAM', - ], + Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], Parameters: [], - TemplateURL: `https://s3.amazonaws.com/${awsDeploy.bucketName}/${awsDeploy.serverless - .service.package.artifactDirectoryName}/${compiledTemplateFileName}`, + TemplateURL: `https://s3.amazonaws.com/${awsDeploy.bucketName}/${awsDeploy.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`, Tags: [{ Key: 'STAGE', Value: awsDeploy.provider.getStage() }], - } - )).to.be.equal(true); - }) - ); + }) + ).to.be.equal(true); + })); it('should include custom stack tags and policy', () => { awsDeploy.serverless.service.provider.stackTags = { STAGE: 'overridden', tag1: 'value1' }; - awsDeploy.serverless.service.provider.stackPolicy = [{ - Effect: 'Allow', - Principal: '*', - Action: 'Update:*', - Resource: '*', - }]; + awsDeploy.serverless.service.provider.stackPolicy = [ + { + Effect: 'Allow', + Principal: '*', + Action: 'Update:*', + Resource: '*', + }, + ]; return awsDeploy.update().then(() => { - expect(updateStackStub.args[0][2].Tags) - .to.deep.equal([ - { Key: 'STAGE', Value: 'overridden' }, - { Key: 'tag1', Value: 'value1' }, - ]); - expect(updateStackStub.args[0][2].StackPolicyBody) - .to.equal( - '{"Statement":[{"Effect":"Allow","Principal":"*","Action":"Update:*","Resource":"*"}]}' - ); + expect(updateStackStub.args[0][2].Tags).to.deep.equal([ + { Key: 'STAGE', Value: 'overridden' }, + { Key: 'tag1', Value: 'value1' }, + ]); + expect(updateStackStub.args[0][2].StackPolicyBody).to.equal( + '{"Statement":[{"Effect":"Allow","Principal":"*","Action":"Update:*","Resource":"*"}]}' + ); }); }); it('should success if no changes to stack happened', () => { awsDeploy.monitorStack.restore(); - sinon.stub(awsDeploy, 'monitorStack') - .rejects(new Error('No updates are to be performed.')); + sinon.stub(awsDeploy, 'monitorStack').rejects(new Error('No updates are to be performed.')); return awsDeploy.update(); }); it('should add CAPABILITY_AUTO_EXPAND if a Transform directive is specified', () => { - awsDeploy.serverless.service.provider - .compiledCloudFormationTemplate = { Transform: 'MyMacro' }; + awsDeploy.serverless.service.provider.compiledCloudFormationTemplate = { + Transform: 'MyMacro', + }; return awsDeploy.update().then(() => { - expect(updateStackStub.args[0][2].Capabilities) - .to.contain('CAPABILITY_AUTO_EXPAND'); + expect(updateStackStub.args[0][2].Capabilities).to.contain('CAPABILITY_AUTO_EXPAND'); }); }); @@ -204,8 +186,9 @@ describe('updateStack', () => { awsDeploy.serverless.service.provider.cfnRole = 'arn:aws:iam::123456789012:role/myrole'; return awsDeploy.update().then(() => { - expect(updateStackStub.args[0][2].RoleARN) - .to.equal('arn:aws:iam::123456789012:role/myrole'); + expect(updateStackStub.args[0][2].RoleARN).to.equal( + 'arn:aws:iam::123456789012:role/myrole' + ); }); }); @@ -214,8 +197,7 @@ describe('updateStack', () => { awsDeploy.serverless.service.provider.notificationArns = [mytopicArn]; return awsDeploy.update().then(() => { - expect(updateStackStub.args[0][2].NotificationARNs) - .to.deep.equal([mytopicArn]); + expect(updateStackStub.args[0][2].NotificationARNs).to.deep.equal([mytopicArn]); }); }); }); @@ -223,10 +205,8 @@ describe('updateStack', () => { describe('#updateStack()', () => { it('should fallback to createStack if createLater flag exists', () => { awsDeploy.createLater = true; - const createFallbackStub = sinon - .stub(awsDeploy, 'createFallback').resolves(); - const updateStub = sinon - .stub(awsDeploy, 'update').resolves(); + const createFallbackStub = sinon.stub(awsDeploy, 'createFallback').resolves(); + const updateStub = sinon.stub(awsDeploy, 'update').resolves(); return awsDeploy.updateStack().then(() => { expect(createFallbackStub.calledOnce).to.be.equal(true); @@ -236,8 +216,7 @@ describe('updateStack', () => { }); it('should run promise chain in order', () => { - const updateStub = sinon - .stub(awsDeploy, 'update').resolves(); + const updateStub = sinon.stub(awsDeploy, 'update').resolves(); return awsDeploy.updateStack().then(() => { expect(updateStub.calledOnce).to.be.equal(true); diff --git a/lib/plugins/aws/lib/validate.js b/lib/plugins/aws/lib/validate.js index 7e9b0b9b9..31ffa85d9 100644 --- a/lib/plugins/aws/lib/validate.js +++ b/lib/plugins/aws/lib/validate.js @@ -6,8 +6,9 @@ module.exports = { validate() { return new BbPromise((resolve, reject) => { if (!this.serverless.config.servicePath) { - const error = new this.serverless.classes - .Error('This command can only be run inside a service directory'); + const error = new this.serverless.classes.Error( + 'This command can only be run inside a service directory' + ); reject(error); } diff --git a/lib/plugins/aws/lib/validate.test.js b/lib/plugins/aws/lib/validate.test.js index ce5fe6ae3..d21aa79eb 100644 --- a/lib/plugins/aws/lib/validate.test.js +++ b/lib/plugins/aws/lib/validate.test.js @@ -31,8 +31,7 @@ describe('#validate', () => { describe('#validate()', () => { it('should succeed if inside service (servicePath defined)', () => - expect(awsPlugin.validate()).to.be.fulfilled - ); + expect(awsPlugin.validate()).to.be.fulfilled); it('should throw error if not inside service (servicePath not defined)', () => { awsPlugin.serverless.config.servicePath = false; @@ -43,10 +42,9 @@ describe('#validate', () => { it('should default to "dev" if stage is not provided', () => { awsPlugin.options.stage = false; - return expect(awsPlugin.validate()).to.be.fulfilled - .then(() => { - expect(awsPlugin.provider.getStage()).to.equal('dev'); - }); + return expect(awsPlugin.validate()).to.be.fulfilled.then(() => { + expect(awsPlugin.provider.getStage()).to.equal('dev'); + }); }); it('should use the service.provider stage if present', () => { @@ -55,18 +53,16 @@ describe('#validate', () => { stage: 'some-stage', }; - return expect(awsPlugin.validate()).to.be.fulfilled - .then(() => { - expect(awsPlugin.provider.getStage()).to.equal('some-stage'); - }); + return expect(awsPlugin.validate()).to.be.fulfilled.then(() => { + expect(awsPlugin.provider.getStage()).to.equal('some-stage'); + }); }); it('should default to "us-east-1" region if region is not provided', () => { awsPlugin.options.region = false; - return expect(awsPlugin.validate()).to.be.fulfilled - .then(() => { - expect(awsPlugin.options.region).to.equal('us-east-1'); - }); + return expect(awsPlugin.validate()).to.be.fulfilled.then(() => { + expect(awsPlugin.options.region).to.equal('us-east-1'); + }); }); it('should use the service.provider region if present', () => { @@ -75,10 +71,9 @@ describe('#validate', () => { region: 'some-region', }; - return expect(awsPlugin.validate()).to.be.fulfilled - .then(() => { - expect(awsPlugin.options.region).to.equal('some-region'); - }); + return expect(awsPlugin.validate()).to.be.fulfilled.then(() => { + expect(awsPlugin.options.region).to.equal('some-region'); + }); }); }); }); diff --git a/lib/plugins/aws/lib/validateS3BucketName.js b/lib/plugins/aws/lib/validateS3BucketName.js index 22286324e..ce3483232 100644 --- a/lib/plugins/aws/lib/validateS3BucketName.js +++ b/lib/plugins/aws/lib/validateS3BucketName.js @@ -14,33 +14,32 @@ module.exports = { * @param bucketName */ validateS3BucketName(bucketName) { - return BbPromise.resolve() - .then(() => { - let error; - if (!bucketName) { - error = 'Bucket name cannot be undefined or empty'; - } else if (bucketName.length < 3) { - error = `Bucket name is shorter than 3 characters. ${bucketName}`; - } else if (bucketName.length > 63) { - error = `Bucket name is longer than 63 characters. ${bucketName}`; - } else if (/[A-Z]/.test(bucketName)) { - error = `Bucket name cannot contain uppercase letters. ${bucketName}`; - } else if (/^[^a-z0-9]/.test(bucketName)) { - error = `Bucket name must start with a letter or number. ${bucketName}`; - } else if (/[^a-z0-9]$/.test(bucketName)) { - error = `Bucket name must end with a letter or number. ${bucketName}`; - } else if (!/^[a-z0-9][a-z.0-9-]+[a-z0-9]$/.test(bucketName)) { - error = `Bucket name contains invalid characters, [a-z.0-9-] ${bucketName}`; - } else if (/\.{2,}/.test(bucketName)) { - error = `Bucket name cannot contain consecutive periods (.) ${bucketName}`; - } else if (/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(bucketName)) { - error = `Bucket name cannot look like an IPv4 address. ${bucketName}`; - } + return BbPromise.resolve().then(() => { + let error; + if (!bucketName) { + error = 'Bucket name cannot be undefined or empty'; + } else if (bucketName.length < 3) { + error = `Bucket name is shorter than 3 characters. ${bucketName}`; + } else if (bucketName.length > 63) { + error = `Bucket name is longer than 63 characters. ${bucketName}`; + } else if (/[A-Z]/.test(bucketName)) { + error = `Bucket name cannot contain uppercase letters. ${bucketName}`; + } else if (/^[^a-z0-9]/.test(bucketName)) { + error = `Bucket name must start with a letter or number. ${bucketName}`; + } else if (/[^a-z0-9]$/.test(bucketName)) { + error = `Bucket name must end with a letter or number. ${bucketName}`; + } else if (!/^[a-z0-9][a-z.0-9-]+[a-z0-9]$/.test(bucketName)) { + error = `Bucket name contains invalid characters, [a-z.0-9-] ${bucketName}`; + } else if (/\.{2,}/.test(bucketName)) { + error = `Bucket name cannot contain consecutive periods (.) ${bucketName}`; + } else if (/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(bucketName)) { + error = `Bucket name cannot look like an IPv4 address. ${bucketName}`; + } - if (error) { - throw new this.serverless.classes.Error(error); - } - return true; - }); + if (error) { + throw new this.serverless.classes.Error(error); + } + return true; + }); }, }; diff --git a/lib/plugins/aws/lib/validateS3BucketName.test.js b/lib/plugins/aws/lib/validateS3BucketName.test.js index f28f12b3f..c33498a6b 100644 --- a/lib/plugins/aws/lib/validateS3BucketName.test.js +++ b/lib/plugins/aws/lib/validateS3BucketName.test.js @@ -22,16 +22,17 @@ describe('#validateS3BucketName()', () => { describe('#validateS3BucketName()', () => { it('should reject an ip address as a name', () => - awsPlugin.validateS3BucketName('127.0.0.1') + awsPlugin + .validateS3BucketName('127.0.0.1') .then(() => { throw new Error('Should not get here'); }) - .catch(err => expect(err.message).to.contain('cannot look like an IPv4 address')) - ); + .catch(err => expect(err.message).to.contain('cannot look like an IPv4 address'))); it('should reject names that are too long', () => { const bucketName = Array.from({ length: 64 }, () => 'j').join(''); - return awsPlugin.validateS3BucketName(bucketName) + return awsPlugin + .validateS3BucketName(bucketName) .then(() => { throw new Error('Should not get here'); }) @@ -39,75 +40,75 @@ describe('#validateS3BucketName()', () => { }); it('should reject names that are too short', () => - awsPlugin.validateS3BucketName('12') + awsPlugin + .validateS3BucketName('12') .then(() => { throw new Error('Should not get here'); }) - .catch(err => expect(err.message).to.contain('shorter than 3 characters')) - ); + .catch(err => expect(err.message).to.contain('shorter than 3 characters'))); it('should reject names that contain invalid characters', () => - awsPlugin.validateS3BucketName('this has b@d characters') + awsPlugin + .validateS3BucketName('this has b@d characters') .then(() => { throw new Error('Should not get here'); }) - .catch(err => expect(err.message).to.contain('contains invalid characters')) - ); + .catch(err => expect(err.message).to.contain('contains invalid characters'))); it('should reject names that have consecutive periods', () => - awsPlugin.validateS3BucketName('otherwise..valid.name') + awsPlugin + .validateS3BucketName('otherwise..valid.name') .then(() => { throw new Error('Should not get here'); }) - .catch(err => expect(err.message).to.contain('cannot contain consecutive periods')) - ); + .catch(err => expect(err.message).to.contain('cannot contain consecutive periods'))); it('should reject names that start with a dash', () => - awsPlugin.validateS3BucketName('-invalid.name') + awsPlugin + .validateS3BucketName('-invalid.name') .then(() => { throw new Error('Should not get here'); }) - .catch(err => expect(err.message).to.contain('start with a letter or number')) - ); + .catch(err => expect(err.message).to.contain('start with a letter or number'))); it('should reject names that start with a period', () => - awsPlugin.validateS3BucketName('.invalid.name') + awsPlugin + .validateS3BucketName('.invalid.name') .then(() => { throw new Error('Should not get here'); }) - .catch(err => expect(err.message).to.contain('start with a letter or number')) - ); + .catch(err => expect(err.message).to.contain('start with a letter or number'))); it('should reject names that end with a dash', () => - awsPlugin.validateS3BucketName('invalid.name-') + awsPlugin + .validateS3BucketName('invalid.name-') .then(() => { throw new Error('Should not get here'); }) - .catch(err => expect(err.message).to.contain('end with a letter or number')) - ); + .catch(err => expect(err.message).to.contain('end with a letter or number'))); it('should reject names that end with a period', () => - awsPlugin.validateS3BucketName('invalid.name.') + awsPlugin + .validateS3BucketName('invalid.name.') .then(() => { throw new Error('Should not get here'); }) - .catch(err => expect(err.message).to.contain('end with a letter or number')) - ); + .catch(err => expect(err.message).to.contain('end with a letter or number'))); it('should reject names that contain uppercase letters', () => - awsPlugin.validateS3BucketName('otherwise.Valid.name') + awsPlugin + .validateS3BucketName('otherwise.Valid.name') .then(() => { throw new Error('Should not get here'); }) - .catch(err => expect(err.message).to.contain('cannot contain uppercase letters')) - ); + .catch(err => expect(err.message).to.contain('cannot contain uppercase letters'))); it('should accept valid names', () => - awsPlugin.validateS3BucketName('1.this.is.valid.2') + awsPlugin + .validateS3BucketName('1.this.is.valid.2') .then(() => awsPlugin.validateS3BucketName('another.valid.name')) .then(() => awsPlugin.validateS3BucketName('1-2-3')) .then(() => awsPlugin.validateS3BucketName('123')) - .then(() => awsPlugin.validateS3BucketName('should.be.allowed-to-mix')) - ); + .then(() => awsPlugin.validateS3BucketName('should.be.allowed-to-mix'))); }); }); diff --git a/lib/plugins/aws/logs/index.js b/lib/plugins/aws/logs/index.js index 9f37783ac..6e24332ff 100644 --- a/lib/plugins/aws/logs/index.js +++ b/lib/plugins/aws/logs/index.js @@ -15,10 +15,11 @@ class AwsLogs { Object.assign(this, validate); this.hooks = { - 'logs:logs': () => BbPromise.bind(this) - .then(this.extendedValidate) - .then(this.getLogStreams) - .then(this.showLogs), + 'logs:logs': () => + BbPromise.bind(this) + .then(this.extendedValidate) + .then(this.getLogStreams) + .then(this.showLogs), }; } @@ -42,21 +43,16 @@ class AwsLogs { orderBy: 'LastEventTime', }; - return this.provider - .request('CloudWatchLogs', - 'describeLogStreams', - params) - .then(reply => { - if (!reply || reply.logStreams.length === 0) { - throw new this.serverless.classes - .Error('No existing streams for the function'); - } + return this.provider.request('CloudWatchLogs', 'describeLogStreams', params).then(reply => { + if (!reply || reply.logStreams.length === 0) { + throw new this.serverless.classes.Error('No existing streams for the function'); + } - return _.chain(reply.logStreams) - .filter(stream => _.includes(stream.logStreamName, '[$LATEST]')) - .map('logStreamName') - .value(); - }); + return _.chain(reply.logStreams) + .filter(stream => _.includes(stream.logStreamName, '[$LATEST]')) + .map('logStreamName') + .value(); + }); } showLogs(logStreamNames) { @@ -78,51 +74,54 @@ class AwsLogs { if (this.options.filter) params.filterPattern = this.options.filter; if (this.options.nextToken) params.nextToken = this.options.nextToken; if (this.options.startTime) { - const since = (['m', 'h', 'd'] - .indexOf(this.options.startTime[this.options.startTime.length - 1]) !== -1); + const since = + ['m', 'h', 'd'].indexOf(this.options.startTime[this.options.startTime.length - 1]) !== -1; if (since) { - params.startTime = moment().subtract(this.options - .startTime.replace(/\D/g, ''), this.options - .startTime.replace(/\d/g, '')).valueOf(); + params.startTime = moment() + .subtract( + this.options.startTime.replace(/\D/g, ''), + this.options.startTime.replace(/\d/g, '') + ) + .valueOf(); } else { params.startTime = moment.utc(this.options.startTime).valueOf(); } } else { - params.startTime = moment().subtract(10, 'm').valueOf(); + params.startTime = moment() + .subtract(10, 'm') + .valueOf(); if (this.options.tail) { - params.startTime = moment().subtract(10, 's').valueOf(); + params.startTime = moment() + .subtract(10, 's') + .valueOf(); } } - return this.provider - .request('CloudWatchLogs', - 'filterLogEvents', - params) - .then(results => { - if (results.events) { - results.events.forEach(e => { - process.stdout.write(formatLambdaLogEvent(e.message)); - }); + return this.provider.request('CloudWatchLogs', 'filterLogEvents', params).then(results => { + if (results.events) { + results.events.forEach(e => { + process.stdout.write(formatLambdaLogEvent(e.message)); + }); + } + + if (results.nextToken) { + this.options.nextToken = results.nextToken; + } else { + delete this.options.nextToken; + } + + if (this.options.tail) { + if (results.events && results.events.length) { + this.options.startTime = _.last(results.events).timestamp + 1; } - if (results.nextToken) { - this.options.nextToken = results.nextToken; - } else { - delete this.options.nextToken; - } + return BbPromise.delay(this.options.interval) + .then(this.getLogStreams.bind(this)) + .then(this.showLogs.bind(this)); + } - if (this.options.tail) { - if (results.events && results.events.length) { - this.options.startTime = _.last(results.events).timestamp + 1; - } - - return BbPromise.delay(this.options.interval) - .then(this.getLogStreams.bind(this)) - .then(this.showLogs.bind(this)); - } - - return BbPromise.resolve(); - }); + return BbPromise.resolve(); + }); } } diff --git a/lib/plugins/aws/logs/index.test.js b/lib/plugins/aws/logs/index.test.js index 1ec522647..e748a4409 100644 --- a/lib/plugins/aws/logs/index.test.js +++ b/lib/plugins/aws/logs/index.test.js @@ -35,12 +35,9 @@ describe('AwsLogs', () => { expect(awsLogs.provider).to.be.instanceof(AwsProvider)); it('should run promise chain in order', () => { - const validateStub = sinon - .stub(awsLogs, 'extendedValidate').resolves(); - const getLogStreamsStub = sinon - .stub(awsLogs, 'getLogStreams').resolves(); - const showLogsStub = sinon - .stub(awsLogs, 'showLogs').resolves(); + const validateStub = sinon.stub(awsLogs, 'extendedValidate').resolves(); + const getLogStreamsStub = sinon.stub(awsLogs, 'getLogStreams').resolves(); + const showLogsStub = sinon.stub(awsLogs, 'showLogs').resolves(); return awsLogs.hooks['logs:logs']().then(() => { expect(validateStub.calledOnce).to.be.equal(true); @@ -83,14 +80,16 @@ describe('AwsLogs', () => { expect(() => awsLogs.extendedValidate()).to.throw(Error); }); - it('it should set default options', () => awsLogs.extendedValidate().then(() => { - expect(awsLogs.options.stage).to.deep.equal('dev'); - expect(awsLogs.options.region).to.deep.equal('us-east-1'); - expect(awsLogs.options.function).to.deep.equal('first'); - expect(awsLogs.options.interval).to.be.equal(1000); - expect(awsLogs.options.logGroupName).to.deep.equal(awsLogs.provider.naming - .getLogGroupName('customName')); - })); + it('it should set default options', () => + awsLogs.extendedValidate().then(() => { + expect(awsLogs.options.stage).to.deep.equal('dev'); + expect(awsLogs.options.region).to.deep.equal('us-east-1'); + expect(awsLogs.options.function).to.deep.equal('first'); + expect(awsLogs.options.interval).to.be.equal(1000); + expect(awsLogs.options.logGroupName).to.deep.equal( + awsLogs.provider.naming.getLogGroupName('customName') + ); + })); }); describe('#getLogStreams()', () => { @@ -119,37 +118,38 @@ describe('AwsLogs', () => { }; const getLogStreamsStub = sinon.stub(awsLogs.provider, 'request').resolves(replyMock); - return awsLogs.getLogStreams() - .then(logStreamNames => { - expect(getLogStreamsStub.calledOnce).to.be.equal(true); - expect(getLogStreamsStub.calledWithExactly( - 'CloudWatchLogs', - 'describeLogStreams', - { - logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), - descending: true, - limit: 50, - orderBy: 'LastEventTime', - } - )).to.be.equal(true); + return awsLogs.getLogStreams().then(logStreamNames => { + expect(getLogStreamsStub.calledOnce).to.be.equal(true); + expect( + getLogStreamsStub.calledWithExactly('CloudWatchLogs', 'describeLogStreams', { + logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), + descending: true, + limit: 50, + orderBy: 'LastEventTime', + }) + ).to.be.equal(true); - expect(logStreamNames[0]) - .to.be.equal('2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba'); - expect(logStreamNames[1]) - .to.be.equal('2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba'); + expect(logStreamNames[0]).to.be.equal( + '2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba' + ); + expect(logStreamNames[1]).to.be.equal( + '2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba' + ); - awsLogs.provider.request.restore(); - }); + awsLogs.provider.request.restore(); + }); }); it('should throw error if no log streams found', () => { sinon.stub(awsLogs.provider, 'request').resolves(); - return awsLogs.getLogStreams() + return awsLogs + .getLogStreams() .then(() => { expect(1).to.equal(2); awsLogs.provider.request.restore(); - }).catch(e => { + }) + .catch(e => { expect(e.name).to.be.equal('ServerlessError'); awsLogs.provider.request.restore(); }); @@ -200,22 +200,19 @@ describe('AwsLogs', () => { filter: 'error', }; - return awsLogs.showLogs(logStreamNamesMock) - .then(() => { - expect(filterLogEventsStub.calledOnce).to.be.equal(true); - expect(filterLogEventsStub.calledWithExactly( - 'CloudWatchLogs', - 'filterLogEvents', - { - logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), - interleaved: true, - logStreamNames: logStreamNamesMock, - filterPattern: 'error', - startTime: fakeTime - (3 * 60 * 60 * 1000), // -3h - } - )).to.be.equal(true); - awsLogs.provider.request.restore(); - }); + return awsLogs.showLogs(logStreamNamesMock).then(() => { + expect(filterLogEventsStub.calledOnce).to.be.equal(true); + expect( + filterLogEventsStub.calledWithExactly('CloudWatchLogs', 'filterLogEvents', { + logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), + interleaved: true, + logStreamNames: logStreamNamesMock, + filterPattern: 'error', + startTime: fakeTime - 3 * 60 * 60 * 1000, // -3h + }) + ).to.be.equal(true); + awsLogs.provider.request.restore(); + }); }); it('should call filterLogEvents API with standard start time', () => { @@ -248,23 +245,20 @@ describe('AwsLogs', () => { filter: 'error', }; - return awsLogs.showLogs(logStreamNamesMock) - .then(() => { - expect(filterLogEventsStub.calledOnce).to.be.equal(true); - expect(filterLogEventsStub.calledWithExactly( - 'CloudWatchLogs', - 'filterLogEvents', - { - logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), - interleaved: true, - logStreamNames: logStreamNamesMock, - startTime: 1287532800000, // '2010-10-20' - filterPattern: 'error', - } - )).to.be.equal(true); + return awsLogs.showLogs(logStreamNamesMock).then(() => { + expect(filterLogEventsStub.calledOnce).to.be.equal(true); + expect( + filterLogEventsStub.calledWithExactly('CloudWatchLogs', 'filterLogEvents', { + logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), + interleaved: true, + logStreamNames: logStreamNamesMock, + startTime: 1287532800000, // '2010-10-20' + filterPattern: 'error', + }) + ).to.be.equal(true); - awsLogs.provider.request.restore(); - }); + awsLogs.provider.request.restore(); + }); }); it('should call filterLogEvents API with latest 10 minutes if startTime not given', () => { @@ -295,22 +289,19 @@ describe('AwsLogs', () => { logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), }; - return awsLogs.showLogs(logStreamNamesMock) - .then(() => { - expect(filterLogEventsStub.calledOnce).to.be.equal(true); - expect(filterLogEventsStub.calledWithExactly( - 'CloudWatchLogs', - 'filterLogEvents', - { - logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), - interleaved: true, - logStreamNames: logStreamNamesMock, - startTime: fakeTime - (10 * 60 * 1000), // fakeTime - 10 minutes - } - )).to.be.equal(true); + return awsLogs.showLogs(logStreamNamesMock).then(() => { + expect(filterLogEventsStub.calledOnce).to.be.equal(true); + expect( + filterLogEventsStub.calledWithExactly('CloudWatchLogs', 'filterLogEvents', { + logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), + interleaved: true, + logStreamNames: logStreamNamesMock, + startTime: fakeTime - 10 * 60 * 1000, // fakeTime - 10 minutes + }) + ).to.be.equal(true); - awsLogs.provider.request.restore(); - }); + awsLogs.provider.request.restore(); + }); }); it('should call filterLogEvents API which starts 10 seconds in the past if tail given', () => { @@ -343,20 +334,19 @@ describe('AwsLogs', () => { }; const bbPromiseDelay = sinon.stub(bluebird, 'delay').rejects(); - return awsLogs.showLogs(logStreamNamesMock) + return awsLogs + .showLogs(logStreamNamesMock) .catch(() => true) // Promise.delay has to reject or it'll loop forever .then(() => { expect(filterLogEventsStub.calledOnce).to.be.equal(true); - expect(filterLogEventsStub.calledWithExactly( - 'CloudWatchLogs', - 'filterLogEvents', - { + expect( + filterLogEventsStub.calledWithExactly('CloudWatchLogs', 'filterLogEvents', { logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), interleaved: true, logStreamNames: logStreamNamesMock, - startTime: fakeTime - (10 * 1000), // fakeTime - 10 minutes - } - )).to.be.equal(true); + startTime: fakeTime - 10 * 1000, // fakeTime - 10 minutes + }) + ).to.be.equal(true); awsLogs.provider.request.restore(); bbPromiseDelay.restore(); diff --git a/lib/plugins/aws/metrics/awsMetrics.js b/lib/plugins/aws/metrics/awsMetrics.js index 98e5640a9..e28c9bd11 100644 --- a/lib/plugins/aws/metrics/awsMetrics.js +++ b/lib/plugins/aws/metrics/awsMetrics.js @@ -15,10 +15,11 @@ class AwsMetrics { Object.assign(this, validate); this.hooks = { - 'metrics:metrics': () => BbPromise.bind(this) - .then(this.extendedValidate) - .then(this.getMetrics) - .then(this.showMetrics), + 'metrics:metrics': () => + BbPromise.bind(this) + .then(this.extendedValidate) + .then(this.getMetrics) + .then(this.showMetrics), }; } @@ -26,12 +27,16 @@ class AwsMetrics { this.validate(); const today = new Date(); - const yesterday = moment().subtract(1, 'day').toDate(); + const yesterday = moment() + .subtract(1, 'day') + .toDate(); if (this.options.startTime) { const sinceDateMatch = this.options.startTime.match(/(\d+)(m|h|d)/); if (sinceDateMatch) { - this.options.startTime = moment().subtract(sinceDateMatch[1], sinceDateMatch[2]).valueOf(); + this.options.startTime = moment() + .subtract(sinceDateMatch[1], sinceDateMatch[2]) + .valueOf(); } } @@ -46,12 +51,12 @@ class AwsMetrics { const StartTime = this.options.startTime; const EndTime = this.options.endTime; const hoursDiff = Math.abs(EndTime - StartTime) / 36e5; - const Period = (hoursDiff > 24) ? 3600 * 24 : 3600; + const Period = hoursDiff > 24 ? 3600 * 24 : 3600; const functions = this.options.function ? [this.serverless.service.getFunction(this.options.function).name] : this.serverless.service.getAllFunctionsNames(); - return BbPromise.map(functions, (functionName) => { + return BbPromise.map(functions, functionName => { const commonParams = { StartTime, EndTime, @@ -81,7 +86,7 @@ class AwsMetrics { Unit: 'Milliseconds', }); - const getMetrics = (params) => + const getMetrics = params => this.provider.request('CloudWatch', 'getMetricStatistics', params); return BbPromise.all([ @@ -89,7 +94,7 @@ class AwsMetrics { getMetrics(throttlesParams), getMetrics(errorsParams), getMetrics(averageDurationParams), - ]).then((metrics) => metrics); + ]).then(metrics => metrics); }); } @@ -107,7 +112,7 @@ class AwsMetrics { message += `${formattedStartTime} - ${formattedEndTime}\n\n`; if (metrics && metrics.length > 0) { - const getDatapointsByLabel = (Label) => + const getDatapointsByLabel = Label => _.chain(metrics) .flatten() .filter({ Label }) @@ -124,7 +129,7 @@ class AwsMetrics { message += `${chalk.yellow('Invocations:', invocationsCount, '\n')}`; message += `${chalk.yellow('Throttles:', throttlesCount, '\n')}`; message += `${chalk.yellow('Errors:', errorsCount, '\n')}`; - message += `${chalk.yellow('Duration (avg.):', `${Number((durationAverage).toFixed(2))}ms`)}`; + message += `${chalk.yellow('Duration (avg.):', `${Number(durationAverage.toFixed(2))}ms`)}`; } else { message += `${chalk.yellow('There are no metrics to show for these options')}`; } diff --git a/lib/plugins/aws/metrics/awsMetrics.test.js b/lib/plugins/aws/metrics/awsMetrics.test.js index a265a84c8..49fcbd206 100644 --- a/lib/plugins/aws/metrics/awsMetrics.test.js +++ b/lib/plugins/aws/metrics/awsMetrics.test.js @@ -42,12 +42,9 @@ describe('AwsMetrics', () => { }); it('should run promise chain in order for "metrics:metrics" hook', () => { - const extendedValidateStub = sinon - .stub(awsMetrics, 'extendedValidate').resolves(); - const getMetricsStub = sinon - .stub(awsMetrics, 'getMetrics').resolves(); - const showMetricsStub = sinon - .stub(awsMetrics, 'showMetrics').resolves(); + const extendedValidateStub = sinon.stub(awsMetrics, 'extendedValidate').resolves(); + const getMetricsStub = sinon.stub(awsMetrics, 'getMetrics').resolves(); + const showMetricsStub = sinon.stub(awsMetrics, 'showMetrics').resolves(); return awsMetrics.hooks['metrics:metrics']().then(() => { expect(extendedValidateStub.calledOnce).to.equal(true); @@ -70,8 +67,7 @@ describe('AwsMetrics', () => { }; awsMetrics.serverless.service.service = 'my-service'; awsMetrics.options.function = 'function1'; - validateStub = sinon - .stub(awsMetrics, 'validate').resolves(); + validateStub = sinon.stub(awsMetrics, 'validate').resolves(); }); afterEach(() => { @@ -81,8 +77,7 @@ describe('AwsMetrics', () => { it('should call the shared validate() function', () => awsMetrics.extendedValidate().then(() => { expect(validateStub.calledOnce).to.equal(true); - }) - ); + })); it('should set the startTime to yesterday as the default value if not provided', () => { awsMetrics.options.startTime = null; @@ -237,44 +232,52 @@ describe('AwsMetrics', () => { const expectedResult = [ [ - { ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func1' }, + { + ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func1' }, Label: 'Invocations', Datapoints: [], }, - { ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func1' }, + { + ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func1' }, Label: 'Throttles', Datapoints: [], }, - { ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func1' }, + { + ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func1' }, Label: 'Errors', Datapoints: [], }, - { ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func1' }, + { + ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func1' }, Label: 'Duration', Datapoints: [], }, ], [ - { ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func2' }, + { + ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func2' }, Label: 'Invocations', Datapoints: [], }, - { ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func2' }, + { + ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func2' }, Label: 'Throttles', Datapoints: [], }, - { ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func2' }, + { + ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func2' }, Label: 'Errors', Datapoints: [], }, - { ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func2' }, + { + ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func2' }, Label: 'Duration', Datapoints: [], }, ], ]; - return awsMetrics.getMetrics().then((result) => { + return awsMetrics.getMetrics().then(result => { expect(result).to.deep.equal(expectedResult); }); }); @@ -311,26 +314,30 @@ describe('AwsMetrics', () => { const expectedResult = [ [ - { ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func1' }, + { + ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func1' }, Label: 'Invocations', Datapoints: [], }, - { ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func1' }, + { + ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func1' }, Label: 'Throttles', Datapoints: [], }, - { ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func1' }, + { + ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func1' }, Label: 'Errors', Datapoints: [], }, - { ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func1' }, + { + ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func1' }, Label: 'Duration', Datapoints: [], }, ], ]; - return awsMetrics.getMetrics().then((result) => { + return awsMetrics.getMetrics().then(result => { expect(result).to.deep.equal(expectedResult); }); }); @@ -340,11 +347,13 @@ describe('AwsMetrics', () => { awsMetrics.options.endTime = new Date('1970-01-01T16:00'); return awsMetrics.getMetrics().then(() => { - expect(requestStub.calledWith( - sinon.match.string, - sinon.match.string, - sinon.match.has('Period', 3600) - )).to.equal(true); + expect( + requestStub.calledWith( + sinon.match.string, + sinon.match.string, + sinon.match.has('Period', 3600) + ) + ).to.equal(true); }); }); @@ -353,11 +362,13 @@ describe('AwsMetrics', () => { awsMetrics.options.endTime = new Date('1970-01-03'); return awsMetrics.getMetrics().then(() => { - expect(requestStub.calledWith( - sinon.match.string, - sinon.match.string, - sinon.match.has('Period', 24 * 3600) - )).to.equal(true); + expect( + requestStub.calledWith( + sinon.match.string, + sinon.match.string, + sinon.match.has('Period', 24 * 3600) + ) + ).to.equal(true); }); }); }); @@ -455,7 +466,7 @@ describe('AwsMetrics', () => { expectedMessage += `${chalk.yellow('Errors: 0 \n')}`; expectedMessage += `${chalk.yellow('Duration (avg.): 1000ms')}`; - return awsMetrics.showMetrics(metrics).then((message) => { + return awsMetrics.showMetrics(metrics).then(message => { expect(consoleLogStub.calledOnce).to.equal(true); expect(message).to.equal(expectedMessage); }); @@ -477,18 +488,15 @@ describe('AwsMetrics', () => { ], ]; - return awsMetrics.showMetrics(metrics).then((message) => { + return awsMetrics.showMetrics(metrics).then(message => { expect(message).to.include('Duration (avg.): 300ms'); }); }); it('should display 0 as average function duration if no data by given period', () => { - const metrics = [ - [], - [], - ]; + const metrics = [[], []]; - return awsMetrics.showMetrics(metrics).then((message) => { + return awsMetrics.showMetrics(metrics).then(message => { expect(message).to.include('Duration (avg.): 0ms'); }); }); @@ -537,7 +545,7 @@ describe('AwsMetrics', () => { expectedMessage += `${chalk.yellow('Errors: 0 \n')}`; expectedMessage += `${chalk.yellow('Duration (avg.): 1000ms')}`; - return awsMetrics.showMetrics(metrics).then((message) => { + return awsMetrics.showMetrics(metrics).then(message => { expect(consoleLogStub.calledOnce).to.equal(true); expect(message).to.equal(expectedMessage); }); @@ -551,7 +559,7 @@ describe('AwsMetrics', () => { expectedMessage += 'January 1, 1970 12:00 AM - January 2, 1970 12:00 AM\n\n'; expectedMessage += `${chalk.yellow('There are no metrics to show for these options')}`; - return awsMetrics.showMetrics().then((message) => { + return awsMetrics.showMetrics().then(message => { expect(consoleLogStub.calledOnce).to.equal(true); expect(message).to.equal(expectedMessage); }); diff --git a/lib/plugins/aws/package/compile/events/alb/index.js b/lib/plugins/aws/package/compile/events/alb/index.js index 08970abe1..b7b4eb30b 100644 --- a/lib/plugins/aws/package/compile/events/alb/index.js +++ b/lib/plugins/aws/package/compile/events/alb/index.js @@ -13,13 +13,7 @@ class AwsCompileAlbEvents { this.options = options; this.provider = this.serverless.getProvider('aws'); - Object.assign( - this, - validate, - compileTargetGroups, - compileListenerRules, - compilePermissions - ); + Object.assign(this, validate, compileTargetGroups, compileListenerRules, compilePermissions); this.hooks = { 'package:compileEvents': () => { diff --git a/lib/plugins/aws/package/compile/events/alb/index.test.js b/lib/plugins/aws/package/compile/events/alb/index.test.js index c4785354e..d3f4815c0 100644 --- a/lib/plugins/aws/package/compile/events/alb/index.test.js +++ b/lib/plugins/aws/package/compile/events/alb/index.test.js @@ -25,12 +25,9 @@ describe('AwsCompileAlbEvents', () => { let compilePermissionsStub; beforeEach(() => { - compileTargetGroupsStub = sinon - .stub(awsCompileAlbEvents, 'compileTargetGroups').resolves(); - compileListenerRulesStub = sinon - .stub(awsCompileAlbEvents, 'compileListenerRules').resolves(); - compilePermissionsStub = sinon - .stub(awsCompileAlbEvents, 'compilePermissions').resolves(); + compileTargetGroupsStub = sinon.stub(awsCompileAlbEvents, 'compileTargetGroups').resolves(); + compileListenerRulesStub = sinon.stub(awsCompileAlbEvents, 'compileListenerRules').resolves(); + compilePermissionsStub = sinon.stub(awsCompileAlbEvents, 'compilePermissions').resolves(); }); afterEach(() => { @@ -50,20 +47,22 @@ describe('AwsCompileAlbEvents', () => { }); it('should run the promise chain in order', () => { - const validateStub = sinon - .stub(awsCompileAlbEvents, 'validate').returns({ - events: [{ + const validateStub = sinon.stub(awsCompileAlbEvents, 'validate').returns({ + events: [ + { functionName: 'first', - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { host: 'example.com', path: '/hello', }, - }], - }); + }, + ], + }); return awsCompileAlbEvents.hooks['package:compileEvents']().then(() => { expect(validateStub.calledOnce).to.be.equal(true); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js index 462a5522c..ab661d780 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.js @@ -2,11 +2,14 @@ module.exports = { compileListenerRules() { - this.validated.events.forEach((event) => { - const listenerRuleLogicalId = this.provider.naming - .getAlbListenerRuleLogicalId(event.functionName, event.priority); - const targetGroupLogicalId = this.provider.naming - .getAlbTargetGroupLogicalId(event.functionName); + this.validated.events.forEach(event => { + const listenerRuleLogicalId = this.provider.naming.getAlbListenerRuleLogicalId( + event.functionName, + event.priority + ); + const targetGroupLogicalId = this.provider.naming.getAlbTargetGroupLogicalId( + event.functionName + ); const Conditions = [ { diff --git a/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js index d9d4f22b4..1e038d885 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/listenerRules.test.js @@ -22,9 +22,10 @@ describe('#compileListenerRules()', () => { events: [ { functionName: 'first', - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { host: ['example.com'], @@ -33,9 +34,10 @@ describe('#compileListenerRules()', () => { }, { functionName: 'second', - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 2, conditions: { path: ['/world'], @@ -46,8 +48,8 @@ describe('#compileListenerRules()', () => { awsCompileAlbEvents.compileListenerRules(); - const resources = awsCompileAlbEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileAlbEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources.FirstAlbListenerRule1).to.deep.equal({ Type: 'AWS::ElasticLoadBalancingV2::ListenerRule', @@ -63,20 +65,17 @@ describe('#compileListenerRules()', () => { Conditions: [ { Field: 'path-pattern', - Values: [ - '/hello', - ], + Values: ['/hello'], }, { Field: 'host-header', - Values: [ - 'example.com', - ], + Values: ['example.com'], }, ], - ListenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + ListenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', Priority: 1, }, }); @@ -94,14 +93,13 @@ describe('#compileListenerRules()', () => { Conditions: [ { Field: 'path-pattern', - Values: [ - '/world', - ], + Values: ['/world'], }, ], - ListenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + ListenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', Priority: 2, }, }); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/permissions.js b/lib/plugins/aws/package/compile/events/alb/lib/permissions.js index 7ba3c44cf..80df61ba2 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/permissions.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/permissions.js @@ -2,13 +2,13 @@ module.exports = { compilePermissions() { - this.validated.events.forEach((event) => { + this.validated.events.forEach(event => { const { functionName } = event; - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); - const albPermissionLogicalId = this.provider.naming - .getLambdaAlbPermissionLogicalId(functionName); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); + const albPermissionLogicalId = this.provider.naming.getLambdaAlbPermissionLogicalId( + functionName + ); Object.assign(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [albPermissionLogicalId]: { diff --git a/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js b/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js index 6042590dd..16473e379 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/permissions.test.js @@ -22,9 +22,10 @@ describe('#compilePermissions()', () => { events: [ { functionName: 'first', - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { host: 'example.com', @@ -33,9 +34,10 @@ describe('#compilePermissions()', () => { }, { functionName: 'second', - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 2, conditions: { path: '/world', @@ -46,18 +48,15 @@ describe('#compilePermissions()', () => { awsCompileAlbEvents.compilePermissions(); - const resources = awsCompileAlbEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileAlbEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources.FirstLambdaPermissionAlb).to.deep.equal({ Type: 'AWS::Lambda::Permission', Properties: { Action: 'lambda:InvokeFunction', FunctionName: { - 'Fn::GetAtt': [ - 'FirstLambdaFunction', - 'Arn', - ], + 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'], }, Principal: 'elasticloadbalancing.amazonaws.com', }, @@ -68,10 +67,7 @@ describe('#compilePermissions()', () => { Properties: { Action: 'lambda:InvokeFunction', FunctionName: { - 'Fn::GetAtt': [ - 'SecondLambdaFunction', - 'Arn', - ], + 'Fn::GetAtt': ['SecondLambdaFunction', 'Arn'], }, Principal: 'elasticloadbalancing.amazonaws.com', }, diff --git a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js index 86f03bd50..ea6ca2207 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js @@ -2,15 +2,14 @@ module.exports = { compileTargetGroups() { - this.validated.events.forEach((event) => { + this.validated.events.forEach(event => { const { functionName } = event; - const targetGroupLogicalId = this.provider.naming - .getAlbTargetGroupLogicalId(functionName); - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaAlbPermissionLogicalId(functionName); + const targetGroupLogicalId = this.provider.naming.getAlbTargetGroupLogicalId(functionName); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); + const lambdaPermissionLogicalId = this.provider.naming.getLambdaAlbPermissionLogicalId( + functionName + ); Object.assign(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [targetGroupLogicalId]: { diff --git a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js index dfba7da92..3280c1757 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js @@ -22,9 +22,10 @@ describe('#compileTargetGroups()', () => { events: [ { functionName: 'first', - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { host: 'example.com', @@ -33,9 +34,10 @@ describe('#compileTargetGroups()', () => { }, { functionName: 'second', - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 2, conditions: { path: '/world', @@ -46,8 +48,8 @@ describe('#compileTargetGroups()', () => { awsCompileAlbEvents.compileTargetGroups(); - const resources = awsCompileAlbEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileAlbEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources.FirstAlbTargetGroup).to.deep.equal({ Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', @@ -57,10 +59,7 @@ describe('#compileTargetGroups()', () => { Targets: [ { Id: { - 'Fn::GetAtt': [ - 'FirstLambdaFunction', - 'Arn', - ], + 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'], }, }, ], @@ -75,10 +74,7 @@ describe('#compileTargetGroups()', () => { Targets: [ { Id: { - 'Fn::GetAtt': [ - 'SecondLambdaFunction', - 'Arn', - ], + 'Fn::GetAtt': ['SecondLambdaFunction', 'Arn'], }, }, ], diff --git a/lib/plugins/aws/package/compile/events/alb/lib/validate.js b/lib/plugins/aws/package/compile/events/alb/lib/validate.js index 76ecd8ffe..e38dc9f70 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/validate.js @@ -11,7 +11,7 @@ module.exports = { const events = []; _.forEach(this.serverless.service.functions, (functionObject, functionName) => { - _.forEach(functionObject.events, (event) => { + _.forEach(functionObject.events, event => { if (_.has(event, 'alb')) { if (_.isObject(event.alb)) { const albObj = { @@ -52,9 +52,11 @@ module.exports = { validateHeaderCondition(event, functionName) { const messageTitle = `Invalid ALB event "header" condition in function "${functionName}".`; - if (!_.isObject(event.alb.conditions.header) - || !event.alb.conditions.header.name - || !event.alb.conditions.header.values) { + if ( + !_.isObject(event.alb.conditions.header) || + !event.alb.conditions.header.name || + !event.alb.conditions.header.values + ) { const errorMessage = [ messageTitle, ' You must provide an object with "name" and "values" properties.', @@ -62,10 +64,7 @@ module.exports = { throw new this.serverless.classes.Error(errorMessage); } if (!_.isArray(event.alb.conditions.header.values)) { - const errorMessage = [ - messageTitle, - ' Property "values" must be an array.', - ].join(''); + const errorMessage = [messageTitle, ' Property "values" must be an array.'].join(''); throw new this.serverless.classes.Error(errorMessage); } return event.alb.conditions.header; @@ -84,8 +83,9 @@ module.exports = { validateIpCondition(event, functionName) { const cidrBlocks = _.concat(event.alb.conditions.ip); - const allValuesAreCidr = cidrBlocks - .every(cidr => CIDR_IPV4_PATTERN.test(cidr) || CIDR_IPV6_PATTERN.test(cidr)); + const allValuesAreCidr = cidrBlocks.every( + cidr => CIDR_IPV4_PATTERN.test(cidr) || CIDR_IPV6_PATTERN.test(cidr) + ); if (!allValuesAreCidr) { const errorMessage = [ diff --git a/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js b/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js index 103831fc1..a5b69e3f5 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/validate.test.js @@ -23,9 +23,10 @@ describe('#validate()', () => { events: [ { alb: { - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { host: 'example.com', @@ -41,9 +42,10 @@ describe('#validate()', () => { events: [ { alb: { - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 2, conditions: { path: '/world', @@ -63,9 +65,10 @@ describe('#validate()', () => { expect(validated.events).to.deep.equal([ { functionName: 'first', - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { host: ['example.com'], @@ -76,9 +79,10 @@ describe('#validate()', () => { }, { functionName: 'second', - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 2, conditions: { path: ['/world'], @@ -97,9 +101,10 @@ describe('#validate()', () => { events: [ { alb: { - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { path: '/hello', @@ -120,9 +125,10 @@ describe('#validate()', () => { events: [ { alb: { - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { path: '/hello', @@ -143,9 +149,10 @@ describe('#validate()', () => { events: [ { alb: { - listenerArn: 'arn:aws:elasticloadbalancing:' - + 'us-east-1:123456789012:listener/app/my-load-balancer/' - + '50dc6c495c0c9188/f2f7dc8efc522ab2', + listenerArn: + 'arn:aws:elasticloadbalancing:' + + 'us-east-1:123456789012:listener/app/my-load-balancer/' + + '50dc6c495c0c9188/f2f7dc8efc522ab2', priority: 1, conditions: { path: '/hello', @@ -168,14 +175,14 @@ describe('#validate()', () => { it('should return the value as array if it is a valid ipv6 cidr block', () => { const event = { alb: { conditions: { ip: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156/127' } } }; - expect(awsCompileAlbEvents.validateIpCondition(event, '')) - .to.deep.equal(['fe80:0000:0000:0000:0204:61ff:fe9d:f156/127']); + expect(awsCompileAlbEvents.validateIpCondition(event, '')).to.deep.equal([ + 'fe80:0000:0000:0000:0204:61ff:fe9d:f156/127', + ]); }); it('should return the value as array if it is a valid ipv4 cidr block', () => { const event = { alb: { conditions: { ip: '192.168.0.1/21' } } }; - expect(awsCompileAlbEvents.validateIpCondition(event, '')) - .to.deep.equal(['192.168.0.1/21']); + expect(awsCompileAlbEvents.validateIpCondition(event, '')).to.deep.equal(['192.168.0.1/21']); }); }); @@ -187,8 +194,7 @@ describe('#validate()', () => { it('should return the value if it is an object', () => { const event = { alb: { conditions: { query: { foo: 'bar' } } } }; - expect(awsCompileAlbEvents.validateQueryCondition(event, '')) - .to.deep.equal({ foo: 'bar' }); + expect(awsCompileAlbEvents.validateQueryCondition(event, '')).to.deep.equal({ foo: 'bar' }); }); }); @@ -205,8 +211,10 @@ describe('#validate()', () => { it('should return the value if it is valid', () => { const event = { alb: { conditions: { header: { name: 'foo', values: ['bar'] } } } }; - expect(awsCompileAlbEvents.validateHeaderCondition(event, '')) - .to.deep.equal({ name: 'foo', values: ['bar'] }); + expect(awsCompileAlbEvents.validateHeaderCondition(event, '')).to.deep.equal({ + name: 'foo', + values: ['bar'], + }); }); }); }); diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.js index 89c863bb4..5b9ca7d91 100644 --- a/lib/plugins/aws/package/compile/events/alexaSkill/index.js +++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.js @@ -13,7 +13,7 @@ class AwsCompileAlexaSkillEvents { } compileAlexaSkillEvents() { - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); let alexaSkillNumberInFunction = 0; @@ -24,7 +24,7 @@ class AwsCompileAlexaSkillEvents { let appId; if (event === 'alexaSkill') { const warningMessage = [ - 'Warning! You are using an old syntax for alexaSkill which doesn\'t', + "Warning! You are using an old syntax for alexaSkill which doesn't", ' restrict the invocation solely to your skill.', ' Please refer to the documentation for additional information.', ].join(''); @@ -54,17 +54,13 @@ class AwsCompileAlexaSkillEvents { } alexaSkillNumberInFunction++; - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); const permissionTemplate = { Type: 'AWS::Lambda::Permission', Properties: { FunctionName: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], }, Action: enabled ? 'lambda:InvokeFunction' : 'lambda:DisableInvokeFunction', Principal: 'alexa-appkit.amazon.com', @@ -75,16 +71,19 @@ class AwsCompileAlexaSkillEvents { permissionTemplate.Properties.EventSourceToken = appId.replace(/\\n|\\r/g, ''); } - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaAlexaSkillPermissionLogicalId(functionName, - alexaSkillNumberInFunction); + const lambdaPermissionLogicalId = this.provider.naming.getLambdaAlexaSkillPermissionLogicalId( + functionName, + alexaSkillNumberInFunction + ); const permissionCloudForamtionResource = { [lambdaPermissionLogicalId]: permissionTemplate, }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - permissionCloudForamtionResource); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + permissionCloudForamtionResource + ); } }); } diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js index cb9090ae6..3c96f9128 100644 --- a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js +++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js @@ -38,9 +38,7 @@ describe('AwsCompileAlexaSkillEvents', () => { it('should show a warning if alexaSkill appId is not specified', () => { awsCompileAlexaSkillEvents.serverless.service.functions = { first: { - events: [ - 'alexaSkill', - ], + events: ['alexaSkill'], }, }; @@ -48,25 +46,25 @@ describe('AwsCompileAlexaSkillEvents', () => { expect(consolePrinted).to.contain.string('old syntax for alexaSkill'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Type + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.FunctionName ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.Action + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.Action ).to.equal('lambda:InvokeFunction'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.Principal + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.Principal ).to.equal('alexa-appkit.amazon.com'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken ).to.be.undefined; }); @@ -120,46 +118,46 @@ describe('AwsCompileAlexaSkillEvents', () => { awsCompileAlexaSkillEvents.compileAlexaSkillEvents(); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Type + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.FunctionName ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.Action + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.Action ).to.equal('lambda:InvokeFunction'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.Principal + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.Principal ).to.equal('alexa-appkit.amazon.com'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken ).to.equal(skillId1); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill2.Type + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill2.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill2.Properties.FunctionName + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill2.Properties.FunctionName ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill2.Properties.Action + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill2.Properties.Action ).to.equal('lambda:InvokeFunction'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill2.Properties.Principal + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill2.Properties.Principal ).to.equal('alexa-appkit.amazon.com'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill2.Properties.EventSourceToken + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill2.Properties.EventSourceToken ).to.equal(skillId2); }); @@ -180,25 +178,25 @@ describe('AwsCompileAlexaSkillEvents', () => { awsCompileAlexaSkillEvents.compileAlexaSkillEvents(); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Type + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.FunctionName ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.Action + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.Action ).to.equal('lambda:DisableInvokeFunction'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.Principal + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.Principal ).to.equal('alexa-appkit.amazon.com'); - expect(awsCompileAlexaSkillEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + expect( + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken ).to.equal(skillId1); }); @@ -212,8 +210,8 @@ describe('AwsCompileAlexaSkillEvents', () => { awsCompileAlexaSkillEvents.compileAlexaSkillEvents(); expect( - awsCompileAlexaSkillEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources + awsCompileAlexaSkillEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources ).to.deep.equal({}); }); diff --git a/lib/plugins/aws/package/compile/events/alexaSmartHome/index.js b/lib/plugins/aws/package/compile/events/alexaSmartHome/index.js index 97d34abf7..24e0efdcc 100644 --- a/lib/plugins/aws/package/compile/events/alexaSmartHome/index.js +++ b/lib/plugins/aws/package/compile/events/alexaSmartHome/index.js @@ -13,7 +13,7 @@ class AwsCompileAlexaSmartHomeEvents { } compileAlexaSmartHomeEvents() { - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); let alexaSmartHomeNumberInFunction = 0; @@ -32,12 +32,13 @@ class AwsCompileAlexaSmartHomeEvents { ' OR an object with "appId" property.', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } EventSourceToken = event.alexaSmartHome.appId; - Action = event.alexaSmartHome.enabled !== false ? - 'lambda:InvokeFunction' : 'lambda:DisableInvokeFunction'; + Action = + event.alexaSmartHome.enabled !== false + ? 'lambda:InvokeFunction' + : 'lambda:DisableInvokeFunction'; } else if (typeof event.alexaSmartHome === 'string') { EventSourceToken = event.alexaSmartHome; Action = 'lambda:InvokeFunction'; @@ -49,17 +50,13 @@ class AwsCompileAlexaSmartHomeEvents { ].join(''); throw new this.serverless.classes.Error(errorMessage); } - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); const permissionTemplate = { Type: 'AWS::Lambda::Permission', Properties: { FunctionName: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], }, Action: Action.replace(/\\n|\\r/g, ''), Principal: 'alexa-connectedhome.amazon.com', @@ -67,16 +64,19 @@ class AwsCompileAlexaSmartHomeEvents { }, }; - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaAlexaSmartHomePermissionLogicalId(functionName, - alexaSmartHomeNumberInFunction); + const lambdaPermissionLogicalId = this.provider.naming.getLambdaAlexaSmartHomePermissionLogicalId( + functionName, + alexaSmartHomeNumberInFunction + ); const permissionCloudFormationResource = { [lambdaPermissionLogicalId]: permissionTemplate, }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - permissionCloudFormationResource); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + permissionCloudFormationResource + ); } }); } diff --git a/lib/plugins/aws/package/compile/events/alexaSmartHome/index.test.js b/lib/plugins/aws/package/compile/events/alexaSmartHome/index.test.js index 4c0692846..2ec877993 100644 --- a/lib/plugins/aws/package/compile/events/alexaSmartHome/index.test.js +++ b/lib/plugins/aws/package/compile/events/alexaSmartHome/index.test.js @@ -78,65 +78,65 @@ describe('AwsCompileAlexaSmartHomeEvents', () => { awsCompileAlexaSmartHomeEvents.compileAlexaSmartHomeEvents(); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome1.Type + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome1.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome2.Type + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome2.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome3.Type + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome3.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome1.Properties.FunctionName + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome1.Properties.FunctionName ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome2.Properties.FunctionName + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome2.Properties.FunctionName ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome3.Properties.FunctionName + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome3.Properties.FunctionName ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome1.Properties.Action + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome1.Properties.Action ).to.equal('lambda:DisableInvokeFunction'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome2.Properties.Action + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome2.Properties.Action ).to.equal('lambda:InvokeFunction'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome3.Properties.Action + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome3.Properties.Action ).to.equal('lambda:InvokeFunction'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome1.Properties.Principal + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome1.Properties.Principal ).to.equal('alexa-connectedhome.amazon.com'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome2.Properties.Principal + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome2.Properties.Principal ).to.equal('alexa-connectedhome.amazon.com'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome3.Properties.Principal + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome3.Properties.Principal ).to.equal('alexa-connectedhome.amazon.com'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome1.Properties.EventSourceToken + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome1.Properties.EventSourceToken ).to.equal('amzn1.ask.skill.xx-xx-xx-xx'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome2.Properties.EventSourceToken + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome2.Properties.EventSourceToken ).to.equal('amzn1.ask.skill.yy-yy-yy-yy'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome3.Properties.EventSourceToken + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome3.Properties.EventSourceToken ).to.equal('amzn1.ask.skill.zz-zz-zz-zz'); }); @@ -170,30 +170,28 @@ describe('AwsCompileAlexaSmartHomeEvents', () => { awsCompileAlexaSmartHomeEvents.compileAlexaSmartHomeEvents(); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome1.Properties.Action + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome1.Properties.Action ).to.equal('lambda:DisableInvokeFunction'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome2.Properties.Action + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome2.Properties.Action ).to.equal('lambda:InvokeFunction'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome3.Properties.Action + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome3.Properties.Action ).to.equal('lambda:InvokeFunction'); - expect(awsCompileAlexaSmartHomeEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSmartHome4.Properties.Action + expect( + awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionAlexaSmartHome4.Properties.Action ).to.equal('lambda:InvokeFunction'); }); it('should not create corresponding resources when alexaSmartHome events are not given', () => { awsCompileAlexaSmartHomeEvents.serverless.service.functions = { first: { - events: [ - 'alexaSkill', - ], + events: ['alexaSkill'], }, }; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/index.js b/lib/plugins/aws/package/compile/events/apiGateway/index.js index 42f4f27f1..b89ca8d96 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/index.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/index.js @@ -90,7 +90,8 @@ class AwsCompileApigEvents { // eslint-disable-next-line no-shadow const validate = require('../../../../lib/validate').validate; // eslint-disable-next-line max-len - const disassociateUsagePlan = require('./lib/hack/disassociateUsagePlan').disassociateUsagePlan; + const disassociateUsagePlan = require('./lib/hack/disassociateUsagePlan') + .disassociateUsagePlan; return BbPromise.bind(this) .then(validate) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/index.test.js b/lib/plugins/aws/package/compile/events/apiGateway/index.test.js index cc15ab4b5..ca4042838 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/index.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/index.test.js @@ -48,24 +48,17 @@ describe('AwsCompileApigEvents', () => { let disassociateUsagePlanStub; beforeEach(() => { - compileRestApiStub = sinon - .stub(awsCompileApigEvents, 'compileRestApi').resolves(); - compileResourcesStub = sinon - .stub(awsCompileApigEvents, 'compileResources').resolves(); - compileMethodsStub = sinon - .stub(awsCompileApigEvents, 'compileMethods').resolves(); - compileDeploymentStub = sinon - .stub(awsCompileApigEvents, 'compileDeployment').resolves(); - compileUsagePlanStub = sinon - .stub(awsCompileApigEvents, 'compileUsagePlan').resolves(); - compilePermissionsStub = sinon - .stub(awsCompileApigEvents, 'compilePermissions').resolves(); - compileStageStub = sinon - .stub(awsCompileApigEvents, 'compileStage').resolves(); - updateStageStub = sinon - .stub(updateStage, 'updateStage').resolves(); + compileRestApiStub = sinon.stub(awsCompileApigEvents, 'compileRestApi').resolves(); + compileResourcesStub = sinon.stub(awsCompileApigEvents, 'compileResources').resolves(); + compileMethodsStub = sinon.stub(awsCompileApigEvents, 'compileMethods').resolves(); + compileDeploymentStub = sinon.stub(awsCompileApigEvents, 'compileDeployment').resolves(); + compileUsagePlanStub = sinon.stub(awsCompileApigEvents, 'compileUsagePlan').resolves(); + compilePermissionsStub = sinon.stub(awsCompileApigEvents, 'compilePermissions').resolves(); + compileStageStub = sinon.stub(awsCompileApigEvents, 'compileStage').resolves(); + updateStageStub = sinon.stub(updateStage, 'updateStage').resolves(); disassociateUsagePlanStub = sinon - .stub(disassociateUsagePlan, 'disassociateUsagePlan').resolves(); + .stub(disassociateUsagePlan, 'disassociateUsagePlan') + .resolves(); }); afterEach(() => { @@ -89,18 +82,17 @@ describe('AwsCompileApigEvents', () => { expect(awsCompileApigEvents.apiGatewayMethodLogicalIds).to.deep.equal([])); it('should run "package:compileEvents" promise chain in order', () => { - const validateStub = sinon - .stub(awsCompileApigEvents, 'validate').returns({ - events: [ - { - functionName: 'first', - http: { - path: 'users', - method: 'POST', - }, + const validateStub = sinon.stub(awsCompileApigEvents, 'validate').returns({ + events: [ + { + functionName: 'first', + http: { + path: 'users', + method: 'POST', }, - ], - }); + }, + ], + }); return awsCompileApigEvents.hooks['package:compileEvents']().then(() => { expect(validateStub.calledOnce).to.be.equal(true); @@ -120,8 +112,7 @@ describe('AwsCompileApigEvents', () => { let getServiceStateStub; beforeEach(() => { - getServiceStateStub = sinon - .stub(getServiceState, 'getServiceState'); + getServiceStateStub = sinon.stub(getServiceState, 'getServiceState'); }); afterEach(() => { diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.js index e176f4c82..ee875c1a9 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.js @@ -14,10 +14,12 @@ function createApiKeyResource(that, apiKey) { Name: name, Value: value, Description: description, - StageKeys: [{ - RestApiId: that.provider.getApiGatewayRestApiId(), - StageName: that.provider.getStage(), - }], + StageKeys: [ + { + RestApiId: that.provider.getApiGatewayRestApiId(), + StageName: that.provider.getStage(), + }, + ], }, DependsOn: that.apiGatewayDeploymentLogicalId, }; @@ -41,22 +43,25 @@ module.exports = { } const resources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources; let keyNumber = 0; - _.forEach(this.serverless.service.provider.apiKeys, (apiKeyDefinition) => { + _.forEach(this.serverless.service.provider.apiKeys, apiKeyDefinition => { // if multiple API key types are used const name = _.first(_.keys(apiKeyDefinition)); - if (_.isObject(apiKeyDefinition) && - _.includes(_.flatten(_.map(this.serverless.service.provider.usagePlan, - (item) => _.keys(item))), name)) { + if ( + _.isObject(apiKeyDefinition) && + _.includes( + _.flatten(_.map(this.serverless.service.provider.usagePlan, item => _.keys(item))), + name + ) + ) { keyNumber = 0; - _.forEach(apiKeyDefinition[name], (key) => { + _.forEach(apiKeyDefinition[name], key => { if (!this.validateApiKeyInput(key)) { throw new this.serverless.classes.Error( 'API Key must be a string or an object which contains name and/or value' ); } keyNumber += 1; - const apiKeyLogicalId = this.provider.naming - .getApiKeyLogicalId(keyNumber, name); + const apiKeyLogicalId = this.provider.naming.getApiKeyLogicalId(keyNumber, name); const resourceTemplate = createApiKeyResource(this, key); _.merge(resources, { [apiKeyLogicalId]: resourceTemplate, @@ -69,8 +74,7 @@ module.exports = { 'API Key must be a string or an object which contains name and/or value' ); } - const apiKeyLogicalId = this.provider.naming - .getApiKeyLogicalId(keyNumber); + const apiKeyLogicalId = this.provider.naming.getApiKeyLogicalId(keyNumber); const resourceTemplate = createApiKeyResource(this, apiKeyDefinition); _.merge(resources, { [apiKeyLogicalId]: resourceTemplate, diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.test.js index e6eaab68c..cba1c2f2e 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/apiKeys.test.js @@ -97,17 +97,16 @@ describe('#compileApiKeys()', () => { describe('when using usage plan notation', () => { it('should support usage plan notation', () => { - awsCompileApigEvents.serverless.service.provider.usagePlan = [ - { free: [] }, - { paid: [] }, - ]; + awsCompileApigEvents.serverless.service.provider.usagePlan = [{ free: [] }, { paid: [] }]; awsCompileApigEvents.serverless.service.provider.apiKeys = [ - { free: [ - '1234567890', - { name: '2345678901' }, - { value: 'valueForKeyWithoutName', description: 'Api key description' }, - { name: '3456789012', value: 'valueForKey3456789012' }, - ] }, + { + free: [ + '1234567890', + { name: '2345678901' }, + { value: 'valueForKeyWithoutName', description: 'Api key description' }, + { name: '3456789012', value: 'valueForKey3456789012' }, + ], + }, { paid: ['0987654321', 'jihgfedcba'] }, ]; @@ -128,57 +127,57 @@ describe('#compileApiKeys()', () => { { name: 'jihgfedcba', value: undefined, description: undefined }, ], }; - _.forEach(awsCompileApigEvents.serverless.service.provider.apiKeys, (plan) => { + _.forEach(awsCompileApigEvents.serverless.service.provider.apiKeys, plan => { const planName = _.first(_.keys(plan)); // free || paid const apiKeys = expectedApiKeys[planName]; _.forEach(apiKeys, (apiKey, index) => { expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) - ].Type + awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) + ].Type ).to.equal('AWS::ApiGateway::ApiKey'); expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) - ].Properties.Enabled + awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) + ].Properties.Enabled ).to.equal(true); expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) - ].Properties.Name + awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) + ].Properties.Name ).to.equal(apiKey.name); expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) - ].Properties.Description + awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) + ].Properties.Description ).to.equal(apiKey.description); expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) - ].Properties.Value + awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) + ].Properties.Value ).to.equal(apiKey.value); expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) - ].Properties.StageKeys[0].RestApiId.Ref + awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) + ].Properties.StageKeys[0].RestApiId.Ref ).to.equal('ApiGatewayRestApi'); expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) - ].Properties.StageKeys[0].StageName + awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) + ].Properties.StageKeys[0].StageName ).to.equal('dev'); expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) - ].DependsOn + awsCompileApigEvents.provider.naming.getApiKeyLogicalId(index + 1, planName) + ].DependsOn ).to.equal('ApiGatewayDeploymentTest'); }); }); @@ -187,9 +186,11 @@ describe('#compileApiKeys()', () => { }); it('throw error if an apiKey is not a valid object', () => { - awsCompileApigEvents.serverless.service.provider.apiKeys = [{ - named: 'invalid', - }]; + awsCompileApigEvents.serverless.service.provider.apiKeys = [ + { + named: 'invalid', + }, + ]; expect(() => awsCompileApigEvents.compileApiKeys()).to.throw(Error); }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js index 118b60325..90eb45db4 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js @@ -6,7 +6,7 @@ const awsArnRegExs = require('../../../../../utils/arnRegularExpressions'); module.exports = { compileAuthorizers() { - this.validated.events.forEach((event) => { + this.validated.events.forEach(event => { if (event.http.authorizer && event.http.authorizer.arn) { const authorizer = event.http.authorizer; const authorizerProperties = { @@ -24,14 +24,16 @@ module.exports = { const authorizerLogicalId = this.provider.naming.getAuthorizerLogicalId(authorizer.name); - if (typeof authorizer.arn === 'string' - && awsArnRegExs.cognitoIdpArnExpr.test(authorizer.arn)) { + if ( + typeof authorizer.arn === 'string' && + awsArnRegExs.cognitoIdpArnExpr.test(authorizer.arn) + ) { authorizerProperties.Type = 'COGNITO_USER_POOLS'; authorizerProperties.ProviderARNs = [authorizer.arn]; } else { - authorizerProperties.AuthorizerUri = - { - 'Fn::Join': ['', + authorizerProperties.AuthorizerUri = { + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js index 5ec1dc893..5b2e4515b 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js @@ -5,7 +5,6 @@ const AwsCompileApigEvents = require('../index'); const Serverless = require('../../../../../../../Serverless'); const AwsProvider = require('../../../../../provider/awsProvider'); - describe('#compileAuthorizers()', () => { let awsCompileApigEvents; @@ -20,27 +19,31 @@ describe('#compileAuthorizers()', () => { }); it('should create an authorizer with minimal configuration', () => { - awsCompileApigEvents.validated.events = [{ - http: { - path: 'users/create', - method: 'POST', - authorizer: { - name: 'authorizer', - arn: { 'Fn::GetAtt': ['SomeLambdaFunction', 'Arn'] }, - resultTtlInSeconds: 300, - identitySource: 'method.request.header.Authorization', + awsCompileApigEvents.validated.events = [ + { + http: { + path: 'users/create', + method: 'POST', + authorizer: { + name: 'authorizer', + arn: { 'Fn::GetAtt': ['SomeLambdaFunction', 'Arn'] }, + resultTtlInSeconds: 300, + identitySource: 'method.request.header.Authorization', + }, }, }, - }]; + ]; return awsCompileApigEvents.compileAuthorizers().then(() => { - const resource = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer; + const resource = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .AuthorizerApiGatewayAuthorizer; expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer'); expect(resource.Properties.AuthorizerResultTtlInSeconds).to.equal(300); expect(resource.Properties.AuthorizerUri).to.deep.equal({ - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -61,27 +64,31 @@ describe('#compileAuthorizers()', () => { }); it('should create an authorizer with provided configuration', () => { - awsCompileApigEvents.validated.events = [{ - http: { - path: 'users/create', - method: 'POST', - authorizer: { - name: 'authorizer', - arn: 'foo', - resultTtlInSeconds: 500, - identitySource: 'method.request.header.Custom', - identityValidationExpression: 'regex', + awsCompileApigEvents.validated.events = [ + { + http: { + path: 'users/create', + method: 'POST', + authorizer: { + name: 'authorizer', + arn: 'foo', + resultTtlInSeconds: 500, + identitySource: 'method.request.header.Custom', + identityValidationExpression: 'regex', + }, }, }, - }]; + ]; return awsCompileApigEvents.compileAuthorizers().then(() => { - const resource = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer; + const resource = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .AuthorizerApiGatewayAuthorizer; expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer'); expect(resource.Properties.AuthorizerUri).to.deep.equal({ - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -103,23 +110,26 @@ describe('#compileAuthorizers()', () => { }); it('should apply optional provided type value to Authorizer Type', () => { - awsCompileApigEvents.validated.events = [{ - http: { - path: 'users/create', - method: 'POST', - authorizer: { - name: 'authorizer', - arn: 'foo', - resultTtlInSeconds: 500, - identityValidationExpression: 'regex', - type: 'request', + awsCompileApigEvents.validated.events = [ + { + http: { + path: 'users/create', + method: 'POST', + authorizer: { + name: 'authorizer', + arn: 'foo', + resultTtlInSeconds: 500, + identityValidationExpression: 'regex', + type: 'request', + }, }, }, - }]; + ]; return awsCompileApigEvents.compileAuthorizers().then(() => { - const resource = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer; + const resource = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .AuthorizerApiGatewayAuthorizer; expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer'); expect(resource.Properties.Type).to.equal('REQUEST'); @@ -127,22 +137,25 @@ describe('#compileAuthorizers()', () => { }); it('should apply TOKEN as authorizer Type when not given a type value', () => { - awsCompileApigEvents.validated.events = [{ - http: { - path: 'users/create', - method: 'POST', - authorizer: { - name: 'authorizer', - arn: 'foo', - resultTtlInSeconds: 500, - identityValidationExpression: 'regex', + awsCompileApigEvents.validated.events = [ + { + http: { + path: 'users/create', + method: 'POST', + authorizer: { + name: 'authorizer', + arn: 'foo', + resultTtlInSeconds: 500, + identityValidationExpression: 'regex', + }, }, }, - }]; + ]; return awsCompileApigEvents.compileAuthorizers().then(() => { - const resource = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer; + const resource = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .AuthorizerApiGatewayAuthorizer; expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer'); expect(resource.Properties.Type).to.equal('TOKEN'); @@ -150,25 +163,29 @@ describe('#compileAuthorizers()', () => { }); it('should create a valid cognito user pool authorizer', () => { - awsCompileApigEvents.validated.events = [{ - http: { - path: 'users/create', - method: 'POST', - authorizer: { - name: 'authorizer', - arn: 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ', + awsCompileApigEvents.validated.events = [ + { + http: { + path: 'users/create', + method: 'POST', + authorizer: { + name: 'authorizer', + arn: 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ', + }, }, }, - }]; + ]; return awsCompileApigEvents.compileAuthorizers().then(() => { - const resource = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer; + const resource = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .AuthorizerApiGatewayAuthorizer; expect(resource.Properties.Name).to.equal('authorizer'); - expect(resource.Properties.ProviderARNs[0] - ).to.equal('arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ'); + expect(resource.Properties.ProviderARNs[0]).to.equal( + 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ' + ); expect(resource.Properties.Type).to.equal('COGNITO_USER_POOLS'); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js index e6286f8f2..7f90c3134 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js @@ -8,11 +8,10 @@ module.exports = { _.forEach(this.validated.corsPreflight, (config, path) => { const resourceName = this.getResourceName(path); const resourceRef = this.getResourceId(path); - const corsMethodLogicalId = this.provider.naming - .getMethodLogicalId(resourceName, 'options'); + const corsMethodLogicalId = this.provider.naming.getMethodLogicalId(resourceName, 'options'); let origin = config.origin; - let origins = (config.origins && Array.isArray(config.origins)) ? config.origins : undefined; + let origins = config.origins && Array.isArray(config.origins) ? config.origins : undefined; if (origin && origin.indexOf(',') !== -1) { origins = origin.split(',').map(a => a.trim()); @@ -50,9 +49,9 @@ module.exports = { } if (_.includes(config.methods, 'ANY')) { - preflightHeaders['Access-Control-Allow-Methods'] = - preflightHeaders['Access-Control-Allow-Methods'] - .replace('ANY', 'DELETE,GET,HEAD,PATCH,POST,PUT'); + preflightHeaders['Access-Control-Allow-Methods'] = preflightHeaders[ + 'Access-Control-Allow-Methods' + ].replace('ANY', 'DELETE,GET,HEAD,PATCH,POST,PUT'); } this.apiGatewayMethodLogicalIds.push(corsMethodLogicalId); @@ -71,8 +70,10 @@ module.exports = { 'application/json': '{statusCode:200}', }, ContentHandling: 'CONVERT_TO_TEXT', - IntegrationResponses: - this.generateCorsIntegrationResponses(preflightHeaders, origins), + IntegrationResponses: this.generateCorsIntegrationResponses( + preflightHeaders, + origins + ), }, ResourceId: resourceRef, RestApiId: this.provider.getApiGatewayRestApiId(), @@ -101,8 +102,10 @@ module.exports = { }, generateCorsIntegrationResponses(preflightHeaders, origins) { - const responseParameters = _.mapKeys(preflightHeaders, - (value, header) => `method.response.header.${header}`); + const responseParameters = _.mapKeys( + preflightHeaders, + (value, header) => `method.response.header.${header}` + ); return [ { @@ -110,16 +113,16 @@ module.exports = { ResponseParameters: responseParameters, ResponseTemplates: { 'application/json': - Array.isArray(origins) && origins.length ? - this.generateCorsResponseTemplate(origins) : '', + Array.isArray(origins) && origins.length + ? this.generateCorsResponseTemplate(origins) + : '', }, }, ]; }, regexifyWildcards(orig) { - return orig.map((str) => str.replace(/\./g, '[.]') - .replace('*', '.*')); + return orig.map(str => str.replace(/\./g, '[.]').replace('*', '.*')); }, generateCorsResponseTemplate(origins) { diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js index f7a7401ac..4415a817e 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js @@ -97,161 +97,143 @@ describe('#compileCors()', () => { return awsCompileApigEvents.compileCors().then(() => { // users/create expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Origin'] - ).to.equal('\'http://localhost:3000\''); + ).to.equal("'http://localhost:3000'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreateOptions.Properties.Integration.IntegrationResponses[0] .ResponseTemplates['application/json'] - ).to.equal('#set($origin = $input.params("Origin"))\n#if($origin == "") #set($origin = $input.params("origin")) #end\n#if($origin.matches("http://localhost:3000") || $origin.matches("https://.*[.]example[.]com")) #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end'); + ).to.equal( + '#set($origin = $input.params("Origin"))\n#if($origin == "") #set($origin = $input.params("origin")) #end\n#if($origin.matches("http://localhost:3000") || $origin.matches("https://.*[.]example[.]com")) #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end' + ); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Headers'] - ).to.equal('\'*\''); + ).to.equal("'*'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Methods'] - ).to.equal('\'OPTIONS,POST\''); + ).to.equal("'OPTIONS,POST'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Credentials'] - ).to.equal('\'true\''); + ).to.equal("'true'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Max-Age'] - ).to.equal('\'86400\''); + ).to.equal("'86400'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Cache-Control'] - ).to.equal('\'max-age=600, s-maxage=600\''); + ).to.equal("'max-age=600, s-maxage=600'"); // users/update expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersUpdateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersUpdateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Origin'] - ).to.equal('\'http://example.com\''); + ).to.equal("'http://example.com'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersUpdateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersUpdateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Methods'] - ).to.equal('\'OPTIONS,PUT\''); + ).to.equal("'OPTIONS,PUT'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersUpdateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersUpdateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Credentials'] - ).to.equal('\'false\''); + ).to.equal("'false'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersUpdateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersUpdateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Max-Age'] - ).to.equal('\'86400\''); + ).to.equal("'86400'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersUpdateOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersUpdateOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Cache-Control'] - ).to.equal('\'max-age=600, s-maxage=600\''); + ).to.equal("'max-age=600, s-maxage=600'"); // users/delete expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersDeleteOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersDeleteOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Origin'] - ).to.equal('\'*\''); + ).to.equal("'*'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersDeleteOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersDeleteOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Headers'] - ).to.equal('\'CustomHeaderA,CustomHeaderB\''); + ).to.equal("'CustomHeaderA,CustomHeaderB'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersDeleteOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersDeleteOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Methods'] - ).to.equal('\'OPTIONS,DELETE\''); + ).to.equal("'OPTIONS,DELETE'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersDeleteOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersDeleteOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Credentials'] - ).to.equal('\'false\''); + ).to.equal("'false'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersDeleteOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersDeleteOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Max-Age'] - ).to.equal('\'86400\''); + ).to.equal("'86400'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersDeleteOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersDeleteOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Cache-Control'] - ).to.equal('\'max-age=600, s-maxage=600\''); + ).to.equal("'max-age=600, s-maxage=600'"); // users/any expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersAnyOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersAnyOptions.Properties.Integration.IntegrationResponses[0] .ResponseTemplates['application/json'] - ).to.equal('#set($origin = $input.params("Origin"))\n#if($origin == "") #set($origin = $input.params("origin")) #end\n#if($origin.matches("http://localhost:3000") || $origin.matches("http://example[.]com")) #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end'); + ).to.equal( + '#set($origin = $input.params("Origin"))\n#if($origin == "") #set($origin = $input.params("origin")) #end\n#if($origin.matches("http://localhost:3000") || $origin.matches("http://example[.]com")) #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end' + ); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersAnyOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersAnyOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Headers'] - ).to.equal('\'*\''); + ).to.equal("'*'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersAnyOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersAnyOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Methods'] - ).to.equal('\'OPTIONS,DELETE,GET,HEAD,PATCH,POST,PUT\''); + ).to.equal("'OPTIONS,DELETE,GET,HEAD,PATCH,POST,PUT'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersAnyOptions - .Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersAnyOptions.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Credentials'] - ).to.equal('\'false\''); + ).to.equal("'false'"); }); }); @@ -264,8 +246,10 @@ describe('#compileCors()', () => { }, }; - expect(() => awsCompileApigEvents.compileCors()) - .to.throw(Error, 'must specify either origin or origins'); + expect(() => awsCompileApigEvents.compileCors()).to.throw( + Error, + 'must specify either origin or origins' + ); }); it('should throw error if maxAge is not an integer greater than 0', () => { @@ -279,8 +263,10 @@ describe('#compileCors()', () => { }, }; - expect(() => awsCompileApigEvents.compileCors()) - .to.throw(Error, 'maxAge should be an integer over 0'); + expect(() => awsCompileApigEvents.compileCors()).to.throw( + Error, + 'maxAge should be an integer over 0' + ); }); it('should throw error if maxAge is not an integer', () => { @@ -294,8 +280,10 @@ describe('#compileCors()', () => { }, }; - expect(() => awsCompileApigEvents.compileCors()) - .to.throw(Error, 'maxAge should be an integer over 0'); + expect(() => awsCompileApigEvents.compileCors()).to.throw( + Error, + 'maxAge should be an integer over 0' + ); }); it('should add the methods resource logical id to the array of method logical ids', () => { @@ -315,11 +303,10 @@ describe('#compileCors()', () => { }, }; return awsCompileApigEvents.compileCors().then(() => { - expect(awsCompileApigEvents.apiGatewayMethodLogicalIds) - .to.deep.equal([ - 'ApiGatewayMethodUsersCreateOptions', - 'ApiGatewayMethodUsersAnyOptions', - ]); + expect(awsCompileApigEvents.apiGatewayMethodLogicalIds).to.deep.equal([ + 'ApiGatewayMethodUsersCreateOptions', + 'ApiGatewayMethodUsersAnyOptions', + ]); }); }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js index d09c789f3..c136d6972 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js @@ -5,8 +5,9 @@ const BbPromise = require('bluebird'); module.exports = { compileDeployment() { - this.apiGatewayDeploymentLogicalId = this.provider.naming - .generateApiGatewayDeploymentLogicalId(this.serverless.instanceId); + this.apiGatewayDeploymentLogicalId = this.provider.naming.generateApiGatewayDeploymentLogicalId( + this.serverless.instanceId + ); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [this.apiGatewayDeploymentLogicalId]: { @@ -25,7 +26,8 @@ module.exports = { ServiceEndpoint: { Description: 'URL of the service endpoint', Value: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'https://', this.provider.getApiGatewayRestApiId(), diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js index 4231ae3b0..abe8f4ef9 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js @@ -28,15 +28,16 @@ describe('#compileDeployment()', () => { awsCompileApigEvents.provider = provider; }); - it('should create a deployment resource', () => awsCompileApigEvents - .compileDeployment().then(() => { - const apiGatewayDeploymentLogicalId = Object - .keys(awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources)[0]; + it('should create a deployment resource', () => + awsCompileApigEvents.compileDeployment().then(() => { + const apiGatewayDeploymentLogicalId = Object.keys( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + )[0]; expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[apiGatewayDeploymentLogicalId] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + apiGatewayDeploymentLogicalId + ] ).to.deep.equal({ Type: 'AWS::ApiGateway::Deployment', DependsOn: ['method-dependency1', 'method-dependency2'], @@ -48,43 +49,41 @@ describe('#compileDeployment()', () => { StageName: 'dev', }, }); - }) - ); + })); it('should create a deployment resource with description', () => { awsCompileApigEvents.serverless.service.provider.apiGateway = { description: 'Some Description', }; - return awsCompileApigEvents - .compileDeployment().then(() => { - const apiGatewayDeploymentLogicalId = Object - .keys(awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources)[0]; + return awsCompileApigEvents.compileDeployment().then(() => { + const apiGatewayDeploymentLogicalId = Object.keys( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + )[0]; - expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[apiGatewayDeploymentLogicalId] - ).to.deep.equal({ - Type: 'AWS::ApiGateway::Deployment', - DependsOn: ['method-dependency1', 'method-dependency2'], - Properties: { - RestApiId: { - Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, - }, - Description: 'Some Description', - StageName: 'dev', - }, - }); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + apiGatewayDeploymentLogicalId + ] + ).to.deep.equal({ + Type: 'AWS::ApiGateway::Deployment', + DependsOn: ['method-dependency1', 'method-dependency2'], + Properties: { + RestApiId: { + Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, + }, + Description: 'Some Description', + StageName: 'dev', + }, }); - } - ); + }); + }); it('should add service endpoint output', () => awsCompileApigEvents.compileDeployment().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.ServiceEndpoint + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .ServiceEndpoint ).to.deep.equal({ Description: 'URL of the service endpoint', Value: { @@ -102,6 +101,5 @@ describe('#compileDeployment()', () => { ], }, }); - }) - ); + })); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.js index c7c1d3f99..a00c52fca 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.js @@ -11,28 +11,40 @@ module.exports = { this.serverless.cli.log('Removing usage plan association...'); const stackName = `${this.provider.naming.getStackName()}`; return BbPromise.all([ - this.provider.request('CloudFormation', - 'describeStackResource', - { - StackName: stackName, - LogicalResourceId: this.provider.naming.getRestApiLogicalId(), - }), - this.provider.request('APIGateway', - 'getUsagePlans', {}), - ]).then((data) => data[1].items.filter((item) => - _.includes(item.apiStages - .map(apistage => apistage.apiId), data[0].StackResourceDetail.PhysicalResourceId))) - .then((items) => BbPromise.all(_.flattenDeep(items.map(item => - item.apiStages.map(apiStage => - this.provider.request('APIGateway', - 'updateUsagePlan', { - usagePlanId: item.id, - patchOperations: [{ - op: 'remove', - path: '/apiStages', - value: `${apiStage.apiId}:${apiStage.stage}`, - }], - })))))); + this.provider.request('CloudFormation', 'describeStackResource', { + StackName: stackName, + LogicalResourceId: this.provider.naming.getRestApiLogicalId(), + }), + this.provider.request('APIGateway', 'getUsagePlans', {}), + ]) + .then(data => + data[1].items.filter(item => + _.includes( + item.apiStages.map(apistage => apistage.apiId), + data[0].StackResourceDetail.PhysicalResourceId + ) + ) + ) + .then(items => + BbPromise.all( + _.flattenDeep( + items.map(item => + item.apiStages.map(apiStage => + this.provider.request('APIGateway', 'updateUsagePlan', { + usagePlanId: item.id, + patchOperations: [ + { + op: 'remove', + path: '/apiStages', + value: `${apiStage.apiId}:${apiStage.stage}`, + }, + ], + }) + ) + ) + ) + ) + ); } return BbPromise.resolve(); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.test.js index 9631e788a..9943a929c 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/disassociateUsagePlan.test.js @@ -30,27 +30,30 @@ describe('#disassociateUsagePlan()', () => { disassociateUsagePlan.options = options; disassociateUsagePlan.provider = awsProvider; - providerRequestStub.withArgs('CloudFormation', 'describeStackResource') + providerRequestStub + .withArgs('CloudFormation', 'describeStackResource') .resolves({ StackResourceDetail: { PhysicalResourceId: 'resource-id' } }); providerRequestStub.withArgs('APIGateway', 'getUsagePlans').resolves({ - items: [{ - apiStages: [ - { - apiId: 'resource-id', - stage: 'dev', - }, - ], - id: 'usage-plan-id', - }, - { - apiStages: [ - { - apiId: 'another-resource-id', - stage: 'dev', - }, - ], - id: 'another-usage-plan-id', - }], + items: [ + { + apiStages: [ + { + apiId: 'resource-id', + stage: 'dev', + }, + ], + id: 'usage-plan-id', + }, + { + apiStages: [ + { + apiId: 'another-resource-id', + stage: 'dev', + }, + ], + id: 'another-usage-plan-id', + }, + ], }); providerRequestStub.withArgs('APIGateway', 'updateUsagePlan').resolves(); }); @@ -65,33 +68,29 @@ describe('#disassociateUsagePlan()', () => { return disassociateUsagePlan.disassociateUsagePlan().then(() => { expect(providerRequestStub.callCount).to.be.equal(3); - expect(providerRequestStub.calledWithExactly( - 'CloudFormation', - 'describeStackResource', - { + expect( + providerRequestStub.calledWithExactly('CloudFormation', 'describeStackResource', { StackName: `${awsProvider.naming.getStackName()}`, LogicalResourceId: 'ApiGatewayRestApi', - } - )).to.be.equal(true); + }) + ).to.be.equal(true); - expect(providerRequestStub.calledWithExactly( - 'APIGateway', - 'getUsagePlans', - {} - )).to.be.equal(true); + expect(providerRequestStub.calledWithExactly('APIGateway', 'getUsagePlans', {})).to.be.equal( + true + ); - expect(providerRequestStub.calledWithExactly( - 'APIGateway', - 'updateUsagePlan', - { + expect( + providerRequestStub.calledWithExactly('APIGateway', 'updateUsagePlan', { usagePlanId: 'usage-plan-id', - patchOperations: [{ - op: 'remove', - path: '/apiStages', - value: 'resource-id:dev', - }], - } - )).to.be.equal(true); + patchOperations: [ + { + op: 'remove', + path: '/apiStages', + value: 'resource-id:dev', + }, + ], + }) + ).to.be.equal(true); }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js index d08165613..429aa4096 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js @@ -21,7 +21,8 @@ module.exports = { this.apiGatewayDeploymentId = null; this.apiGatewayRestApiId = null; - return resolveAccountId.call(this) + return resolveAccountId + .call(this) .then(resolveRestApiId.bind(this)) .then(() => { if (!this.apiGatewayRestApiId) { @@ -43,7 +44,7 @@ module.exports = { 'In given setup stage specific options such as ', '`tracing`, `logs` and `tags` are not supported.\n\n', 'Please update your configuration (or open up an issue if you feel ', - 'that there\'s a way to support your setup).', + "that there's a way to support your setup).", ].join(''); throw new Error(errorMessage); @@ -65,7 +66,7 @@ module.exports = { function resolveAccountId() { // eslint-disable-next-line no-return-assign - return this.provider.getAccountId().then((id) => this.accountId = id); + return this.provider.getAccountId().then(id => (this.accountId = id)); } function resolveRestApiId() { @@ -93,34 +94,39 @@ function resolveRestApiId() { function resolveStage() { const restApiId = this.apiGatewayRestApiId; - return this.provider.request('APIGateway', 'getStage', { - restApiId, - stageName: this.options.stage, - }).then((res) => { - this.apiGatewayStageState = res; - }).catch(() => { - // fail silently... - }); + return this.provider + .request('APIGateway', 'getStage', { + restApiId, + stageName: this.options.stage, + }) + .then(res => { + this.apiGatewayStageState = res; + }) + .catch(() => { + // fail silently... + }); } function resolveDeploymentId() { if (_.isEmpty(this.apiGatewayStageState)) { const restApiId = this.apiGatewayRestApiId; - return this.provider.request('APIGateway', 'getDeployments', { - restApiId, - limit: 500, - }).then(res => { - if (res.items.length) { - // there will ever only be 1 deployment associated - const deployment = res.items.shift(); - return deployment.id; - } - return null; - }) - .then((deploymentId) => { - this.apiGatewayDeploymentId = deploymentId; - }); + return this.provider + .request('APIGateway', 'getDeployments', { + restApiId, + limit: 500, + }) + .then(res => { + if (res.items.length) { + // there will ever only be 1 deployment associated + const deployment = res.items.shift(); + return deployment.id; + } + return null; + }) + .then(deploymentId => { + this.apiGatewayDeploymentId = deploymentId; + }); } return BbPromise.resolve(); @@ -199,11 +205,13 @@ function handleLogs() { function handleTags() { const provider = this.state.service.provider; - const tagsMerged = _.mapValues(Object.assign({}, provider.stackTags, provider.tags), - v => String(v)); + const tagsMerged = _.mapValues(Object.assign({}, provider.stackTags, provider.tags), v => + String(v) + ); const currentTags = this.apiGatewayStageState.tags || {}; - const tagKeysToBeRemoved = Object.keys(currentTags) - .filter(currentKey => !_.isString(tagsMerged[currentKey])); + const tagKeysToBeRemoved = Object.keys(currentTags).filter( + currentKey => !_.isString(tagsMerged[currentKey]) + ); const restApiId = this.apiGatewayRestApiId; const stageName = this.options.stage; @@ -226,13 +234,15 @@ function handleTags() { function addTags() { const requests = this.apiGatewayTagResourceParams.map(tagResourceParam => - this.provider.request('APIGateway', 'tagResource', tagResourceParam)); + this.provider.request('APIGateway', 'tagResource', tagResourceParam) + ); return BbPromise.all(requests); } function removeTags() { const requests = this.apiGatewayUntagResourceParams.map(untagResourceParam => - this.provider.request('APIGateway', 'untagResource', untagResourceParam)); + this.provider.request('APIGateway', 'untagResource', untagResourceParam) + ); return BbPromise.all(requests); } @@ -263,11 +273,13 @@ function removeLogGroup() { // ensure that the log group is removed. Otherwise we'll run into duplicate // log group name issues when logs are enabled again if (!logs) { - return this.provider.request('CloudWatchLogs', 'deleteLogGroup', { - logGroupName, - }).catch(() => { - // fail silently... - }); + return this.provider + .request('CloudWatchLogs', 'deleteLogGroup', { + logGroupName, + }) + .catch(() => { + // fail silently... + }); } return BbPromise.resolve(); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js index da5967b16..7c94155f4 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js @@ -28,8 +28,7 @@ describe('#updateStage()', () => { options = { stage: 'dev', region: 'us-east-1' }; awsProvider = new AwsProvider(serverless, options); serverless.setProvider('aws', awsProvider); - providerGetAccountIdStub = sinon.stub(awsProvider, 'getAccountId') - .resolves(123456); + providerGetAccountIdStub = sinon.stub(awsProvider, 'getAccountId').resolves(123456); providerRequestStub = sinon.stub(awsProvider, 'request'); context = { @@ -58,9 +57,11 @@ describe('#updateStage()', () => { }, }); - providerRequestStub.withArgs('CloudWatchLogs', 'deleteLogGroup', { - logGroupName: '/aws/api-gateway/my-service-dev', - }).resolves(); + providerRequestStub + .withArgs('CloudWatchLogs', 'deleteLogGroup', { + logGroupName: '/aws/api-gateway/my-service-dev', + }) + .resolves(); }); afterEach(() => { @@ -71,7 +72,7 @@ describe('#updateStage()', () => { it('should update the stage based on the serverless file configuration', () => { context.state.service.provider.tags = { 'Containing Space': 'bar', - bar: 'high-priority', + 'bar': 'high-priority', }; context.state.service.provider.stackTags = { bar: 'low-priority', @@ -87,8 +88,17 @@ describe('#updateStage()', () => { return updateStage.call(context).then(() => { const patchOperations = [ { op: 'replace', path: '/tracingEnabled', value: 'true' }, - { op: 'replace', path: '/accessLogSettings/destinationArn', value: 'arn:aws:logs:us-east-1:123456:log-group:/aws/api-gateway/my-service-dev' }, - { op: 'replace', path: '/accessLogSettings/format', value: 'requestId: $context.requestId, ip: $context.identity.sourceIp, caller: $context.identity.caller, user: $context.identity.user, requestTime: $context.requestTime, httpMethod: $context.httpMethod, resourcePath: $context.resourcePath, status: $context.status, protocol: $context.protocol, responseLength: $context.responseLength' }, + { + op: 'replace', + path: '/accessLogSettings/destinationArn', + value: 'arn:aws:logs:us-east-1:123456:log-group:/aws/api-gateway/my-service-dev', + }, + { + op: 'replace', + path: '/accessLogSettings/format', + value: + 'requestId: $context.requestId, ip: $context.identity.sourceIp, caller: $context.identity.caller, user: $context.identity.user, requestTime: $context.requestTime, httpMethod: $context.httpMethod, resourcePath: $context.resourcePath, status: $context.status, protocol: $context.protocol, responseLength: $context.responseLength', + }, { op: 'replace', path: '/*/*/logging/dataTrace', value: 'true' }, { op: 'replace', path: '/*/*/logging/loglevel', value: 'INFO' }, ]; @@ -103,7 +113,10 @@ describe('#updateStage()', () => { }); expect(providerRequestStub.args[1][0]).to.equal('APIGateway'); expect(providerRequestStub.args[1][1]).to.equal('getStage'); - expect(providerRequestStub.args[1][2]).to.deep.equal({ restApiId: 'someRestApiId', stageName: 'dev' }); + expect(providerRequestStub.args[1][2]).to.deep.equal({ + restApiId: 'someRestApiId', + stageName: 'dev', + }); expect(providerRequestStub.args[2][0]).to.equal('APIGateway'); expect(providerRequestStub.args[2][1]).to.equal('updateStage'); expect(providerRequestStub.args[2][2]).to.deep.equal({ @@ -117,8 +130,8 @@ describe('#updateStage()', () => { resourceArn: 'arn:aws:apigateway:us-east-1::/restapis/someRestApiId/stages/dev', tags: { 'Containing Space': 'bar', - bar: 'high-priority', - num: '123', + 'bar': 'high-priority', + 'num': '123', }, }); expect(providerRequestStub.args[4][0]).to.equal('APIGateway'); @@ -151,7 +164,10 @@ describe('#updateStage()', () => { }); expect(providerRequestStub.args[1][0]).to.equal('APIGateway'); expect(providerRequestStub.args[1][1]).to.equal('getStage'); - expect(providerRequestStub.args[1][2]).to.deep.equal({ restApiId: 'someRestApiId', stageName: 'dev' }); + expect(providerRequestStub.args[1][2]).to.deep.equal({ + restApiId: 'someRestApiId', + stageName: 'dev', + }); expect(providerRequestStub.args[2][0]).to.equal('APIGateway'); expect(providerRequestStub.args[2][1]).to.equal('updateStage'); expect(providerRequestStub.args[2][2]).to.deep.equal({ @@ -168,22 +184,29 @@ describe('#updateStage()', () => { }); it('should create a new stage and proceed as usual if none can be found', () => { - providerRequestStub.withArgs('APIGateway', 'getStage', { - restApiId: 'someRestApiId', stageName: 'dev', - }).rejects(); + providerRequestStub + .withArgs('APIGateway', 'getStage', { + restApiId: 'someRestApiId', + stageName: 'dev', + }) + .rejects(); - providerRequestStub.withArgs('APIGateway', 'getDeployments', { - restApiId: 'someRestApiId', - limit: 500, - }).resolves({ - items: [{ id: 'someDeploymentId' }], - }); + providerRequestStub + .withArgs('APIGateway', 'getDeployments', { + restApiId: 'someRestApiId', + limit: 500, + }) + .resolves({ + items: [{ id: 'someDeploymentId' }], + }); - providerRequestStub.withArgs('APIGateway', 'createStage', { - deploymentId: 'someDeploymentId', - restApiId: 'someRestApiId', - stageName: 'dev', - }).resolves(); + providerRequestStub + .withArgs('APIGateway', 'createStage', { + deploymentId: 'someDeploymentId', + restApiId: 'someRestApiId', + stageName: 'dev', + }) + .resolves(); return updateStage.call(context).then(() => { const patchOperations = [ @@ -202,10 +225,16 @@ describe('#updateStage()', () => { }); expect(providerRequestStub.args[1][0]).to.equal('APIGateway'); expect(providerRequestStub.args[1][1]).to.equal('getStage'); - expect(providerRequestStub.args[1][2]).to.deep.equal({ restApiId: 'someRestApiId', stageName: 'dev' }); + expect(providerRequestStub.args[1][2]).to.deep.equal({ + restApiId: 'someRestApiId', + stageName: 'dev', + }); expect(providerRequestStub.args[2][0]).to.equal('APIGateway'); expect(providerRequestStub.args[2][1]).to.equal('getDeployments'); - expect(providerRequestStub.args[2][2]).to.deep.equal({ restApiId: 'someRestApiId', limit: 500 }); + expect(providerRequestStub.args[2][2]).to.deep.equal({ + restApiId: 'someRestApiId', + limit: 500, + }); expect(providerRequestStub.args[3][0]).to.equal('APIGateway'); expect(providerRequestStub.args[3][1]).to.equal('createStage'); expect(providerRequestStub.args[3][2]).to.deep.equal({ diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js index 21ae109d0..ce62a8781 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js @@ -21,22 +21,22 @@ module.exports = { AuthorizerId: http.authorizer.authorizerId, }, }; - if (http.authorizer.type === 'COGNITO_USER_POOLS' - && http.authorizer.scopes - && http.authorizer.scopes.length) { + if ( + http.authorizer.type === 'COGNITO_USER_POOLS' && + http.authorizer.scopes && + http.authorizer.scopes.length + ) { authReturn.Properties.AuthorizationScopes = http.authorizer.scopes; } return authReturn; } - const authorizerLogicalId = this.provider.naming - .getAuthorizerLogicalId(http.authorizer.name); + const authorizerLogicalId = this.provider.naming.getAuthorizerLogicalId(http.authorizer.name); const authorizerArn = http.authorizer.arn; let authorizationType; - if (typeof authorizerArn === 'string' - && awsArnRegExs.cognitoIdpArnExpr.test(authorizerArn)) { + if (typeof authorizerArn === 'string' && awsArnRegExs.cognitoIdpArnExpr.test(authorizerArn)) { authorizationType = 'COGNITO_USER_POOLS'; const cognitoReturn = { Properties: { diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js index 0f2269ee6..e846393a2 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js @@ -7,7 +7,7 @@ module.exports = { compileMethods() { this.permissionMapping = []; - this.validated.events.forEach((event) => { + this.validated.events.forEach(event => { const resourceId = this.getResourceId(event.http.path); const resourceName = this.getResourceName(event.http.path); const requestParameters = (event.http.request && event.http.request.parameters) || {}; @@ -28,17 +28,21 @@ module.exports = { template.Properties.ApiKeyRequired = false; } - const methodLogicalId = this.provider.naming - .getMethodLogicalId(resourceName, event.http.method); - const validatorLogicalId = this.provider.naming - .getValidatorLogicalId(resourceName, event.http.method); - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(event.functionName); + const methodLogicalId = this.provider.naming.getMethodLogicalId( + resourceName, + event.http.method + ); + const validatorLogicalId = this.provider.naming.getValidatorLogicalId( + resourceName, + event.http.method + ); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(event.functionName); const singlePermissionMapping = { resourceName, lambdaLogicalId, event }; this.permissionMapping.push(singlePermissionMapping); - _.merge(template, + _.merge( + template, this.getMethodAuthorization(event.http), this.getMethodIntegration(event.http, lambdaLogicalId, methodLogicalId), this.getMethodResponses(event.http) @@ -47,7 +51,7 @@ module.exports = { let extraCognitoPoolClaims; if (event.http.authorizer) { const claims = event.http.authorizer.claims || []; - extraCognitoPoolClaims = _.map(claims, (claim) => { + extraCognitoPoolClaims = _.map(claims, claim => { if (typeof claim === 'string') { const colonIndex = claim.indexOf(':'); if (colonIndex !== -1) { @@ -74,8 +78,11 @@ module.exports = { const contentType = requestSchema[0]; const schema = requestSchema[1]; - const modelLogicalId = this.provider.naming - .getModelLogicalId(resourceName, event.http.method, contentType); + const modelLogicalId = this.provider.naming.getModelLogicalId( + resourceName, + event.http.method, + contentType + ); template.Properties.RequestValidatorId = { Ref: validatorLogicalId }; template.Properties.RequestModels = { [contentType]: { Ref: modelLogicalId } }; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js index e6871ab9d..cdfe6ba94 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js @@ -71,8 +71,8 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePostValidator + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePostValidator ).to.deep.equal({ Type: 'AWS::ApiGateway::RequestValidator', Properties: { @@ -82,8 +82,8 @@ describe('#compileMethods()', () => { }, }); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePostApplicationJsonModel + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePostApplicationJsonModel ).to.deep.equal({ Type: 'AWS::ApiGateway::Model', Properties: { @@ -93,14 +93,14 @@ describe('#compileMethods()', () => { }, }); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.RequestModels + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.RequestModels ).to.deep.equal({ 'application/json': { Ref: 'ApiGatewayMethodUsersCreatePostApplicationJsonModel' }, }); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.RequestValidatorId + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.RequestValidatorId ).to.deep.equal({ Ref: 'ApiGatewayMethodUsersCreatePostValidator' }); }); }); @@ -135,34 +135,32 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties - .RequestParameters['method.request.header.foo'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.RequestParameters['method.request.header.foo'] ).to.equal(true); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties - .RequestParameters['method.request.header.bar'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.RequestParameters['method.request.header.bar'] ).to.equal(false); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties - .RequestParameters['method.request.querystring.foo'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.RequestParameters[ + 'method.request.querystring.foo' + ] ).to.equal(true); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties - .RequestParameters['method.request.querystring.bar'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.RequestParameters[ + 'method.request.querystring.bar' + ] ).to.equal(false); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties - .RequestParameters['method.request.path.foo'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.RequestParameters['method.request.path.foo'] ).to.equal(true); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties - .RequestParameters['method.request.path.bar'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.RequestParameters['method.request.path.bar'] ).to.equal(false); }); }); @@ -180,8 +178,8 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration ).to.not.have.key('RequestParameters'); }); }); @@ -205,12 +203,12 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Type ).to.equal('AWS::ApiGateway::Method'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Type ).to.equal('AWS::ApiGateway::Method'); }); }); @@ -238,12 +236,12 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.Type ).to.equal('AWS'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestParameters + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestParameters ).to.deep.equal({ 'integration.request.querystring.foo': 'method.request.querystring.foo', 'integration.request.querystring.bar': 'method.request.querystring.bar', @@ -268,8 +266,8 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.Type ).to.equal('AWS_PROXY'); }); }); @@ -298,24 +296,24 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.Type ).to.equal('HTTP'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.Uri + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.Uri ).to.equal('https://example.com'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.IntegrationHttpMethod + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.IntegrationHttpMethod ).to.equal('POST'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestTemplates + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestTemplates ).to.equal(undefined); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestParameters + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestParameters ).to.deep.equal({ 'integration.request.querystring.foo': 'method.request.querystring.foo', 'integration.request.querystring.bar': 'method.request.querystring.bar', @@ -344,16 +342,16 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.Type ).to.equal('HTTP'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.Uri + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.Uri ).to.equal('https://example.com'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.IntegrationHttpMethod + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.IntegrationHttpMethod ).to.equal('PUT'); }); }); @@ -383,20 +381,20 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.Type ).to.equal('HTTP_PROXY'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.Uri + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.Uri ).to.equal('https://example.com'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.IntegrationHttpMethod + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.IntegrationHttpMethod ).to.equal('PATCH'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestParameters + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestParameters ).to.deep.equal({ 'integration.request.querystring.foo': 'method.request.querystring.foo', 'integration.request.querystring.bar': 'method.request.querystring.bar', @@ -421,8 +419,8 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration.Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.Type ).to.equal('MOCK'); }); }); @@ -440,9 +438,10 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration - .RequestParameters['integration.request.header.X-Amz-Invocation-Type'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestParameters[ + 'integration.request.header.X-Amz-Invocation-Type' + ] ).to.equal("'Event'"); }); }); @@ -461,9 +460,10 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.Integration - .RequestParameters['integration.request.header.X-Amz-Invocation-Type'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestParameters[ + 'integration.request.header.X-Amz-Invocation-Type' + ] ).to.equal("'Event'"); }); }); @@ -483,8 +483,8 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType ).to.equal('AWS_IAM'); }); }); @@ -505,12 +505,12 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType ).to.equal('COGNITO_USER_POOLS'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizerId + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizerId ).to.equal('gy7lyj'); }); }); @@ -531,13 +531,13 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType ).to.equal('CUSTOM'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizerId.Ref + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizerId.Ref ).to.equal('AuthorizerApiGatewayAuthorizer'); }); }); @@ -561,24 +561,25 @@ describe('#compileMethods()', () => { return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType ).to.equal('COGNITO_USER_POOLS'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizationScopes + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizationScopes ).to.contain('myapp/read'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizerId.Ref + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizerId.Ref ).to.equal('AuthorizerApiGatewayAuthorizer'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties - .Integration.RequestTemplates['application/json'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestTemplates[ + 'application/json' + ] ).to.not.match(/undefined/); }); }); @@ -603,24 +604,25 @@ describe('#compileMethods()', () => { return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType ).to.equal('COGNITO_USER_POOLS'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizationScopes + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizationScopes ).to.contain('myapp/read'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizerId.Ref + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.AuthorizerId.Ref ).to.equal('CognitoAuthorizer'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties - .Integration.RequestTemplates['application/json'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestTemplates[ + 'application/json' + ] ).to.not.match(/undefined/); }); }); @@ -645,13 +647,12 @@ describe('#compileMethods()', () => { return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties ).to.not.have.property('AuthorizationScopes'); }); }); - it('should set claims for a cognito user pool', () => { awsCompileApigEvents.validated.events = [ { @@ -670,9 +671,11 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { - const jsonRequestTemplatesString = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties - .Integration.RequestTemplates['application/json']; + const jsonRequestTemplatesString = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestTemplates[ + 'application/json' + ]; const cognitoPoolClaimsRegex = /"cognitoPoolClaims"\s*:\s*(\{[^}]*\})/; const cognitoPoolClaimsString = jsonRequestTemplatesString.match(cognitoPoolClaimsRegex)[1]; const cognitoPoolClaims = JSON.parse(cognitoPoolClaimsString); @@ -698,9 +701,11 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { - const jsonRequestTemplatesString = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties - .Integration.RequestTemplates['application/json']; + const jsonRequestTemplatesString = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestTemplates[ + 'application/json' + ]; const cognitoPoolClaimsRegex = /"cognitoPoolClaims"\s*:\s*(\{[^}]*\})/; const cognitoPoolClaimsString = jsonRequestTemplatesString.match(cognitoPoolClaimsRegex)[1]; const cognitoPoolClaims = JSON.parse(cognitoPoolClaimsString); @@ -727,18 +732,19 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { - const jsonRequestTemplatesString = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties - .Integration.RequestTemplates['application/json']; + const jsonRequestTemplatesString = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestTemplates[ + 'application/json' + ]; const cognitoPoolClaimsRegex = /"cognitoPoolClaims"\s*:\s*(\{[^}]*\})/; const cognitoPoolClaimsString = jsonRequestTemplatesString.match(cognitoPoolClaimsRegex)[1]; const cognitoPoolClaims = JSON.parse(cognitoPoolClaimsString); expect(cognitoPoolClaims.email).to.equal('$context.authorizer.claims.email'); - expect(cognitoPoolClaims.score).to.equal('$context.authorizer.claims[\'custom:score\']'); + expect(cognitoPoolClaims.score).to.equal("$context.authorizer.claims['custom:score']"); }); }); - it('should replace the extra claims in the template if there are none', () => { awsCompileApigEvents.validated.events = [ { @@ -757,9 +763,10 @@ describe('#compileMethods()', () => { return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties - .Integration.RequestTemplates['application/json'] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.RequestTemplates[ + 'application/json' + ] ).to.not.match(/extraCognitoPoolClaims/); }); }); @@ -813,8 +820,8 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.ApiKeyRequired + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.ApiKeyRequired ).to.equal(true); }); }); @@ -831,8 +838,8 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties.ApiKeyRequired + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.ApiKeyRequired ).to.equal(false); }); }); @@ -855,30 +862,36 @@ describe('#compileMethods()', () => { }, ]; return awsCompileApigEvents.compileMethods().then(() => { - expect(awsCompileApigEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .ApiGatewayMethodUsersCreatePost.Properties.Integration.Uri + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.Uri ).to.deep.equal({ 'Fn::Join': [ - '', [ + '', + [ 'arn:', { Ref: 'AWS::Partition' }, - ':apigateway:', { Ref: 'AWS::Region' }, - ':lambda:path/2015-03-31/functions/', { 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }, '/invocations', ], ], }); - expect(awsCompileApigEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .ApiGatewayMethodUsersListGet.Properties.Integration.Uri + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.Uri ).to.deep.equal({ 'Fn::Join': [ - '', [ + '', + [ 'arn:', { Ref: 'AWS::Partition' }, - ':apigateway:', { Ref: 'AWS::Region' }, - ':lambda:path/2015-03-31/functions/', { 'Fn::GetAtt': ['SecondLambdaFunction', 'Arn'] }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': ['SecondLambdaFunction', 'Arn'] }, '/invocations', ], ], @@ -942,26 +955,23 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersCreatePost.Properties - .Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersCreatePost.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Origin'] - ).to.equal('\'http://example.com\''); + ).to.equal("'http://example.com'"); // CORS not enabled! expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties - .Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Origin'] - ).to.not.equal('\'*\''); + ).to.not.equal("'*'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersUpdatePut.Properties - .Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersUpdatePut.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Access-Control-Allow-Origin'] - ).to.equal('\'*\''); + ).to.equal("'*'"); }); }); @@ -985,9 +995,11 @@ describe('#compileMethods()', () => { }, ]; return awsCompileApigEvents.compileMethods().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties - .Integration.RequestTemplates['application/json'] + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.RequestTemplates[ + 'application/json' + ] ).to.have.length.above(0); }); }); @@ -1011,9 +1023,11 @@ describe('#compileMethods()', () => { }, ]; return awsCompileApigEvents.compileMethods().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties - .Integration.RequestTemplates['application/x-www-form-urlencoded'] + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.RequestTemplates[ + 'application/x-www-form-urlencoded' + ] ).to.have.length.above(0); }); }); @@ -1040,8 +1054,9 @@ describe('#compileMethods()', () => { }, ]; return awsCompileApigEvents.compileMethods().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.PassthroughBehavior + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.PassthroughBehavior ).to.equal('WHEN_NO_TEMPLATES'); }); }); @@ -1071,14 +1086,14 @@ describe('#compileMethods()', () => { }, ]; return awsCompileApigEvents.compileMethods().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties - .Integration.RequestTemplates['template/1'] + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.RequestTemplates['template/1'] ).to.equal('{ "stage" : "$context.stage" }'); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties - .Integration.RequestTemplates['template/2'] + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.RequestTemplates['template/2'] ).to.equal('{ "httpMethod" : "$context.httpMethod" }'); }); }); @@ -1107,9 +1122,11 @@ describe('#compileMethods()', () => { }, ]; return awsCompileApigEvents.compileMethods().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties - .Integration.RequestTemplates['application/json'] + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.RequestTemplates[ + 'application/json' + ] ).to.equal('overwritten-request-template-content'); }); }); @@ -1140,13 +1157,13 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Content-Type'] ).to.equal("'text/plain'"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.My-Custom-Header'] ).to.equal('my/custom/header'); }); @@ -1173,8 +1190,8 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .ResponseTemplates['application/json'] ).to.equal("$input.path('$.foo')"); }); @@ -1204,12 +1221,12 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.MethodResponses[0].StatusCode + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.MethodResponses[0].StatusCode ).to.equal(200); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.MethodResponses[1].StatusCode + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.MethodResponses[1].StatusCode ).to.equal(202); }); }); @@ -1237,8 +1254,8 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] ).to.deep.equal({ StatusCode: 202, SelectionPattern: 'foo', @@ -1246,8 +1263,8 @@ describe('#compileMethods()', () => { ResponseTemplates: {}, }); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] ).to.deep.equal({ StatusCode: 200, SelectionPattern: '', @@ -1281,13 +1298,13 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .ResponseTemplates['application/json'] ).to.equal('foo'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Content-Type'] ).to.equal('text/csv'); }); @@ -1305,11 +1322,11 @@ describe('#compileMethods()', () => { statusCodes: { 200: { pattern: '', - template: '$input.path(\'$.foo\')', + template: "$input.path('$.foo')", }, 404: { pattern: '.*"statusCode":404,.*', - template: '$input.path(\'$.errorMessage\')', + template: "$input.path('$.errorMessage')", headers: { 'Content-Type': 'text/html', }, @@ -1321,28 +1338,28 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .ResponseTemplates['application/json'] ).to.equal("$input.path('$.foo')"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .SelectionPattern ).to.equal(''); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] .ResponseTemplates['application/json'] ).to.equal("$input.path('$.errorMessage')"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] .SelectionPattern ).to.equal('.*"statusCode":404,.*'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] .ResponseParameters['method.response.header.Content-Type'] ).to.equal('text/html'); }); @@ -1359,7 +1376,7 @@ describe('#compileMethods()', () => { response: { statusCodes: { 200: { - template: '$input.path(\'$.foo\')', + template: "$input.path('$.foo')", headers: { 'Content-Type': 'text/csv', }, @@ -1367,8 +1384,8 @@ describe('#compileMethods()', () => { 404: { pattern: '.*"statusCode":404,.*', template: { - 'application/json': '$input.path(\'$.errorMessage\')', - 'application/xml': '$input.path(\'$.xml.errorMessage\')', + 'application/json': "$input.path('$.errorMessage')", + 'application/xml': "$input.path('$.xml.errorMessage')", }, headers: { 'Content-Type': 'text/html', @@ -1381,38 +1398,38 @@ describe('#compileMethods()', () => { ]; return awsCompileApigEvents.compileMethods().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .ResponseTemplates['application/json'] ).to.equal("$input.path('$.foo')"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .SelectionPattern ).to.equal(''); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0] .ResponseParameters['method.response.header.Content-Type'] ).to.equal('text/csv'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] .ResponseTemplates['application/json'] ).to.equal("$input.path('$.errorMessage')"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] .ResponseTemplates['application/xml'] ).to.equal("$input.path('$.xml.errorMessage')"); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] .SelectionPattern ).to.equal('.*"statusCode":404,.*'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[1] .ResponseParameters['method.response.header.Content-Type'] ).to.equal('text/html'); }); @@ -1429,13 +1446,12 @@ describe('#compileMethods()', () => { }, ]; return awsCompileApigEvents.compileMethods().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayMethodGet.Properties.ResourceId).to.deep.equal({ - 'Fn::GetAtt': [ - 'ApiGatewayRestApi', - 'RootResourceId', - ], - }); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayMethodGet.Properties.ResourceId + ).to.deep.equal({ + 'Fn::GetAtt': ['ApiGatewayRestApi', 'RootResourceId'], + }); }); }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js index de7da698c..ef7844947 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js @@ -63,7 +63,8 @@ module.exports = { if (type === 'AWS' || type === 'AWS_PROXY') { _.assign(integration, { Uri: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -92,8 +93,11 @@ module.exports = { IntegrationResponses: this.getIntegrationResponses(http), }); } - if (((type === 'AWS' || type === 'HTTP' || type === 'HTTP_PROXY') && - (http.request && !_.isEmpty(http.request.parameters))) || http.async) { + if ( + ((type === 'AWS' || type === 'HTTP' || type === 'HTTP_PROXY') && + (http.request && !_.isEmpty(http.request.parameters))) || + http.async + ) { _.assign(integration, { RequestParameters: this.getIntegrationRequestParameters(http), }); @@ -129,8 +133,10 @@ module.exports = { } _.each(http.response.statusCodes, (config, statusCode) => { - const responseParameters = _.mapKeys(integrationResponseHeaders, - (value, header) => `method.response.header.${header}`); + const responseParameters = _.mapKeys( + integrationResponseHeaders, + (value, header) => `method.response.header.${header}` + ); const integrationResponse = { StatusCode: parseInt(statusCode, 10), @@ -140,8 +146,10 @@ module.exports = { }; if (config.headers) { - _.merge(integrationResponse.ResponseParameters, _.mapKeys(config.headers, - (value, header) => `method.response.header.${header}`)); + _.merge( + integrationResponse.ResponseParameters, + _.mapKeys(config.headers, (value, header) => `method.response.header.${header}`) + ); } if (http.response.template) { @@ -151,9 +159,10 @@ module.exports = { } if (config.template) { - const template = typeof config.template === 'string' ? - { 'application/json': config.template } - : config.template; + const template = + typeof config.template === 'string' + ? { 'application/json': config.template } + : config.template; _.merge(integrationResponse.ResponseTemplates, template); } diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/responses.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/responses.js index 5d57455e1..f44bc6ce3 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/responses.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/responses.js @@ -3,7 +3,6 @@ const _ = require('lodash'); module.exports = { - getMethodResponses(http) { const methodResponses = []; @@ -34,12 +33,16 @@ module.exports = { StatusCode: parseInt(statusCode, 10), }; - _.merge(methodResponse.ResponseParameters, - this.getMethodResponseHeaders(methodResponseHeaders)); + _.merge( + methodResponse.ResponseParameters, + this.getMethodResponseHeaders(methodResponseHeaders) + ); if (config.headers) { - _.merge(methodResponse.ResponseParameters, - this.getMethodResponseHeaders(config.headers)); + _.merge( + methodResponse.ResponseParameters, + this.getMethodResponseHeaders(config.headers) + ); } methodResponses.push(methodResponse); @@ -63,5 +66,4 @@ module.exports = { return methodResponseHeaders; }, - }; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js index eed70c66c..decf7ff49 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js @@ -5,11 +5,11 @@ const BbPromise = require('bluebird'); const awsArnRegExs = require('../../../../../utils/arnRegularExpressions'); module.exports = { - compilePermissions() { - this.permissionMapping.forEach((singlePermissionMapping) => { - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaApiGatewayPermissionLogicalId(singlePermissionMapping.event.functionName); + this.permissionMapping.forEach(singlePermissionMapping => { + const lambdaPermissionLogicalId = this.provider.naming.getLambdaApiGatewayPermissionLogicalId( + singlePermissionMapping.event.functionName + ); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [lambdaPermissionLogicalId]: { @@ -21,7 +21,8 @@ module.exports = { Action: 'lambda:InvokeFunction', Principal: 'apigateway.amazonaws.com', SourceArn: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -39,14 +40,19 @@ module.exports = { }, }); - if (singlePermissionMapping.event.http.authorizer && - singlePermissionMapping.event.http.authorizer.arn) { + if ( + singlePermissionMapping.event.http.authorizer && + singlePermissionMapping.event.http.authorizer.arn + ) { const authorizer = singlePermissionMapping.event.http.authorizer; - const authorizerPermissionLogicalId = this.provider.naming - .getLambdaApiGatewayPermissionLogicalId(authorizer.name); + const authorizerPermissionLogicalId = this.provider.naming.getLambdaApiGatewayPermissionLogicalId( + authorizer.name + ); - if (typeof authorizer.arn === 'string' - && awsArnRegExs.cognitoIdpArnExpr.test(authorizer.arn)) { + if ( + typeof authorizer.arn === 'string' && + awsArnRegExs.cognitoIdpArnExpr.test(authorizer.arn) + ) { return; } diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.test.js index a0ff32a67..853d7fd23 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.test.js @@ -44,12 +44,14 @@ describe('#awsCompilePermissions()', () => { ]; return awsCompileApigEvents.compilePermissions().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstLambdaPermissionApiGateway - .Properties.FunctionName['Fn::GetAtt'][0]).to.equal('FirstLambdaFunction'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionApiGateway.Properties.FunctionName['Fn::GetAtt'][0] + ).to.equal('FirstLambdaFunction'); const deepObj = { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -64,9 +66,10 @@ describe('#awsCompilePermissions()', () => { ], }; - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstLambdaPermissionApiGateway - .Properties.SourceArn).to.deep.equal(deepObj); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionApiGateway.Properties.SourceArn + ).to.deep.equal(deepObj); }); }); @@ -99,12 +102,14 @@ describe('#awsCompilePermissions()', () => { ]; return awsCompileApigEvents.compilePermissions().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstLambdaPermissionApiGateway - .Properties.FunctionName['Fn::GetAtt'][0]).to.equal('FirstLambdaFunction'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionApiGateway.Properties.FunctionName['Fn::GetAtt'][0] + ).to.equal('FirstLambdaFunction'); const deepObj = { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -119,9 +124,10 @@ describe('#awsCompilePermissions()', () => { ], }; - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstLambdaPermissionApiGateway - .Properties.SourceArn).to.deep.equal(deepObj); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionApiGateway.Properties.SourceArn + ).to.deep.equal(deepObj); }); }); @@ -158,9 +164,10 @@ describe('#awsCompilePermissions()', () => { }, ]; return awsCompileApigEvents.compilePermissions().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.AuthorizerLambdaPermissionApiGateway - .Properties.FunctionName['Fn::GetAtt'][0]).to.equal('AuthorizerLambdaFunction'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .AuthorizerLambdaPermissionApiGateway.Properties.FunctionName['Fn::GetAtt'][0] + ).to.equal('AuthorizerLambdaFunction'); }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/resources.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/resources.js index fac0b36ca..5fb01f1a5 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/resources.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/resources.js @@ -4,12 +4,11 @@ const BbPromise = require('bluebird'); const _ = require('lodash'); module.exports = { - compileResources() { this.apiGatewayResources = this.getResourcePaths(); // ['users', 'users/create', 'users/create/something'] - _.keys(this.apiGatewayResources).forEach((path) => { + _.keys(this.apiGatewayResources).forEach(path => { const resource = this.apiGatewayResources[path]; if (resource.resourceId) { return; @@ -18,8 +17,7 @@ module.exports = { resource.resourceLogicalId = this.provider.naming.getResourceLogicalId(path); resource.resourceId = { Ref: resource.resourceLogicalId }; - const parentRef = resource.parent - ? resource.parent.resourceId : this.getResourceId(); + const parentRef = resource.parent ? resource.parent.resourceId : this.getResourceId(); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [resource.resourceLogicalId]: { @@ -46,13 +44,17 @@ module.exports = { r[node.path].name = self.provider.naming.normalizePath(node.path); } - node.children.forEach((child) => getNodePaths(result, child)); + node.children.forEach(child => getNodePaths(result, child)); } - return _.reduce(trees, (result, tree) => { - getNodePaths(result, tree); - return result; - }, {}); + return _.reduce( + trees, + (result, tree) => { + getNodePaths(result, tree); + return result; + }, + {} + ); }, getResourcePaths() { @@ -61,7 +63,6 @@ module.exports = { const methodNodes = []; const predefinedResources = this.provider.getApiGatewayPredefinedResources(); - function cutBranch(node) { if (!node.parent) { return; @@ -71,7 +72,7 @@ module.exports = { if (node.parent.children.length <= 1) { n.parent.children = []; } else { - n.parent.children = node.parent.children.filter((c) => c.path !== n.path); + n.parent.children = node.parent.children.filter(c => c.path !== n.path); n.parent.isCut = true; } n.parent = null; @@ -91,13 +92,13 @@ module.exports = { n.name = resource.name; if (resource.resourceId) { n.resourceId = resource.resourceId; - if (_.every(predefinedResourceNodes, (iter) => iter.path !== n.path)) { + if (_.every(predefinedResourceNodes, iter => iter.path !== n.path)) { predefinedResourceNodes.push(node); } } if (isMethod && !node.hasMethod) { n.hasMethod = true; - if (_.every(methodNodes, (iter) => iter.path !== n.path)) { + if (_.every(methodNodes, iter => iter.path !== n.path)) { methodNodes.push(node); } } @@ -108,7 +109,7 @@ module.exports = { pathParts.forEach((pathPart, index) => { currentPath = currentPath ? `${currentPath}/${pathPart}` : pathPart; - root = root || _.find(trees, (node) => node.path === currentPath); + root = root || _.find(trees, node => node.path === currentPath); parent = parent || root; let node; @@ -117,7 +118,7 @@ module.exports = { applyNodeResource(parent, pathParts, index); return; } else if (parent.children.length > 0) { - node = _.find(parent.children, (n) => n.path === currentPath); + node = _.find(parent.children, n => n.path === currentPath); if (node) { applyNodeResource(node, pathParts, index); return; @@ -148,7 +149,7 @@ module.exports = { } predefinedResources.forEach(applyResource); - this.validated.events.forEach((event) => { + this.validated.events.forEach(event => { if (event.http.path) { applyResource(event.http, true); } @@ -160,23 +161,28 @@ module.exports = { } // if all methods have resource ID already, no need to validate resource trees - if (_.every(this.validated.events, (event) => - _.some(predefinedResourceNodes, (node) => - node.path === event.http.path))) { - return _.reduce(predefinedResources, (resourceMap, resource) => { - const r = resourceMap; - r[resource.path] = resource; + if ( + _.every(this.validated.events, event => + _.some(predefinedResourceNodes, node => node.path === event.http.path) + ) + ) { + return _.reduce( + predefinedResources, + (resourceMap, resource) => { + const r = resourceMap; + r[resource.path] = resource; - if (!resource.name) { - r[resource.path].name = this.provider.naming.normalizePath(resource.path); - } - return r; - }, {}); + if (!resource.name) { + r[resource.path].name = this.provider.naming.normalizePath(resource.path); + } + return r; + }, + {} + ); } // cut resource branches from trees - const sortedResourceNodes = _.sortBy(predefinedResourceNodes, - node => node.level); + const sortedResourceNodes = _.sortBy(predefinedResourceNodes, node => node.level); const validatedTrees = []; for (let i = sortedResourceNodes.length - 1; i >= 0; i--) { @@ -198,12 +204,12 @@ module.exports = { } // get branches that begin from root resource - methodNodes.forEach((node) => { + methodNodes.forEach(node => { let iter = node; while (iter) { if (iter.resourceId) { cutBranch(iter); - if (_.every(validatedTrees, (t) => t.path !== node.path)) { + if (_.every(validatedTrees, t => t.path !== node.path)) { validatedTrees.push(iter); } @@ -235,10 +241,13 @@ module.exports = { throw new Error(`Can not find API Gateway resource from path ${path}`); } - if (!this.apiGatewayResources[path].resourceId - && this.apiGatewayResources[path].resourceLogicalId) { - this.apiGatewayResources[path].resourceId = - { Ref: this.apiGatewayResources[path].resourceLogicalId }; + if ( + !this.apiGatewayResources[path].resourceId && + this.apiGatewayResources[path].resourceLogicalId + ) { + this.apiGatewayResources[path].resourceId = { + Ref: this.apiGatewayResources[path].resourceLogicalId, + }; } return this.apiGatewayResources[path].resourceId; }, diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/resources.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/resources.test.js index 18daad469..1c649d385 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/resources.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/resources.test.js @@ -117,18 +117,22 @@ describe('#compileResources()', () => { }, ]; return awsCompileApigEvents.compileResources().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceFoo.Properties.ParentId['Fn::GetAtt'][0]) - .to.equal('ApiGatewayRestApi'); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceFoo.Properties.ParentId['Fn::GetAtt'][1]) - .to.equal('RootResourceId'); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceFooBar.Properties.ParentId.Ref) - .to.equal('ApiGatewayResourceFoo'); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceBarIdVar.Properties.ParentId.Ref) - .to.equal('ApiGatewayResourceBar'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceFoo.Properties.ParentId['Fn::GetAtt'][0] + ).to.equal('ApiGatewayRestApi'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceFoo.Properties.ParentId['Fn::GetAtt'][1] + ).to.equal('RootResourceId'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceFooBar.Properties.ParentId.Ref + ).to.equal('ApiGatewayResourceFoo'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceBarIdVar.Properties.ParentId.Ref + ).to.equal('ApiGatewayResourceBar'); }); }); @@ -161,15 +165,16 @@ describe('#compileResources()', () => { ]; return awsCompileApigEvents.compileResources().then(() => { const expectedResourceLogicalIds = { - baz: 'ApiGatewayResourceBaz', + 'baz': 'ApiGatewayResourceBaz', 'baz/foo': 'ApiGatewayResourceBazFoo', - foo: 'ApiGatewayResourceFoo', + 'foo': 'ApiGatewayResourceFoo', 'foo/{foo_id}': 'ApiGatewayResourceFooFooidVar', 'foo/{foo_id}/bar': 'ApiGatewayResourceFooFooidVarBar', }; - Object.keys(expectedResourceLogicalIds).forEach((path) => { - expect(awsCompileApigEvents.apiGatewayResources[path].resourceLogicalId) - .equal(expectedResourceLogicalIds[path]); + Object.keys(expectedResourceLogicalIds).forEach(path => { + expect(awsCompileApigEvents.apiGatewayResources[path].resourceLogicalId).equal( + expectedResourceLogicalIds[path] + ); }); }); }); @@ -191,13 +196,14 @@ describe('#compileResources()', () => { ]; return awsCompileApigEvents.compileResources().then(() => { const expectedResourceLogicalIds = { - foo: 'ApiGatewayResourceFoo', + 'foo': 'ApiGatewayResourceFoo', 'foo/bar': 'ApiGatewayResourceFooBar', 'foo/{bar}': 'ApiGatewayResourceFooBarVar', }; - Object.keys(expectedResourceLogicalIds).forEach((path) => { - expect(awsCompileApigEvents.apiGatewayResources[path].resourceLogicalId) - .equal(expectedResourceLogicalIds[path]); + Object.keys(expectedResourceLogicalIds).forEach(path => { + expect(awsCompileApigEvents.apiGatewayResources[path].resourceLogicalId).equal( + expectedResourceLogicalIds[path] + ); }); }); }); @@ -224,15 +230,18 @@ describe('#compileResources()', () => { }, ]; return awsCompileApigEvents.compileResources().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceFooBar.Properties.PathPart) - .to.equal('bar'); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceFooBarVar.Properties.PathPart) - .to.equal('{bar}'); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceFooBarVarBaz.Properties.PathPart) - .to.equal('baz'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceFooBar.Properties.PathPart + ).to.equal('bar'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceFooBarVar.Properties.PathPart + ).to.equal('{bar}'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceFooBarVarBaz.Properties.PathPart + ).to.equal('baz'); }); }); @@ -246,8 +255,9 @@ describe('#compileResources()', () => { }, ]; return awsCompileApigEvents.compileResources().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources).to.deep.equal({}); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + ).to.deep.equal({}); }); }); @@ -333,25 +343,34 @@ describe('#compileResources()', () => { expect(e.message).to.equal('Can not find API Gateway resource from path users/{userId}'); } - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceFoo).to.equal(undefined); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceBar.Properties.RestApiId) - .to.equal('6fyzt1pfpk'); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceBar.Properties.ParentId) - .to.equal('z5d4qh4oqi'); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceFooBar.Properties.ParentId) - .to.equal('axcybf2i39'); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceBarIdVar.Properties.ParentId.Ref) - .to.equal('ApiGatewayResourceBar'); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceUsersMePosts).not.equal(undefined); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceUsersFriendsComments.Properties.ParentId) - .to.equal('fcasdoojp1'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceFoo + ).to.equal(undefined); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceBar.Properties.RestApiId + ).to.equal('6fyzt1pfpk'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceBar.Properties.ParentId + ).to.equal('z5d4qh4oqi'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceFooBar.Properties.ParentId + ).to.equal('axcybf2i39'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceBarIdVar.Properties.ParentId.Ref + ).to.equal('ApiGatewayResourceBar'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceUsersMePosts + ).not.equal(undefined); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceUsersFriendsComments.Properties.ParentId + ).to.equal('fcasdoojp1'); }); }); @@ -360,8 +379,8 @@ describe('#compileResources()', () => { restApiId: '6fyzt1pfpk', restApiRootResourceId: 'z5d4qh4oqi', restApiResources: { - foo: 'axcybf2i39', - users: 'zxcvbnmasd', + 'foo': 'axcybf2i39', + 'users': 'zxcvbnmasd', 'users/friends': 'fcasdoojp1', 'users/is/this/a/long/path': 'sadvgpoujk', }, @@ -395,14 +414,22 @@ describe('#compileResources()', () => { ]; return awsCompileApigEvents.compileResources().then(() => { - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceFoo).to.equal(undefined); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceUsers).to.equal(undefined); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceUsersFriends).to.equal(undefined); - expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ApiGatewayResourceUsersIsThis).to.equal(undefined); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceFoo + ).to.equal(undefined); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceUsers + ).to.equal(undefined); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceUsersFriends + ).to.equal(undefined); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ApiGatewayResourceUsersIsThis + ).to.equal(undefined); }); }); @@ -430,8 +457,10 @@ describe('#compileResources()', () => { }, ]; - expect(() => awsCompileApigEvents.compileResources()) - .to.throw(Error, 'Resource ID for path users is required'); + expect(() => awsCompileApigEvents.compileResources()).to.throw( + Error, + 'Resource ID for path users is required' + ); }); it('should named all method paths if all resources are predefined', () => { @@ -478,11 +507,13 @@ describe('#compileResources()', () => { ]; return awsCompileApigEvents.compileResources().then(() => { - expect(Object.keys(awsCompileApigEvents.serverless - .service.provider.compiledCloudFormationTemplate - .Resources).every((k) => ['ApiGatewayMethodundefinedGet', - 'ApiGatewayMethodundefinedPost'].indexOf(k) === -1)) - .to.equal(true); + expect( + Object.keys( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + ).every( + k => ['ApiGatewayMethodundefinedGet', 'ApiGatewayMethodundefinedPost'].indexOf(k) === -1 + ) + ).to.equal(true); }); }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.js index baf6f7b9e..b420b2881 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.js @@ -28,10 +28,10 @@ module.exports = { throw new this.serverless.classes.Error('endpointType must be a string'); } - if (!_.includes(validEndpointTypes, endpointType.toUpperCase())) { - const message = 'endpointType must be one of "REGIONAL" or "EDGE" or "PRIVATE". ' + - `You provided ${endpointType}.`; + const message = + 'endpointType must be one of "REGIONAL" or "EDGE" or "PRIVATE". ' + + `You provided ${endpointType}.`; throw new this.serverless.classes.Error(message); } endpointType = endpointType.toUpperCase(); @@ -55,10 +55,14 @@ module.exports = { Version: '2012-10-17', Statement: this.serverless.service.provider.resourcePolicy, }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.apiGatewayRestApiLogicalId].Properties, { - Policy: policy, - }); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + this.apiGatewayRestApiLogicalId + ].Properties, + { + Policy: policy, + } + ); } if (!_.isEmpty(apiGateway.apiKeySourceType)) { @@ -73,8 +77,9 @@ module.exports = { } _.merge( - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.apiGatewayRestApiLogicalId].Properties, + this.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + this.apiGatewayRestApiLogicalId + ].Properties, { ApiKeySourceType: apiKeySourceType } ); } @@ -97,8 +102,9 @@ module.exports = { } _.merge( - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.apiGatewayRestApiLogicalId].Properties, + this.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + this.apiGatewayRestApiLogicalId + ].Properties, { MinimumCompressionSize: minimumCompressionSize } ); } diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js index aee58434e..4c7ffa6df 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js @@ -38,8 +38,8 @@ describe('#compileRestApi()', () => { it('should create a REST API resource', () => awsCompileApigEvents.compileRestApi().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources.ApiGatewayRestApi).to.deep.equal({ Type: 'AWS::ApiGateway::RestApi', @@ -68,8 +68,8 @@ describe('#compileRestApi()', () => { }, ]; return awsCompileApigEvents.compileRestApi().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources.ApiGatewayRestApi).to.deep.equal({ Type: 'AWS::ApiGateway::RestApi', @@ -106,33 +106,26 @@ describe('#compileRestApi()', () => { restApiRootResourceId: 'z5d4qh4oqi', }; return awsCompileApigEvents.compileRestApi().then(() => { - expect(awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources).to.deep.equal( - {} - ); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + ).to.deep.equal({}); }); }); it('should set binary media types if defined at the apiGateway provider config level', () => { awsCompileApigEvents.serverless.service.provider.apiGateway = { - binaryMediaTypes: [ - '*/*', - ], + binaryMediaTypes: ['*/*'], }; return awsCompileApigEvents.compileRestApi().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources.ApiGatewayRestApi).to.deep.equal({ Type: 'AWS::ApiGateway::RestApi', Properties: { - BinaryMediaTypes: [ - '*/*', - ], + BinaryMediaTypes: ['*/*'], EndpointConfiguration: { - Types: [ - 'EDGE', - ], + Types: ['EDGE'], }, Name: 'dev-new-service', }, @@ -166,8 +159,9 @@ describe('#compileRestApi()', () => { }); it('should compile correctly if apiKeySourceType property is AUTHORIZER', () => { - awsCompileApigEvents.serverless.service.provider.apiGateway = - { apiKeySourceType: 'AUTHORIZER' }; + awsCompileApigEvents.serverless.service.provider.apiGateway = { + apiKeySourceType: 'AUTHORIZER', + }; expect(() => awsCompileApigEvents.compileRestApi()).to.not.throw(Error); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js index e32df1ad3..4ac905e68 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js @@ -32,15 +32,11 @@ module.exports = { // --- currently commented out since this is done via the SDK in updateStage.js --- // const deploymentId = this.apiGatewayDeploymentLogicalId; // -------------------------------------------------------------------------------- - const logGroupLogicalId = this.provider.naming - .getApiGatewayLogGroupLogicalId(); - const logsRoleLogicalId = this.provider.naming - .getApiGatewayLogsRoleLogicalId(); - const accountLogicalid = this.provider.naming - .getApiGatewayAccountLogicalId(); + const logGroupLogicalId = this.provider.naming.getApiGatewayLogGroupLogicalId(); + const logsRoleLogicalId = this.provider.naming.getApiGatewayLogsRoleLogicalId(); + const accountLogicalid = this.provider.naming.getApiGatewayAccountLogicalId(); - this.apiGatewayStageLogicalId = this.provider.naming - .getStageLogicalId(); + this.apiGatewayStageLogicalId = this.provider.naming.getStageLogicalId(); // NOTE: right now we're only using a dedicated Stage resource // - if AWS X-Ray tracing is enabled @@ -109,16 +105,16 @@ module.exports = { }; function getLogGroupResource(service, stage) { - return ({ + return { Type: 'AWS::Logs::LogGroup', Properties: { LogGroupName: `/aws/api-gateway/${service}-${stage}`, }, - }); + }; } function getIamRoleResource(service, stage) { - return ({ + return { Type: 'AWS::IAM::Role', Properties: { AssumeRolePolicyDocument: { @@ -127,13 +123,9 @@ function getIamRoleResource(service, stage) { { Effect: 'Allow', Principal: { - Service: [ - 'apigateway.amazonaws.com', - ], + Service: ['apigateway.amazonaws.com'], }, - Action: [ - 'sts:AssumeRole', - ], + Action: ['sts:AssumeRole'], }, ], }, @@ -155,19 +147,16 @@ function getIamRoleResource(service, stage) { ], }, }, - }); + }; } function getAccountResource(logsRoleLogicalId) { - return ({ + return { Type: 'AWS::ApiGateway::Account', Properties: { CloudWatchRoleArn: { - 'Fn::GetAtt': [ - logsRoleLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [logsRoleLogicalId, 'Arn'], }, }, - }); + }; } diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.test.js index 229ddefa7..deaac6988 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.test.js @@ -33,22 +33,19 @@ describe('#compileStage()', () => { awsCompileApigEvents.apiGatewayDeploymentLogicalId = 'ApiGatewayDeploymentTest'; awsCompileApigEvents.provider = provider; stage = awsCompileApigEvents.provider.getStage(); - stageLogicalId = awsCompileApigEvents.provider.naming - .getStageLogicalId(); - accountLogicalid = awsCompileApigEvents.provider.naming - .getApiGatewayAccountLogicalId(); - logsRoleLogicalId = awsCompileApigEvents.provider.naming - .getApiGatewayLogsRoleLogicalId(); - logGroupLogicalId = awsCompileApigEvents.provider.naming - .getApiGatewayLogGroupLogicalId(); + stageLogicalId = awsCompileApigEvents.provider.naming.getStageLogicalId(); + accountLogicalid = awsCompileApigEvents.provider.naming.getApiGatewayAccountLogicalId(); + logsRoleLogicalId = awsCompileApigEvents.provider.naming.getApiGatewayLogsRoleLogicalId(); + logGroupLogicalId = awsCompileApigEvents.provider.naming.getApiGatewayLogGroupLogicalId(); // mocking the result of a Deployment resource since we remove the stage name // when using the Stage resource - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId] = { - Properties: { - StageName: stage, - }, - }; + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsCompileApigEvents.apiGatewayDeploymentLogicalId + ] = { + Properties: { + StageName: stage, + }, + }; }); describe('tracing', () => { @@ -61,8 +58,8 @@ describe('#compileStage()', () => { it.skip('should create a dedicated stage resource if tracing is configured', () => awsCompileApigEvents.compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources[stageLogicalId]).to.deep.equal({ Type: 'AWS::ApiGateway::Stage', @@ -82,15 +79,14 @@ describe('#compileStage()', () => { expect(resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId]).to.deep.equal({ Properties: {}, }); - }) - ); + })); it('should NOT create a dedicated stage resource if tracing is not enabled', () => { awsCompileApigEvents.serverless.service.provider.tracing = {}; return awsCompileApigEvents.compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; // eslint-disable-next-line expect(resources[stageLogicalId]).not.to.exist; @@ -111,8 +107,8 @@ describe('#compileStage()', () => { }; awsCompileApigEvents.compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId]).to.deep.equal({ Properties: {}, }); @@ -128,9 +124,7 @@ describe('#compileStage()', () => { }, StageName: stage, TracingEnabled: false, - Tags: [ - { Key: 'foo', Value: '1' }, - ], + Tags: [{ Key: 'foo', Value: '1' }], }, }); }); @@ -142,8 +136,8 @@ describe('#compileStage()', () => { }; awsCompileApigEvents.compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId]).to.deep.equal({ Properties: {}, }); @@ -159,9 +153,7 @@ describe('#compileStage()', () => { }, StageName: stage, TracingEnabled: false, - Tags: [ - { Key: 'foo', Value: '1' }, - ], + Tags: [{ Key: 'foo', Value: '1' }], }, }); }); @@ -178,8 +170,8 @@ describe('#compileStage()', () => { }; awsCompileApigEvents.compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources[stageLogicalId]).to.deep.equal({ Type: 'AWS::ApiGateway::Stage', @@ -213,8 +205,8 @@ describe('#compileStage()', () => { it.skip('should create a dedicated stage resource if logs are configured', () => awsCompileApigEvents.compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources[stageLogicalId]).to.deep.equal({ Type: 'AWS::ApiGateway::Stage', @@ -238,13 +230,11 @@ describe('#compileStage()', () => { ], AccessLogSetting: { DestinationArn: { - 'Fn::GetAtt': [ - logGroupLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [logGroupLogicalId, 'Arn'], }, // eslint-disable-next-line - Format: 'requestId: $context.requestId, ip: $context.identity.sourceIp, caller: $context.identity.caller, user: $context.identity.user, requestTime: $context.requestTime, httpMethod: $context.httpMethod, resourcePath: $context.resourcePath, status: $context.status, protocol: $context.protocol, responseLength: $context.responseLength', + Format: + 'requestId: $context.requestId, ip: $context.identity.sourceIp, caller: $context.identity.caller, user: $context.identity.user, requestTime: $context.requestTime, httpMethod: $context.httpMethod, resourcePath: $context.resourcePath, status: $context.status, protocol: $context.protocol, responseLength: $context.responseLength', }, }, }); @@ -256,8 +246,8 @@ describe('#compileStage()', () => { it('should create a Log Group resource', () => awsCompileApigEvents.compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources[logGroupLogicalId]).to.deep.equal({ Type: 'AWS::Logs::LogGroup', @@ -269,8 +259,8 @@ describe('#compileStage()', () => { it('should create a IAM Role resource', () => awsCompileApigEvents.compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources[logsRoleLogicalId]).to.deep.equal({ Type: 'AWS::IAM::Role', @@ -278,14 +268,10 @@ describe('#compileStage()', () => { AssumeRolePolicyDocument: { Statement: [ { - Action: [ - 'sts:AssumeRole', - ], + Action: ['sts:AssumeRole'], Effect: 'Allow', Principal: { - Service: [ - 'apigateway.amazonaws.com', - ], + Service: ['apigateway.amazonaws.com'], }, }, ], @@ -314,17 +300,14 @@ describe('#compileStage()', () => { it('should create an Account resource', () => awsCompileApigEvents.compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; expect(resources[accountLogicalid]).to.deep.equal({ Type: 'AWS::ApiGateway::Account', Properties: { CloudWatchRoleArn: { - 'Fn::GetAtt': [ - logsRoleLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [logsRoleLogicalId, 'Arn'], }, }, }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js index 861e8b78c..3a8990e3e 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js @@ -14,76 +14,90 @@ function createUsagePlanResource(that, name) { Stage: that.provider.getStage(), }, ], - Description: `Usage plan "${name}" for ${that.serverless.service.service} ${ - that.provider.getStage()} stage`, - UsagePlanName: `${that.serverless.service.service}-${name}-${ - that.provider.getStage()}`, + Description: `Usage plan "${name}" for ${ + that.serverless.service.service + } ${that.provider.getStage()} stage`, + UsagePlanName: `${that.serverless.service.service}-${name}-${that.provider.getStage()}`, }, }; const template = _.cloneDeep(resourceTemplate); // this is done for backward compatibility if (name === 'default') { // create old legacy resources - template.Properties.UsagePlanName = - `${that.serverless.service.service}-${that.provider.getStage()}`; - template.Properties.Description = - `Usage plan for ${that.serverless.service.service} ${that.provider.getStage()} stage`; + template.Properties.UsagePlanName = `${ + that.serverless.service.service + }-${that.provider.getStage()}`; + template.Properties.Description = `Usage plan for ${ + that.serverless.service.service + } ${that.provider.getStage()} stage`; // assign quota - if (_.has(that.serverless.service.provider, 'usagePlan.quota') - && that.serverless.service.provider.usagePlan.quota !== null) { + if ( + _.has(that.serverless.service.provider, 'usagePlan.quota') && + that.serverless.service.provider.usagePlan.quota !== null + ) { _.merge(template, { Properties: { Quota: _.merge( { Limit: that.serverless.service.provider.usagePlan.quota.limit }, { Offset: that.serverless.service.provider.usagePlan.quota.offset }, - { Period: that.serverless.service.provider.usagePlan.quota.period }), + { Period: that.serverless.service.provider.usagePlan.quota.period } + ), }, }); } // assign throttle - if (_.has(that.serverless.service.provider, 'usagePlan.throttle') - && that.serverless.service.provider.usagePlan.throttle !== null) { + if ( + _.has(that.serverless.service.provider, 'usagePlan.throttle') && + that.serverless.service.provider.usagePlan.throttle !== null + ) { _.merge(template, { Properties: { Throttle: _.merge( { BurstLimit: that.serverless.service.provider.usagePlan.throttle.burstLimit }, - { RateLimit: that.serverless.service.provider.usagePlan.throttle.rateLimit }), + { RateLimit: that.serverless.service.provider.usagePlan.throttle.rateLimit } + ), }, }); } } else { // assign quota - const quotaProperties = that.serverless.service.provider.usagePlan - .reduce((accum, planObject) => { + const quotaProperties = that.serverless.service.provider.usagePlan.reduce( + (accum, planObject) => { if (planObject[name] && planObject[name].quota) { return planObject[name].quota; } return accum; - }, {}); + }, + {} + ); if (!_.isEmpty(quotaProperties)) { _.merge(template, { Properties: { Quota: _.merge( { Limit: quotaProperties.limit }, { Offset: quotaProperties.offset }, - { Period: quotaProperties.period }), + { Period: quotaProperties.period } + ), }, }); } // assign throttle - const throttleProperties = that.serverless.service.provider.usagePlan - .reduce((accum, planObject) => { + const throttleProperties = that.serverless.service.provider.usagePlan.reduce( + (accum, planObject) => { if (planObject[name] && planObject[name].throttle) { return planObject[name].throttle; } return accum; - }, {}); + }, + {} + ); if (!_.isEmpty(throttleProperties)) { _.merge(template, { Properties: { Throttle: _.merge( { BurstLimit: throttleProperties.burstLimit }, - { RateLimit: throttleProperties.rateLimit }), + { RateLimit: throttleProperties.rateLimit } + ), }, }); } @@ -98,7 +112,7 @@ module.exports = { this.apiGatewayUsagePlanNames = []; if (_.isArray(this.serverless.service.provider.usagePlan)) { - _.forEach(this.serverless.service.provider.usagePlan, (planObject) => { + _.forEach(this.serverless.service.provider.usagePlan, planObject => { const usagePlanName = Object.keys(planObject)[0]; const logicalId = this.provider.naming.getUsagePlanLogicalId(usagePlanName); const resourceTemplate = createUsagePlanResource(this, usagePlanName); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js index b46263c13..53caa3cec 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js @@ -30,40 +30,34 @@ describe('#compileUsagePlan()', () => { serverless.service.provider.apiKeys = ['1234567890']; return awsCompileApigEvents.compileUsagePlan().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() - ].Type + ].Type ).to.equal('AWS::ApiGateway::UsagePlan'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() - ].DependsOn + ].DependsOn ).to.equal('ApiGatewayDeploymentTest'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() - ].Properties.ApiStages[0].ApiId.Ref + ].Properties.ApiStages[0].ApiId.Ref ).to.equal('ApiGatewayRestApi'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() - ].Properties.ApiStages[0].Stage + ].Properties.ApiStages[0].Stage ).to.equal('dev'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() - ].Properties.Description + ].Properties.Description ).to.equal('Usage plan for first-service dev stage'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() - ].Properties.UsagePlanName + ].Properties.UsagePlanName ).to.equal('first-service-dev'); expect(awsCompileApigEvents.apiGatewayUsagePlanNames).to.deep.equal(['default']); @@ -87,43 +81,51 @@ describe('#compileUsagePlan()', () => { const logicalId = awsCompileApigEvents.provider.naming.getUsagePlanLogicalId(); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalId].Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalId + ].Type ).to.equal('AWS::ApiGateway::UsagePlan'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalId].DependsOn + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalId + ].DependsOn ).to.equal('ApiGatewayDeploymentTest'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalId].Properties.ApiStages[0].ApiId.Ref + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalId + ].Properties.ApiStages[0].ApiId.Ref ).to.equal('ApiGatewayRestApi'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalId].Properties.ApiStages[0].Stage + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalId + ].Properties.ApiStages[0].Stage ).to.equal('dev'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalId].Properties.Description + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalId + ].Properties.Description ).to.equal('Usage plan for first-service dev stage'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalId].Properties.Quota + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalId + ].Properties.Quota ).to.deep.equal({ Limit: 500, Offset: 10, Period: 'MONTH', }); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalId].Properties.Throttle + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalId + ].Properties.Throttle ).to.deep.equal({ BurstLimit: 200, RateLimit: 100, }); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalId].Properties.UsagePlanName + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalId + ].Properties.UsagePlanName ).to.equal('first-service-dev'); expect(awsCompileApigEvents.apiGatewayUsagePlanNames).to.deep.equal(['default']); @@ -168,88 +170,105 @@ describe('#compileUsagePlan()', () => { return awsCompileApigEvents.compileUsagePlan().then(() => { // resources for the "free" plan expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdFree].Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdFree + ].Type ).to.equal('AWS::ApiGateway::UsagePlan'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdFree].DependsOn + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdFree + ].DependsOn ).to.equal('ApiGatewayDeploymentTest'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdFree].Properties.ApiStages[0].ApiId.Ref + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdFree + ].Properties.ApiStages[0].ApiId.Ref ).to.equal('ApiGatewayRestApi'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdFree].Properties.ApiStages[0].Stage + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdFree + ].Properties.ApiStages[0].Stage ).to.equal('dev'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdFree].Properties.Description + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdFree + ].Properties.Description ).to.equal(`Usage plan "${freePlanName}" for first-service dev stage`); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdFree].Properties.Quota + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdFree + ].Properties.Quota ).to.deep.equal({ Limit: 1000, Offset: 100, Period: 'MONTH', }); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdFree].Properties.Throttle + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdFree + ].Properties.Throttle ).to.deep.equal({ BurstLimit: 1, RateLimit: 1, }); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdFree].Properties.UsagePlanName + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdFree + ].Properties.UsagePlanName ).to.equal(`first-service-${freePlanName}-dev`); // resources for the "paid" plan expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdPaid].Type + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdPaid + ].Type ).to.equal('AWS::ApiGateway::UsagePlan'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdPaid].DependsOn + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdPaid + ].DependsOn ).to.equal('ApiGatewayDeploymentTest'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdPaid].Properties.ApiStages[0].ApiId.Ref + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdPaid + ].Properties.ApiStages[0].ApiId.Ref ).to.equal('ApiGatewayRestApi'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdPaid].Properties.ApiStages[0].Stage + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdPaid + ].Properties.ApiStages[0].Stage ).to.equal('dev'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdPaid].Properties.Description + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdPaid + ].Properties.Description ).to.equal(`Usage plan "${paidPlanName}" for first-service dev stage`); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdPaid].Properties.Quota + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdPaid + ].Properties.Quota ).to.deep.equal({ Limit: 1000000, Offset: 200, Period: 'MONTH', }); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdPaid].Properties.Throttle + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdPaid + ].Properties.Throttle ).to.deep.equal({ BurstLimit: 1000, RateLimit: 1000, }); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[logicalIdPaid].Properties.UsagePlanName + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + logicalIdPaid + ].Properties.UsagePlanName ).to.equal(`first-service-${paidPlanName}-dev`); expect(awsCompileApigEvents.apiGatewayUsagePlanNames).to.deep.equal([ - freePlanName, paidPlanName, + freePlanName, + paidPlanName, ]); }); }); @@ -262,10 +281,9 @@ describe('#compileUsagePlan()', () => { return awsCompileApigEvents.compileUsagePlan().then(() => { expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() - ].Properties.ApiStages[0].ApiId + ].Properties.ApiStages[0].ApiId ).to.equal('xxxxx'); }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.js index 9d395da70..3ed7a8953 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.js @@ -33,29 +33,38 @@ module.exports = { const resources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources; let keyNumber = 0; - _.forEach(this.serverless.service.provider.apiKeys, (apiKeyDefinition) => { + _.forEach(this.serverless.service.provider.apiKeys, apiKeyDefinition => { // if multiple API key types are used const apiKey = _.first(_.entries(apiKeyDefinition)); const name = _.first(apiKey); const value = _.last(apiKey); - if (this.apiGatewayUsagePlanNames.length > 0 && - !_.includes(this.apiGatewayUsagePlanNames, name) && _.isObject(value)) { + if ( + this.apiGatewayUsagePlanNames.length > 0 && + !_.includes(this.apiGatewayUsagePlanNames, name) && + _.isObject(value) + ) { throw new this.serverless.classes.Error(`API key "${name}" has no usage plan defined`); } if (_.isObject(apiKeyDefinition) && _.includes(this.apiGatewayUsagePlanNames, name)) { keyNumber = 0; - _.forEach(apiKeyDefinition[name], (key) => { + _.forEach(apiKeyDefinition[name], key => { if (!apiKeys.validateApiKeyInput(key)) { throw new this.serverless.classes.Error( 'API Key must be a string or an object which contains name and/or value' ); } keyNumber += 1; - const usagePlanKeyLogicalId = this.provider.naming - .getUsagePlanKeyLogicalId(keyNumber, name); + const usagePlanKeyLogicalId = this.provider.naming.getUsagePlanKeyLogicalId( + keyNumber, + name + ); const usagePlanLogicalId = this.provider.naming.getUsagePlanLogicalId(name); - const resourceTemplate = - createUsagePlanKeyResource(this, usagePlanLogicalId, keyNumber, name); + const resourceTemplate = createUsagePlanKeyResource( + this, + usagePlanLogicalId, + keyNumber, + name + ); _.merge(resources, { [usagePlanKeyLogicalId]: resourceTemplate, }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.test.js index 0949467c0..80ada8ea5 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlanKeys.test.js @@ -28,8 +28,7 @@ describe('#compileUsagePlanKeys()', () => { }); it('should support api key notation', () => { - const defaultUsagePlanLogicalId = awsCompileApigEvents - .provider.naming.getUsagePlanLogicalId(); + const defaultUsagePlanLogicalId = awsCompileApigEvents.provider.naming.getUsagePlanLogicalId(); awsCompileApigEvents.apiGatewayUsagePlanNames = ['default']; awsCompileApigEvents.serverless.service.provider.apiKeys = [ '1234567890', @@ -39,54 +38,46 @@ describe('#compileUsagePlanKeys()', () => { return awsCompileApigEvents.compileUsagePlanKeys().then(() => { // key 1 expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1) - ].Type + ].Type ).to.equal('AWS::ApiGateway::UsagePlanKey'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1) - ].Properties.KeyId.Ref + ].Properties.KeyId.Ref ).to.equal('ApiGatewayApiKey1'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1) - ].Properties.KeyType + ].Properties.KeyType ).to.equal('API_KEY'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(1) - ].Properties.UsagePlanId.Ref + ].Properties.UsagePlanId.Ref ).to.equal(defaultUsagePlanLogicalId); // key 2 expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2) - ].Type + ].Type ).to.equal('AWS::ApiGateway::UsagePlanKey'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2) - ].Properties.KeyId.Ref + ].Properties.KeyId.Ref ).to.equal('ApiGatewayApiKey2'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2) - ].Properties.KeyType + ].Properties.KeyType ).to.equal('API_KEY'); expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources[ + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources[ awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(2) - ].Properties.UsagePlanId.Ref + ].Properties.UsagePlanId.Ref ).to.equal(defaultUsagePlanLogicalId); }); }); @@ -96,10 +87,8 @@ describe('#compileUsagePlanKeys()', () => { const freeUsagePlanName = 'free'; const paidUsagePlanName = 'paid'; const logicalIds = { - free: awsCompileApigEvents - .provider.naming.getUsagePlanLogicalId(freeUsagePlanName), - paid: awsCompileApigEvents - .provider.naming.getUsagePlanLogicalId(paidUsagePlanName), + free: awsCompileApigEvents.provider.naming.getUsagePlanLogicalId(freeUsagePlanName), + paid: awsCompileApigEvents.provider.naming.getUsagePlanLogicalId(paidUsagePlanName), }; awsCompileApigEvents.apiGatewayUsagePlanNames = [freeUsagePlanName, paidUsagePlanName]; awsCompileApigEvents.serverless.service.provider.apiKeys = [ @@ -108,37 +97,33 @@ describe('#compileUsagePlanKeys()', () => { ]; return awsCompileApigEvents.compileUsagePlanKeys().then(() => { - _.forEach(awsCompileApigEvents.serverless.service.provider.apiKeys, (plan) => { + _.forEach(awsCompileApigEvents.serverless.service.provider.apiKeys, plan => { const planName = _.first(_.keys(plan)); // free || paid const apiKeys = plan[planName]; _.forEach(apiKeys, (apiKey, index) => { expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider - .naming.getUsagePlanKeyLogicalId(index + 1, planName) - ].Type + awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(index + 1, planName) + ].Type ).to.equal('AWS::ApiGateway::UsagePlanKey'); expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider - .naming.getUsagePlanKeyLogicalId(index + 1, planName) - ].Properties.KeyId.Ref + awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(index + 1, planName) + ].Properties.KeyId.Ref ).to.equal(`ApiGatewayApiKey${_.capitalize(planName)}${index + 1}`); expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider - .naming.getUsagePlanKeyLogicalId(index + 1, planName) - ].Properties.KeyType + awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(index + 1, planName) + ].Properties.KeyType ).to.equal('API_KEY'); expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ - awsCompileApigEvents.provider - .naming.getUsagePlanKeyLogicalId(index + 1, planName) - ].Properties.UsagePlanId.Ref + awsCompileApigEvents.provider.naming.getUsagePlanKeyLogicalId(index + 1, planName) + ].Properties.UsagePlanId.Ref ).to.equal(logicalIds[planName]); }); }); @@ -147,20 +132,18 @@ describe('#compileUsagePlanKeys()', () => { it('should throw if api key name does not match a usage plan', () => { awsCompileApigEvents.apiGatewayUsagePlanNames = ['default']; - awsCompileApigEvents.serverless.service.provider.apiKeys = [ - { free: ['1234567890'] }, - ]; - expect(() => awsCompileApigEvents.compileUsagePlanKeys()) - .to.throw(/has no usage plan defined/); + awsCompileApigEvents.serverless.service.provider.apiKeys = [{ free: ['1234567890'] }]; + expect(() => awsCompileApigEvents.compileUsagePlanKeys()).to.throw( + /has no usage plan defined/ + ); }); it('should throw if api key definitions are not strings or objects', () => { awsCompileApigEvents.apiGatewayUsagePlanNames = ['free']; - awsCompileApigEvents.serverless.service.provider.apiKeys = [ - { free: [{ foo: 'bar' }] }, - ]; - expect(() => awsCompileApigEvents.compileUsagePlanKeys()) - .to.throw(/must be a string or an object/); + awsCompileApigEvents.serverless.service.provider.apiKeys = [{ free: [{ foo: 'bar' }] }]; + expect(() => awsCompileApigEvents.compileUsagePlanKeys()).to.throw( + /must be a string or an object/ + ); }); }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js index 5bfc634e6..d242924c2 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -40,7 +40,7 @@ module.exports = { const corsPreflight = {}; _.forEach(this.serverless.service.functions, (functionObject, functionName) => { - _.forEach(functionObject.events, (event) => { + _.forEach(functionObject.events, event => { if (_.has(event, 'http')) { const http = this.getHttp(event, functionName); @@ -76,8 +76,10 @@ module.exports = { http.integration = this.getIntegration(http, functionName); - if ((http.integration === 'HTTP' || http.integration === 'HTTP_PROXY') && - (!http.request || !http.request.uri)) { + if ( + (http.integration === 'HTTP' || http.integration === 'HTTP_PROXY') && + (!http.request || !http.request.uri) + ) { const errorMessage = [ `You need to set the request uri when using the ${http.integration} integration.`, ]; @@ -189,9 +191,7 @@ module.exports = { if (typeof http.method === 'string') { const method = http.method.toLowerCase(); - const allowedMethods = [ - 'get', 'post', 'put', 'patch', 'options', 'head', 'delete', 'any', - ]; + const allowedMethods = ['get', 'post', 'put', 'patch', 'options', 'head', 'delete', 'any']; if (allowedMethods.indexOf(method) === -1) { const errorMessage = [ `Invalid APIG method "${http.method}" in function "${functionName}".`, @@ -280,11 +280,13 @@ module.exports = { } const integration = this.getIntegration(http); - if (integration === 'AWS_PROXY' - && typeof arn === 'string' - && awsArnRegExs.cognitoIdpArnExpr.test(arn) - && claims - && claims.length > 0) { + if ( + integration === 'AWS_PROXY' && + typeof arn === 'string' && + awsArnRegExs.cognitoIdpArnExpr.test(arn) && + claims && + claims.length > 0 + ) { const errorMessage = [ 'Cognito claims can only be filtered when using the lambda integration type', ]; @@ -372,7 +374,13 @@ module.exports = { // normalize the integration for further processing const normalizedIntegration = http.integration.toUpperCase().replace('-', '_'); const allowedIntegrations = [ - 'LAMBDA_PROXY', 'LAMBDA', 'AWS', 'AWS_PROXY', 'HTTP', 'HTTP_PROXY', 'MOCK', + 'LAMBDA_PROXY', + 'LAMBDA', + 'AWS', + 'AWS_PROXY', + 'HTTP', + 'HTTP_PROXY', + 'MOCK', ]; // check if the user has entered a non-valid integration if (allowedIntegrations.indexOf(normalizedIntegration) === NOT_FOUND) { @@ -432,7 +440,7 @@ module.exports = { const parameters = {}; // only these locations are currently supported const locations = ['querystrings', 'paths', 'headers']; - _.each(locations, (location) => { + _.each(locations, location => { // strip the plural s const singular = location.substring(0, location.length - 1); _.each(httpRequest.parameters[location], (value, key) => { @@ -443,9 +451,7 @@ module.exports = { }, getRequestPassThrough(http) { - const requestPassThroughBehaviors = [ - 'NEVER', 'WHEN_NO_MATCH', 'WHEN_NO_TEMPLATES', - ]; + const requestPassThroughBehaviors = ['NEVER', 'WHEN_NO_MATCH', 'WHEN_NO_TEMPLATES']; if (http.request.passThrough) { if (requestPassThroughBehaviors.indexOf(http.request.passThrough) === -1) { diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js index e66f709b7..a01f5b033 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js @@ -31,7 +31,9 @@ describe('#validate()', () => { }, }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(0); + expect(validated.events) + .to.be.an('Array') + .with.length(0); }); it('should reject an invalid http event', () => { @@ -61,7 +63,7 @@ describe('#validate()', () => { expect(() => awsCompileApigEvents.validate()).to.throw(Error); }); - it('should throw a helpful error if http event type object doesn\'t have a path property', () => { + it("should throw a helpful error if http event type object doesn't have a path property", () => { /** * This can happen with surprising subtle syntax error such as when path is not * indented under http in yml. @@ -76,8 +78,9 @@ describe('#validate()', () => { }, }; - expect(() => awsCompileApigEvents.validate()).to - .throw(/invalid "path" property in function "first"/); + expect(() => awsCompileApigEvents.validate()).to.throw( + /invalid "path" property in function "first"/ + ); }); it('should validate the http events "path" property', () => { @@ -133,7 +136,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(2); + expect(validated.events) + .to.be.an('Array') + .with.length(2); }); it('should validate the http events string syntax method is case insensitive', () => { @@ -151,7 +156,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(2); + expect(validated.events) + .to.be.an('Array') + .with.length(2); }); it('should throw an error if the method is invalid', () => { @@ -194,7 +201,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); }); it('should discard a starting slash from paths', () => { @@ -214,7 +223,9 @@ describe('#validate()', () => { }, }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(2); + expect(validated.events) + .to.be.an('Array') + .with.length(2); expect(validated.events[0].http).to.have.property('path', 'foo/bar'); expect(validated.events[1].http).to.have.property('path', 'foo/bar'); }); @@ -266,10 +277,7 @@ describe('#validate()', () => { integration: 'lambda-proxy', authorizer: { arn: 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ', - claims: [ - 'email', - 'nickname', - ], + claims: ['email', 'nickname'], }, }, }, @@ -374,7 +382,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(2); + expect(validated.events) + .to.be.an('Array') + .with.length(2); expect(validated.events[0].http.authorizer.type).to.equal('AWS_IAM'); expect(validated.events[1].http.authorizer.type).to.equal('AWS_IAM'); }); @@ -407,13 +417,12 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(2); + expect(validated.events) + .to.be.an('Array') + .with.length(2); expect(validated.events[0].http.authorizer.name).to.equal('foo'); expect(validated.events[0].http.authorizer.arn).to.deep.equal({ - 'Fn::GetAtt': [ - 'FooLambdaFunction', - 'Arn', - ], + 'Fn::GetAtt': ['FooLambdaFunction', 'Arn'], }); expect(validated.events[1].http.authorizer.name).to.equal('authorizer'); expect(validated.events[1].http.authorizer.arn).to.equal('sss:dev-authorizer'); @@ -543,8 +552,7 @@ describe('#validate()', () => { }, }; - expect(() => awsCompileApigEvents.validate()) - .to.throw(Error, 'can only use'); + expect(() => awsCompileApigEvents.validate()).to.throw(Error, 'can only use'); }); it('should process cors defaults', () => { @@ -563,10 +571,18 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.cors).to.deep.equal({ - headers: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', - 'X-Amz-Security-Token', 'X-Amz-User-Agent'], + headers: [ + 'Content-Type', + 'X-Amz-Date', + 'Authorization', + 'X-Api-Key', + 'X-Amz-Security-Token', + 'X-Amz-User-Agent', + ], methods: ['OPTIONS', 'POST'], origin: '*', origins: ['*'], @@ -717,7 +733,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.cors).to.deep.equal({ headers: ['X-Foo-Bar'], methods: ['POST', 'OPTIONS'], @@ -737,43 +755,38 @@ describe('#validate()', () => { method: 'GET', path: 'users', cors: { - origins: [ - 'http://example.com', - ], + origins: ['http://example.com'], allowCredentials: true, maxAge: 10000, cacheControl: 'max-age=600, s-maxage=600, proxy-revalidate', }, }, - }, { + }, + { http: { method: 'POST', path: 'users', cors: { - origins: [ - 'http://example2.com', - ], + origins: ['http://example2.com'], maxAge: 86400, }, }, - }, { + }, + { http: { method: 'PUT', path: 'users/{id}', cors: { - headers: [ - 'TestHeader', - ], + headers: ['TestHeader'], }, }, - }, { + }, + { http: { method: 'DELETE', path: 'users/{id}', cors: { - headers: [ - 'TestHeader2', - ], + headers: ['TestHeader2'], }, }, }, @@ -782,20 +795,25 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.corsPreflight['users/{id}'].methods) - .to.deep.equal(['OPTIONS', 'DELETE', 'PUT']); - expect(validated.corsPreflight.users.origins) - .to.deep.equal(['http://example2.com', 'http://example.com']); - expect(validated.corsPreflight['users/{id}'].headers) - .to.deep.equal(['TestHeader2', 'TestHeader']); - expect(validated.corsPreflight.users.maxAge) - .to.equal(86400); - expect(validated.corsPreflight.users.cacheControl) - .to.equal('max-age=600, s-maxage=600, proxy-revalidate'); - expect(validated.corsPreflight.users.allowCredentials) - .to.equal(true); - expect(validated.corsPreflight['users/{id}'].allowCredentials) - .to.equal(false); + expect(validated.corsPreflight['users/{id}'].methods).to.deep.equal([ + 'OPTIONS', + 'DELETE', + 'PUT', + ]); + expect(validated.corsPreflight.users.origins).to.deep.equal([ + 'http://example2.com', + 'http://example.com', + ]); + expect(validated.corsPreflight['users/{id}'].headers).to.deep.equal([ + 'TestHeader2', + 'TestHeader', + ]); + expect(validated.corsPreflight.users.maxAge).to.equal(86400); + expect(validated.corsPreflight.users.cacheControl).to.equal( + 'max-age=600, s-maxage=600, proxy-revalidate' + ); + expect(validated.corsPreflight.users.allowCredentials).to.equal(true); + expect(validated.corsPreflight['users/{id}'].allowCredentials).to.equal(false); }); it('should throw an error if the maxAge is not a positive integer', () => { @@ -832,7 +850,7 @@ describe('#validate()', () => { statusCodes: { 404: { pattern: '.*"statusCode":404,.*', - template: '$input.path(\'$.errorMessage\')', + template: "$input.path('$.errorMessage')", headers: { 'Content-Type': 'text/html', }, @@ -846,14 +864,16 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.response.statusCodes).to.deep.equal({ 200: { pattern: '', }, 404: { pattern: '.*"statusCode":404,.*', - template: '$input.path(\'$.errorMessage\')', + template: "$input.path('$.errorMessage')", headers: { 'Content-Type': 'text/html', }, @@ -874,7 +894,7 @@ describe('#validate()', () => { statusCodes: { 418: { pattern: '', - template: '$input.path(\'$.foo\')', + template: "$input.path('$.foo')", }, }, }, @@ -885,11 +905,13 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.response.statusCodes).to.deep.equal({ 418: { pattern: '', - template: '$input.path(\'$.foo\')', + template: "$input.path('$.foo')", }, }); }); @@ -912,7 +934,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.cors.methods).to.deep.equal(['POST', 'OPTIONS']); }); @@ -950,7 +974,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.authorizer.name).to.equal('authorizer'); expect(validated.events[0].http.authorizer.arn).to.deep.equal({ 'Fn::GetAtt': ['AuthorizerLambdaFunction', 'Arn'], @@ -973,7 +999,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.authorizer.name).to.equal('authorizer'); expect(validated.events[0].http.authorizer.arn).to.equal('xxx:dev-authorizer'); }); @@ -997,13 +1025,12 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.authorizer.name).to.equal('authorizer'); expect(validated.events[0].http.authorizer.arn).to.deep.equal({ - 'Fn::GetAtt': [ - 'AuthorizerLambdaFunction', - 'Arn', - ], + 'Fn::GetAtt': ['AuthorizerLambdaFunction', 'Arn'], }); }); @@ -1025,7 +1052,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.authorizer.name).to.equal('authorizer'); expect(validated.events[0].http.authorizer.arn).to.equal('xxx:dev-authorizer'); }); @@ -1049,7 +1078,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.authorizer.name).to.equal('custom-name'); expect(validated.events[0].http.authorizer.arn).to.equal('xxx:dev-authorizer'); }); @@ -1124,7 +1155,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.request.parameters).to.deep.equal({ 'method.request.querystring.foo': true, 'method.request.querystring.bar': false, @@ -1167,7 +1200,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.request.parameters).to.deep.equal({ 'method.request.querystring.foo': true, 'method.request.querystring.bar': false, @@ -1264,7 +1299,9 @@ describe('#validate()', () => { }, }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.integration).to.equal('AWS_PROXY'); }); @@ -1312,7 +1349,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(5); + expect(validated.events) + .to.be.an('Array') + .with.length(5); expect(validated.events[0].http.integration).to.equal('AWS'); expect(validated.events[1].http.integration).to.equal('AWS'); expect(validated.events[2].http.integration).to.equal('AWS_PROXY'); @@ -1339,7 +1378,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.integration).to.equal('HTTP'); }); @@ -1376,7 +1417,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.request.parameters).to.deep.equal({ 'method.request.querystring.foo': true, 'method.request.querystring.bar': false, @@ -1424,7 +1467,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.integration).to.equal('HTTP_PROXY'); }); @@ -1461,7 +1506,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.request.parameters).to.deep.equal({ 'method.request.querystring.foo': true, 'method.request.querystring.bar': false, @@ -1598,7 +1645,9 @@ describe('#validate()', () => { sinon.stub(serverless.cli, 'log'); const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.response).to.equal(undefined); expect(validated.events[0].http.request.uri).to.equal('http://www.example.com'); expect(validated.events[0].http.request.parameters).to.deep.equal({ @@ -1623,7 +1672,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.integration).to.equal('MOCK'); }); @@ -1643,7 +1694,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.integration).to.equal('AWS'); expect(validated.events[0].http.async); }); @@ -1753,7 +1806,9 @@ describe('#validate()', () => { sinon.stub(serverless.cli, 'log'); const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.response).to.equal(undefined); expect(validated.events[0].http.request.parameters).to.deep.equal({ 'method.request.path.foo': true, @@ -1798,7 +1853,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.request.passThrough).to.equal('WHEN_NO_MATCH'); }); @@ -1818,7 +1875,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.request.passThrough).to.equal('NEVER'); }); @@ -1842,7 +1901,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.request.passThrough).to.equal(undefined); }); @@ -1863,7 +1924,9 @@ describe('#validate()', () => { }; const validated = awsCompileApigEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events) + .to.be.an('Array') + .with.length(1); expect(validated.events[0].http.response.statusCodes).to.deep.equal({ 200: { pattern: '', @@ -1885,7 +1948,7 @@ describe('#validate()', () => { }, 500: { pattern: - '[\\s\\S]*(Process\\s?exited\\s?before\\s?completing\\s?request|\\[500\\])[\\s\\S]*', + '[\\s\\S]*(Process\\s?exited\\s?before\\s?completing\\s?request|\\[500\\])[\\s\\S]*', }, 502: { pattern: '[\\s\\S]*\\[502\\][\\s\\S]*', diff --git a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js index 7762aba4b..d89c830b5 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js @@ -13,7 +13,7 @@ class AwsCompileCloudWatchEventEvents { } compileCloudWatchEventEvents() { - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); let cloudWatchEventNumberInFunction = 0; @@ -35,8 +35,7 @@ class AwsCompileCloudWatchEventEvents { `Missing "event" property for cloudwatch event in function ${functionName}`, ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } EventPattern = JSON.stringify(event.cloudwatchEvent.event); @@ -75,17 +74,18 @@ class AwsCompileCloudWatchEventEvents { `CloudWatch event of function "${functionName}" is not an object`, ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); - const cloudWatchLogicalId = this.provider.naming - .getCloudWatchEventLogicalId(functionName, cloudWatchEventNumberInFunction); - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaCloudWatchEventPermissionLogicalId(functionName, - cloudWatchEventNumberInFunction); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); + const cloudWatchLogicalId = this.provider.naming.getCloudWatchEventLogicalId( + functionName, + cloudWatchEventNumberInFunction + ); + const lambdaPermissionLogicalId = this.provider.naming.getLambdaCloudWatchEventPermissionLogicalId( + functionName, + cloudWatchEventNumberInFunction + ); const cloudWatchId = this.provider.naming.getCloudWatchEventId(functionName); const cloudWatchEventRuleTemplate = ` @@ -111,8 +111,7 @@ class AwsCompileCloudWatchEventEvents { { "Type": "AWS::Lambda::Permission", "Properties": { - "FunctionName": { "Fn::GetAtt": ["${ - lambdaLogicalId}", "Arn"] }, + "FunctionName": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] }, "Action": "lambda:InvokeFunction", "Principal": "events.amazonaws.com", "SourceArn": { "Fn::GetAtt": ["${cloudWatchLogicalId}", "Arn"] } @@ -128,8 +127,11 @@ class AwsCompileCloudWatchEventEvents { [lambdaPermissionLogicalId]: JSON.parse(permissionTemplate), }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newCloudWatchEventRuleObject, newPermissionObject); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newCloudWatchEventRuleObject, + newPermissionObject + ); } }); } @@ -140,7 +142,7 @@ class AwsCompileCloudWatchEventEvents { if (!inputTransformer.inputTemplate) { throw new this.serverless.classes.Error( 'The inputTemplate key is required when specifying an ' + - 'inputTransformer for a cloudwatchEvent event' + 'inputTransformer for a cloudwatchEvent event' ); } const cfmOutput = { diff --git a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js index e7decdb2e..685da8540 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js @@ -72,9 +72,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, }, @@ -82,9 +82,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: true, }, @@ -95,19 +95,21 @@ describe('awsCompileCloudWatchEventEvents', () => { awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1.Type + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent1.Type ).to.equal('AWS::Events::Rule'); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent2.Type + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent2.Type ).to.equal('AWS::Events::Rule'); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionEventsRuleCloudWatchEvent1.Type + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionEventsRuleCloudWatchEvent1.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionEventsRuleCloudWatchEvent2.Type + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionEventsRuleCloudWatchEvent2.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -118,9 +120,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, }, @@ -128,9 +130,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: true, }, @@ -138,9 +140,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, }, }, @@ -150,17 +152,17 @@ describe('awsCompileCloudWatchEventEvents', () => { awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 - .Properties.State + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent1.Properties.State ).to.equal('DISABLED'); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent2 - .Properties.State + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent2.Properties.State ).to.equal('ENABLED'); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent3 - .Properties.State + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent3.Properties.State ).to.equal('ENABLED'); }); @@ -171,9 +173,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, inputPath: '$.stageVariables', @@ -185,9 +187,9 @@ describe('awsCompileCloudWatchEventEvents', () => { awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 - .Properties.Targets[0].InputPath + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent1.Properties.Targets[0].InputPath ).to.equal('$.stageVariables'); }); @@ -198,9 +200,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, input: '{"key":"value"}', @@ -212,9 +214,9 @@ describe('awsCompileCloudWatchEventEvents', () => { awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 - .Properties.Targets[0].Input + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent1.Properties.Targets[0].Input ).to.equal('{"key":"value"}'); }); @@ -225,9 +227,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, inputTransformer: { @@ -244,16 +246,15 @@ describe('awsCompileCloudWatchEventEvents', () => { awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 - .Properties.Targets[0].InputTransformer + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent1.Properties.Targets[0].InputTransformer ).to.eql({ InputTemplate: '{"time": , "key1": "value1"}', InputPathsMap: { eventTime: '$.time' }, }); }); - it('should respect description variable', () => { awsCompileCloudWatchEventEvents.serverless.service.functions = { first: { @@ -261,9 +262,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, input: '{"key":"value"}', @@ -276,9 +277,9 @@ describe('awsCompileCloudWatchEventEvents', () => { awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 - .Properties.Description + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent1.Properties.Description ).to.equal('test description'); }); @@ -289,9 +290,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, input: '{"key":"value"}', @@ -304,9 +305,9 @@ describe('awsCompileCloudWatchEventEvents', () => { awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 - .Properties.Name + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent1.Properties.Name ).to.equal('test-event-name'); }); @@ -317,9 +318,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, input: { @@ -333,9 +334,9 @@ describe('awsCompileCloudWatchEventEvents', () => { awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 - .Properties.Targets[0].Input + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent1.Properties.Targets[0].Input ).to.equal('{"key":"value"}'); }); @@ -346,9 +347,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, input: { @@ -371,9 +372,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, input: { @@ -401,9 +402,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, inputTransformer: { @@ -427,9 +428,9 @@ describe('awsCompileCloudWatchEventEvents', () => { { cloudwatchEvent: { event: { - source: ['aws.ec2'], + 'source': ['aws.ec2'], 'detail-type': ['EC2 Instance State-change Notification \n with newline'], - detail: { state: ['pending'] }, + 'detail': { state: ['pending'] }, }, enabled: false, input: { @@ -442,13 +443,13 @@ describe('awsCompileCloudWatchEventEvents', () => { }; awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstEventsRuleCloudWatchEvent1.Properties.EventPattern['detail-type'][0] + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent1.Properties.EventPattern['detail-type'][0] ).to.equal('EC2 Instance State-change Notification with newline'); - expect(awsCompileCloudWatchEventEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstEventsRuleCloudWatchEvent1.Properties.Targets[0].Input + expect( + awsCompileCloudWatchEventEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleCloudWatchEvent1.Properties.Targets[0].Input ).to.equal('{"key":"value"}'); }); diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js index 1bf50ad62..58024492c 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js @@ -15,7 +15,7 @@ class AwsCompileCloudWatchLogEvents { compileCloudWatchLogEvents() { const logGroupNames = []; - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); let cloudWatchLogNumberInFunction = 0; @@ -34,8 +34,7 @@ class AwsCompileCloudWatchLogEvents { 'Missing "logGroup" property for cloudwatchLog event ', `in function ${functionName} Please check the docs for more info.`, ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } if (event.cloudwatchLog.filter && typeof event.cloudwatchLog.filter !== 'string') { @@ -43,13 +42,13 @@ class AwsCompileCloudWatchLogEvents { `"filter" property for cloudwatchLog event in function ${functionName} `, 'should be string. Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } LogGroupName = event.cloudwatchLog.logGroup.replace(/\r?\n/g, ''); - FilterPattern = event.cloudwatchLog.filter ? - event.cloudwatchLog.filter.replace(/\r?\n/g, '') : ''; + FilterPattern = event.cloudwatchLog.filter + ? event.cloudwatchLog.filter.replace(/\r?\n/g, '') + : ''; } else if (typeof event.cloudwatchLog === 'string') { LogGroupName = event.cloudwatchLog.replace(/\r?\n/g, ''); FilterPattern = ''; @@ -58,8 +57,7 @@ class AwsCompileCloudWatchLogEvents { `cloudwatchLog event of function "${functionName}" is not an object or a string`, ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } if (_.indexOf(logGroupNames, LogGroupName) !== -1) { @@ -67,18 +65,19 @@ class AwsCompileCloudWatchLogEvents { `"${LogGroupName}" logGroup for cloudwatchLog event is duplicated.`, ' This property can only be set once per CloudFormation stack.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } logGroupNames.push(LogGroupName); logGroupNamesThisFunction.push(LogGroupName); - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); - const cloudWatchLogLogicalId = this.provider.naming - .getCloudWatchLogLogicalId(functionName, cloudWatchLogNumberInFunction); - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaCloudWatchLogPermissionLogicalId(functionName); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); + const cloudWatchLogLogicalId = this.provider.naming.getCloudWatchLogLogicalId( + functionName, + cloudWatchLogNumberInFunction + ); + const lambdaPermissionLogicalId = this.provider.naming.getLambdaCloudWatchLogPermissionLogicalId( + functionName + ); // unescape quotes once when the first quote is detected escaped const idxFirstSlash = FilterPattern.indexOf('\\'); @@ -105,8 +104,7 @@ class AwsCompileCloudWatchLogEvents { { "Type": "AWS::Lambda::Permission", "Properties": { - "FunctionName": { "Fn::GetAtt": ["${ - lambdaLogicalId}", "Arn"] }, + "FunctionName": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] }, "Action": "lambda:InvokeFunction", "Principal": { "Fn::Join": [ "", [ @@ -140,8 +138,11 @@ class AwsCompileCloudWatchLogEvents { [lambdaPermissionLogicalId]: JSON.parse(permissionTemplate), }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newCloudWatchLogRuleObject, newPermissionObject); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newCloudWatchLogRuleObject, + newPermissionObject + ); } }); } @@ -158,7 +159,7 @@ class AwsCompileCloudWatchLogEvents { } return last; }, first); - return longestCommon + ((longestCommon === first) ? '' : '*'); + return longestCommon + (longestCommon === first ? '' : '*'); } } diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js index ddab8a51e..9161f09dc 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js @@ -82,33 +82,33 @@ describe('AwsCompileCloudWatchLogEvents', () => { }; awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents(); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLogsSubscriptionFilterCloudWatchLog1.Type + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Type ).to.equal('AWS::Logs::SubscriptionFilter'); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLogsSubscriptionFilterCloudWatchLog2.Type + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog2.Type ).to.equal('AWS::Logs::SubscriptionFilter'); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.LogGroupName + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.LogGroupName ).to.equal('/aws/lambda/hello1'); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog2 - .Properties.LogGroupName + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog2.Properties.LogGroupName ).to.equal('/aws/lambda/hello2'); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.FilterPattern + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.FilterPattern ).to.equal(''); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog2 - .Properties.FilterPattern + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog2.Properties.FilterPattern ).to.equal(''); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionLogsSubscriptionFilterCloudWatchLog.Type + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionLogsSubscriptionFilterCloudWatchLog.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -128,9 +128,9 @@ describe('AwsCompileCloudWatchLogEvents', () => { awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents(); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.FilterPattern + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.FilterPattern ).to.equal('{$.userIdentity.type = Root}'); }); @@ -150,9 +150,9 @@ describe('AwsCompileCloudWatchLogEvents', () => { awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents(); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.FilterPattern + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.FilterPattern ).to.equal('"Total amount" -"level=Debug"'); }); @@ -163,7 +163,7 @@ describe('AwsCompileCloudWatchLogEvents', () => { { cloudwatchLog: { logGroup: '/aws/lambda/hello1', - filter: "\\\"Total amount\\\" -\\\"level=Debug\\\"", // eslint-disable-line quotes + filter: '\\"Total amount\\" -\\"level=Debug\\"', // eslint-disable-line quotes }, }, ], @@ -172,14 +172,13 @@ describe('AwsCompileCloudWatchLogEvents', () => { awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents(); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.FilterPattern + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.FilterPattern ).to.equal('"Total amount" -"level=Debug"'); }); - it('should set an empty string for FilterPattern statement when "filter" variable is not given' - , () => { + it('should set an empty string for FilterPattern statement when "filter" variable is not given', () => { awsCompileCloudWatchLogEvents.serverless.service.functions = { first: { events: [ @@ -194,9 +193,9 @@ describe('AwsCompileCloudWatchLogEvents', () => { awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents(); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.FilterPattern + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.FilterPattern ).to.equal(''); }); @@ -232,8 +231,7 @@ describe('AwsCompileCloudWatchLogEvents', () => { expect(() => awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents()).to.throw(Error); }); - it('should create corresponding resources when cloudwatchLog events are given as a string', - () => { + it('should create corresponding resources when cloudwatchLog events are given as a string', () => { awsCompileCloudWatchLogEvents.serverless.service.functions = { first: { events: [ @@ -248,61 +246,71 @@ describe('AwsCompileCloudWatchLogEvents', () => { }; awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents(); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLogsSubscriptionFilterCloudWatchLog1.Type + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Type ).to.equal('AWS::Logs::SubscriptionFilter'); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLogsSubscriptionFilterCloudWatchLog2.Type + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog2.Type ).to.equal('AWS::Logs::SubscriptionFilter'); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.LogGroupName + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.LogGroupName ).to.equal('/aws/lambda/hello1'); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog2 - .Properties.LogGroupName + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog2.Properties.LogGroupName ).to.equal('/aws/lambda/hello2'); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.FilterPattern + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.FilterPattern ).to.equal(''); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog2 - .Properties.FilterPattern + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog2.Properties.FilterPattern ).to.equal(''); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionLogsSubscriptionFilterCloudWatchLog.Type + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionLogsSubscriptionFilterCloudWatchLog.Type ).to.equal('AWS::Lambda::Permission'); }); it('should create a longest-common suffix of logGroup to minimize scope', () => { - expect(awsCompileCloudWatchLogEvents - .longestCommonSuffix(['/aws/lambda/hello1'])) - .to.equal('/aws/lambda/hello1'); - expect(awsCompileCloudWatchLogEvents - .longestCommonSuffix(['/aws/lambda/hello1', '/aws/lambda/hello2'])) - .to.equal('/aws/lambda/hello*'); - expect(awsCompileCloudWatchLogEvents - .longestCommonSuffix(['/aws/lambda/hello1', '/aws/lambda/hot'])) - .to.equal('/aws/lambda/h*'); - expect(awsCompileCloudWatchLogEvents - .longestCommonSuffix(['/aws/lambda/hello1', '/aws/lambda/tweet'])) - .to.equal('/aws/lambda/*'); - expect(awsCompileCloudWatchLogEvents - .longestCommonSuffix(['/aws/lambda/hello1', '/aws/lex/log1', '/aws/lightsail/log1'])) - .to.equal('/aws/l*'); - expect(awsCompileCloudWatchLogEvents - .longestCommonSuffix(['/aws/lambda/hello1', '/aws/batch/log1'])) - .to.equal('/aws/*'); - expect(awsCompileCloudWatchLogEvents - .longestCommonSuffix(['/aws/*', '/aws/lambda/hello'])) - .to.equal('/aws/*'); - expect(awsCompileCloudWatchLogEvents - .longestCommonSuffix(['/aws/lambda/*', '/aws/lambda/hello'])) - .to.equal('/aws/lambda/*'); + expect(awsCompileCloudWatchLogEvents.longestCommonSuffix(['/aws/lambda/hello1'])).to.equal( + '/aws/lambda/hello1' + ); + expect( + awsCompileCloudWatchLogEvents.longestCommonSuffix([ + '/aws/lambda/hello1', + '/aws/lambda/hello2', + ]) + ).to.equal('/aws/lambda/hello*'); + expect( + awsCompileCloudWatchLogEvents.longestCommonSuffix(['/aws/lambda/hello1', '/aws/lambda/hot']) + ).to.equal('/aws/lambda/h*'); + expect( + awsCompileCloudWatchLogEvents.longestCommonSuffix([ + '/aws/lambda/hello1', + '/aws/lambda/tweet', + ]) + ).to.equal('/aws/lambda/*'); + expect( + awsCompileCloudWatchLogEvents.longestCommonSuffix([ + '/aws/lambda/hello1', + '/aws/lex/log1', + '/aws/lightsail/log1', + ]) + ).to.equal('/aws/l*'); + expect( + awsCompileCloudWatchLogEvents.longestCommonSuffix(['/aws/lambda/hello1', '/aws/batch/log1']) + ).to.equal('/aws/*'); + expect( + awsCompileCloudWatchLogEvents.longestCommonSuffix(['/aws/*', '/aws/lambda/hello']) + ).to.equal('/aws/*'); + expect( + awsCompileCloudWatchLogEvents.longestCommonSuffix(['/aws/lambda/*', '/aws/lambda/hello']) + ).to.equal('/aws/lambda/*'); }); it('should throw an error if "logGroup" is duplicated in one CloudFormation stack', () => { @@ -365,13 +373,13 @@ describe('AwsCompileCloudWatchLogEvents', () => { awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents(); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.LogGroupName + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.LogGroupName ).to.equal('/aws/lambda/hello1'); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.FilterPattern + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.FilterPattern ).to.equal('{$.userIdentity.type = Root}'); awsCompileCloudWatchLogEvents.serverless.service.functions = { @@ -386,19 +394,16 @@ describe('AwsCompileCloudWatchLogEvents', () => { awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents(); - expect(awsCompileCloudWatchLogEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1 - .Properties.LogGroupName + expect( + awsCompileCloudWatchLogEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLogsSubscriptionFilterCloudWatchLog1.Properties.LogGroupName ).to.equal('/aws/lambda/hello3'); }); - it('should not create corresponding resources when cloudwatchLog event is not given', - () => { + it('should not create corresponding resources when cloudwatchLog event is not given', () => { awsCompileCloudWatchLogEvents.serverless.service.functions = { first: { - events: [ - {}, - ], + events: [{}], }, }; diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js index 80812d46c..345f68e9d 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js @@ -29,32 +29,34 @@ class AwsCompileCognitoUserPoolEvents { const cognitoUserPoolTriggerFunctions = []; // Iterate through all functions declared in `serverless.yml` - _.forEach(this.serverless.service.getAllFunctions(), (functionName) => { + _.forEach(this.serverless.service.getAllFunctions(), functionName => { const functionObj = this.serverless.service.getFunction(functionName); if (functionObj.events) { - _.forEach(functionObj.events, (event) => { + _.forEach(functionObj.events, event => { if (event.cognitoUserPool) { // Check event definition for `cognitoUserPool` object if (typeof event.cognitoUserPool === 'object') { // Check `cognitoUserPool` object has required properties if (!event.cognitoUserPool.pool || !event.cognitoUserPool.trigger) { - throw new this.serverless.classes - .Error([ + throw new this.serverless.classes.Error( + [ `Cognito User Pool event of function "${functionName}" is not an object.`, 'The correct syntax is an object with the "pool" and "trigger" properties.', 'Please check the docs for more info.', - ].join(' ')); + ].join(' ') + ); } // Check `cognitoUserPool` trigger is valid if (!_.includes(validTriggerSources, event.cognitoUserPool.trigger)) { - throw new this.serverless.classes - .Error([ + throw new this.serverless.classes.Error( + [ 'Cognito User Pool trigger source is invalid, must be one of:', `${validTriggerSources.join(', ')}.`, 'Please check the docs for more info.', - ].join(' ')); + ].join(' ') + ); } // Save trigger functions so we can use them to generate @@ -69,12 +71,13 @@ class AwsCompileCognitoUserPoolEvents { // CloudFormation resources later userPools.push(event.cognitoUserPool.pool); } else { - throw new this.serverless.classes - .Error([ + throw new this.serverless.classes.Error( + [ `Cognito User Pool event of function "${functionName}" is not an object.`, 'The correct syntax is an object with the "pool" and "trigger" properties.', 'Please check the docs for more info.', - ].join(' ')); + ].join(' ') + ); } } }); @@ -85,25 +88,27 @@ class AwsCompileCognitoUserPoolEvents { } generateTemplateForPool(poolName, currentPoolTriggerFunctions) { - const lambdaConfig = _.reduce(currentPoolTriggerFunctions, (result, value) => { - const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(value.functionName); + const lambdaConfig = _.reduce( + currentPoolTriggerFunctions, + (result, value) => { + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(value.functionName); - // Return a new object to avoid lint errors - return Object.assign({}, result, { - [value.triggerSource]: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], - }, - }); - }, {}); + // Return a new object to avoid lint errors + return Object.assign({}, result, { + [value.triggerSource]: { + 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], + }, + }); + }, + {} + ); const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); // Attach `DependsOn` for any relevant Lambdas - const DependsOn = _.map(currentPoolTriggerFunctions, (value) => this - .provider.naming.getLambdaLogicalId(value.functionName)); + const DependsOn = _.map(currentPoolTriggerFunctions, value => + this.provider.naming.getLambdaLogicalId(value.functionName) + ); return { [userPoolLogicalId]: { @@ -123,51 +128,53 @@ class AwsCompileCognitoUserPoolEvents { const userPools = result.userPools; // Generate CloudFormation templates for Cognito User Pool changes - _.forEach(userPools, (poolName) => { + _.forEach(userPools, poolName => { const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { poolName }); const userPoolCFResource = this.generateTemplateForPool( poolName, currentPoolTriggerFunctions ); - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - userPoolCFResource); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + userPoolCFResource + ); }); // Generate CloudFormation templates for IAM permissions to allow Cognito to trigger Lambda - _.forEach(cognitoUserPoolTriggerFunctions, (cognitoUserPoolTriggerFunction) => { - const userPoolLogicalId = this.provider.naming - .getCognitoUserPoolLogicalId(cognitoUserPoolTriggerFunction.poolName); - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(cognitoUserPoolTriggerFunction.functionName); + _.forEach(cognitoUserPoolTriggerFunctions, cognitoUserPoolTriggerFunction => { + const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId( + cognitoUserPoolTriggerFunction.poolName + ); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId( + cognitoUserPoolTriggerFunction.functionName + ); const permissionTemplate = { Type: 'AWS::Lambda::Permission', Properties: { FunctionName: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], }, Action: 'lambda:InvokeFunction', Principal: 'cognito-idp.amazonaws.com', SourceArn: { - 'Fn::GetAtt': [ - userPoolLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [userPoolLogicalId, 'Arn'], }, }, }; - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaCognitoUserPoolPermissionLogicalId(cognitoUserPoolTriggerFunction.functionName, - cognitoUserPoolTriggerFunction.poolName, cognitoUserPoolTriggerFunction.triggerSource); + const lambdaPermissionLogicalId = this.provider.naming.getLambdaCognitoUserPoolPermissionLogicalId( + cognitoUserPoolTriggerFunction.functionName, + cognitoUserPoolTriggerFunction.poolName, + cognitoUserPoolTriggerFunction.triggerSource + ); const permissionCFResource = { [lambdaPermissionLogicalId]: permissionTemplate, }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - permissionCFResource); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + permissionCFResource + ); }); } @@ -176,7 +183,7 @@ class AwsCompileCognitoUserPoolEvents { const cognitoUserPoolTriggerFunctions = result.cognitoUserPoolTriggerFunctions; const userPools = result.userPools; - _.forEach(userPools, (poolName) => { + _.forEach(userPools, poolName => { const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { poolName }); const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); @@ -193,17 +200,14 @@ class AwsCompileCognitoUserPoolEvents { const DependsOn = generatedUserPool.DependsOn.concat(customUserPoolDependsOn); // Merge default and custom resources, and `DependsOn` clause - const mergedTemplate = Object.assign( - {}, - _.merge(generatedUserPool, customUserPool), - { DependsOn } - ); + const mergedTemplate = Object.assign({}, _.merge(generatedUserPool, customUserPool), { + DependsOn, + }); // Merge resource back into `Resources` - _.merge( - this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - { [userPoolLogicalId]: mergedTemplate } - ); + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { + [userPoolLogicalId]: mergedTemplate, + }); } }); } diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js index a67e80bb7..a639bf0f7 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js @@ -113,25 +113,30 @@ describe('AwsCompileCognitoUserPoolEvents', () => { awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool1.DependsOn ).to.have.lengthOf(1); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool2.DependsOn ).to.have.lengthOf(1); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .SecondLambdaPermissionCognitoUserPoolMyUserPool2TriggerSourcePostConfirmation.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.SecondLambdaPermissionCognitoUserPoolMyUserPool2TriggerSourcePostConfirmation + .Type ).to.equal('AWS::Lambda::Permission'); }); @@ -157,25 +162,30 @@ describe('AwsCompileCognitoUserPoolEvents', () => { awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool1.DependsOn ).to.have.lengthOf(1); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool2.DependsOn ).to.have.lengthOf(1); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionCognitoUserPoolMyUserPool2TriggerSourcePostConfirmation.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionCognitoUserPoolMyUserPool2TriggerSourcePostConfirmation + .Type ).to.equal('AWS::Lambda::Permission'); }); @@ -205,37 +215,43 @@ describe('AwsCompileCognitoUserPoolEvents', () => { awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool1.DependsOn ).to.have.lengthOf(1); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1 - .Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] - ).to.equal(serverless.service.serverless.getProvider('aws') - .naming.getLambdaLogicalId('first')); + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool1.Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] + ).to.equal( + serverless.service.serverless.getProvider('aws').naming.getLambdaLogicalId('first') + ); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool2.DependsOn ).to.have.lengthOf(1); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2 - .Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] - ).to.equal(serverless.service.serverless.getProvider('aws') - .naming.getLambdaLogicalId('second')); + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool2.Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] + ).to.equal( + serverless.service.serverless.getProvider('aws').naming.getLambdaLogicalId('second') + ); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .SecondLambdaPermissionCognitoUserPoolMyUserPool2TriggerSourcePreSignUp.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.SecondLambdaPermissionCognitoUserPoolMyUserPool2TriggerSourcePreSignUp.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -265,25 +281,28 @@ describe('AwsCompileCognitoUserPoolEvents', () => { awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Type ).to.equal('AWS::Cognito::UserPool'); - expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + expect( + _.keys( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Properties.LambdaConfig + ) ).to.have.lengthOf(2); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.DependsOn + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.DependsOn ).to.have.lengthOf(2); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .SecondLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePostConfirmation.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.SecondLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePostConfirmation + .Type ).to.equal('AWS::Lambda::Permission'); }); @@ -297,8 +316,8 @@ describe('AwsCompileCognitoUserPoolEvents', () => { awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); expect( - awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources ).to.deep.equal({}); }); }); @@ -322,21 +341,25 @@ describe('AwsCompileCognitoUserPoolEvents', () => { awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Type ).to.equal('AWS::Cognito::UserPool'); - expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties) + expect( + _.keys( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Properties + ) ).to.have.lengthOf(2); - expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + expect( + _.keys( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Properties.LambdaConfig + ) ).to.have.lengthOf(1); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -369,25 +392,29 @@ describe('AwsCompileCognitoUserPoolEvents', () => { awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Type ).to.equal('AWS::Cognito::UserPool'); - expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties) + expect( + _.keys( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Properties + ) ).to.have.lengthOf(6); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.DependsOn + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.DependsOn ).to.have.lengthOf(1); - expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + expect( + _.keys( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Properties.LambdaConfig + ) ).to.have.lengthOf(1); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -421,25 +448,29 @@ describe('AwsCompileCognitoUserPoolEvents', () => { awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Type ).to.equal('AWS::Cognito::UserPool'); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.DependsOn + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.DependsOn ).to.have.lengthOf(4); - expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties) + expect( + _.keys( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Properties + ) ).to.have.lengthOf(6); - expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + expect( + _.keys( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.CognitoUserPoolMyUserPool.Properties.LambdaConfig + ) ).to.have.lengthOf(1); - expect(awsCompileCognitoUserPoolEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + expect( + awsCompileCognitoUserPoolEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type ).to.equal('AWS::Lambda::Permission'); }); }); diff --git a/lib/plugins/aws/package/compile/events/iot/index.js b/lib/plugins/aws/package/compile/events/iot/index.js index 1ae11ec5c..0f86e8b7b 100644 --- a/lib/plugins/aws/package/compile/events/iot/index.js +++ b/lib/plugins/aws/package/compile/events/iot/index.js @@ -13,7 +13,7 @@ class AwsCompileIoTEvents { } compileIoTEvents() { - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); let iotNumberInFunction = 0; @@ -41,24 +41,30 @@ class AwsCompileIoTEvents { `IoT event of function "${functionName}" is not an object`, ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); - const iotLogicalId = this.provider.naming - .getIotLogicalId(functionName, iotNumberInFunction); - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaIotPermissionLogicalId(functionName, iotNumberInFunction); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); + const iotLogicalId = this.provider.naming.getIotLogicalId( + functionName, + iotNumberInFunction + ); + const lambdaPermissionLogicalId = this.provider.naming.getLambdaIotPermissionLogicalId( + functionName, + iotNumberInFunction + ); const iotTemplate = ` { "Type": "AWS::IoT::TopicRule", "Properties": { ${RuleName ? `"RuleName": "${RuleName.replace(/\r?\n/g, '')}",` : ''} "TopicRulePayload": { - ${AwsIotSqlVersion ? `"AwsIotSqlVersion": - "${AwsIotSqlVersion.replace(/\r?\n/g, '')}",` : ''} + ${ + AwsIotSqlVersion + ? `"AwsIotSqlVersion": + "${AwsIotSqlVersion.replace(/\r?\n/g, '')}",` + : '' + } ${Description ? `"Description": "${Description.replace(/\r?\n/g, '')}",` : ''} "RuleDisabled": "${RuleDisabled}", "Sql": "${Sql.replace(/\r?\n/g, '')}", @@ -105,8 +111,11 @@ class AwsCompileIoTEvents { [lambdaPermissionLogicalId]: JSON.parse(permissionTemplate), }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newIotObject, newPermissionObject); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newIotObject, + newPermissionObject + ); } }); } diff --git a/lib/plugins/aws/package/compile/events/iot/index.test.js b/lib/plugins/aws/package/compile/events/iot/index.test.js index 44e9c7561..0fe860fbe 100644 --- a/lib/plugins/aws/package/compile/events/iot/index.test.js +++ b/lib/plugins/aws/package/compile/events/iot/index.test.js @@ -57,35 +57,37 @@ describe('AwsCompileIoTEvents', () => { awsCompileIoTEvents.compileIoTEvents(); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstIotTopicRule1.Type + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Type ).to.equal('AWS::IoT::TopicRule'); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstIotTopicRule2.Type + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule2.Type ).to.equal('AWS::IoT::TopicRule'); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.TopicRulePayload.Sql - ).to.equal('SELECT * FROM \'topic_1\''); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule2.Properties.TopicRulePayload.Sql - ).to.equal('SELECT * FROM \'topic_2\''); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.TopicRulePayload.RuleDisabled + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.TopicRulePayload.Sql + ).to.equal("SELECT * FROM 'topic_1'"); + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule2.Properties.TopicRulePayload.Sql + ).to.equal("SELECT * FROM 'topic_2'"); + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.TopicRulePayload.RuleDisabled ).to.equal('false'); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule2.Properties.TopicRulePayload.RuleDisabled + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule2.Properties.TopicRulePayload.RuleDisabled ).to.equal('false'); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionIotTopicRule1.Type + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionIotTopicRule1.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionIotTopicRule2.Type + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionIotTopicRule2.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -105,9 +107,9 @@ describe('AwsCompileIoTEvents', () => { awsCompileIoTEvents.compileIoTEvents(); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.RuleName + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.RuleName ).to.equal('iotEventName'); }); @@ -137,13 +139,13 @@ describe('AwsCompileIoTEvents', () => { awsCompileIoTEvents.compileIoTEvents(); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.TopicRulePayload.RuleDisabled + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.TopicRulePayload.RuleDisabled ).to.equal('true'); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .SecondIotTopicRule1.Properties.TopicRulePayload.RuleDisabled + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .SecondIotTopicRule1.Properties.TopicRulePayload.RuleDisabled ).to.equal('false'); }); @@ -163,9 +165,9 @@ describe('AwsCompileIoTEvents', () => { awsCompileIoTEvents.compileIoTEvents(); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.TopicRulePayload.AwsIotSqlVersion + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.TopicRulePayload.AwsIotSqlVersion ).to.equal('2016-03-23'); }); @@ -185,9 +187,9 @@ describe('AwsCompileIoTEvents', () => { awsCompileIoTEvents.compileIoTEvents(); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.TopicRulePayload.Description + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.TopicRulePayload.Description ).to.equal('iot event description'); }); @@ -206,9 +208,9 @@ describe('AwsCompileIoTEvents', () => { awsCompileIoTEvents.compileIoTEvents(); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.TopicRulePayload.RuleDisabled + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.TopicRulePayload.RuleDisabled ).to.equal('false'); }); @@ -229,21 +231,21 @@ describe('AwsCompileIoTEvents', () => { }; awsCompileIoTEvents.compileIoTEvents(); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.TopicRulePayload.Sql + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.TopicRulePayload.Sql ).to.equal("SELECT * FROM 'topic_1' WHERE value = 2"); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.TopicRulePayload.AwsIotSqlVersion + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.TopicRulePayload.AwsIotSqlVersion ).to.equal('beta'); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.TopicRulePayload.Description + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.TopicRulePayload.Description ).to.equal('iot event description with newline'); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstIotTopicRule1.Properties.RuleName + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstIotTopicRule1.Properties.RuleName ).to.equal('iotEventName'); }); @@ -256,8 +258,8 @@ describe('AwsCompileIoTEvents', () => { awsCompileIoTEvents.compileIoTEvents(); - expect(awsCompileIoTEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources + expect( + awsCompileIoTEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources ).to.deep.equal({}); }); }); diff --git a/lib/plugins/aws/package/compile/events/s3/index.js b/lib/plugins/aws/package/compile/events/s3/index.js index 30a1e67e8..1fc7281e3 100644 --- a/lib/plugins/aws/package/compile/events/s3/index.js +++ b/lib/plugins/aws/package/compile/events/s3/index.js @@ -15,7 +15,7 @@ class AwsCompileS3Events { compileS3Events() { const bucketsLambdaConfigurations = {}; const s3EnabledFunctions = []; - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); if (functionObj.events) { @@ -32,8 +32,7 @@ class AwsCompileS3Events { ' The correct syntax is: s3: bucketName OR an object with "bucket" property.', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } bucketName = event.s3.bucket; if (event.s3.event) { @@ -46,8 +45,7 @@ class AwsCompileS3Events { ' The correct syntax is: rules: [{ Name: Value }]', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } const rules = []; event.s3.rules.forEach(rule => { @@ -57,8 +55,7 @@ class AwsCompileS3Events { ' The correct syntax is: { Name: Value }', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } const name = Object.keys(rule)[0]; const value = rule[name]; @@ -74,12 +71,10 @@ class AwsCompileS3Events { ' The correct syntax is: s3: bucketName OR an object with "bucket" property.', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); // check if the bucket already defined // in another S3 event in the service @@ -87,29 +82,19 @@ class AwsCompileS3Events { let newLambdaConfiguration = { Event: notificationEvent, Function: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], }, }; // Assign 'filter' if not empty - newLambdaConfiguration = _.assign( - newLambdaConfiguration, - filter - ); - bucketsLambdaConfigurations[bucketName] - .push(newLambdaConfiguration); + newLambdaConfiguration = _.assign(newLambdaConfiguration, filter); + bucketsLambdaConfigurations[bucketName].push(newLambdaConfiguration); } else { bucketsLambdaConfigurations[bucketName] = [ { Event: notificationEvent, Function: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], }, }, ]; @@ -141,62 +126,61 @@ class AwsCompileS3Events { }; // create the DependsOn properties for the buckets permissions (which are created later on) - const dependsOnToCreate = s3EnabledFunctions - .filter(func => func.bucketName === bucketName); + const dependsOnToCreate = s3EnabledFunctions.filter(func => func.bucketName === bucketName); - _.forEach(dependsOnToCreate, (item) => { - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaS3PermissionLogicalId(item.functionName, - item.bucketName); + _.forEach(dependsOnToCreate, item => { + const lambdaPermissionLogicalId = this.provider.naming.getLambdaS3PermissionLogicalId( + item.functionName, + item.bucketName + ); bucketTemplate.DependsOn.push(lambdaPermissionLogicalId); }); - const bucketLogicalId = this.provider.naming - .getBucketLogicalId(bucketName); + const bucketLogicalId = this.provider.naming.getBucketLogicalId(bucketName); const bucketCFResource = { [bucketLogicalId]: bucketTemplate, }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - bucketCFResource); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + bucketCFResource + ); }); // iterate over all functions with S3 events // and give S3 permission to invoke them all // by adding Lambda::Permission resource for each s3EnabledFunctions.forEach(s3EnabledFunction => { - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(s3EnabledFunction.functionName); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId( + s3EnabledFunction.functionName + ); const permissionTemplate = { Type: 'AWS::Lambda::Permission', Properties: { FunctionName: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], }, Action: 'lambda:InvokeFunction', Principal: 's3.amazonaws.com', SourceArn: { - 'Fn::Join': ['', - [ - 'arn:', - { Ref: 'AWS::Partition' }, - `:s3:::${s3EnabledFunction.bucketName}`, - ], + 'Fn::Join': [ + '', + ['arn:', { Ref: 'AWS::Partition' }, `:s3:::${s3EnabledFunction.bucketName}`], ], }, }, }; - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaS3PermissionLogicalId(s3EnabledFunction.functionName, - s3EnabledFunction.bucketName); + const lambdaPermissionLogicalId = this.provider.naming.getLambdaS3PermissionLogicalId( + s3EnabledFunction.functionName, + s3EnabledFunction.bucketName + ); const permissionCFResource = { [lambdaPermissionLogicalId]: permissionTemplate, }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - permissionCFResource); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + permissionCFResource + ); }); } } diff --git a/lib/plugins/aws/package/compile/events/s3/index.test.js b/lib/plugins/aws/package/compile/events/s3/index.test.js index 00c085d83..f81990dd6 100644 --- a/lib/plugins/aws/package/compile/events/s3/index.test.js +++ b/lib/plugins/aws/package/compile/events/s3/index.test.js @@ -100,9 +100,7 @@ describe('AwsCompileS3Events', () => { s3: { bucket: 'first-function-bucket-two', event: 's3:ObjectCreated:Put', - rules: [ - { prefix: 'subfolder/' }, - ], + rules: [{ prefix: 'subfolder/' }], }, }, ], @@ -111,23 +109,29 @@ describe('AwsCompileS3Events', () => { awsCompileS3Events.compileS3Events(); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.S3BucketFirstfunctionbucketone.Type + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .S3BucketFirstfunctionbucketone.Type ).to.equal('AWS::S3::Bucket'); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.S3BucketFirstfunctionbuckettwo.Type + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .S3BucketFirstfunctionbuckettwo.Type ).to.equal('AWS::S3::Bucket'); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstLambdaPermissionFirstfunctionbucketoneS3.Type + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionFirstfunctionbucketoneS3.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstLambdaPermissionFirstfunctionbuckettwoS3.Type + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionFirstfunctionbuckettwoS3.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.S3BucketFirstfunctionbuckettwo.Properties.NotificationConfiguration - .LambdaConfigurations[0].Filter).to.deep.equal({ - S3Key: { Rules: [{ Name: 'prefix', Value: 'subfolder/' }] }, - }); + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .S3BucketFirstfunctionbuckettwo.Properties.NotificationConfiguration + .LambdaConfigurations[0].Filter + ).to.deep.equal({ + S3Key: { Rules: [{ Name: 'prefix', Value: 'subfolder/' }] }, + }); }); it('should create single bucket resource when the same bucket referenced repeatedly', () => { @@ -141,9 +145,7 @@ describe('AwsCompileS3Events', () => { s3: { bucket: 'first-function-bucket-one', event: 's3:ObjectCreated:Put', - rules: [ - { prefix: 'subfolder/' }, - ], + rules: [{ prefix: 'subfolder/' }], }, }, ], @@ -152,14 +154,18 @@ describe('AwsCompileS3Events', () => { awsCompileS3Events.compileS3Events(); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.S3BucketFirstfunctionbucketone.Type + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .S3BucketFirstfunctionbucketone.Type ).to.equal('AWS::S3::Bucket'); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.S3BucketFirstfunctionbucketone.Properties.NotificationConfiguration - .LambdaConfigurations.length).to.equal(2); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstLambdaPermissionFirstfunctionbucketoneS3.Type + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .S3BucketFirstfunctionbucketone.Properties.NotificationConfiguration.LambdaConfigurations + .length + ).to.equal(2); + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionFirstfunctionbucketoneS3.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -181,23 +187,29 @@ describe('AwsCompileS3Events', () => { awsCompileS3Events.compileS3Events(); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.S3BucketFirstfunctionbucketone.Type + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .S3BucketFirstfunctionbucketone.Type ).to.equal('AWS::S3::Bucket'); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.S3BucketFirstfunctionbuckettwo.Type + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .S3BucketFirstfunctionbuckettwo.Type ).to.equal('AWS::S3::Bucket'); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstLambdaPermissionFirstfunctionbucketoneS3.Type + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionFirstfunctionbucketoneS3.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstLambdaPermissionFirstfunctionbuckettwoS3.Type + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionFirstfunctionbuckettwoS3.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.S3BucketFirstfunctionbucketone.DependsOn + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .S3BucketFirstfunctionbucketone.DependsOn ).to.deep.equal(['FirstLambdaPermissionFirstfunctionbucketoneS3']); - expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate - .Resources.S3BucketFirstfunctionbuckettwo.DependsOn + expect( + awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources + .S3BucketFirstfunctionbuckettwo.DependsOn ).to.deep.equal(['FirstLambdaPermissionFirstfunctionbuckettwoS3']); }); diff --git a/lib/plugins/aws/package/compile/events/schedule/index.js b/lib/plugins/aws/package/compile/events/schedule/index.js index 65394aacc..938ab925e 100644 --- a/lib/plugins/aws/package/compile/events/schedule/index.js +++ b/lib/plugins/aws/package/compile/events/schedule/index.js @@ -2,10 +2,8 @@ const _ = require('lodash'); -const rateSyntaxPattern = - /^rate\((?:1 (?:minute|hour|day)|(?:1\d+|[2-9]\d*) (?:minute|hour|day)s)\)$/; -const cronSyntaxPattern = - /^cron\(\S+ \S+ \S+ \S+ \S+ \S+\)$/; +const rateSyntaxPattern = /^rate\((?:1 (?:minute|hour|day)|(?:1\d+|[2-9]\d*) (?:minute|hour|day)s)\)$/; +const cronSyntaxPattern = /^cron\(\S+ \S+ \S+ \S+ \S+ \S+\)$/; class AwsCompileScheduledEvents { constructor(serverless) { @@ -28,7 +26,7 @@ class AwsCompileScheduledEvents { } compileScheduledEvents() { - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); let scheduleNumberInFunction = 0; @@ -47,8 +45,7 @@ class AwsCompileScheduledEvents { if (typeof event.schedule === 'object') { if (!this.validateScheduleSyntax(event.schedule.rate)) { const errorMessage = this.buildValidationErrorMessage(functionName); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } ScheduleExpression = event.schedule.rate; State = 'ENABLED'; @@ -68,8 +65,7 @@ class AwsCompileScheduledEvents { 'properties at the same time for schedule events. ', 'Please check the AWS docs for more info', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } if (Input && typeof Input === 'object') { @@ -83,8 +79,7 @@ class AwsCompileScheduledEvents { ' but it failed to parse to a JSON object.', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } } Input = JSON.stringify(Input); @@ -101,16 +96,18 @@ class AwsCompileScheduledEvents { State = 'ENABLED'; } else { const errorMessage = this.buildValidationErrorMessage(functionName); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); - const scheduleLogicalId = this.provider.naming - .getScheduleLogicalId(functionName, scheduleNumberInFunction); - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaSchedulePermissionLogicalId(functionName, scheduleNumberInFunction); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); + const scheduleLogicalId = this.provider.naming.getScheduleLogicalId( + functionName, + scheduleNumberInFunction + ); + const lambdaPermissionLogicalId = this.provider.naming.getLambdaSchedulePermissionLogicalId( + functionName, + scheduleNumberInFunction + ); const scheduleId = this.provider.naming.getScheduleId(functionName); const scheduleTemplate = ` @@ -136,8 +133,7 @@ class AwsCompileScheduledEvents { { "Type": "AWS::Lambda::Permission", "Properties": { - "FunctionName": { "Fn::GetAtt": ["${ - lambdaLogicalId}", "Arn"] }, + "FunctionName": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] }, "Action": "lambda:InvokeFunction", "Principal": "events.amazonaws.com", "SourceArn": { "Fn::GetAtt": ["${scheduleLogicalId}", "Arn"] } @@ -153,8 +149,11 @@ class AwsCompileScheduledEvents { [lambdaPermissionLogicalId]: JSON.parse(permissionTemplate), }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newScheduleObject, newPermissionObject); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newScheduleObject, + newPermissionObject + ); } }); } @@ -162,15 +161,16 @@ class AwsCompileScheduledEvents { } validateScheduleSyntax(input) { - return typeof input === 'string' && - (rateSyntaxPattern.test(input) || cronSyntaxPattern.test(input)); + return ( + typeof input === 'string' && (rateSyntaxPattern.test(input) || cronSyntaxPattern.test(input)) + ); } formatInputTransformer(inputTransformer) { if (!inputTransformer.inputTemplate) { throw new this.serverless.classes.Error( 'The inputTemplate key is required when specifying an ' + - 'inputTransformer for a schedule event' + 'inputTransformer for a schedule event' ); } const cfmOutput = { diff --git a/lib/plugins/aws/package/compile/events/schedule/index.test.js b/lib/plugins/aws/package/compile/events/schedule/index.test.js index 1be50220e..0ea7e2fb4 100644 --- a/lib/plugins/aws/package/compile/events/schedule/index.test.js +++ b/lib/plugins/aws/package/compile/events/schedule/index.test.js @@ -213,30 +213,33 @@ describe('AwsCompileScheduledEvents', () => { awsCompileScheduledEvents.compileScheduledEvents(); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule1.Type + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule1.Type ).to.equal('AWS::Events::Rule'); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule2.Type + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule2.Type ).to.equal('AWS::Events::Rule'); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule3.Type + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule3.Type ).to.equal('AWS::Events::Rule'); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionEventsRuleSchedule1.Type + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionEventsRuleSchedule1.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionEventsRuleSchedule2.Type + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionEventsRuleSchedule2.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionEventsRuleSchedule3.Type + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionEventsRuleSchedule3.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionEventsRuleSchedule4.Type + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionEventsRuleSchedule4.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -270,21 +273,21 @@ describe('AwsCompileScheduledEvents', () => { awsCompileScheduledEvents.compileScheduledEvents(); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule1 - .Properties.State + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule1.Properties.State ).to.equal('DISABLED'); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule2 - .Properties.State + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule2.Properties.State ).to.equal('ENABLED'); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule3 - .Properties.State + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule3.Properties.State ).to.equal('ENABLED'); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule4 - .Properties.State + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule4.Properties.State ).to.equal('ENABLED'); }); @@ -305,9 +308,9 @@ describe('AwsCompileScheduledEvents', () => { awsCompileScheduledEvents.compileScheduledEvents(); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule1 - .Properties.Name + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule1.Properties.Name ).to.equal('your-scheduled-event-name'); }); @@ -328,9 +331,9 @@ describe('AwsCompileScheduledEvents', () => { awsCompileScheduledEvents.compileScheduledEvents(); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule1 - .Properties.Description + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule1.Properties.Description ).to.equal('your scheduled event description'); }); @@ -351,9 +354,9 @@ describe('AwsCompileScheduledEvents', () => { awsCompileScheduledEvents.compileScheduledEvents(); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule1 - .Properties.Targets[0].InputPath + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule1.Properties.Targets[0].InputPath ).to.equal('$.stageVariables'); }); @@ -374,9 +377,9 @@ describe('AwsCompileScheduledEvents', () => { awsCompileScheduledEvents.compileScheduledEvents(); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule1 - .Properties.Targets[0].Input + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule1.Properties.Targets[0].Input ).to.equal('{"key":"value"}'); }); @@ -399,9 +402,9 @@ describe('AwsCompileScheduledEvents', () => { awsCompileScheduledEvents.compileScheduledEvents(); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule1 - .Properties.Targets[0].Input + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule1.Properties.Targets[0].Input ).to.equal('{"key":"value"}'); }); @@ -427,16 +430,15 @@ describe('AwsCompileScheduledEvents', () => { awsCompileScheduledEvents.compileScheduledEvents(); - expect(awsCompileScheduledEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleSchedule1 - .Properties.Targets[0].InputTransformer + expect( + awsCompileScheduledEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventsRuleSchedule1.Properties.Targets[0].InputTransformer ).to.eql({ InputTemplate: '{"time": , "key1": "value1"}', InputPathsMap: { eventTime: '$.time' }, }); }); - it('should throw an error when both Input and InputPath are set', () => { awsCompileScheduledEvents.serverless.service.functions = { first: { diff --git a/lib/plugins/aws/package/compile/events/sns/index.js b/lib/plugins/aws/package/compile/events/sns/index.js index e3e1e9f90..4d065c677 100644 --- a/lib/plugins/aws/package/compile/events/sns/index.js +++ b/lib/plugins/aws/package/compile/events/sns/index.js @@ -28,8 +28,10 @@ class AwsCompileSNSEvents { if (Object.keys(variable).length !== 1) { return false; } - if (_.has(variable, 'Fn::ImportValue') && - (_.has(variable, 'Fn::ImportValue.Fn::GetAtt') || _.has(variable, 'Fn::ImportValue.Ref'))) { + if ( + _.has(variable, 'Fn::ImportValue') && + (_.has(variable, 'Fn::ImportValue.Fn::GetAtt') || _.has(variable, 'Fn::ImportValue.Ref')) + ) { return false; } const intrinsicFunctions = ['Fn::ImportValue', 'Ref', 'Fn::GetAtt', 'Fn::Sub', 'Fn::Join']; @@ -39,7 +41,7 @@ class AwsCompileSNSEvents { compileSNSEvents() { const template = this.serverless.service.provider.compiledCloudFormationTemplate; - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); if (functionObj.events) { @@ -54,33 +56,38 @@ class AwsCompileSNSEvents { topicArn = event.sns.arn; if (typeof topicArn === 'object') { if (!this.isValidStackImport(topicArn)) { - throw new this.serverless.classes - .Error(this.invalidPropertyErrorMessage(functionName, 'arn')); + throw new this.serverless.classes.Error( + this.invalidPropertyErrorMessage(functionName, 'arn') + ); } } else if (typeof topicArn === 'string') { if (topicArn.indexOf('arn:') === 0) { const splitArn = topicArn.split(':'); topicName = splitArn[splitArn.length - 1]; } else { - throw new this.serverless.classes - .Error(this.invalidPropertyErrorMessage(functionName, 'arn')); + throw new this.serverless.classes.Error( + this.invalidPropertyErrorMessage(functionName, 'arn') + ); } } else { - throw new this.serverless.classes - .Error(this.invalidPropertyErrorMessage(functionName, 'arn')); + throw new this.serverless.classes.Error( + this.invalidPropertyErrorMessage(functionName, 'arn') + ); } topicName = topicName || event.sns.topicName; if (!topicName || typeof topicName !== 'string') { - throw new this.serverless.classes - .Error(this.invalidPropertyErrorMessage(functionName, 'topicName')); + throw new this.serverless.classes.Error( + this.invalidPropertyErrorMessage(functionName, 'topicName') + ); } } else { - ['topicName', 'displayName'].forEach((property) => { + ['topicName', 'displayName'].forEach(property => { if (typeof event.sns[property] === 'string') { return; } - throw new this.serverless.classes - .Error(this.invalidPropertyErrorMessage(functionName, property)); + throw new this.serverless.classes.Error( + this.invalidPropertyErrorMessage(functionName, property) + ); }); displayName = event.sns.displayName; topicName = event.sns.topicName; @@ -102,19 +109,19 @@ class AwsCompileSNSEvents { ' topicName and displayName.', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); const endpoint = { 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], }; - const subscriptionLogicalId = this.provider.naming - .getLambdaSnsSubscriptionLogicalId(functionName, topicName); + const subscriptionLogicalId = this.provider.naming.getLambdaSnsSubscriptionLogicalId( + functionName, + topicName + ); if (topicArn) { _.merge(template.Resources, { @@ -130,7 +137,8 @@ class AwsCompileSNSEvents { }); } else { topicArn = { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -144,8 +152,7 @@ class AwsCompileSNSEvents { ], }; - const topicLogicalId = this.provider.naming - .getTopicLogicalId(topicName); + const topicLogicalId = this.provider.naming.getTopicLogicalId(topicName); const subscription = { Endpoint: endpoint, @@ -168,26 +175,26 @@ class AwsCompileSNSEvents { _.merge(template.Resources, { [subscriptionLogicalId]: { Type: 'AWS::SNS::Subscription', - Properties: - _.merge(subscription, { - TopicArn: { - Ref: topicLogicalId, - }, - FilterPolicy: event.sns.filterPolicy, - }), + Properties: _.merge(subscription, { + TopicArn: { + Ref: topicLogicalId, + }, + FilterPolicy: event.sns.filterPolicy, + }), }, }); } else { if (!template.Resources[topicLogicalId].Properties.Subscription) { template.Resources[topicLogicalId].Properties.Subscription = []; } - template.Resources[topicLogicalId] - .Properties.Subscription.push(subscription); + template.Resources[topicLogicalId].Properties.Subscription.push(subscription); } } - const lambdaPermissionLogicalId = this.provider.naming - .getLambdaSnsPermissionLogicalId(functionName, topicName); + const lambdaPermissionLogicalId = this.provider.naming.getLambdaSnsPermissionLogicalId( + functionName, + topicName + ); _.merge(template.Resources, { [lambdaPermissionLogicalId]: { diff --git a/lib/plugins/aws/package/compile/events/sns/index.test.js b/lib/plugins/aws/package/compile/events/sns/index.test.js index 1603f0b2f..c312459c4 100644 --- a/lib/plugins/aws/package/compile/events/sns/index.test.js +++ b/lib/plugins/aws/package/compile/events/sns/index.test.js @@ -58,24 +58,29 @@ describe('AwsCompileSNSEvents', () => { awsCompileSNSEvents.compileSNSEvents(); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.SNSTopicTopic1.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .SNSTopicTopic1.Type ).to.equal('AWS::SNS::Topic'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.SNSTopicTopic2.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .SNSTopicTopic2.Type ).to.equal('AWS::SNS::Topic'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic1SNS.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionTopic1SNS.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic2SNS.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionTopic2SNS.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstSnsSubscriptionTopic1.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionTopic1.Type ).to.equal('AWS::SNS::Subscription'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate - .Resources.FirstSnsSubscriptionTopic1.Properties.FilterPolicy + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionTopic1.Properties.FilterPolicy ).to.eql({ pet: ['dog', 'cat'] }); }); @@ -99,8 +104,9 @@ describe('AwsCompileSNSEvents', () => { }, }; - Object.assign(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources, { + Object.assign( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources, + { SNSTopicTopic2: { Type: 'AWS::SNS::Topic', Properties: { @@ -108,28 +114,34 @@ describe('AwsCompileSNSEvents', () => { DisplayName: 'Display name for topic 2', }, }, - }); + } + ); awsCompileSNSEvents.compileSNSEvents(); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.SNSTopicTopic1.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .SNSTopicTopic1.Type ).to.equal('AWS::SNS::Topic'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.SNSTopicTopic2.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .SNSTopicTopic2.Type ).to.equal('AWS::SNS::Topic'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic1SNS.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionTopic1SNS.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic2SNS.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionTopic2SNS.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstSnsSubscriptionTopic1.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionTopic1.Type ).to.equal('AWS::SNS::Subscription'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate - .Resources.FirstSnsSubscriptionTopic1.Properties.FilterPolicy + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionTopic1.Properties.FilterPolicy ).to.eql({ pet: ['dog', 'cat'] }); }); @@ -152,14 +164,18 @@ describe('AwsCompileSNSEvents', () => { awsCompileSNSEvents.compileSNSEvents(); - expect(Object.keys(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources) + expect( + Object.keys( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + ) ).to.have.length(2); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.SNSTopicTopic1.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .SNSTopicTopic1.Type ).to.equal('AWS::SNS::Topic'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionTopic1SNS.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionTopic1SNS.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -176,7 +192,9 @@ describe('AwsCompileSNSEvents', () => { }, }; - expect(() => { awsCompileSNSEvents.compileSNSEvents(); }).to.throw(Error); + expect(() => { + awsCompileSNSEvents.compileSNSEvents(); + }).to.throw(Error); }); it('should not create corresponding resources when SNS events are not given', () => { @@ -189,8 +207,7 @@ describe('AwsCompileSNSEvents', () => { awsCompileSNSEvents.compileSNSEvents(); expect( - awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources ).to.deep.equal({}); }); @@ -207,18 +224,22 @@ describe('AwsCompileSNSEvents', () => { awsCompileSNSEvents.compileSNSEvents(); - expect(Object.keys(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources) + expect( + Object.keys( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + ) ).to.have.length(2); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstSnsSubscriptionFoo.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionFoo.Type ).to.equal('AWS::SNS::Subscription'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionFooSNS.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionFooSNS.Type ).to.equal('AWS::Lambda::Permission'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate - .Resources.FirstSnsSubscriptionFoo.Properties.FilterPolicy + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionFoo.Properties.FilterPolicy ).to.equal(undefined); }); @@ -237,14 +258,18 @@ describe('AwsCompileSNSEvents', () => { awsCompileSNSEvents.compileSNSEvents(); - expect(Object.keys(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources) + expect( + Object.keys( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + ) ).to.have.length(2); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstSnsSubscriptionFoo.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionFoo.Type ).to.equal('AWS::SNS::Subscription'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionFooSNS.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionFooSNS.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -261,7 +286,9 @@ describe('AwsCompileSNSEvents', () => { }, }; - expect(() => { awsCompileSNSEvents.compileSNSEvents(); }).to.throw(Error); + expect(() => { + awsCompileSNSEvents.compileSNSEvents(); + }).to.throw(Error); }); it('should create SNS topic when arn and topicName are given as object properties', () => { @@ -280,14 +307,18 @@ describe('AwsCompileSNSEvents', () => { awsCompileSNSEvents.compileSNSEvents(); - expect(Object.keys(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources) + expect( + Object.keys( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + ) ).to.have.length(2); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstSnsSubscriptionBar.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionBar.Type ).to.equal('AWS::SNS::Subscription'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionBarSNS.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionBarSNS.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -300,15 +331,7 @@ describe('AwsCompileSNSEvents', () => { sns: { topicName: 'bar', arn: { - 'Fn::Join': [ - ':', - [ - 'arn:aws:sns', - '${AWS::Region}', - '${AWS::AccountId}', - 'bar', - ], - ], + 'Fn::Join': [':', ['arn:aws:sns', '${AWS::Region}', '${AWS::AccountId}', 'bar']], }, }, }, @@ -318,14 +341,18 @@ describe('AwsCompileSNSEvents', () => { awsCompileSNSEvents.compileSNSEvents(); - expect(Object.keys(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources) + expect( + Object.keys( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + ) ).to.have.length(2); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstSnsSubscriptionBar.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionBar.Type ).to.equal('AWS::SNS::Subscription'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionBarSNS.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionBarSNS.Type ).to.equal('AWS::Lambda::Permission'); }); @@ -337,15 +364,7 @@ describe('AwsCompileSNSEvents', () => { { sns: { arn: { - 'Fn::Join': [ - ':', - [ - 'arn:aws:sns', - '${AWS::Region}', - '${AWS::AccountId}', - 'bar', - ], - ], + 'Fn::Join': [':', ['arn:aws:sns', '${AWS::Region}', '${AWS::AccountId}', 'bar']], }, }, }, @@ -353,7 +372,9 @@ describe('AwsCompileSNSEvents', () => { }, }; - expect(() => { awsCompileSNSEvents.compileSNSEvents(); }).to.throw(Error); + expect(() => { + awsCompileSNSEvents.compileSNSEvents(); + }).to.throw(Error); }); // eslint-disable-next-line max-len @@ -375,7 +396,9 @@ describe('AwsCompileSNSEvents', () => { }, }; - expect(() => { awsCompileSNSEvents.compileSNSEvents(); }).to.throw(Error); + expect(() => { + awsCompileSNSEvents.compileSNSEvents(); + }).to.throw(Error); awsCompileSNSEvents.serverless.service.functions = { first: { @@ -385,10 +408,7 @@ describe('AwsCompileSNSEvents', () => { topicName: 'bar', arn: { 'Fn::ImportValue': { - 'Fn::GetAtt': [ - 'BarTopic', - 'Arn', - ], + 'Fn::GetAtt': ['BarTopic', 'Arn'], }, }, }, @@ -397,7 +417,9 @@ describe('AwsCompileSNSEvents', () => { }, }; - expect(() => { awsCompileSNSEvents.compileSNSEvents(); }).to.throw(Error); + expect(() => { + awsCompileSNSEvents.compileSNSEvents(); + }).to.throw(Error); }); it('should create SNS topic when arn, topicName, and filterPolicy are given as object', () => { @@ -419,18 +441,22 @@ describe('AwsCompileSNSEvents', () => { awsCompileSNSEvents.compileSNSEvents(); - expect(Object.keys(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources) + expect( + Object.keys( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + ) ).to.have.length(2); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstSnsSubscriptionBar.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionBar.Type ).to.equal('AWS::SNS::Subscription'); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate - .Resources.FirstSnsSubscriptionBar.Properties.FilterPolicy + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstSnsSubscriptionBar.Properties.FilterPolicy ).to.eql({ pet: ['dog', 'cat'] }); - expect(awsCompileSNSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstLambdaPermissionBarSNS.Type + expect( + awsCompileSNSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionBarSNS.Type ).to.equal('AWS::Lambda::Permission'); }); }); diff --git a/lib/plugins/aws/package/compile/events/sqs/index.js b/lib/plugins/aws/package/compile/events/sqs/index.js index 3a4904e27..2b03923dd 100644 --- a/lib/plugins/aws/package/compile/events/sqs/index.js +++ b/lib/plugins/aws/package/compile/events/sqs/index.js @@ -13,17 +13,13 @@ class AwsCompileSQSEvents { } compileSQSEvents() { - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); if (functionObj.events) { const sqsStatement = { Effect: 'Allow', - Action: [ - 'sqs:ReceiveMessage', - 'sqs:DeleteMessage', - 'sqs:GetQueueAttributes', - ], + Action: ['sqs:ReceiveMessage', 'sqs:DeleteMessage', 'sqs:GetQueueAttributes'], Resource: [], }; @@ -42,28 +38,29 @@ class AwsCompileSQSEvents { ' OR an object with an "arn" property.', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } if (typeof event.sqs.arn !== 'string') { // for dynamic arns (GetAtt/ImportValue) - if (Object.keys(event.sqs.arn).length !== 1 - || !(_.has(event.sqs.arn, 'Fn::ImportValue') - || _.has(event.sqs.arn, 'Fn::GetAtt') - || _.has(event.sqs.arn, 'Fn::Join'))) { + if ( + Object.keys(event.sqs.arn).length !== 1 || + !( + _.has(event.sqs.arn, 'Fn::ImportValue') || + _.has(event.sqs.arn, 'Fn::GetAtt') || + _.has(event.sqs.arn, 'Fn::Join') + ) + ) { const errorMessage = [ `Bad dynamic ARN property on sqs event in function "${functionName}"`, ' If you use a dynamic "arn" (such as with Fn::GetAtt or Fn::ImportValue)', ' there must only be one key (either Fn::GetAtt or Fn::ImportValue) in the arn', ' object. Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } } EventSourceArn = event.sqs.arn; - BatchSize = event.sqs.batchSize - || BatchSize; + BatchSize = event.sqs.batchSize || BatchSize; if (typeof event.sqs.enabled !== 'undefined') { Enabled = event.sqs.enabled ? 'True' : 'False'; } @@ -76,11 +73,10 @@ class AwsCompileSQSEvents { ' OR an object with an "arn" property.', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } - const queueName = (function () { + const queueName = (function() { if (EventSourceArn['Fn::GetAtt']) { return EventSourceArn['Fn::GetAtt'][0]; } else if (EventSourceArn['Fn::ImportValue']) { @@ -90,22 +86,22 @@ class AwsCompileSQSEvents { return EventSourceArn['Fn::Join'][1].slice(-1).pop(); } return EventSourceArn.split(':').pop(); - }()); + })(); - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); - const queueLogicalId = this.provider.naming - .getQueueLogicalId(functionName, queueName); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); + const queueLogicalId = this.provider.naming.getQueueLogicalId(functionName, queueName); const funcRole = functionObj.role || this.serverless.service.provider.role; let dependsOn = '"IamRoleLambdaExecution"'; if (funcRole) { - if ( // check whether the custom role is an ARN + if ( + // check whether the custom role is an ARN typeof funcRole === 'string' && funcRole.indexOf(':') !== -1 ) { dependsOn = '[]'; - } else if ( // otherwise, check if we have an in-service reference to a role ARN + } else if ( + // otherwise, check if we have an in-service reference to a role ARN typeof funcRole === 'object' && 'Fn::GetAtt' in funcRole && Array.isArray(funcRole['Fn::GetAtt']) && @@ -115,7 +111,8 @@ class AwsCompileSQSEvents { funcRole['Fn::GetAtt'][1] === 'Arn' ) { dependsOn = `"${funcRole['Fn::GetAtt'][0]}"`; - } else if ( // otherwise, check if we have an import + } else if ( + // otherwise, check if we have an import typeof funcRole === 'object' && 'Fn::ImportValue' in funcRole ) { @@ -149,21 +146,20 @@ class AwsCompileSQSEvents { [queueLogicalId]: JSON.parse(sqsTemplate), }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newSQSObject); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newSQSObject + ); } }); // update the PolicyDocument statements (if default policy is used) - if (this.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution) { - const statement = this.serverless.service.provider.compiledCloudFormationTemplate - .Resources + if ( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution - .Properties - .Policies[0] - .PolicyDocument - .Statement; + ) { + const statement = this.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement; if (sqsStatement.Resource.length) { statement.push(sqsStatement); } diff --git a/lib/plugins/aws/package/compile/events/sqs/index.test.js b/lib/plugins/aws/package/compile/events/sqs/index.test.js index 2e21dfa15..b2cbe301f 100644 --- a/lib/plugins/aws/package/compile/events/sqs/index.test.js +++ b/lib/plugins/aws/package/compile/events/sqs/index.test.js @@ -79,14 +79,14 @@ describe('AwsCompileSQSEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileSQSEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution = null; + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); - expect(awsCompileSQSEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution + expect(() => { + awsCompileSQSEvents.compileSQSEvents(); + }).to.not.throw(Error); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution ).to.equal(null); }); @@ -103,18 +103,22 @@ describe('AwsCompileSQSEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileSQSEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution = null; + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.be.instanceof(Array); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn.length).to.equal(0); - expect(awsCompileSQSEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution + expect(() => { + awsCompileSQSEvents.compileSQSEvents(); + }).to.not.throw(Error); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.DependsOn + ).to.be.instanceof(Array); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.DependsOn.length + ).to.equal(0); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution ).to.equal(null); }); @@ -132,14 +136,19 @@ describe('AwsCompileSQSEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution = null; + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.equal(roleLogicalId); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution).to.equal(null); + expect(() => { + awsCompileSQSEvents.compileSQSEvents(); + }).to.not.throw(Error); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.DependsOn + ).to.equal(roleLogicalId); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); }); it('should not throw error if custom IAM role reference is set in function', () => { @@ -156,14 +165,19 @@ describe('AwsCompileSQSEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution = null; + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.equal(roleLogicalId); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution).to.equal(null); + expect(() => { + awsCompileSQSEvents.compileSQSEvents(); + }).to.not.throw(Error); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.DependsOn + ).to.equal(roleLogicalId); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); }); it('should not throw error if custom IAM role is set in provider', () => { @@ -178,21 +192,24 @@ describe('AwsCompileSQSEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileSQSEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution = null; + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - awsCompileSQSEvents.serverless.service.provider - .role = 'arn:aws:iam::account:role/foo'; + awsCompileSQSEvents.serverless.service.provider.role = 'arn:aws:iam::account:role/foo'; - expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.be.instanceof(Array); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn.length).to.equal(0); - expect(awsCompileSQSEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution + expect(() => { + awsCompileSQSEvents.compileSQSEvents(); + }).to.not.throw(Error); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.DependsOn + ).to.be.instanceof(Array); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.DependsOn.length + ).to.equal(0); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution ).to.equal(null); }); @@ -209,17 +226,21 @@ describe('AwsCompileSQSEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution = null; + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn.length).to.equal(0); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution).to.equal(null); + expect(() => { + awsCompileSQSEvents.compileSQSEvents(); + }).to.not.throw(Error); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.DependsOn.length + ).to.equal(0); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); }); - it('should not throw error if custom IAM role reference is set in provider', () => { const roleLogicalId = 'RoleLogicalId'; awsCompileSQSEvents.serverless.service.functions = { @@ -233,17 +254,23 @@ describe('AwsCompileSQSEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution = null; + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - awsCompileSQSEvents.serverless.service.provider - .role = { 'Fn::GetAtt': [roleLogicalId, 'Arn'] }; + awsCompileSQSEvents.serverless.service.provider.role = { + 'Fn::GetAtt': [roleLogicalId, 'Arn'], + }; - expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.equal(roleLogicalId); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution).to.equal(null); + expect(() => { + awsCompileSQSEvents.compileSQSEvents(); + }).to.not.throw(Error); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.DependsOn + ).to.equal(roleLogicalId); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); }); it('should not throw error if custom IAM role name reference is set in provider', () => { @@ -259,17 +286,21 @@ describe('AwsCompileSQSEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution = null; + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - awsCompileSQSEvents.serverless.service.provider - .role = roleLogicalId; + awsCompileSQSEvents.serverless.service.provider.role = roleLogicalId; - expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.equal(roleLogicalId); - expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution).to.equal(null); + expect(() => { + awsCompileSQSEvents.compileSQSEvents(); + }).to.not.throw(Error); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.DependsOn + ).to.equal(roleLogicalId); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); }); describe('when a queue ARN is given', () => { @@ -299,81 +330,69 @@ describe('AwsCompileSQSEvents', () => { awsCompileSQSEvents.compileSQSEvents(); // event 1 - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyFirstQueue - .Type + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyFirstQueue.Type ).to.equal('AWS::Lambda::EventSourceMapping'); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyFirstQueue - .DependsOn + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyFirstQueue.DependsOn ).to.equal('IamRoleLambdaExecution'); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyFirstQueue - .Properties.EventSourceArn - ).to.equal( - awsCompileSQSEvents.serverless.service.functions.first.events[0] - .sqs.arn - ); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyFirstQueue - .Properties.BatchSize - ).to.equal( - awsCompileSQSEvents.serverless.service.functions.first.events[0] - .sqs.batchSize - ); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyFirstQueue - .Properties.Enabled + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyFirstQueue.Properties.EventSourceArn + ).to.equal(awsCompileSQSEvents.serverless.service.functions.first.events[0].sqs.arn); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyFirstQueue.Properties.BatchSize + ).to.equal(awsCompileSQSEvents.serverless.service.functions.first.events[0].sqs.batchSize); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyFirstQueue.Properties.Enabled ).to.equal('False'); // event 2 - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMySecondQueue - .Type + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMySecondQueue.Type ).to.equal('AWS::Lambda::EventSourceMapping'); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMySecondQueue - .DependsOn + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMySecondQueue.DependsOn ).to.equal('IamRoleLambdaExecution'); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMySecondQueue - .Properties.EventSourceArn - ).to.equal( - awsCompileSQSEvents.serverless.service.functions.first.events[1] - .sqs.arn - ); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMySecondQueue - .Properties.BatchSize + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMySecondQueue.Properties.EventSourceArn + ).to.equal(awsCompileSQSEvents.serverless.service.functions.first.events[1].sqs.arn); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMySecondQueue.Properties.BatchSize ).to.equal(10); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMySecondQueue - .Properties.Enabled + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMySecondQueue.Properties.Enabled ).to.equal('True'); // event 3 - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyThirdQueue - .Type + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyThirdQueue.Type ).to.equal('AWS::Lambda::EventSourceMapping'); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyThirdQueue - .DependsOn + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyThirdQueue.DependsOn ).to.equal('IamRoleLambdaExecution'); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyThirdQueue - .Properties.EventSourceArn - ).to.equal( - awsCompileSQSEvents.serverless.service.functions.first.events[2] - .sqs - ); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyThirdQueue - .Properties.BatchSize + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyThirdQueue.Properties.EventSourceArn + ).to.equal(awsCompileSQSEvents.serverless.service.functions.first.events[2].sqs); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyThirdQueue.Properties.BatchSize ).to.equal(10); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyThirdQueue - .Properties.Enabled + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyThirdQueue.Properties.Enabled ).to.equal('True'); }); @@ -395,8 +414,12 @@ describe('AwsCompileSQSEvents', () => { sqs: { arn: { 'Fn::Join': [ - ':', [ - 'arn', 'aws', 'sqs', { + ':', + [ + 'arn', + 'aws', + 'sqs', + { Ref: 'AWS::Region', }, { @@ -414,80 +437,66 @@ describe('AwsCompileSQSEvents', () => { awsCompileSQSEvents.compileSQSEvents(); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution - .Properties.Policies[0].PolicyDocument.Statement[0] - ).to.deep.equal( - { - Action: [ - 'sqs:ReceiveMessage', - 'sqs:DeleteMessage', - 'sqs:GetQueueAttributes', - ], - Effect: 'Allow', - Resource: [ - { - 'Fn::GetAtt': [ - 'SomeQueue', - 'Arn', + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement[0] + ).to.deep.equal({ + Action: ['sqs:ReceiveMessage', 'sqs:DeleteMessage', 'sqs:GetQueueAttributes'], + Effect: 'Allow', + Resource: [ + { + 'Fn::GetAtt': ['SomeQueue', 'Arn'], + }, + { + 'Fn::ImportValue': 'ForeignQueue', + }, + { + 'Fn::Join': [ + ':', + [ + 'arn', + 'aws', + 'sqs', + { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'MyQueue', ], - }, - { - 'Fn::ImportValue': 'ForeignQueue', - }, - { - 'Fn::Join': [ - ':', - [ - 'arn', - 'aws', - 'sqs', - { - Ref: 'AWS::Region', - }, - { - Ref: 'AWS::AccountId', - }, - 'MyQueue', - ], - ], - }, - ], - } - ); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstEventSourceMappingSQSSomeQueue.Properties.EventSourceArn - ).to.deep.equal( - { 'Fn::GetAtt': ['SomeQueue', 'Arn'] } - ); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstEventSourceMappingSQSForeignQueue.Properties.EventSourceArn - ).to.deep.equal( - { 'Fn::ImportValue': 'ForeignQueue' } - ); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstEventSourceMappingSQSMyQueue.Properties.EventSourceArn - ).to.deep.equal( - { - 'Fn::Join': [ - ':', - [ - 'arn', - 'aws', - 'sqs', - { - Ref: 'AWS::Region', - }, - { - Ref: 'AWS::AccountId', - }, - 'MyQueue', ], + }, + ], + }); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSSomeQueue.Properties.EventSourceArn + ).to.deep.equal({ 'Fn::GetAtt': ['SomeQueue', 'Arn'] }); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSForeignQueue.Properties.EventSourceArn + ).to.deep.equal({ 'Fn::ImportValue': 'ForeignQueue' }); + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSMyQueue.Properties.EventSourceArn + ).to.deep.equal({ + 'Fn::Join': [ + ':', + [ + 'arn', + 'aws', + 'sqs', + { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'MyQueue', ], - }); + ], + }); }); it('fails if keys other than Fn::GetAtt/ImportValue/Join are used for dynamic ARNs', () => { @@ -498,7 +507,7 @@ describe('AwsCompileSQSEvents', () => { sqs: { arn: { 'Fn::GetAtt': ['SomeQueue', 'Arn'], - batchSize: 1, + 'batchSize': 1, }, }, }, @@ -526,11 +535,7 @@ describe('AwsCompileSQSEvents', () => { const iamRoleStatements = [ { Effect: 'Allow', - Action: [ - 'sqs:ReceiveMessage', - 'sqs:DeleteMessage', - 'sqs:GetQueueAttributes', - ], + Action: ['sqs:ReceiveMessage', 'sqs:DeleteMessage', 'sqs:GetQueueAttributes'], Resource: [ 'arn:aws:sqs:region:account:MyFirstQueue', 'arn:aws:sqs:region:account:MySecondQueue', @@ -540,10 +545,9 @@ describe('AwsCompileSQSEvents', () => { awsCompileSQSEvents.compileSQSEvents(); - expect(awsCompileSQSEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution.Properties.Policies[0] - .PolicyDocument.Statement + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement ).to.deep.equal(iamRoleStatements); }); }); @@ -559,8 +563,9 @@ describe('AwsCompileSQSEvents', () => { // should be 1 because we've mocked the IamRoleLambdaExecution above expect( - Object.keys(awsCompileSQSEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources).length + Object.keys( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + ).length ).to.equal(1); }); @@ -574,10 +579,8 @@ describe('AwsCompileSQSEvents', () => { awsCompileSQSEvents.compileSQSEvents(); expect( - awsCompileSQSEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution.Properties.Policies[0] - .PolicyDocument.Statement.length + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement.length ).to.equal(0); }); @@ -594,8 +597,8 @@ describe('AwsCompileSQSEvents', () => { awsCompileSQSEvents.compileSQSEvents(); - expect(awsCompileSQSEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources + expect( + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources ).to.have.any.keys('FirstEventSourceMappingSQSSomequeuename'); }); }); diff --git a/lib/plugins/aws/package/compile/events/stream/index.js b/lib/plugins/aws/package/compile/events/stream/index.js index bf550a7ed..bf65ed3ad 100644 --- a/lib/plugins/aws/package/compile/events/stream/index.js +++ b/lib/plugins/aws/package/compile/events/stream/index.js @@ -13,7 +13,7 @@ class AwsCompileStreamEvents { } compileStreamEvents() { - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObj = this.serverless.service.getFunction(functionName); if (functionObj.events) { @@ -54,8 +54,7 @@ class AwsCompileStreamEvents { ' OR an object with an "arn" property.', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } if (typeof event.stream.arn !== 'string') { // for dynamic arns (GetAtt/ImportValue) @@ -66,28 +65,28 @@ class AwsCompileStreamEvents { ' then a "type" must be provided for the stream, either "kinesis" or,', ' "dynamodb". Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } - if (Object.keys(event.stream.arn).length !== 1 - || !(_.has(event.stream.arn, 'Fn::ImportValue') - || _.has(event.stream.arn, 'Fn::GetAtt') - || _.has(event.stream.arn, 'Fn::Join'))) { + if ( + Object.keys(event.stream.arn).length !== 1 || + !( + _.has(event.stream.arn, 'Fn::ImportValue') || + _.has(event.stream.arn, 'Fn::GetAtt') || + _.has(event.stream.arn, 'Fn::Join') + ) + ) { const errorMessage = [ `Bad dynamic ARN property on stream event in function "${functionName}"`, ' If you use a dynamic "arn" (such as with Fn::GetAtt, Fn::Join', ' or Fn::ImportValue) there must only be one key (either Fn::GetAtt, Fn::Join', ' or Fn::ImportValue) in the arn object. Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } } EventSourceArn = event.stream.arn; - BatchSize = event.stream.batchSize - || BatchSize; - StartingPosition = event.stream.startingPosition - || StartingPosition; + BatchSize = event.stream.batchSize || BatchSize; + StartingPosition = event.stream.startingPosition || StartingPosition; if (typeof event.stream.enabled !== 'undefined') { Enabled = event.stream.enabled ? 'True' : 'False'; } @@ -100,12 +99,11 @@ class AwsCompileStreamEvents { ' OR an object with an "arn" property.', ' Please check the docs for more info.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + throw new this.serverless.classes.Error(errorMessage); } const streamType = event.stream.type || EventSourceArn.split(':')[2]; - const streamName = (function () { + const streamName = (function() { if (EventSourceArn['Fn::GetAtt']) { return EventSourceArn['Fn::GetAtt'][0]; } else if (EventSourceArn['Fn::ImportValue']) { @@ -119,22 +117,26 @@ class AwsCompileStreamEvents { return name; } return EventSourceArn.split('/')[1]; - }()); + })(); - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); - const streamLogicalId = this.provider.naming - .getStreamLogicalId(functionName, streamType, streamName); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(functionName); + const streamLogicalId = this.provider.naming.getStreamLogicalId( + functionName, + streamType, + streamName + ); const funcRole = functionObj.role || this.serverless.service.provider.role; let dependsOn = '"IamRoleLambdaExecution"'; if (funcRole) { - if ( // check whether the custom role is an ARN + if ( + // check whether the custom role is an ARN typeof funcRole === 'string' && funcRole.indexOf(':') !== -1 ) { dependsOn = '[]'; - } else if ( // otherwise, check if we have an in-service reference to a role ARN + } else if ( + // otherwise, check if we have an in-service reference to a role ARN typeof funcRole === 'object' && 'Fn::GetAtt' in funcRole && Array.isArray(funcRole['Fn::GetAtt']) && @@ -144,7 +146,8 @@ class AwsCompileStreamEvents { funcRole['Fn::GetAtt'][1] === 'Arn' ) { dependsOn = `"${funcRole['Fn::GetAtt'][0]}"`; - } else if ( // otherwise, check if we have an import + } else if ( + // otherwise, check if we have an import typeof funcRole === 'object' && 'Fn::ImportValue' in funcRole ) { @@ -181,34 +184,31 @@ class AwsCompileStreamEvents { const errorMessage = [ `Stream event of function '${functionName}' had unsupported stream type of`, ` '${streamType}'. Valid stream event source types include 'dynamodb' and`, - ' \'kinesis\'. Please check the docs for more info.', + " 'kinesis'. Please check the docs for more info.", ].join(''); - throw new this.serverless.classes - .Properties - .Policies[0] - .PolicyDocument - .Error(errorMessage); + throw new this.serverless.classes.Properties.Policies[0].PolicyDocument.Error( + errorMessage + ); } const newStreamObject = { [streamLogicalId]: JSON.parse(streamTemplate), }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newStreamObject); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newStreamObject + ); } }); // update the PolicyDocument statements (if default policy is used) - if (this.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution) { - const statement = this.serverless.service.provider.compiledCloudFormationTemplate - .Resources + if ( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution - .Properties - .Policies[0] - .PolicyDocument - .Statement; + ) { + const statement = this.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement; if (dynamodbStreamStatement.Resource.length) { statement.push(dynamodbStreamStatement); } diff --git a/lib/plugins/aws/package/compile/events/stream/index.test.js b/lib/plugins/aws/package/compile/events/stream/index.test.js index cd15293da..b5d1b6666 100644 --- a/lib/plugins/aws/package/compile/events/stream/index.test.js +++ b/lib/plugins/aws/package/compile/events/stream/index.test.js @@ -96,14 +96,14 @@ describe('AwsCompileStreamEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileStreamEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution = null; + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); - expect(awsCompileStreamEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution + expect(() => { + awsCompileStreamEvents.compileStreamEvents(); + }).to.not.throw(Error); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution ).to.equal(null); }); @@ -121,18 +121,22 @@ describe('AwsCompileStreamEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileStreamEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution = null; + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn).to.be.instanceof(Array); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn.length).to.equal(0); - expect(awsCompileStreamEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution + expect(() => { + awsCompileStreamEvents.compileStreamEvents(); + }).to.not.throw(Error); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingDynamodbFoo.DependsOn + ).to.be.instanceof(Array); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingDynamodbFoo.DependsOn.length + ).to.equal(0); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution ).to.equal(null); }); @@ -151,14 +155,19 @@ describe('AwsCompileStreamEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution = null; + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn).to.equal(roleLogicalId); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution).to.equal(null); + expect(() => { + awsCompileStreamEvents.compileStreamEvents(); + }).to.not.throw(Error); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingDynamodbFoo.DependsOn + ).to.equal(roleLogicalId); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); }); it('should not throw error if custom IAM role reference is set in function', () => { @@ -176,14 +185,19 @@ describe('AwsCompileStreamEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution = null; + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn).to.equal(roleLogicalId); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution).to.equal(null); + expect(() => { + awsCompileStreamEvents.compileStreamEvents(); + }).to.not.throw(Error); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingDynamodbFoo.DependsOn + ).to.equal(roleLogicalId); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); }); it('should not throw error if custom IAM role is set in provider', () => { @@ -199,21 +213,24 @@ describe('AwsCompileStreamEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileStreamEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution = null; + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - awsCompileStreamEvents.serverless.service.provider - .role = 'arn:aws:iam::account:role/foo'; + awsCompileStreamEvents.serverless.service.provider.role = 'arn:aws:iam::account:role/foo'; - expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn).to.be.instanceof(Array); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn.length).to.equal(0); - expect(awsCompileStreamEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution + expect(() => { + awsCompileStreamEvents.compileStreamEvents(); + }).to.not.throw(Error); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingDynamodbFoo.DependsOn + ).to.be.instanceof(Array); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingDynamodbFoo.DependsOn.length + ).to.equal(0); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution ).to.equal(null); }); @@ -231,17 +248,21 @@ describe('AwsCompileStreamEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution = null; + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn.length).to.equal(0); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution).to.equal(null); + expect(() => { + awsCompileStreamEvents.compileStreamEvents(); + }).to.not.throw(Error); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingDynamodbFoo.DependsOn.length + ).to.equal(0); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); }); - it('should not throw error if custom IAM role reference is set in provider', () => { const roleLogicalId = 'RoleLogicalId'; awsCompileStreamEvents.serverless.service.functions = { @@ -256,17 +277,23 @@ describe('AwsCompileStreamEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution = null; + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - awsCompileStreamEvents.serverless.service.provider - .role = { 'Fn::GetAtt': [roleLogicalId, 'Arn'] }; + awsCompileStreamEvents.serverless.service.provider.role = { + 'Fn::GetAtt': [roleLogicalId, 'Arn'], + }; - expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn).to.equal(roleLogicalId); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution).to.equal(null); + expect(() => { + awsCompileStreamEvents.compileStreamEvents(); + }).to.not.throw(Error); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingDynamodbFoo.DependsOn + ).to.equal(roleLogicalId); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); }); it('should not throw error if custom IAM role name reference is set in provider', () => { @@ -283,17 +310,21 @@ describe('AwsCompileStreamEvents', () => { }; // pretend that the default IamRoleLambdaExecution is not in place - awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution = null; + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; - awsCompileStreamEvents.serverless.service.provider - .role = roleLogicalId; + awsCompileStreamEvents.serverless.service.provider.role = roleLogicalId; - expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn).to.equal(roleLogicalId); - expect(awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution).to.equal(null); + expect(() => { + awsCompileStreamEvents.compileStreamEvents(); + }).to.not.throw(Error); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingDynamodbFoo.DependsOn + ).to.equal(roleLogicalId); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); }); describe('when a DynamoDB stream ARN is given', () => { @@ -324,96 +355,86 @@ describe('AwsCompileStreamEvents', () => { awsCompileStreamEvents.compileStreamEvents(); // event 1 - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbFoo - .Type + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbFoo.Type ).to.equal('AWS::Lambda::EventSourceMapping'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbFoo - .DependsOn + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn ).to.equal('IamRoleLambdaExecution'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbFoo - .Properties.EventSourceArn + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbFoo.Properties.EventSourceArn + ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[0].stream.arn); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbFoo.Properties.BatchSize ).to.equal( - awsCompileStreamEvents.serverless.service.functions.first.events[0] - .stream.arn + awsCompileStreamEvents.serverless.service.functions.first.events[0].stream.batchSize ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbFoo - .Properties.BatchSize + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbFoo.Properties.StartingPosition ).to.equal( - awsCompileStreamEvents.serverless.service.functions.first.events[0] - .stream.batchSize + awsCompileStreamEvents.serverless.service.functions.first.events[0].stream + .startingPosition ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbFoo - .Properties.StartingPosition - ).to.equal( - awsCompileStreamEvents.serverless.service.functions.first.events[0] - .stream.startingPosition - ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbFoo - .Properties.Enabled + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbFoo.Properties.Enabled ).to.equal('False'); // event 2 - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBar - .Type + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBar.Type ).to.equal('AWS::Lambda::EventSourceMapping'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBar - .DependsOn + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBar.DependsOn ).to.equal('IamRoleLambdaExecution'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBar - .Properties.EventSourceArn - ).to.equal( - awsCompileStreamEvents.serverless.service.functions.first.events[1] - .stream.arn - ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBar - .Properties.BatchSize + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBar.Properties.EventSourceArn + ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[1].stream.arn); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBar.Properties.BatchSize ).to.equal(10); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBar - .Properties.StartingPosition + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBar.Properties.StartingPosition ).to.equal('TRIM_HORIZON'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBar - .Properties.Enabled + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBar.Properties.Enabled ).to.equal('True'); // event 3 - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBaz - .Type + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBaz.Type ).to.equal('AWS::Lambda::EventSourceMapping'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBaz - .DependsOn + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBaz.DependsOn ).to.equal('IamRoleLambdaExecution'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBaz - .Properties.EventSourceArn - ).to.equal( - awsCompileStreamEvents.serverless.service.functions.first.events[2] - .stream - ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBaz - .Properties.BatchSize + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBaz.Properties.EventSourceArn + ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[2].stream); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBaz.Properties.BatchSize ).to.equal(10); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBaz - .Properties.StartingPosition + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBaz.Properties.StartingPosition ).to.equal('TRIM_HORIZON'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingDynamodbBaz - .Properties.Enabled + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbBaz.Properties.Enabled ).to.equal('True'); }); @@ -437,10 +458,15 @@ describe('AwsCompileStreamEvents', () => { stream: { arn: { 'Fn::Join': [ - ':', [ - 'arn', 'aws', 'kinesis', { + ':', + [ + 'arn', + 'aws', + 'kinesis', + { Ref: 'AWS::Region', - }, { + }, + { Ref: 'AWS::AccountId', }, 'stream/MyStream', @@ -457,64 +483,55 @@ describe('AwsCompileStreamEvents', () => { awsCompileStreamEvents.compileStreamEvents(); // dynamodb with Fn::GetAtt - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstEventSourceMappingDynamodbSomeDdbTable.Properties.EventSourceArn - ).to.deep.equal( - { 'Fn::GetAtt': ['SomeDdbTable', 'StreamArn'] } - ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution - .Properties.Policies[0].PolicyDocument.Statement[0] - ).to.deep.equal( - { - Action: [ - 'dynamodb:GetRecords', - 'dynamodb:GetShardIterator', - 'dynamodb:DescribeStream', - 'dynamodb:ListStreams', - ], - Effect: 'Allow', - Resource: [ - { - 'Fn::GetAtt': [ - 'SomeDdbTable', - 'StreamArn', - ], - }, - ], - } - ); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingDynamodbSomeDdbTable.Properties.EventSourceArn + ).to.deep.equal({ 'Fn::GetAtt': ['SomeDdbTable', 'StreamArn'] }); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement[0] + ).to.deep.equal({ + Action: [ + 'dynamodb:GetRecords', + 'dynamodb:GetShardIterator', + 'dynamodb:DescribeStream', + 'dynamodb:ListStreams', + ], + Effect: 'Allow', + Resource: [ + { + 'Fn::GetAtt': ['SomeDdbTable', 'StreamArn'], + }, + ], + }); // kinesis with Fn::ImportValue - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstEventSourceMappingKinesisForeignKinesis.Properties.EventSourceArn - ).to.deep.equal( - { 'Fn::ImportValue': 'ForeignKinesis' } - ); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisForeignKinesis.Properties.EventSourceArn + ).to.deep.equal({ 'Fn::ImportValue': 'ForeignKinesis' }); // kinesis with Fn::Join - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources - .FirstEventSourceMappingKinesisMyStream.Properties.EventSourceArn - ).to.deep.equal( - { - 'Fn::Join': [ - ':', [ - 'arn', - 'aws', - 'kinesis', - { - Ref: 'AWS::Region', - }, { - Ref: 'AWS::AccountId', - }, - 'stream/MyStream', - ], + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisMyStream.Properties.EventSourceArn + ).to.deep.equal({ + 'Fn::Join': [ + ':', + [ + 'arn', + 'aws', + 'kinesis', + { + Ref: 'AWS::Region', + }, + { + Ref: 'AWS::AccountId', + }, + 'stream/MyStream', ], - } - ); + ], + }); }); it('fails if Fn::GetAtt/dynamic stream ARN is used without a type', () => { @@ -533,26 +550,25 @@ describe('AwsCompileStreamEvents', () => { expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); - it('fails if keys other than Fn::GetAtt/ImportValue/Join are used for dynamic stream ARN', - () => { - awsCompileStreamEvents.serverless.service.functions = { - first: { - events: [ - { - stream: { - type: 'dynamodb', - arn: { - 'Fn::GetAtt': ['SomeDdbTable', 'StreamArn'], - batchSize: 1, - }, + it('fails if keys other than Fn::GetAtt/ImportValue/Join are used for dynamic stream ARN', () => { + awsCompileStreamEvents.serverless.service.functions = { + first: { + events: [ + { + stream: { + type: 'dynamodb', + arn: { + 'Fn::GetAtt': ['SomeDdbTable', 'StreamArn'], + 'batchSize': 1, }, }, - ], - }, - }; + }, + ], + }, + }; - expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); - }); + expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); + }); it('should add the necessary IAM role statements', () => { awsCompileStreamEvents.serverless.service.functions = { @@ -586,10 +602,9 @@ describe('AwsCompileStreamEvents', () => { awsCompileStreamEvents.compileStreamEvents(); - expect(awsCompileStreamEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution.Properties.Policies[0] - .PolicyDocument.Statement + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement ).to.deep.equal(iamRoleStatements); }); }); @@ -622,96 +637,86 @@ describe('AwsCompileStreamEvents', () => { awsCompileStreamEvents.compileStreamEvents(); // event 1 - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisFoo - .Type + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisFoo.Type ).to.equal('AWS::Lambda::EventSourceMapping'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisFoo - .DependsOn + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisFoo.DependsOn ).to.equal('IamRoleLambdaExecution'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisFoo - .Properties.EventSourceArn + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisFoo.Properties.EventSourceArn + ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[0].stream.arn); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisFoo.Properties.BatchSize ).to.equal( - awsCompileStreamEvents.serverless.service.functions.first.events[0] - .stream.arn + awsCompileStreamEvents.serverless.service.functions.first.events[0].stream.batchSize ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisFoo - .Properties.BatchSize + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisFoo.Properties.StartingPosition ).to.equal( - awsCompileStreamEvents.serverless.service.functions.first.events[0] - .stream.batchSize + awsCompileStreamEvents.serverless.service.functions.first.events[0].stream + .startingPosition ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisFoo - .Properties.StartingPosition - ).to.equal( - awsCompileStreamEvents.serverless.service.functions.first.events[0] - .stream.startingPosition - ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisFoo - .Properties.Enabled + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisFoo.Properties.Enabled ).to.equal('False'); // event 2 - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBar - .Type + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBar.Type ).to.equal('AWS::Lambda::EventSourceMapping'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBar - .DependsOn + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBar.DependsOn ).to.equal('IamRoleLambdaExecution'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBar - .Properties.EventSourceArn - ).to.equal( - awsCompileStreamEvents.serverless.service.functions.first.events[1] - .stream.arn - ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBar - .Properties.BatchSize + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBar.Properties.EventSourceArn + ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[1].stream.arn); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBar.Properties.BatchSize ).to.equal(10); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBar - .Properties.StartingPosition + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBar.Properties.StartingPosition ).to.equal('TRIM_HORIZON'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBar - .Properties.Enabled + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBar.Properties.Enabled ).to.equal('True'); // event 3 - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBaz - .Type + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBaz.Type ).to.equal('AWS::Lambda::EventSourceMapping'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBaz - .DependsOn + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBaz.DependsOn ).to.equal('IamRoleLambdaExecution'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBaz - .Properties.EventSourceArn - ).to.equal( - awsCompileStreamEvents.serverless.service.functions.first.events[2] - .stream - ); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBaz - .Properties.BatchSize + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBaz.Properties.EventSourceArn + ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[2].stream); + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBaz.Properties.BatchSize ).to.equal(10); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBaz - .Properties.StartingPosition + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBaz.Properties.StartingPosition ).to.equal('TRIM_HORIZON'); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingKinesisBaz - .Properties.Enabled + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingKinesisBaz.Properties.Enabled ).to.equal('True'); }); @@ -747,10 +752,9 @@ describe('AwsCompileStreamEvents', () => { awsCompileStreamEvents.compileStreamEvents(); - expect(awsCompileStreamEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution.Properties.Policies[0] - .PolicyDocument.Statement + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement ).to.deep.equal(iamRoleStatements); }); }); @@ -766,8 +770,10 @@ describe('AwsCompileStreamEvents', () => { // should be 1 because we've mocked the IamRoleLambdaExecution above expect( - Object.keys(awsCompileStreamEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources).length + Object.keys( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources + ).length ).to.equal(1); }); @@ -781,10 +787,8 @@ describe('AwsCompileStreamEvents', () => { awsCompileStreamEvents.compileStreamEvents(); expect( - awsCompileStreamEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources - .IamRoleLambdaExecution.Properties.Policies[0] - .PolicyDocument.Statement.length + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement.length ).to.equal(0); }); @@ -801,8 +805,8 @@ describe('AwsCompileStreamEvents', () => { awsCompileStreamEvents.compileStreamEvents(); - expect(awsCompileStreamEvents.serverless.service - .provider.compiledCloudFormationTemplate.Resources + expect( + awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources ).to.have.any.keys('FirstEventSourceMappingKinesisSomelongname'); }); }); diff --git a/lib/plugins/aws/package/compile/events/websockets/index.test.js b/lib/plugins/aws/package/compile/events/websockets/index.test.js index ad3826e25..ee1978186 100644 --- a/lib/plugins/aws/package/compile/events/websockets/index.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/index.test.js @@ -42,20 +42,21 @@ describe('AwsCompileWebsocketsEvents', () => { let compileStageStub; beforeEach(() => { - compileApiStub = sinon - .stub(awsCompileWebsocketsEvents, 'compileApi').resolves(); + compileApiStub = sinon.stub(awsCompileWebsocketsEvents, 'compileApi').resolves(); compileIntegrationsStub = sinon - .stub(awsCompileWebsocketsEvents, 'compileIntegrations').resolves(); + .stub(awsCompileWebsocketsEvents, 'compileIntegrations') + .resolves(); compileAuthorizersStub = sinon - .stub(awsCompileWebsocketsEvents, 'compileAuthorizers').resolves(); + .stub(awsCompileWebsocketsEvents, 'compileAuthorizers') + .resolves(); compilePermissionsStub = sinon - .stub(awsCompileWebsocketsEvents, 'compilePermissions').resolves(); - compileRoutesStub = sinon - .stub(awsCompileWebsocketsEvents, 'compileRoutes').resolves(); + .stub(awsCompileWebsocketsEvents, 'compilePermissions') + .resolves(); + compileRoutesStub = sinon.stub(awsCompileWebsocketsEvents, 'compileRoutes').resolves(); compileDeploymentStub = sinon - .stub(awsCompileWebsocketsEvents, 'compileDeployment').resolves(); - compileStageStub = sinon - .stub(awsCompileWebsocketsEvents, 'compileStage').resolves(); + .stub(awsCompileWebsocketsEvents, 'compileDeployment') + .resolves(); + compileStageStub = sinon.stub(awsCompileWebsocketsEvents, 'compileStage').resolves(); }); afterEach(() => { @@ -79,17 +80,16 @@ describe('AwsCompileWebsocketsEvents', () => { }); it('should run the promise chain in order', () => { - const validateStub = sinon - .stub(awsCompileWebsocketsEvents, 'validate').returns({ - events: [ - { - functionName: 'first', - websocket: { - route: 'echo', - }, + const validateStub = sinon.stub(awsCompileWebsocketsEvents, 'validate').returns({ + events: [ + { + functionName: 'first', + websocket: { + route: 'echo', }, - ], - }); + }, + ], + }); return awsCompileWebsocketsEvents.hooks['package:compileEvents']().then(() => { expect(validateStub.calledOnce).to.be.equal(true); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/api.js b/lib/plugins/aws/package/compile/events/websockets/lib/api.js index 50212050c..5ec6bb1e1 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/api.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/api.js @@ -14,8 +14,9 @@ module.exports = { this.websocketsApiLogicalId = this.provider.naming.getWebsocketsApiLogicalId(); - const RouteSelectionExpression = this.serverless.service.provider - .websocketsApiRouteSelectionExpression || '$request.body.action'; + const RouteSelectionExpression = + this.serverless.service.provider.websocketsApiRouteSelectionExpression || + '$request.body.action'; _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [this.websocketsApiLogicalId]: { @@ -23,8 +24,8 @@ module.exports = { Properties: { Name: this.provider.naming.getWebsocketsApiName(), RouteSelectionExpression, - Description: this.serverless.service.provider - .websocketsDescription || 'Serverless Websockets', + Description: + this.serverless.service.provider.websocketsDescription || 'Serverless Websockets', ProtocolType: 'WEBSOCKET', }, }, @@ -41,13 +42,9 @@ module.exports = { Resource: ['arn:aws:execute-api:*:*:*/@connections/*'], }; - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement - .push(websocketsPolicy); + this.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + this.provider.naming.getRoleLogicalId() + ].Properties.Policies[0].PolicyDocument.Statement.push(websocketsPolicy); } return BbPromise.resolve(); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js index fbe7242f0..86328168a 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js @@ -18,26 +18,26 @@ describe('#compileApi()', () => { awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); roleLogicalId = awsCompileWebsocketsEvents.provider.naming.getRoleLogicalId(); - awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources = { - [roleLogicalId]: { - Properties: { - Policies: [ - { - PolicyDocument: { - Statement: [], - }, + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources = { + [roleLogicalId]: { + Properties: { + Policies: [ + { + PolicyDocument: { + Statement: [], }, - ], - }, + }, + ], }, - }; + }, + }; }); - it('should create a websocket api resource', () => awsCompileWebsocketsEvents - .compileApi().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + it('should create a websocket api resource', () => + awsCompileWebsocketsEvents.compileApi().then(() => { + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources.WebsocketsApi).to.deep.equal({ Type: 'AWS::ApiGatewayV2::Api', @@ -55,17 +55,19 @@ describe('#compileApi()', () => { websocketApiId: '5ezys3sght', }; return awsCompileWebsocketsEvents.compileApi().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources).to.not.have.property('WebsocketsApi'); }); }); - it('should add the websockets policy', () => awsCompileWebsocketsEvents - .compileApi().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + it('should add the websockets policy', () => + awsCompileWebsocketsEvents.compileApi().then(() => { + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources[roleLogicalId]).to.deep.equal({ Properties: { @@ -74,13 +76,9 @@ describe('#compileApi()', () => { PolicyDocument: { Statement: [ { - Action: [ - 'execute-api:ManageConnections', - ], + Action: ['execute-api:ManageConnections'], Effect: 'Allow', - Resource: [ - 'arn:aws:execute-api:*:*:*/@connections/*', - ], + Resource: ['arn:aws:execute-api:*:*:*/@connections/*'], }, ], }, @@ -91,15 +89,14 @@ describe('#compileApi()', () => { })); it('should NOT add the websockets policy if role resource does not exist', () => { - awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources = {}; + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources = {}; - return awsCompileWebsocketsEvents - .compileApi().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + return awsCompileWebsocketsEvents.compileApi().then(() => { + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; - expect(resources[roleLogicalId]).to.deep.equal(undefined); - }); + expect(resources[roleLogicalId]).to.deep.equal(undefined); + }); }); }); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js index 48f413080..ba277f7a7 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.js @@ -9,8 +9,9 @@ module.exports = { if (!event.authorizer) { return; } - const websocketsAuthorizerLogicalId = this.provider.naming - .getWebsocketsAuthorizerLogicalId(event.authorizer.name); + const websocketsAuthorizerLogicalId = this.provider.naming.getWebsocketsAuthorizerLogicalId( + event.authorizer.name + ); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [websocketsAuthorizerLogicalId]: { diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js index 5728ae0b6..697dd283f 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js @@ -16,8 +16,7 @@ describe('#compileAuthorizers()', () => { awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); - awsCompileWebsocketsEvents.websocketsApiLogicalId - = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + awsCompileWebsocketsEvents.websocketsApiLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); awsCompileWebsocketsEvents.validated = { events: [ @@ -27,7 +26,8 @@ describe('#compileAuthorizers()', () => { authorizer: { name: 'auth', uri: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -48,8 +48,9 @@ describe('#compileAuthorizers()', () => { it('should create an authorizer resource', () => { return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources).to.deep.equal({ AuthWebsocketsAuthorizer: { @@ -74,10 +75,7 @@ describe('#compileAuthorizers()', () => { }, ':lambda:path/2015-03-31/functions/', { - 'Fn::GetAtt': [ - 'AuthLambdaFunction', - 'Arn', - ], + 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'], }, '/invocations', ], @@ -96,8 +94,9 @@ describe('#compileAuthorizers()', () => { }; return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources.AuthWebsocketsAuthorizer.Properties).to.contain({ ApiId: '5ezys3sght', @@ -114,8 +113,7 @@ describe('#compileAuthorizers()', () => { awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); - awsCompileWebsocketsEvents.websocketsApiLogicalId - = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + awsCompileWebsocketsEvents.websocketsApiLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); awsCompileWebsocketsEvents.validated = { events: [ @@ -138,8 +136,9 @@ describe('#compileAuthorizers()', () => { }; return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources).to.deep.equal({}); }); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/deployment.js b/lib/plugins/aws/package/compile/events/websockets/lib/deployment.js index 144c5d8a1..f7ced266e 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/deployment.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/deployment.js @@ -6,12 +6,12 @@ const BbPromise = require('bluebird'); module.exports = { compileDeployment() { const routeLogicalIds = this.validated.events.map(event => { - const routeLogicalId = this.provider.naming - .getWebsocketsRouteLogicalId(event.route); + const routeLogicalId = this.provider.naming.getWebsocketsRouteLogicalId(event.route); return routeLogicalId; }); - this.websocketsDeploymentLogicalId = this.provider.naming - .getWebsocketsDeploymentLogicalId(this.serverless.instanceId); + this.websocketsDeploymentLogicalId = this.provider.naming.getWebsocketsDeploymentLogicalId( + this.serverless.instanceId + ); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [this.websocketsDeploymentLogicalId]: { @@ -19,8 +19,8 @@ module.exports = { DependsOn: routeLogicalIds, Properties: { ApiId: this.provider.getApiGatewayWebsocketApiId(), - Description: this.serverless.service.provider - .websocketsDescription || 'Serverless Websockets', + Description: + this.serverless.service.provider.websocketsDescription || 'Serverless Websockets', }, }, }); @@ -29,7 +29,8 @@ module.exports = { ServiceEndpointWebsocket: { Description: 'URL of the service endpoint', Value: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'wss://', this.provider.getApiGatewayWebsocketApiId(), diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/deployment.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/deployment.test.js index 5137cafdb..1e5876eb9 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/deployment.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/deployment.test.js @@ -18,8 +18,7 @@ describe('#compileDeployment()', () => { awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); - awsCompileWebsocketsEvents.websocketsApiLogicalId - = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + awsCompileWebsocketsEvents.websocketsApiLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); }); it('should create a deployment resource and output', () => { @@ -37,20 +36,19 @@ describe('#compileDeployment()', () => { }; return awsCompileWebsocketsEvents.compileDeployment().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; - const outputs = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Outputs; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; + const outputs = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Outputs; const deploymentLogicalId = Object.keys(resources)[0]; expect(deploymentLogicalId).to.match(/WebsocketsDeployment/); expect(resources[deploymentLogicalId]).to.deep.equal({ Type: 'AWS::ApiGatewayV2::Deployment', - DependsOn: [ - 'SconnectWebsocketsRoute', - 'SdisconnectWebsocketsRoute', - ], + DependsOn: ['SconnectWebsocketsRoute', 'SdisconnectWebsocketsRoute'], Properties: { ApiId: { Ref: 'WebsocketsApi', diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/integrations.js b/lib/plugins/aws/package/compile/events/websockets/lib/integrations.js index 1dd44c520..5eaf0d25b 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/integrations.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/integrations.js @@ -6,8 +6,9 @@ const BbPromise = require('bluebird'); module.exports = { compileIntegrations() { this.validated.events.forEach(event => { - const websocketsIntegrationLogicalId = this.provider.naming - .getWebsocketsIntegrationLogicalId(event.functionName); + const websocketsIntegrationLogicalId = this.provider.naming.getWebsocketsIntegrationLogicalId( + event.functionName + ); const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(event.functionName); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { @@ -17,7 +18,8 @@ module.exports = { ApiId: this.provider.getApiGatewayWebsocketApiId(), IntegrationType: 'AWS_PROXY', IntegrationUri: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/integrations.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/integrations.test.js index 021d07342..50613492a 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/integrations.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/integrations.test.js @@ -15,8 +15,7 @@ describe('#compileIntegrations()', () => { awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); - awsCompileWebsocketsEvents.websocketsApiLogicalId - = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + awsCompileWebsocketsEvents.websocketsApiLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); }); it('should create an integration resource for every event', () => { @@ -34,8 +33,9 @@ describe('#compileIntegrations()', () => { }; return awsCompileWebsocketsEvents.compileIntegrations().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources).to.deep.equal({ FirstWebsocketsIntegration: { @@ -59,11 +59,7 @@ describe('#compileIntegrations()', () => { }, ':lambda:path/2015-03-31/functions/', { - 'Fn::GetAtt': - [ - 'FirstLambdaFunction', - 'Arn', - ], + 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'], }, '/invocations', ], @@ -92,10 +88,7 @@ describe('#compileIntegrations()', () => { }, ':lambda:path/2015-03-31/functions/', { - 'Fn::GetAtt': [ - 'SecondLambdaFunction', - 'Arn', - ], + 'Fn::GetAtt': ['SecondLambdaFunction', 'Arn'], }, '/invocations', ], diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js index 08833474f..6789194d4 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.js @@ -9,15 +9,17 @@ module.exports = { const websocketApiId = this.provider.getApiGatewayWebsocketApiId(); const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(event.functionName); - const websocketsPermissionLogicalId = this.provider.naming - .getLambdaWebsocketsPermissionLogicalId(event.functionName); + const websocketsPermissionLogicalId = this.provider.naming.getLambdaWebsocketsPermissionLogicalId( + event.functionName + ); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [websocketsPermissionLogicalId]: { Type: 'AWS::Lambda::Permission', - DependsOn: (websocketApiId.Ref !== undefined) - ? [websocketApiId.Ref, lambdaLogicalId] - : [lambdaLogicalId], + DependsOn: + websocketApiId.Ref !== undefined + ? [websocketApiId.Ref, lambdaLogicalId] + : [lambdaLogicalId], Properties: { FunctionName: { 'Fn::GetAtt': [lambdaLogicalId, 'Arn'], @@ -29,15 +31,14 @@ module.exports = { }); if (event.authorizer) { - const websocketsAuthorizerPermissionLogicalId = this.provider.naming - .getLambdaWebsocketsPermissionLogicalId(event.authorizer.name); + const websocketsAuthorizerPermissionLogicalId = this.provider.naming.getLambdaWebsocketsPermissionLogicalId( + event.authorizer.name + ); const authorizerPermissionTemplate = { [websocketsAuthorizerPermissionLogicalId]: { Type: 'AWS::Lambda::Permission', - DependsOn: (websocketApiId.Ref !== undefined) - ? [websocketApiId.Ref] - : [], + DependsOn: websocketApiId.Ref !== undefined ? [websocketApiId.Ref] : [], Properties: { Action: 'lambda:InvokeFunction', Principal: 'apigateway.amazonaws.com', @@ -46,20 +47,25 @@ module.exports = { }; if (event.authorizer.permission.includes(':')) { - authorizerPermissionTemplate[websocketsAuthorizerPermissionLogicalId] - .Properties.FunctionName = event.authorizer.permission; + authorizerPermissionTemplate[ + websocketsAuthorizerPermissionLogicalId + ].Properties.FunctionName = event.authorizer.permission; } else { - authorizerPermissionTemplate[websocketsAuthorizerPermissionLogicalId] - .Properties.FunctionName = { - 'Fn::GetAtt': [event.authorizer.permission, 'Arn'], - }; + authorizerPermissionTemplate[ + websocketsAuthorizerPermissionLogicalId + ].Properties.FunctionName = { + 'Fn::GetAtt': [event.authorizer.permission, 'Arn'], + }; - authorizerPermissionTemplate[websocketsAuthorizerPermissionLogicalId] - .DependsOn.push(event.authorizer.permission); + authorizerPermissionTemplate[websocketsAuthorizerPermissionLogicalId].DependsOn.push( + event.authorizer.permission + ); } - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - authorizerPermissionTemplate); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + authorizerPermissionTemplate + ); } }); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js index 2f96f2c56..4d9605d0f 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js @@ -15,8 +15,7 @@ describe('#compilePermissions()', () => { awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); - awsCompileWebsocketsEvents.websocketsApiLogicalId - = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + awsCompileWebsocketsEvents.websocketsApiLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); }); it('should create a permission resource for every event', () => { @@ -34,21 +33,17 @@ describe('#compilePermissions()', () => { }; return awsCompileWebsocketsEvents.compilePermissions().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources).to.deep.equal({ FirstLambdaPermissionWebsockets: { Type: 'AWS::Lambda::Permission', - DependsOn: [ - 'WebsocketsApi', - 'FirstLambdaFunction', - ], + DependsOn: ['WebsocketsApi', 'FirstLambdaFunction'], Properties: { FunctionName: { - 'Fn::GetAtt': [ - 'FirstLambdaFunction', 'Arn', - ], + 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'], }, Action: 'lambda:InvokeFunction', Principal: 'apigateway.amazonaws.com', @@ -56,16 +51,10 @@ describe('#compilePermissions()', () => { }, SecondLambdaPermissionWebsockets: { Type: 'AWS::Lambda::Permission', - DependsOn: [ - 'WebsocketsApi', - 'SecondLambdaFunction', - ], + DependsOn: ['WebsocketsApi', 'SecondLambdaFunction'], Properties: { FunctionName: { - 'Fn::GetAtt': [ - 'SecondLambdaFunction', - 'Arn', - ], + 'Fn::GetAtt': ['SecondLambdaFunction', 'Arn'], }, Action: 'lambda:InvokeFunction', Principal: 'apigateway.amazonaws.com', @@ -90,21 +79,17 @@ describe('#compilePermissions()', () => { }; return awsCompileWebsocketsEvents.compilePermissions().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources).to.deep.equal({ FirstLambdaPermissionWebsockets: { Type: 'AWS::Lambda::Permission', - DependsOn: [ - 'WebsocketsApi', - 'FirstLambdaFunction', - ], + DependsOn: ['WebsocketsApi', 'FirstLambdaFunction'], Properties: { FunctionName: { - 'Fn::GetAtt': [ - 'FirstLambdaFunction', 'Arn', - ], + 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'], }, Action: 'lambda:InvokeFunction', Principal: 'apigateway.amazonaws.com', @@ -112,16 +97,10 @@ describe('#compilePermissions()', () => { }, AuthLambdaPermissionWebsockets: { Type: 'AWS::Lambda::Permission', - DependsOn: [ - 'WebsocketsApi', - 'AuthLambdaPermissionWebsockets', - ], + DependsOn: ['WebsocketsApi', 'AuthLambdaPermissionWebsockets'], Properties: { FunctionName: { - 'Fn::GetAtt': [ - 'AuthLambdaPermissionWebsockets', - 'Arn', - ], + 'Fn::GetAtt': ['AuthLambdaPermissionWebsockets', 'Arn'], }, Action: 'lambda:InvokeFunction', Principal: 'apigateway.amazonaws.com', diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/routes.js b/lib/plugins/aws/package/compile/events/websockets/lib/routes.js index 2817e624b..c88f32f89 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/routes.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/routes.js @@ -6,11 +6,13 @@ const BbPromise = require('bluebird'); module.exports = { compileRoutes() { this.validated.events.forEach(event => { - const websocketsIntegrationLogicalId = this.provider.naming - .getWebsocketsIntegrationLogicalId(event.functionName); + const websocketsIntegrationLogicalId = this.provider.naming.getWebsocketsIntegrationLogicalId( + event.functionName + ); - const websocketsRouteLogicalId = this.provider.naming - .getWebsocketsRouteLogicalId(event.route); + const websocketsRouteLogicalId = this.provider.naming.getWebsocketsRouteLogicalId( + event.route + ); const routeTemplate = { [websocketsRouteLogicalId]: { @@ -20,12 +22,7 @@ module.exports = { RouteKey: event.route, AuthorizationType: 'NONE', Target: { - 'Fn::Join': ['/', - [ - 'integrations', - { Ref: websocketsIntegrationLogicalId }, - ], - ], + 'Fn::Join': ['/', ['integrations', { Ref: websocketsIntegrationLogicalId }]], }, }, }, @@ -34,12 +31,13 @@ module.exports = { if (event.authorizer) { routeTemplate[websocketsRouteLogicalId].Properties.AuthorizationType = 'CUSTOM'; routeTemplate[websocketsRouteLogicalId].Properties.AuthorizerId = { - Ref: this.provider.naming - .getWebsocketsAuthorizerLogicalId(event.authorizer.name), + Ref: this.provider.naming.getWebsocketsAuthorizerLogicalId(event.authorizer.name), }; } - _.merge(this.serverless.service.provider - .compiledCloudFormationTemplate.Resources, routeTemplate); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + routeTemplate + ); }); return BbPromise.resolve(); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js index 5a4fb203a..7a3d1ee3a 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js @@ -15,8 +15,7 @@ describe('#compileRoutes()', () => { awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); - awsCompileWebsocketsEvents.websocketsApiLogicalId - = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + awsCompileWebsocketsEvents.websocketsApiLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); }); it('should create a route resource for every event', () => { @@ -34,8 +33,9 @@ describe('#compileRoutes()', () => { }; return awsCompileWebsocketsEvents.compileRoutes().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources).to.deep.equal({ SconnectWebsocketsRoute: { @@ -50,7 +50,8 @@ describe('#compileRoutes()', () => { 'Fn::Join': [ '/', [ - 'integrations', { + 'integrations', + { Ref: 'FirstWebsocketsIntegration', }, ], @@ -97,8 +98,9 @@ describe('#compileRoutes()', () => { }; return awsCompileWebsocketsEvents.compileRoutes().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources).to.deep.equal({ SconnectWebsocketsRoute: { @@ -110,14 +112,16 @@ describe('#compileRoutes()', () => { RouteKey: '$connect', AuthorizationType: 'CUSTOM', AuthorizerId: { - Ref: awsCompileWebsocketsEvents.provider.naming - .getWebsocketsAuthorizerLogicalId('auth'), + Ref: awsCompileWebsocketsEvents.provider.naming.getWebsocketsAuthorizerLogicalId( + 'auth' + ), }, Target: { 'Fn::Join': [ '/', [ - 'integrations', { + 'integrations', + { Ref: 'FirstWebsocketsIntegration', }, ], diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/stage.js b/lib/plugins/aws/package/compile/events/websockets/lib/stage.js index 2ee8ad2f0..71c3498e6 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/stage.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/stage.js @@ -11,14 +11,10 @@ module.exports = { // logs const logsEnabled = provider.logs && provider.logs.websocket; - const stageLogicalId = this.provider.naming - .getWebsocketsStageLogicalId(); - const logGroupLogicalId = this.provider.naming - .getWebsocketsLogGroupLogicalId(); - const logsRoleLogicalId = this.provider.naming - .getWebsocketsLogsRoleLogicalId(); - const accountLogicalid = this.provider.naming - .getWebsocketsAccountLogicalId(); + const stageLogicalId = this.provider.naming.getWebsocketsStageLogicalId(); + const logGroupLogicalId = this.provider.naming.getWebsocketsLogGroupLogicalId(); + const logsRoleLogicalId = this.provider.naming.getWebsocketsLogsRoleLogicalId(); + const accountLogicalid = this.provider.naming.getWebsocketsAccountLogicalId(); const stageResource = { Type: 'AWS::ApiGatewayV2::Stage', @@ -28,8 +24,8 @@ module.exports = { Ref: this.websocketsDeploymentLogicalId, }, StageName: this.provider.getStage(), - Description: this.serverless.service.provider - .websocketsDescription || 'Serverless Websockets', + Description: + this.serverless.service.provider.websocketsDescription || 'Serverless Websockets', }, }; @@ -38,10 +34,7 @@ module.exports = { Object.assign(stageResource.Properties, { AccessLogSettings: { DestinationArn: { - 'Fn::GetAtt': [ - logGroupLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [logGroupLogicalId, 'Arn'], }, Format: [ '$context.identity.sourceIp', @@ -73,16 +66,16 @@ module.exports = { }; function getLogGroupResource(service, stage) { - return ({ + return { Type: 'AWS::Logs::LogGroup', Properties: { LogGroupName: `/aws/websocket/${service}-${stage}`, }, - }); + }; } function getIamRoleResource(service, stage) { - return ({ + return { Type: 'AWS::IAM::Role', Properties: { AssumeRolePolicyDocument: { @@ -91,13 +84,9 @@ function getIamRoleResource(service, stage) { { Effect: 'Allow', Principal: { - Service: [ - 'apigateway.amazonaws.com', - ], + Service: ['apigateway.amazonaws.com'], }, - Action: [ - 'sts:AssumeRole', - ], + Action: ['sts:AssumeRole'], }, ], }, @@ -119,19 +108,16 @@ function getIamRoleResource(service, stage) { ], }, }, - }); + }; } function getAccountResource(logsRoleLogicalId) { - return ({ + return { Type: 'AWS::ApiGateway::Account', Properties: { CloudWatchRoleArn: { - 'Fn::GetAtt': [ - logsRoleLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [logsRoleLogicalId, 'Arn'], }, }, - }); + }; } diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js index 1dc2bdb0e..ab931abd3 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js @@ -23,37 +23,36 @@ describe('#compileStage()', () => { serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless, options); - stageLogicalId = awsCompileWebsocketsEvents.provider.naming - .getWebsocketsStageLogicalId(); - accountLogicalid = awsCompileWebsocketsEvents.provider.naming - .getWebsocketsAccountLogicalId(); - logsRoleLogicalId = awsCompileWebsocketsEvents.provider.naming - .getWebsocketsLogsRoleLogicalId(); - logGroupLogicalId = awsCompileWebsocketsEvents.provider.naming - .getWebsocketsLogGroupLogicalId(); - awsCompileWebsocketsEvents.websocketsApiLogicalId - = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); - awsCompileWebsocketsEvents.websocketsDeploymentLogicalId - = awsCompileWebsocketsEvents.provider.naming.getWebsocketsDeploymentLogicalId(1234); + stageLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsStageLogicalId(); + accountLogicalid = awsCompileWebsocketsEvents.provider.naming.getWebsocketsAccountLogicalId(); + logsRoleLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsLogsRoleLogicalId(); + logGroupLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsLogGroupLogicalId(); + awsCompileWebsocketsEvents.websocketsApiLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + awsCompileWebsocketsEvents.websocketsDeploymentLogicalId = awsCompileWebsocketsEvents.provider.naming.getWebsocketsDeploymentLogicalId( + 1234 + ); }); - it('should create a stage resource', () => awsCompileWebsocketsEvents.compileStage().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; - const resourceKeys = Object.keys(resources); + it('should create a stage resource', () => + awsCompileWebsocketsEvents.compileStage().then(() => { + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; + const resourceKeys = Object.keys(resources); - expect(resourceKeys[0]).to.equal(stageLogicalId); - expect(resources.WebsocketsDeploymentStage.Type).to.equal('AWS::ApiGatewayV2::Stage'); - expect(resources.WebsocketsDeploymentStage.Properties.ApiId).to.deep.equal({ - Ref: awsCompileWebsocketsEvents.websocketsApiLogicalId, - }); - expect(resources.WebsocketsDeploymentStage.Properties.DeploymentId).to.deep.equal({ - Ref: awsCompileWebsocketsEvents.websocketsDeploymentLogicalId, - }); - expect(resources.WebsocketsDeploymentStage.Properties.StageName).to.equal('dev'); - expect(resources.WebsocketsDeploymentStage.Properties.Description) - .to.equal('Serverless Websockets'); - })); + expect(resourceKeys[0]).to.equal(stageLogicalId); + expect(resources.WebsocketsDeploymentStage.Type).to.equal('AWS::ApiGatewayV2::Stage'); + expect(resources.WebsocketsDeploymentStage.Properties.ApiId).to.deep.equal({ + Ref: awsCompileWebsocketsEvents.websocketsApiLogicalId, + }); + expect(resources.WebsocketsDeploymentStage.Properties.DeploymentId).to.deep.equal({ + Ref: awsCompileWebsocketsEvents.websocketsDeploymentLogicalId, + }); + expect(resources.WebsocketsDeploymentStage.Properties.StageName).to.equal('dev'); + expect(resources.WebsocketsDeploymentStage.Properties.Description).to.equal( + 'Serverless Websockets' + ); + })); describe('logs', () => { beforeEach(() => { @@ -65,8 +64,9 @@ describe('#compileStage()', () => { it('should create a dedicated stage resource if logs are configured', () => awsCompileWebsocketsEvents.compileStage().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources[stageLogicalId]).to.deep.equal({ Type: 'AWS::ApiGatewayV2::Stage', @@ -81,10 +81,7 @@ describe('#compileStage()', () => { Description: 'Serverless Websockets', AccessLogSettings: { DestinationArn: { - 'Fn::GetAtt': [ - logGroupLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [logGroupLogicalId, 'Arn'], }, Format: [ '$context.identity.sourceIp', @@ -106,8 +103,9 @@ describe('#compileStage()', () => { it('should create a Log Group resource', () => awsCompileWebsocketsEvents.compileStage().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources[logGroupLogicalId]).to.deep.equal({ Type: 'AWS::Logs::LogGroup', @@ -119,8 +117,9 @@ describe('#compileStage()', () => { it('should create a IAM Role resource', () => awsCompileWebsocketsEvents.compileStage().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources[logsRoleLogicalId]).to.deep.equal({ Type: 'AWS::IAM::Role', @@ -128,14 +127,10 @@ describe('#compileStage()', () => { AssumeRolePolicyDocument: { Statement: [ { - Action: [ - 'sts:AssumeRole', - ], + Action: ['sts:AssumeRole'], Effect: 'Allow', Principal: { - Service: [ - 'apigateway.amazonaws.com', - ], + Service: ['apigateway.amazonaws.com'], }, }, ], @@ -164,17 +159,15 @@ describe('#compileStage()', () => { it('should create an Account resource', () => awsCompileWebsocketsEvents.compileStage().then(() => { - const resources = awsCompileWebsocketsEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + const resources = + awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources; expect(resources[accountLogicalid]).to.deep.equal({ Type: 'AWS::ApiGateway::Account', Properties: { CloudWatchRoleArn: { - 'Fn::GetAtt': [ - logsRoleLogicalId, - 'Arn', - ], + 'Fn::GetAtt': [logsRoleLogicalId, 'Arn'], }, }, }); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/validate.js b/lib/plugins/aws/package/compile/events/websockets/lib/validate.js index f225bdc05..6eab1856a 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/validate.js @@ -6,13 +6,13 @@ module.exports = { validate() { const events = []; - const getAuthorizerNameFromArn = (arn) => { + const getAuthorizerNameFromArn = arn => { const splitArn = arn.split(':'); return splitArn[splitArn.length - 1]; }; _.forEach(this.serverless.service.functions, (functionObject, functionName) => { - _.forEach(functionObject.events, (event) => { + _.forEach(functionObject.events, event => { // check if we have both, `http` and `websocket` events which is not supported if (_.has(event, 'websocket') && _.has(event, 'http')) { const errorMessage = 'The event type can either be "http" or "websocket" but not both.'; @@ -33,11 +33,13 @@ module.exports = { // authorizers if (_.isString(event.websocket.authorizer)) { - if (event.websocket.authorizer.includes(':')) { // arn + if (event.websocket.authorizer.includes(':')) { + // arn websocketObj.authorizer = { name: getAuthorizerNameFromArn(event.websocket.authorizer), uri: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -52,13 +54,16 @@ module.exports = { identitySource: ['route.request.header.Auth'], permission: event.websocket.authorizer, }; - } else { // reference function - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(event.websocket.authorizer); + } else { + // reference function + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId( + event.websocket.authorizer + ); websocketObj.authorizer = { name: event.websocket.authorizer, uri: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -77,10 +82,12 @@ module.exports = { } else if (_.isObject(event.websocket.authorizer)) { websocketObj.authorizer = {}; if (event.websocket.authorizer.arn) { - websocketObj.authorizer.name = - getAuthorizerNameFromArn(event.websocket.authorizer.arn); + websocketObj.authorizer.name = getAuthorizerNameFromArn( + event.websocket.authorizer.arn + ); websocketObj.authorizer.uri = { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -95,10 +102,12 @@ module.exports = { websocketObj.authorizer.permission = event.websocket.authorizer.arn; } else if (event.websocket.authorizer.name) { websocketObj.authorizer.name = event.websocket.authorizer.name; - const lambdaLogicalId = this.provider.naming - .getLambdaLogicalId(event.websocket.authorizer.name); + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId( + event.websocket.authorizer.name + ); websocketObj.authorizer.uri = { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -124,7 +133,7 @@ module.exports = { } } events.push(websocketObj); - // dealing with the simplified string representation + // dealing with the simplified string representation } else if (_.isString(event.websocket)) { events.push({ functionName, diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js index 5f03b4317..072c14a15 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js @@ -80,7 +80,8 @@ describe('#validate()', () => { authorizer: { name: 'auth', uri: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -120,7 +121,8 @@ describe('#validate()', () => { authorizer: { name: 'auth', uri: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -163,7 +165,8 @@ describe('#validate()', () => { authorizer: { name: 'auth', uri: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -206,7 +209,8 @@ describe('#validate()', () => { authorizer: { name: 'auth', uri: { - 'Fn::Join': ['', + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, @@ -236,7 +240,9 @@ describe('#validate()', () => { }, }; const validated = awsCompileWebsocketsEvents.validate(); - expect(validated.events).to.be.an('Array').with.length(0); + expect(validated.events) + .to.be.an('Array') + .with.length(0); }); it('should reject a websocket event without a route', () => { diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index e2627b1f4..b7d09106d 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -12,27 +12,31 @@ class AwsCompileFunctions { this.serverless = serverless; this.options = options; const servicePath = this.serverless.config.servicePath || ''; - this.packagePath = this.serverless.service.package.path || - path.join(servicePath || '.', '.serverless'); + this.packagePath = + this.serverless.service.package.path || path.join(servicePath || '.', '.serverless'); this.provider = this.serverless.getProvider('aws'); - if (this.serverless.service.provider.versionFunctions === undefined || - this.serverless.service.provider.versionFunctions === null) { + if ( + this.serverless.service.provider.versionFunctions === undefined || + this.serverless.service.provider.versionFunctions === null + ) { this.serverless.service.provider.versionFunctions = true; } this.hooks = { - 'package:compileFunctions': () => BbPromise.bind(this) - .then(this.downloadPackageArtifacts) - .then(this.compileFunctions), + 'package:compileFunctions': () => + BbPromise.bind(this) + .then(this.downloadPackageArtifacts) + .then(this.compileFunctions), }; } compileRole(newFunction, role) { const compiledFunction = newFunction; - const unnsupportedRoleError = new this.serverless.classes - .Error(`Unsupported role provided: "${JSON.stringify(role)}"`); + const unnsupportedRoleError = new this.serverless.classes.Error( + `Unsupported role provided: "${JSON.stringify(role)}"` + ); switch (typeof role) { case 'object': @@ -54,9 +58,7 @@ class AwsCompileFunctions { } else if (role === 'IamRoleLambdaExecution') { // role is the default role generated by the framework compiledFunction.Properties.Role = { 'Fn::GetAtt': [role, 'Arn'] }; - compiledFunction.DependsOn = [ - 'IamRoleLambdaExecution', - ]; + compiledFunction.DependsOn = ['IamRoleLambdaExecution']; } else { // role is a Logical Role Name compiledFunction.Properties.Role = { 'Fn::GetAtt': [role, 'Arn'] }; @@ -73,7 +75,8 @@ class AwsCompileFunctions { const S3 = new AWS.S3({ region }); const functionObject = this.serverless.service.getFunction(functionName); - const artifactFilePath = _.get(functionObject, 'package.artifact') || + const artifactFilePath = + _.get(functionObject, 'package.artifact') || _.get(this, 'serverless.service.package.artifact'); const regex = new RegExp('.*s3.amazonaws.com/(.+)/(.+)'); @@ -91,8 +94,9 @@ class AwsCompileFunctions { const writeStream = fs.createWriteStream(filePath); - readStream.on('error', (error) => reject(error)); - readStream.pipe(writeStream) + readStream.on('error', error => reject(error)); + readStream + .pipe(writeStream) .on('error', reject) .on('close', () => { if (functionObject.package.artifact) { @@ -116,18 +120,23 @@ class AwsCompileFunctions { const serviceArtifactFileName = this.provider.naming.getServiceArtifactName(); const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(functionName); - let artifactFilePath = functionObject.package.artifact || - this.serverless.service.package.artifact; + let artifactFilePath = + functionObject.package.artifact || this.serverless.service.package.artifact; - if (!artifactFilePath || - (this.serverless.service.artifact && !functionObject.package.artifact)) { + if ( + !artifactFilePath || + (this.serverless.service.artifact && !functionObject.package.artifact) + ) { let artifactFileName = serviceArtifactFileName; if (this.serverless.service.package.individually || functionObject.package.individually) { artifactFileName = functionArtifactFileName; } - artifactFilePath = path.join(this.serverless.config.servicePath - , '.serverless', artifactFileName); + artifactFilePath = path.join( + this.serverless.config.servicePath, + '.serverless', + artifactFileName + ); } if (this.serverless.service.package.deploymentBucket) { @@ -150,15 +159,14 @@ class AwsCompileFunctions { const Handler = functionObject.handler; const FunctionName = functionObject.name; - const MemorySize = Number(functionObject.memorySize) - || Number(this.serverless.service.provider.memorySize) - || 1024; - const Timeout = Number(functionObject.timeout) - || Number(this.serverless.service.provider.timeout) - || 6; - const Runtime = functionObject.runtime - || this.serverless.service.provider.runtime - || 'nodejs10.x'; + const MemorySize = + Number(functionObject.memorySize) || + Number(this.serverless.service.provider.memorySize) || + 1024; + const Timeout = + Number(functionObject.timeout) || Number(this.serverless.service.provider.timeout) || 6; + const Runtime = + functionObject.runtime || this.serverless.service.provider.runtime || 'nodejs10.x'; newFunction.Properties.Handler = Handler; newFunction.Properties.FunctionName = FunctionName; @@ -176,11 +184,7 @@ class AwsCompileFunctions { } if (functionObject.tags || this.serverless.service.provider.tags) { - const tags = Object.assign( - {}, - this.serverless.service.provider.tags, - functionObject.tags - ); + const tags = Object.assign({}, this.serverless.service.provider.tags, functionObject.tags); newFunction.Properties.Tags = []; _.forEach(tags, (Value, Key) => { newFunction.Properties.Tags.push({ Key, Value }); @@ -205,9 +209,7 @@ class AwsCompileFunctions { if (dlqType === 'sns') { stmt = { Effect: 'Allow', - Action: [ - 'sns:Publish', - ], + Action: ['sns:Publish'], Resource: [arn], }; } else if (dlqType === 'sqs') { @@ -253,7 +255,7 @@ class AwsCompileFunctions { if (typeof arn === 'string') { const splittedArn = arn.split(':'); - if (splittedArn[0] === 'arn' && (splittedArn[2] === 'kms')) { + if (splittedArn[0] === 'arn' && splittedArn[2] === 'kms') { const iamRoleLambdaExecution = this.serverless.service.provider .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution; @@ -261,9 +263,7 @@ class AwsCompileFunctions { const stmt = { Effect: 'Allow', - Action: [ - 'kms:Decrypt', - ], + Action: ['kms:Decrypt'], Resource: [arn], }; @@ -285,9 +285,9 @@ class AwsCompileFunctions { } } - const tracing = functionObject.tracing - || (this.serverless.service.provider.tracing - && this.serverless.service.provider.tracing.lambda); + const tracing = + functionObject.tracing || + (this.serverless.service.provider.tracing && this.serverless.service.provider.tracing.lambda); if (tracing) { if (typeof tracing === 'boolean' || typeof tracing === 'string') { @@ -306,10 +306,7 @@ class AwsCompileFunctions { const stmt = { Effect: 'Allow', - Action: [ - 'xray:PutTraceSegments', - 'xray:PutTelemetryRecords', - ], + Action: ['xray:PutTraceSegments', 'xray:PutTelemetryRecords'], Resource: ['*'], }; @@ -336,25 +333,23 @@ class AwsCompileFunctions { ); let invalidEnvVar = null; - _.forEach( - _.keys(newFunction.Properties.Environment.Variables), - key => { // eslint-disable-line consistent-return - // taken from the bash man pages - if (!key.match(/^[A-Za-z_][a-zA-Z0-9_]*$/)) { - invalidEnvVar = `Invalid characters in environment variable ${key}`; - return false; // break loop with lodash - } - const value = newFunction.Properties.Environment.Variables[key]; - if (_.isObject(value)) { - const isCFRef = _.isObject(value) && - !_.some(value, (v, k) => k !== 'Ref' && !_.startsWith(k, 'Fn::')); - if (!isCFRef) { - invalidEnvVar = `Environment variable ${key} must contain string`; - return false; - } + _.forEach(_.keys(newFunction.Properties.Environment.Variables), key => { + // eslint-disable-line consistent-return + // taken from the bash man pages + if (!key.match(/^[A-Za-z_][a-zA-Z0-9_]*$/)) { + invalidEnvVar = `Invalid characters in environment variable ${key}`; + return false; // break loop with lodash + } + const value = newFunction.Properties.Environment.Variables[key]; + if (_.isObject(value)) { + const isCFRef = + _.isObject(value) && !_.some(value, (v, k) => k !== 'Ref' && !_.startsWith(k, 'Fn::')); + if (!isCFRef) { + invalidEnvVar = `Environment variable ${key} must contain string`; + return false; } } - ); + }); if (invalidEnvVar) { return BbPromise.reject(new this.serverless.classes.Error(invalidEnvVar)); @@ -373,13 +368,16 @@ class AwsCompileFunctions { if (!this.serverless.service.provider.vpc) this.serverless.service.provider.vpc = {}; newFunction.Properties.VpcConfig = { - SecurityGroupIds: functionObject.vpc.securityGroupIds || - this.serverless.service.provider.vpc.securityGroupIds, + SecurityGroupIds: + functionObject.vpc.securityGroupIds || + this.serverless.service.provider.vpc.securityGroupIds, SubnetIds: functionObject.vpc.subnetIds || this.serverless.service.provider.vpc.subnetIds, }; - if (!newFunction.Properties.VpcConfig.SecurityGroupIds - || !newFunction.Properties.VpcConfig.SubnetIds) { + if ( + !newFunction.Properties.VpcConfig.SecurityGroupIds || + !newFunction.Properties.VpcConfig.SubnetIds + ) { delete newFunction.Properties.VpcConfig; } @@ -399,24 +397,28 @@ class AwsCompileFunctions { } } - newFunction.DependsOn = [this.provider.naming.getLogGroupLogicalId(functionName)] - .concat(newFunction.DependsOn || []); + newFunction.DependsOn = [this.provider.naming.getLogGroupLogicalId(functionName)].concat( + newFunction.DependsOn || [] + ); if (functionObject.layers && _.isArray(functionObject.layers)) { newFunction.Properties.Layers = functionObject.layers; - } else if (this.serverless.service.provider.layers && _.isArray( - this.serverless.service.provider.layers)) { + } else if ( + this.serverless.service.provider.layers && + _.isArray(this.serverless.service.provider.layers) + ) { newFunction.Properties.Layers = this.serverless.service.provider.layers; } - const functionLogicalId = this.provider.naming - .getLambdaLogicalId(functionName); + const functionLogicalId = this.provider.naming.getLambdaLogicalId(functionName); const newFunctionObject = { [functionLogicalId]: newFunction, }; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newFunctionObject); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newFunctionObject + ); const newVersion = this.cfLambdaVersionTemplate(); @@ -433,18 +435,18 @@ class AwsCompileFunctions { return BbPromise.fromCallback(cb => { const readStream = fs.createReadStream(artifactFilePath); - readStream.on('data', chunk => { - fileHash.write(chunk); - versionHash.write(chunk); - }) - .on('close', () => { - cb(); - }) - .on('error', error => { - cb(error); - }); - }) - .then(() => { + readStream + .on('data', chunk => { + fileHash.write(chunk); + versionHash.write(chunk); + }) + .on('close', () => { + cb(); + }) + .on('error', error => { + cb(error); + }); + }).then(() => { // Include function configuration in version id hash (without the Code part) const properties = _.omit(_.get(newFunction, 'Properties', {}), 'Code'); _.forOwn(properties, value => { @@ -468,19 +470,24 @@ class AwsCompileFunctions { // use the version SHA in the logical resource ID of the version because // AWS::Lambda::Version resource will not support updates const versionLogicalId = this.provider.naming.getLambdaVersionLogicalId( - functionName, versionDigest); + functionName, + versionDigest + ); const newVersionObject = { [versionLogicalId]: newVersion, }; if (this.serverless.service.provider.versionFunctions) { - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newVersionObject); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newVersionObject + ); } // Add function versions to Outputs section - const functionVersionOutputLogicalId = this.provider.naming - .getLambdaVersionOutputLogicalId(functionName); + const functionVersionOutputLogicalId = this.provider.naming.getLambdaVersionOutputLogicalId( + functionName + ); const newVersionOutput = this.cfOutputLatestVersionTemplate(); newVersionOutput.Value = { Ref: versionLogicalId }; @@ -497,22 +504,20 @@ class AwsCompileFunctions { downloadPackageArtifacts() { const allFunctions = this.serverless.service.getAllFunctions(); - return BbPromise.each( - allFunctions, - functionName => this.downloadPackageArtifact(functionName)); + return BbPromise.each(allFunctions, functionName => this.downloadPackageArtifact(functionName)); } compileFunctions() { const allFunctions = this.serverless.service.getAllFunctions(); - return BbPromise.each( - allFunctions, - functionName => this.compileFunction(functionName)); + return BbPromise.each(allFunctions, functionName => this.compileFunction(functionName)); } // helper functions isArnRefGetAttOrImportValue(arn) { - return typeof arn === 'object' && - _.some(_.keys(arn), (k) => _.includes(['Ref', 'Fn::GetAtt', 'Fn::ImportValue'], k)); + return ( + typeof arn === 'object' && + _.some(_.keys(arn), k => _.includes(['Ref', 'Fn::GetAtt', 'Fn::ImportValue'], k)) + ); } cfLambdaFunctionTemplate() { diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index f478397cc..62e50d462 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -42,21 +42,26 @@ describe('AwsCompileFunctions', () => { const individualArtifact = 'test.zip'; awsCompileFunctions.packagePath = getTmpDirPath(); // The contents of the test artifacts need to be predictable so the hashes stay the same - serverless.utils.writeFileSync(path.join(awsCompileFunctions.packagePath, - serviceArtifact), 'foobar'); - serverless.utils.writeFileSync(path.join(awsCompileFunctions.packagePath, - individualArtifact), 'barbaz'); + serverless.utils.writeFileSync( + path.join(awsCompileFunctions.packagePath, serviceArtifact), + 'foobar' + ); + serverless.utils.writeFileSync( + path.join(awsCompileFunctions.packagePath, individualArtifact), + 'barbaz' + ); awsCompileFunctions.serverless.service.service = 'new-service'; awsCompileFunctions.serverless.service.package.artifactDirectoryName = 'somedir'; - awsCompileFunctions.serverless.service.package - .artifact = path.join(awsCompileFunctions.packagePath, serviceArtifact); + awsCompileFunctions.serverless.service.package.artifact = path.join( + awsCompileFunctions.packagePath, + serviceArtifact + ); awsCompileFunctions.serverless.service.functions = {}; awsCompileFunctions.serverless.service.functions[functionName] = { name: 'test', package: { - artifact: path.join(awsCompileFunctions.packagePath, - individualArtifact), + artifact: path.join(awsCompileFunctions.packagePath, individualArtifact), }, handler: 'handler.hello', }; @@ -69,13 +74,15 @@ describe('AwsCompileFunctions', () => { describe('#isArnRefGetAttOrImportValue()', () => { it('should accept a Ref', () => - expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ Ref: 'DLQ' })).to.equal(true)); + expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ Ref: 'DLQ' })).to.equal(true)); it('should accept a Fn::GetAtt', () => - expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ 'Fn::GetAtt': ['DLQ', 'Arn'] })) - .to.equal(true)); + expect( + awsCompileFunctions.isArnRefGetAttOrImportValue({ 'Fn::GetAtt': ['DLQ', 'Arn'] }) + ).to.equal(true)); it('should accept a Fn::ImportValue', () => - expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ 'Fn::ImportValue': 'DLQ' })) - .to.equal(true)); + expect( + awsCompileFunctions.isArnRefGetAttOrImportValue({ 'Fn::ImportValue': 'DLQ' }) + ).to.equal(true)); it('should reject other objects', () => expect(awsCompileFunctions.isArnRefGetAttOrImportValue({ Blah: 'vtha' })).to.equal(false)); }); @@ -103,34 +110,35 @@ describe('AwsCompileFunctions', () => { it('should download the file and replace the artifact path for function packages', () => { awsCompileFunctions.serverless.service.package.individually = true; - awsCompileFunctions.serverless.service.functions[functionName] - .package.artifact = `https://s3.amazonaws.com/${s3BucketName}/${s3ArtifactName}`; + awsCompileFunctions.serverless.service.functions[ + functionName + ].package.artifact = `https://s3.amazonaws.com/${s3BucketName}/${s3ArtifactName}`; - return expect(awsCompileFunctions.downloadPackageArtifacts()).to.be.fulfilled - .then(() => { - const artifactFileName = awsCompileFunctions.serverless.service - .functions[functionName].package.artifact.split(path.sep).pop(); + return expect(awsCompileFunctions.downloadPackageArtifacts()).to.be.fulfilled.then(() => { + const artifactFileName = awsCompileFunctions.serverless.service.functions[ + functionName + ].package.artifact + .split(path.sep) + .pop(); - expect(requestStub.callCount).to.equal(1); - expect(artifactFileName).to.equal(s3ArtifactName); - }); + expect(requestStub.callCount).to.equal(1); + expect(artifactFileName).to.equal(s3ArtifactName); + }); }); it('should download the file and replace the artifact path for service-wide packages', () => { awsCompileFunctions.serverless.service.package.individually = false; - awsCompileFunctions.serverless.service.functions[functionName] - .package.artifact = false; - awsCompileFunctions.serverless.service.package - .artifact = `https://s3.amazonaws.com/${s3BucketName}/${s3ArtifactName}`; + awsCompileFunctions.serverless.service.functions[functionName].package.artifact = false; + awsCompileFunctions.serverless.service.package.artifact = `https://s3.amazonaws.com/${s3BucketName}/${s3ArtifactName}`; - return expect(awsCompileFunctions.downloadPackageArtifacts()).to.be.fulfilled - .then(() => { - const artifactFileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + return expect(awsCompileFunctions.downloadPackageArtifacts()).to.be.fulfilled.then(() => { + const artifactFileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); - expect(requestStub.callCount).to.equal(1); - expect(artifactFileName).to.equal(s3ArtifactName); - }); + expect(requestStub.callCount).to.equal(1); + expect(artifactFileName).to.equal(s3ArtifactName); + }); }); }); @@ -140,17 +148,18 @@ describe('AwsCompileFunctions', () => { const artifactTemp = awsCompileFunctions.serverless.service.functions.test.package.artifact; awsCompileFunctions.serverless.service.functions.test.package.artifact = false; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const functionResource = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate.Resources[compiledFunctionName]; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const functionResource = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + compiledFunctionName + ]; const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); - expect(functionResource.Properties.Code.S3Key) - .to.deep.equal(`${s3Folder}/${s3FileName}`); + expect(functionResource.Properties.Code.S3Key).to.deep.equal(`${s3Folder}/${s3FileName}`); awsCompileFunctions.serverless.service.functions.test.package.artifact = artifactTemp; }); }); @@ -158,34 +167,40 @@ describe('AwsCompileFunctions', () => { it('should use function artifact if individually', () => { awsCompileFunctions.serverless.service.package.individually = true; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const functionResource = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate.Resources[compiledFunctionName]; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const functionResource = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + compiledFunctionName + ]; const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; - const s3FileName = awsCompileFunctions.serverless.service - .functions[functionName].package.artifact.split(path.sep).pop(); + const s3FileName = awsCompileFunctions.serverless.service.functions[ + functionName + ].package.artifact + .split(path.sep) + .pop(); - expect(functionResource.Properties.Code.S3Key) - .to.deep.equal(`${s3Folder}/${s3FileName}`); + expect(functionResource.Properties.Code.S3Key).to.deep.equal(`${s3Folder}/${s3FileName}`); }); }); it('should use function artifact if individually at function level', () => { awsCompileFunctions.serverless.service.functions[functionName].package.individually = true; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const functionResource = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate.Resources[compiledFunctionName]; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const functionResource = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + compiledFunctionName + ]; const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; - const s3FileName = awsCompileFunctions.serverless.service - .functions[functionName].package.artifact.split(path.sep).pop(); + const s3FileName = awsCompileFunctions.serverless.service.functions[ + functionName + ].package.artifact + .split(path.sep) + .pop(); - expect(functionResource.Properties.Code.S3Key) - .to.deep.equal(`${s3Folder}/${s3FileName}`); + expect(functionResource.Properties.Code.S3Key).to.deep.equal(`${s3Folder}/${s3FileName}`); awsCompileFunctions.serverless.service.functions[functionName].package = { individually: false, }; @@ -202,12 +217,14 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.DependsOn).to.deep.equal(['FuncLogGroup']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.Properties.Role + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role ).to.deep.equal(awsCompileFunctions.serverless.service.provider.role); }); }); @@ -222,18 +239,16 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.DependsOn + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn ).to.deep.equal(['FuncLogGroup', 'LogicalNameRole']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.Properties.Role + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role ).to.deep.equal({ - 'Fn::GetAtt': [ - awsCompileFunctions.serverless.service.provider.role, - 'Arn', - ], + 'Fn::GetAtt': [awsCompileFunctions.serverless.service.provider.role, 'Arn'], }); }); }); @@ -241,10 +256,7 @@ describe('AwsCompileFunctions', () => { it('should add a "Fn::GetAtt" Object provider role', () => { awsCompileFunctions.serverless.service.provider.name = 'aws'; awsCompileFunctions.serverless.service.provider.role = { - 'Fn::GetAtt': [ - 'LogicalRoleName', - 'Arn', - ], + 'Fn::GetAtt': ['LogicalRoleName', 'Arn'], }; awsCompileFunctions.serverless.service.functions = { func: { @@ -253,13 +265,14 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.DependsOn + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn ).to.deep.equal(['FuncLogGroup', 'LogicalRoleName']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.Properties.Role + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role ).to.deep.equal(awsCompileFunctions.serverless.service.provider.role); }); }); @@ -274,12 +287,14 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.DependsOn).to.deep.equal(['FuncLogGroup']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.Properties.Role + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func.role); }); }); @@ -294,18 +309,16 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.DependsOn + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn ).to.deep.equal(['FuncLogGroup', 'LogicalRoleName']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.Properties.Role + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role ).to.deep.equal({ - 'Fn::GetAtt': [ - awsCompileFunctions.serverless.service.functions.func.role, - 'Arn', - ], + 'Fn::GetAtt': [awsCompileFunctions.serverless.service.functions.func.role, 'Arn'], }); }); }); @@ -317,21 +330,19 @@ describe('AwsCompileFunctions', () => { handler: 'func.function.handler', name: 'new-service-dev-func', role: { - 'Fn::GetAtt': [ - 'LogicalRoleName', - 'Arn', - ], + 'Fn::GetAtt': ['LogicalRoleName', 'Arn'], }, }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.DependsOn + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn ).to.deep.equal(['FuncLogGroup', 'LogicalRoleName']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.Properties.Role + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func.role); }); }); @@ -348,12 +359,14 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.DependsOn).to.deep.equal(['FuncLogGroup']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.Properties.Role + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func.role); }); }); @@ -369,12 +382,14 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.DependsOn).to.deep.equal(['FuncLogGroup']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.Properties.Role + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.DependsOn + ).to.deep.equal(['FuncLogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Role ).to.equal(awsCompileFunctions.serverless.service.functions.func.role); }); }); @@ -394,18 +409,23 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.Func0LambdaFunction.DependsOn).to.deep.equal(['Func0LogGroup']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.Func0LambdaFunction.Properties.Role + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func0LambdaFunction.DependsOn + ).to.deep.equal(['Func0LogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func0LambdaFunction.Properties.Role ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func0.role); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.Func1LambdaFunction.DependsOn).to.deep.equal(['Func1LogGroup']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.Func1LambdaFunction.Properties.Role + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func1LambdaFunction.DependsOn + ).to.deep.equal(['Func1LogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func1LambdaFunction.Properties.Role ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func1.role); }); }); @@ -425,18 +445,23 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.Func0LambdaFunction.DependsOn).to.deep.equal(['Func0LogGroup']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.Func0LambdaFunction.Properties.Role + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func0LambdaFunction.DependsOn + ).to.deep.equal(['Func0LogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func0LambdaFunction.Properties.Role ).to.deep.equal(awsCompileFunctions.serverless.service.provider.role); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.Func1LambdaFunction.DependsOn).to.deep.equal(['Func1LogGroup']); - expect(awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.Func1LambdaFunction.Properties.Role + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func1LambdaFunction.DependsOn + ).to.deep.equal(['Func1LogGroup']); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Func1LambdaFunction.Properties.Role ).to.deep.equal(awsCompileFunctions.serverless.service.functions.func1.role); }); }); @@ -454,7 +479,8 @@ describe('AwsCompileFunctions', () => { it('should create a simple function resource', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -463,10 +489,7 @@ describe('AwsCompileFunctions', () => { }; const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -481,11 +504,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -493,7 +515,8 @@ describe('AwsCompileFunctions', () => { it('should create a function resource with provider level vpc config', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.provider.vpc = { securityGroupIds: ['xxx'], subnetIds: ['xxx'], @@ -507,10 +530,7 @@ describe('AwsCompileFunctions', () => { }; const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -529,11 +549,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -541,7 +560,8 @@ describe('AwsCompileFunctions', () => { it('should create a function resource with function level vpc config', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -554,10 +574,7 @@ describe('AwsCompileFunctions', () => { }; const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -576,11 +593,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -588,7 +604,8 @@ describe('AwsCompileFunctions', () => { it('should create a function resource with provider level tags', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -603,10 +620,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -618,18 +632,14 @@ describe('AwsCompileFunctions', () => { Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs10.x', Timeout: 6, - Tags: [ - { Key: 'foo', Value: 'bar' }, - { Key: 'baz', Value: 'qux' }, - ], + Tags: [{ Key: 'foo', Value: 'bar' }, { Key: 'baz', Value: 'qux' }], }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -637,7 +647,8 @@ describe('AwsCompileFunctions', () => { it('should create a function resource with function level tags', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -651,10 +662,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -666,18 +674,14 @@ describe('AwsCompileFunctions', () => { Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, Runtime: 'nodejs10.x', Timeout: 6, - Tags: [ - { Key: 'foo', Value: 'bar' }, - { Key: 'baz', Value: 'qux' }, - ], + Tags: [{ Key: 'foo', Value: 'bar' }, { Key: 'baz', Value: 'qux' }], }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -685,7 +689,8 @@ describe('AwsCompileFunctions', () => { it('should create a function resource with provider and function level tags', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -704,10 +709,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -727,11 +729,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -742,8 +743,7 @@ describe('AwsCompileFunctions', () => { beforeEach(() => { s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; - s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + s3FileName = awsCompileFunctions.serverless.service.package.artifact.split(path.sep).pop(); }); it('should reject if config is provided as a number', () => { @@ -787,18 +787,17 @@ describe('AwsCompileFunctions', () => { describe('when IamRoleLambdaExecution is used', () => { beforeEach(() => { // pretend that the IamRoleLambdaExecution is used - awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = { - Properties: { - Policies: [ - { - PolicyDocument: { - Statement: [], - }, + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = { + Properties: { + Policies: [ + { + PolicyDocument: { + Statement: [], }, - ], - }, - }; + }, + ], + }, + }; }); it('should create necessary resources if a SNS arn is provided', () => { @@ -812,10 +811,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -835,20 +831,18 @@ describe('AwsCompileFunctions', () => { const compiledDlqStatement = { Effect: 'Allow', - Action: [ - 'sns:Publish', - ], + Action: ['sns:Publish'], Resource: ['arn:aws:sns:region:accountid:foo'], }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; - const dlqStatement = compiledCfTemplate.Resources - .IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement[0]; + const dlqStatement = + compiledCfTemplate.Resources.IamRoleLambdaExecution.Properties.Policies[0] + .PolicyDocument.Statement[0]; expect(functionResource).to.deep.equal(compiledFunction); expect(dlqStatement).to.deep.equal(compiledDlqStatement); @@ -864,8 +858,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()) - .to.be.rejectedWith('only supports SNS'); + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith( + 'only supports SNS' + ); }); it('should create necessary resources if a Ref is provided', () => { @@ -881,10 +876,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -904,10 +896,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; expect(functionResource).to.deep.equal(compiledFunction); @@ -927,10 +918,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -950,10 +938,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; expect(functionResource).to.deep.equal(compiledFunction); @@ -973,10 +960,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -996,10 +980,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; expect(functionResource).to.deep.equal(compiledFunction); @@ -1019,10 +1002,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1040,10 +1020,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; @@ -1060,8 +1039,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()) - .to.be.rejectedWith('only supports SNS'); + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith( + 'only supports SNS' + ); }); }); }); @@ -1072,8 +1052,7 @@ describe('AwsCompileFunctions', () => { beforeEach(() => { s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; - s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + s3FileName = awsCompileFunctions.serverless.service.package.artifact.split(path.sep).pop(); }); it('should reject if config is provided as a number', () => { @@ -1085,8 +1064,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()) - .to.be.rejectedWith('provided as a string'); + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith( + 'provided as a string' + ); }); it('should reject if config is provided as an object', () => { @@ -1100,8 +1080,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()) - .to.be.rejectedWith('provided as a string'); + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith( + 'provided as a string' + ); }); it('should throw an error if config is not a KMS key arn', () => { @@ -1113,8 +1094,7 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()) - .to.be.rejectedWith('KMS key arn'); + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith('KMS key arn'); }); it('should use a the service KMS key arn if provided', () => { @@ -1132,10 +1112,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1151,10 +1128,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; expect(functionResource).to.deep.equal(compiledFunction); }); @@ -1180,10 +1156,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction1 = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'Func1LogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['Func1LogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1201,10 +1174,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction2 = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'Func2LogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['Func2LogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1220,10 +1190,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const function1Resource = compiledCfTemplate.Resources.Func1LambdaFunction; const function2Resource = compiledCfTemplate.Resources.Func2LambdaFunction; @@ -1235,18 +1204,17 @@ describe('AwsCompileFunctions', () => { describe('when IamRoleLambdaExecution is used', () => { beforeEach(() => { // pretend that the IamRoleLambdaExecution is used - awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = { - Properties: { - Policies: [ - { - PolicyDocument: { - Statement: [], - }, + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = { + Properties: { + Policies: [ + { + PolicyDocument: { + Statement: [], }, - ], - }, - }; + }, + ], + }, + }; }); it('should create necessary resources if a KMS key arn is provided', () => { @@ -1260,10 +1228,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1281,20 +1246,18 @@ describe('AwsCompileFunctions', () => { const compiledKmsStatement = { Effect: 'Allow', - Action: [ - 'kms:Decrypt', - ], + Action: ['kms:Decrypt'], Resource: ['arn:aws:kms:region:accountid:foo/bar'], }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; - const dlqStatement = compiledCfTemplate.Resources - .IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement[0]; + const dlqStatement = + compiledCfTemplate.Resources.IamRoleLambdaExecution.Properties.Policies[0] + .PolicyDocument.Statement[0]; expect(functionResource).to.deep.equal(compiledFunction); expect(dlqStatement).to.deep.equal(compiledKmsStatement); @@ -1314,10 +1277,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1333,10 +1293,9 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; @@ -1352,8 +1311,7 @@ describe('AwsCompileFunctions', () => { beforeEach(() => { s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; - s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + s3FileName = awsCompileFunctions.serverless.service.package.artifact.split(path.sep).pop(); }); it('should throw an error if config paramter is not a string', () => { @@ -1365,8 +1323,7 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()) - .to.be.rejectedWith('as a string'); + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith('as a string'); }); it('should use a the provider wide tracing config if provided', () => { @@ -1385,10 +1342,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1407,8 +1361,8 @@ describe('AwsCompileFunctions', () => { }; return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; expect(functionResource).to.deep.equal(compiledFunction); }); @@ -1435,10 +1389,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction1 = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'Func1LogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['Func1LogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1458,10 +1409,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction2 = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'Func2LogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['Func2LogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1480,8 +1428,8 @@ describe('AwsCompileFunctions', () => { }; return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const function1Resource = compiledCfTemplate.Resources.Func1LambdaFunction; const function2Resource = compiledCfTemplate.Resources.Func2LambdaFunction; @@ -1493,18 +1441,17 @@ describe('AwsCompileFunctions', () => { describe('when IamRoleLambdaExecution is used', () => { beforeEach(() => { // pretend that the IamRoleLambdaExecution is used - awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = { - Properties: { - Policies: [ - { - PolicyDocument: { - Statement: [], - }, + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = { + Properties: { + Policies: [ + { + PolicyDocument: { + Statement: [], }, - ], - }, - }; + }, + ], + }, + }; }); it('should create necessary resources if a tracing config is provided', () => { @@ -1518,10 +1465,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1541,20 +1485,18 @@ describe('AwsCompileFunctions', () => { const compiledXrayStatement = { Effect: 'Allow', - Action: [ - 'xray:PutTraceSegments', - 'xray:PutTelemetryRecords', - ], + Action: ['xray:PutTraceSegments', 'xray:PutTelemetryRecords'], Resource: ['*'], }; return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; - const xrayStatement = compiledCfTemplate.Resources - .IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement[0]; + const xrayStatement = + compiledCfTemplate.Resources.IamRoleLambdaExecution.Properties.Policies[0] + .PolicyDocument.Statement[0]; expect(functionResource).to.deep.equal(compiledFunction); expect(xrayStatement).to.deep.equal(compiledXrayStatement); @@ -1574,10 +1516,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1596,8 +1535,8 @@ describe('AwsCompileFunctions', () => { }; return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { - const compiledCfTemplate = awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate; + const compiledCfTemplate = + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate; const functionResource = compiledCfTemplate.Resources.FuncLambdaFunction; @@ -1610,7 +1549,8 @@ describe('AwsCompileFunctions', () => { it('should create a function resource with environment config', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -1628,10 +1568,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1653,11 +1590,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -1665,7 +1601,8 @@ describe('AwsCompileFunctions', () => { it('should create a function resource with function level environment config', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -1678,10 +1615,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1701,11 +1635,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -1713,7 +1646,8 @@ describe('AwsCompileFunctions', () => { it('should create a function resource with provider level environment config', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -1727,10 +1661,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1750,11 +1681,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -1762,7 +1692,8 @@ describe('AwsCompileFunctions', () => { it('should overwrite a provider level environment config when function config is given', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.provider.environment = { variable: 'overwrite-me', }; @@ -1779,10 +1710,7 @@ describe('AwsCompileFunctions', () => { const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1802,11 +1730,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -1818,7 +1745,7 @@ describe('AwsCompileFunctions', () => { name: 'new-service-dev-func', environment: { '1test1': 'test1', - test2: 'test2', + 'test2': 'test2', }, }, }; @@ -1837,11 +1764,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect(awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.Properties.Environment.Variables.counter + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Environment.Variables.counter ).to.equal(18); }); }); @@ -1862,27 +1788,27 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - awsCompileFunctions.serverless.service.functions = { - func: { - handler: 'func.function.handler', - name: 'new-service-dev-func', - environment: { - counter: { - NotRef: 'TestVariable', - }, + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + environment: { + counter: { + NotRef: 'TestVariable', }, }, - }; - return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); - }); + }, + }; + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); + }); }); it('should consider function based config when creating a function resource', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { name: 'customized-func-function', @@ -1893,10 +1819,7 @@ describe('AwsCompileFunctions', () => { }; const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1911,61 +1834,61 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); - it('should allow functions to use a different runtime' + - ' than the service default runtime if specified', () => { - const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; - const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); - awsCompileFunctions.serverless.service.functions = { - func: { - handler: 'func.function.handler', - name: 'new-service-dev-func', - runtime: 'python2.7', - }, - }; - - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - const compiledFunction = { - Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], - Properties: { - Code: { - S3Key: `${s3Folder}/${s3FileName}`, - S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, - }, - FunctionName: 'new-service-dev-func', - Handler: 'func.function.handler', - MemorySize: 1024, - Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'python2.7', - Timeout: 6, + it( + 'should allow functions to use a different runtime' + + ' than the service default runtime if specified', + () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + runtime: 'python2.7', }, }; - expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction - ).to.deep.equal(compiledFunction); - }); - }); + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'python2.7', + Timeout: 6, + }, + }; + + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + } + ); it('should default to the nodejs10.x runtime when no provider runtime is given', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.provider.runtime = null; awsCompileFunctions.serverless.service.functions = { func: { @@ -1975,10 +1898,7 @@ describe('AwsCompileFunctions', () => { }; const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -1993,61 +1913,60 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); - it('should consider the providers runtime and memorySize ' + - 'when creating a function resource', () => { - const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; - const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); - awsCompileFunctions.serverless.service.provider.runtime = 'python2.7'; - awsCompileFunctions.serverless.service.provider.memorySize = 128; - awsCompileFunctions.serverless.service.functions = { - func: { - handler: 'func.function.handler', - name: 'new-service-dev-func', - }, - }; - const compiledFunction = { - Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], - Properties: { - Code: { - S3Key: `${s3Folder}/${s3FileName}`, - S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + it( + 'should consider the providers runtime and memorySize ' + 'when creating a function resource', + () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + awsCompileFunctions.serverless.service.provider.runtime = 'python2.7'; + awsCompileFunctions.serverless.service.provider.memorySize = 128; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', }, - FunctionName: 'new-service-dev-func', - Handler: 'func.function.handler', - MemorySize: 128, - Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'python2.7', - Timeout: 6, - }, - }; + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 128, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'python2.7', + Timeout: 6, + }, + }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction - ).to.deep.equal(compiledFunction); - }); - }); + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + } + ); it('should use a custom bucket if specified', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); const bucketName = 'com.serverless.deploys'; awsCompileFunctions.serverless.service.package.deploymentBucket = bucketName; @@ -2061,10 +1980,7 @@ describe('AwsCompileFunctions', () => { }; const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -2079,22 +1995,14 @@ describe('AwsCompileFunctions', () => { }, }; const coreCloudFormationTemplate = awsCompileFunctions.serverless.utils.readFileSync( - path.join( - __dirname, - '..', - '..', - 'lib', - 'core-cloudformation-template.json' - ) + path.join(__dirname, '..', '..', 'lib', 'core-cloudformation-template.json') ); - awsCompileFunctions.serverless.service.provider - .compiledCloudFormationTemplate = coreCloudFormationTemplate; + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = coreCloudFormationTemplate; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -2107,11 +2015,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction.Properties.Description + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction.Properties.Description ).to.equal('Lambda function description'); }); }); @@ -2139,14 +2046,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Outputs - ).to.deep.equal( - expectedOutputs - ); + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + ).to.deep.equal(expectedOutputs); }); }); @@ -2173,47 +2076,37 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Outputs - ).to.deep.equal( - expectedOutputs - ); + return expect(awsCompileFunctions.compileFunctions()) + .to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + ).to.deep.equal(expectedOutputs); - // Change configuration - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { - Resources: {}, - Outputs: {}, - }; + // Change configuration + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { + Resources: {}, + Outputs: {}, + }; - _.set( - awsCompileFunctions, - 'serverless.service.functions.func.environment.MY_ENV_VAR', - 'myvalue' - ); + _.set( + awsCompileFunctions, + 'serverless.service.functions.func.environment.MY_ENV_VAR', + 'myvalue' + ); - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled; - }) - .then(() => { - // Expect different version hash - _.set( - expectedOutputs, - 'FuncLambdaFunctionQualifiedArn', - { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled; + }) + .then(() => { + // Expect different version hash + _.set(expectedOutputs, 'FuncLambdaFunctionQualifiedArn', { Description: 'Current Lambda function version', Value: { Ref: 'FuncLambdaVersionI1xWetHMVQO8bvzGqgmokPl25rtJA0A8g6lZNYdkdg' }, - } - ); + }); - expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Outputs - ).to.deep.equal( - expectedOutputs - ); - }); + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + ).to.deep.equal(expectedOutputs); + }); }); it('should include description under version too if function is specified', () => { @@ -2224,12 +2117,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaVersionBzAYHivcbYLoEZcl7hN9cBrakBNygN0PiUC9UjQVMA - .Properties.Description + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaVersionBzAYHivcbYLoEZcl7hN9cBrakBNygN0PiUC9UjQVMA.Properties.Description ).to.equal('Lambda function description'); }); }); @@ -2247,21 +2138,18 @@ describe('AwsCompileFunctions', () => { const expectedOutputs = {}; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Outputs - ).to.deep.equal( - expectedOutputs - ); + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Outputs + ).to.deep.equal(expectedOutputs); }); }); it('should set function declared reserved concurrency limit', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -2271,10 +2159,7 @@ describe('AwsCompileFunctions', () => { }; const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -2290,19 +2175,19 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction - ).to.deep.equal(compiledFunction); - }); + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); }); it('should set function declared reserved concurrency limit even if it is zero', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { handler: 'func.function.handler', @@ -2312,10 +2197,7 @@ describe('AwsCompileFunctions', () => { }; const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -2331,32 +2213,34 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { - expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction - ).to.deep.equal(compiledFunction); - }); + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); }); - it('should throw an informative error message if non-integer reserved concurrency limit set ' + - 'on function', () => { - awsCompileFunctions.serverless.service.functions = { - func: { - handler: 'func.function.handler', - name: 'new-service-dev-func', - reservedConcurrency: 'a', - }, - }; + it( + 'should throw an informative error message if non-integer reserved concurrency limit set ' + + 'on function', + () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + reservedConcurrency: 'a', + }, + }; - const errorMessage = [ - 'You should use integer as reservedConcurrency value on function: ', - 'new-service-dev-func', - ].join(''); + const errorMessage = [ + 'You should use integer as reservedConcurrency value on function: ', + 'new-service-dev-func', + ].join(''); - return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(errorMessage); - }); + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(errorMessage); + } + ); }); describe('#compileRole()', () => { @@ -2366,15 +2250,10 @@ describe('AwsCompileFunctions', () => { awsCompileFunctions.compileRole(resource, role); expect(resource).to.deep.equal({ - DependsOn: [ - role, - ], + DependsOn: [role], Properties: { Role: { - 'Fn::GetAtt': [ - role, - 'Arn', - ], + 'Fn::GetAtt': [role, 'Arn'], }, }, }); @@ -2386,15 +2265,10 @@ describe('AwsCompileFunctions', () => { awsCompileFunctions.compileRole(resource, role); expect(resource).to.deep.equal({ - DependsOn: [ - role, - ], + DependsOn: [role], Properties: { Role: { - 'Fn::GetAtt': [ - role, - 'Arn', - ], + 'Fn::GetAtt': [role, 'Arn'], }, }, }); @@ -2406,9 +2280,7 @@ describe('AwsCompileFunctions', () => { awsCompileFunctions.compileRole(resource, role); expect(resource).to.deep.equal({ - DependsOn: [ - 'Foo', - ], + DependsOn: ['Foo'], Properties: { Role: role, }, @@ -2444,34 +2316,29 @@ describe('AwsCompileFunctions', () => { const role = { Ref: 'Foo' }; const resource = { Properties: {} }; - expect(() => - awsCompileFunctions.compileRole(resource, role) - ).to.throw(Error); + expect(() => awsCompileFunctions.compileRole(resource, role)).to.throw(Error); }); it('should throw for object type Buffer', () => { const role = new Buffer('Foo'); const resource = { Properties: {} }; - expect(() => - awsCompileFunctions.compileRole(resource, role) - ).to.throw(Error); + expect(() => awsCompileFunctions.compileRole(resource, role)).to.throw(Error); }); it('should throw for object type Array', () => { const role = [1, 2, 3]; const resource = { Properties: {} }; - expect(() => - awsCompileFunctions.compileRole(resource, role) - ).to.throw(Error); + expect(() => awsCompileFunctions.compileRole(resource, role)).to.throw(Error); }); }); it('should not set unset properties when not specified in yml (layers, vpc, etc)', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { @@ -2481,10 +2348,7 @@ describe('AwsCompileFunctions', () => { }; const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -2499,11 +2363,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); @@ -2511,7 +2374,8 @@ describe('AwsCompileFunctions', () => { it('should set Layers when specified', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileFunctions.serverless.service.functions = { func: { @@ -2522,10 +2386,7 @@ describe('AwsCompileFunctions', () => { }; const compiledFunction = { Type: 'AWS::Lambda::Function', - DependsOn: [ - 'FuncLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], Properties: { Code: { S3Key: `${s3Folder}/${s3FileName}`, @@ -2541,11 +2402,10 @@ describe('AwsCompileFunctions', () => { }, }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled - .then(() => { + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FuncLambdaFunction + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction ).to.deep.equal(compiledFunction); }); }); diff --git a/lib/plugins/aws/package/compile/layers/index.js b/lib/plugins/aws/package/compile/layers/index.js index d79dd29cc..b75e5ec88 100644 --- a/lib/plugins/aws/package/compile/layers/index.js +++ b/lib/plugins/aws/package/compile/layers/index.js @@ -10,14 +10,13 @@ class AwsCompileLayers { this.serverless = serverless; this.options = options; const servicePath = this.serverless.config.servicePath || ''; - this.packagePath = this.serverless.service.package.path || - path.join(servicePath || '.', '.serverless'); + this.packagePath = + this.serverless.service.package.path || path.join(servicePath || '.', '.serverless'); this.provider = this.serverless.getProvider('aws'); this.hooks = { - 'package:compileLayers': () => BbPromise.bind(this) - .then(this.compileLayers), + 'package:compileLayers': () => BbPromise.bind(this).then(this.compileLayers), }; } @@ -27,9 +26,10 @@ class AwsCompileLayers { layerObject.package = layerObject.package || {}; const artifactFileName = this.provider.naming.getLayerArtifactName(layerName); - const artifactFilePath = layerObject.package && layerObject.package.artifact - ? layerObject.package.artifact - : path.join(this.serverless.config.servicePath, '.serverless', artifactFileName); + const artifactFilePath = + layerObject.package && layerObject.package.artifact + ? layerObject.package.artifact + : path.join(this.serverless.config.servicePath, '.serverless', artifactFileName); if (this.serverless.service.package.deploymentBucket) { newLayer.Properties.Content.S3Bucket = this.serverless.service.package.deploymentBucket; @@ -52,7 +52,10 @@ class AwsCompileLayers { let layerLogicalId = this.provider.naming.getLambdaLayerLogicalId(layerName); if (layerObject.retain) { - const sha = crypto.createHash('sha1').update(JSON.stringify(newLayer)).digest('hex'); + const sha = crypto + .createHash('sha1') + .update(JSON.stringify(newLayer)) + .digest('hex'); layerLogicalId = `${layerLogicalId}${sha}`; newLayer.DeletionPolicy = 'Retain'; } @@ -71,18 +74,21 @@ class AwsCompileLayers { newPermission.Properties.LayerVersionArn = { Ref: layerLogicalId }; newPermission.Properties.Principal = parsedAccount; const layerPermLogicalId = this.provider.naming.getLambdaLayerPermissionLogicalId( - layerName, parsedAccount); + layerName, + parsedAccount + ); newLayerObject[layerPermLogicalId] = newPermission; return newPermission; }); } - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newLayerObject); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newLayerObject + ); // Add layer to Outputs section - const layerOutputLogicalId = this.provider.naming - .getLambdaLayerOutputLogicalId(layerName); + const layerOutputLogicalId = this.provider.naming.getLambdaLayerOutputLogicalId(layerName); const newLayerOutput = this.cfOutputLayerTemplate(); newLayerOutput.Value = { Ref: layerLogicalId }; @@ -94,10 +100,7 @@ class AwsCompileLayers { compileLayers() { const allLayers = this.serverless.service.getAllLayers(); - return BbPromise.each( - allLayers, - layerName => this.compileLayer(layerName) - ); + return BbPromise.each(allLayers, layerName => this.compileLayer(layerName)); } cfLambdaLayerTemplate() { diff --git a/lib/plugins/aws/package/compile/layers/index.test.js b/lib/plugins/aws/package/compile/layers/index.test.js index 0687ce350..dfadb7011 100644 --- a/lib/plugins/aws/package/compile/layers/index.test.js +++ b/lib/plugins/aws/package/compile/layers/index.test.js @@ -37,21 +37,26 @@ describe('AwsCompileLayers', () => { const individualArtifact = 'test.zip'; awsCompileLayers.packagePath = getTmpDirPath(); // The contents of the test artifacts need to be predictable so the hashes stay the same - serverless.utils.writeFileSync(path.join(awsCompileLayers.packagePath, - serviceArtifact), 'foobar'); - serverless.utils.writeFileSync(path.join(awsCompileLayers.packagePath, - individualArtifact), 'barbaz'); + serverless.utils.writeFileSync( + path.join(awsCompileLayers.packagePath, serviceArtifact), + 'foobar' + ); + serverless.utils.writeFileSync( + path.join(awsCompileLayers.packagePath, individualArtifact), + 'barbaz' + ); awsCompileLayers.serverless.service.service = 'new-service'; awsCompileLayers.serverless.service.package.artifactDirectoryName = 'somedir'; - awsCompileLayers.serverless.service.package - .artifact = path.join(awsCompileLayers.packagePath, serviceArtifact); + awsCompileLayers.serverless.service.package.artifact = path.join( + awsCompileLayers.packagePath, + serviceArtifact + ); awsCompileLayers.serverless.service.layers = {}; awsCompileLayers.serverless.service.layers[layerName] = { name: 'test', package: { - artifact: path.join(awsCompileLayers.packagePath, - individualArtifact), + artifact: path.join(awsCompileLayers.packagePath, individualArtifact), }, handler: 'handler.hello', }; @@ -66,24 +71,26 @@ describe('AwsCompileLayers', () => { it('should use layer artifact if individually', () => { awsCompileLayers.serverless.service.package.individually = true; - return expect(awsCompileLayers.compileLayers()).to.be.fulfilled - .then(() => { - const layerResource = awsCompileLayers.serverless.service.provider - .compiledCloudFormationTemplate.Resources[compiledLayerName]; + return expect(awsCompileLayers.compileLayers()).to.be.fulfilled.then(() => { + const layerResource = + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + compiledLayerName + ]; const s3Folder = awsCompileLayers.serverless.service.package.artifactDirectoryName; - const s3FileName = awsCompileLayers.serverless.service - .layers[layerName].package.artifact.split(path.sep).pop(); + const s3FileName = awsCompileLayers.serverless.service.layers[layerName].package.artifact + .split(path.sep) + .pop(); - expect(layerResource.Properties.Content.S3Key) - .to.deep.equal(`${s3Folder}/${s3FileName}`); + expect(layerResource.Properties.Content.S3Key).to.deep.equal(`${s3Folder}/${s3FileName}`); }); }); it('should create a simple layer resource', () => { const s3Folder = awsCompileLayers.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileLayers.serverless.service.layers.test.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileLayers.serverless.service.layers = { test: { path: 'layer', @@ -106,15 +113,14 @@ describe('AwsCompileLayers', () => { }, }; - return expect(awsCompileLayers.compileLayers()).to.be.fulfilled - .then(() => { + return expect(awsCompileLayers.compileLayers()).to.be.fulfilled.then(() => { expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Resources.TestLambdaLayer + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Resources + .TestLambdaLayer ).to.deep.equal(compiledLayer); expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.TestLambdaLayerQualifiedArn + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .TestLambdaLayerQualifiedArn ).to.deep.equal(compiledLayerOutput); }); }); @@ -122,7 +128,8 @@ describe('AwsCompileLayers', () => { it('should create a layer resource with a retention policy', () => { const s3Folder = awsCompileLayers.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileLayers.serverless.service.layers.test.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileLayers.serverless.service.layers = { test: { path: 'layer', @@ -139,7 +146,10 @@ describe('AwsCompileLayers', () => { LayerName: 'test', }, }; - const sha = crypto.createHash('sha1').update(JSON.stringify(compiledLayer)).digest('hex'); + const sha = crypto + .createHash('sha1') + .update(JSON.stringify(compiledLayer)) + .digest('hex'); compiledLayer.DeletionPolicy = 'Retain'; const compiledLayerOutput = { Description: 'Current Lambda layer version', @@ -148,15 +158,15 @@ describe('AwsCompileLayers', () => { }, }; - return expect(awsCompileLayers.compileLayers()).to.be.fulfilled - .then(() => { + return expect(awsCompileLayers.compileLayers()).to.be.fulfilled.then(() => { expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Resources[`TestLambdaLayer${sha}`] + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + `TestLambdaLayer${sha}` + ] ).to.deep.equal(compiledLayer); expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.TestLambdaLayerQualifiedArn + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .TestLambdaLayerQualifiedArn ).to.deep.equal(compiledLayerOutput); }); }); @@ -164,7 +174,8 @@ describe('AwsCompileLayers', () => { it('should create a layer resource with permissions', () => { const s3Folder = awsCompileLayers.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileLayers.serverless.service.layers.test.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileLayers.serverless.service.layers = { test: { path: 'layer', @@ -198,19 +209,18 @@ describe('AwsCompileLayers', () => { }, }; - return expect(awsCompileLayers.compileLayers()).to.be.fulfilled - .then(() => { + return expect(awsCompileLayers.compileLayers()).to.be.fulfilled.then(() => { expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Resources.TestLambdaLayer + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Resources + .TestLambdaLayer ).to.deep.equal(compiledLayer); expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.TestLambdaLayerQualifiedArn + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .TestLambdaLayerQualifiedArn ).to.deep.equal(compiledLayerOutput); expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Resources.TestWildLambdaLayerPermission + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Resources + .TestWildLambdaLayerPermission ).to.deep.equal(compiledLayerVersion); }); }); @@ -218,7 +228,8 @@ describe('AwsCompileLayers', () => { it('should create a layer resource with permissions per account', () => { const s3Folder = awsCompileLayers.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileLayers.serverless.service.layers.test.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileLayers.serverless.service.layers = { test: { path: 'layer', @@ -263,23 +274,22 @@ describe('AwsCompileLayers', () => { }, }; - return expect(awsCompileLayers.compileLayers()).to.be.fulfilled - .then(() => { + return expect(awsCompileLayers.compileLayers()).to.be.fulfilled.then(() => { expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Resources.TestLambdaLayer + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Resources + .TestLambdaLayer ).to.deep.equal(compiledLayer); expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.TestLambdaLayerQualifiedArn + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .TestLambdaLayerQualifiedArn ).to.deep.equal(compiledLayerOutput); expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Resources.Test1111111LambdaLayerPermission + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Test1111111LambdaLayerPermission ).to.deep.equal(compiledLayerVersionNumber); expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Resources.Test2222222LambdaLayerPermission + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Resources + .Test2222222LambdaLayerPermission ).to.deep.equal(compiledLayerVersionString); }); }); @@ -287,7 +297,8 @@ describe('AwsCompileLayers', () => { it('should create a layer resource with metadata options set', () => { const s3Folder = awsCompileLayers.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileLayers.serverless.service.layers.test.package.artifact - .split(path.sep).pop(); + .split(path.sep) + .pop(); awsCompileLayers.serverless.service.layers = { test: { path: 'layer', @@ -316,15 +327,14 @@ describe('AwsCompileLayers', () => { }, }; - return expect(awsCompileLayers.compileLayers()).to.be.fulfilled - .then(() => { + return expect(awsCompileLayers.compileLayers()).to.be.fulfilled.then(() => { expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Resources.TestLambdaLayer + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Resources + .TestLambdaLayer ).to.deep.equal(compiledLayer); expect( - awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.TestLambdaLayerQualifiedArn + awsCompileLayers.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .TestLambdaLayerQualifiedArn ).to.deep.equal(compiledLayerOutput); }); }); diff --git a/lib/plugins/aws/package/index.js b/lib/plugins/aws/package/index.js index b461fdb2c..5ed9b0e5d 100644 --- a/lib/plugins/aws/package/index.js +++ b/lib/plugins/aws/package/index.js @@ -14,7 +14,8 @@ class AwsPackage { this.serverless = serverless; this.options = options; this.servicePath = this.serverless.config.servicePath || ''; - this.packagePath = this.options.package || + this.packagePath = + this.options.package || this.serverless.service.package.path || path.join(this.servicePath || '.', '.serverless'); this.provider = this.serverless.getProvider('aws'); @@ -37,10 +38,7 @@ class AwsPackage { package: { commands: { finalize: { - lifecycleEvents: [ - 'mergeCustomProviderResources', - 'saveServiceState', - ], + lifecycleEvents: ['mergeCustomProviderResources', 'saveServiceState'], }, }, }, @@ -52,37 +50,39 @@ class AwsPackage { /** * Outer lifecycle hooks */ - 'package:cleanup': () => BbPromise.bind(this) - .then(() => this.serverless.pluginManager.spawn('aws:common:validate')) - .then(() => this.serverless.pluginManager.spawn('aws:common:cleanupTempDir')), + 'package:cleanup': () => + BbPromise.bind(this) + .then(() => this.serverless.pluginManager.spawn('aws:common:validate')) + .then(() => this.serverless.pluginManager.spawn('aws:common:cleanupTempDir')), - 'package:initialize': () => BbPromise.bind(this) - .then(this.generateCoreTemplate), + 'package:initialize': () => BbPromise.bind(this).then(this.generateCoreTemplate), - 'package:setupProviderConfiguration': () => BbPromise.bind(this) - .then(this.mergeIamTemplates), + 'package:setupProviderConfiguration': () => BbPromise.bind(this).then(this.mergeIamTemplates), - 'before:package:compileFunctions': () => BbPromise.bind(this) - .then(this.generateArtifactDirectoryName), + 'before:package:compileFunctions': () => + BbPromise.bind(this).then(this.generateArtifactDirectoryName), - 'before:package:compileLayers': () => BbPromise.bind(this) - .then(this.generateArtifactDirectoryName), + 'before:package:compileLayers': () => + BbPromise.bind(this).then(this.generateArtifactDirectoryName), - 'package:finalize': () => BbPromise.bind(this) - .then(() => this.serverless.pluginManager.spawn('aws:package:finalize')), + 'package:finalize': () => + BbPromise.bind(this).then(() => + this.serverless.pluginManager.spawn('aws:package:finalize') + ), /** * Inner lifecycle hooks */ // Package finalize inner lifecycle - 'aws:package:finalize:mergeCustomProviderResources': () => BbPromise.bind(this) - .then(this.mergeCustomProviderResources), + 'aws:package:finalize:mergeCustomProviderResources': () => + BbPromise.bind(this).then(this.mergeCustomProviderResources), - 'aws:package:finalize:saveServiceState': () => BbPromise.bind(this) - .then(this.saveCompiledTemplate) - .then(this.saveServiceState) - .then(() => this.serverless.pluginManager.spawn('aws:common:moveArtifactsToPackage')), + 'aws:package:finalize:saveServiceState': () => + BbPromise.bind(this) + .then(this.saveCompiledTemplate) + .then(this.saveServiceState) + .then(() => this.serverless.pluginManager.spawn('aws:common:moveArtifactsToPackage')), }; } } diff --git a/lib/plugins/aws/package/index.test.js b/lib/plugins/aws/package/index.test.js index 9c912b57c..fdfa003e3 100644 --- a/lib/plugins/aws/package/index.test.js +++ b/lib/plugins/aws/package/index.test.js @@ -85,20 +85,17 @@ describe('AwsPackage', () => { let saveServiceStateStub; beforeEach(() => { - spawnStub = sinon - .stub(serverless.pluginManager, 'spawn'); - generateCoreTemplateStub = sinon - .stub(awsPackage, 'generateCoreTemplate').resolves(); - mergeIamTemplatesStub = sinon - .stub(awsPackage, 'mergeIamTemplates').resolves(); + spawnStub = sinon.stub(serverless.pluginManager, 'spawn'); + generateCoreTemplateStub = sinon.stub(awsPackage, 'generateCoreTemplate').resolves(); + mergeIamTemplatesStub = sinon.stub(awsPackage, 'mergeIamTemplates').resolves(); generateArtifactDirectoryNameStub = sinon - .stub(awsPackage, 'generateArtifactDirectoryName').resolves(); + .stub(awsPackage, 'generateArtifactDirectoryName') + .resolves(); mergeCustomProviderResourcesStub = sinon - .stub(awsPackage, 'mergeCustomProviderResources').resolves(); - saveCompiledTemplateStub = sinon - .stub(awsPackage, 'saveCompiledTemplate').resolves(); - saveServiceStateStub = sinon - .stub(awsPackage, 'saveServiceState').resolves(); + .stub(awsPackage, 'mergeCustomProviderResources') + .resolves(); + saveCompiledTemplateStub = sinon.stub(awsPackage, 'saveCompiledTemplate').resolves(); + saveServiceStateStub = sinon.stub(awsPackage, 'saveServiceState').resolves(); }); afterEach(() => { @@ -113,33 +110,32 @@ describe('AwsPackage', () => { it('should run "package:cleanup" hook', () => { const spawnAwsCommonValidateStub = spawnStub.withArgs('aws:common:validate').resolves(); - const spawnAwsCommonCleanupTempDirStub = spawnStub.withArgs('aws:common:cleanupTempDir') + const spawnAwsCommonCleanupTempDirStub = spawnStub + .withArgs('aws:common:cleanupTempDir') .resolves(); return awsPackage.hooks['package:cleanup']().then(() => { expect(spawnAwsCommonValidateStub.calledOnce).to.equal(true); - expect(spawnAwsCommonCleanupTempDirStub.calledAfter(spawnAwsCommonValidateStub)) - .to.equal(true); + expect(spawnAwsCommonCleanupTempDirStub.calledAfter(spawnAwsCommonValidateStub)).to.equal( + true + ); }); }); - it('should run "package:initialize" hook', () => awsPackage - .hooks['package:initialize']().then(() => { + it('should run "package:initialize" hook', () => + awsPackage.hooks['package:initialize']().then(() => { expect(generateCoreTemplateStub.calledOnce).to.equal(true); - }) - ); + })); - it('should run "package:setupProviderConfiguration" hook', () => awsPackage - .hooks['package:setupProviderConfiguration']().then(() => { + it('should run "package:setupProviderConfiguration" hook', () => + awsPackage.hooks['package:setupProviderConfiguration']().then(() => { expect(mergeIamTemplatesStub.calledOnce).to.equal(true); - }) - ); + })); - it('should run "before:package:compileFunctions" hook', () => awsPackage - .hooks['before:package:compileFunctions']().then(() => { + it('should run "before:package:compileFunctions" hook', () => + awsPackage.hooks['before:package:compileFunctions']().then(() => { expect(generateArtifactDirectoryNameStub.calledOnce).to.equal(true); - }) - ); + })); it('should run "package:finalize" hook', () => { const spawnAwsPackageFinalzeStub = spawnStub.withArgs('aws:package:finalize').resolves(); @@ -149,21 +145,22 @@ describe('AwsPackage', () => { }); }); - it('should run "aws:package:finalize:mergeCustomProviderResources" hook', () => awsPackage - .hooks['aws:package:finalize:mergeCustomProviderResources']().then(() => { + it('should run "aws:package:finalize:mergeCustomProviderResources" hook', () => + awsPackage.hooks['aws:package:finalize:mergeCustomProviderResources']().then(() => { expect(mergeCustomProviderResourcesStub.calledOnce).to.equal(true); - }) - ); + })); it('should run "aws:package:finalize:saveServiceState" hook', () => { const spawnAwsCommonMoveArtifactsToPackageStub = spawnStub - .withArgs('aws:common:moveArtifactsToPackage').resolves(); + .withArgs('aws:common:moveArtifactsToPackage') + .resolves(); return awsPackage.hooks['aws:package:finalize:saveServiceState']().then(() => { expect(saveCompiledTemplateStub.calledOnce).to.equal(true); expect(saveServiceStateStub.calledAfter(saveCompiledTemplateStub)).to.equal(true); - expect(spawnAwsCommonMoveArtifactsToPackageStub.calledAfter(saveServiceStateStub)) - .to.equal(true); + expect(spawnAwsCommonMoveArtifactsToPackageStub.calledAfter(saveServiceStateStub)).to.equal( + true + ); }); }); }); diff --git a/lib/plugins/aws/package/lib/core-cloudformation-template.json b/lib/plugins/aws/package/lib/core-cloudformation-template.json index 501b507f8..195b41fd7 100644 --- a/lib/plugins/aws/package/lib/core-cloudformation-template.json +++ b/lib/plugins/aws/package/lib/core-cloudformation-template.json @@ -3,8 +3,8 @@ "Description": "The AWS CloudFormation template for this Serverless application", "Resources": { "ServerlessDeploymentBucket": { - "Type" : "AWS::S3::Bucket", - "Properties" : { + "Type": "AWS::S3::Bucket", + "Properties": { "BucketEncryption": { "ServerSideEncryptionConfiguration": [ { diff --git a/lib/plugins/aws/package/lib/generateArtifactDirectoryName.js b/lib/plugins/aws/package/lib/generateArtifactDirectoryName.js index c0d1f6b33..1c5de71f9 100644 --- a/lib/plugins/aws/package/lib/generateArtifactDirectoryName.js +++ b/lib/plugins/aws/package/lib/generateArtifactDirectoryName.js @@ -10,8 +10,7 @@ module.exports = { const serviceStage = `${this.serverless.service.service}/${this.provider.getStage()}`; const dateString = `${date.getTime().toString()}-${date.toISOString()}`; const prefix = this.provider.getDeploymentPrefix(); - this.serverless.service.package - .artifactDirectoryName = `${prefix}/${serviceStage}/${dateString}`; + this.serverless.service.package.artifactDirectoryName = `${prefix}/${serviceStage}/${dateString}`; } return BbPromise.resolve(); diff --git a/lib/plugins/aws/package/lib/generateArtifactDirectoryName.test.js b/lib/plugins/aws/package/lib/generateArtifactDirectoryName.test.js index 4a39df554..f4022872b 100644 --- a/lib/plugins/aws/package/lib/generateArtifactDirectoryName.test.js +++ b/lib/plugins/aws/package/lib/generateArtifactDirectoryName.test.js @@ -20,9 +20,8 @@ describe('#generateArtifactDirectoryName()', () => { awsPackage.serverless.cli = new serverless.classes.CLI(); }); - it('should generate a name for the artifact directory based on the current time', () => awsPackage - .generateArtifactDirectoryName().then(() => { + it('should generate a name for the artifact directory based on the current time', () => + awsPackage.generateArtifactDirectoryName().then(() => { expect(awsPackage.serverless.service.package.artifactDirectoryName).to.match(/[0-9]+-.+/); - }) - ); + })); }); diff --git a/lib/plugins/aws/package/lib/generateCoreTemplate.js b/lib/plugins/aws/package/lib/generateCoreTemplate.js index 3f99b2060..8e62d6347 100644 --- a/lib/plugins/aws/package/lib/generateCoreTemplate.js +++ b/lib/plugins/aws/package/lib/generateCoreTemplate.js @@ -8,20 +8,18 @@ const validateS3BucketName = require('../../lib/validateS3BucketName'); module.exports = { generateCoreTemplate() { - _.assign( - this, - validateS3BucketName - ); + _.assign(this, validateS3BucketName); - this.serverless.service.provider - .compiledCloudFormationTemplate = this.serverless.utils.readFileSync( - path.join(this.serverless.config.serverlessPath, - 'plugins', - 'aws', - 'package', - 'lib', - 'core-cloudformation-template.json') - ); + this.serverless.service.provider.compiledCloudFormationTemplate = this.serverless.utils.readFileSync( + path.join( + this.serverless.config.serverlessPath, + 'plugins', + 'aws', + 'package', + 'lib', + 'core-cloudformation-template.json' + ) + ); const bucketName = this.serverless.service.provider.deploymentBucket; @@ -31,15 +29,19 @@ module.exports = { const tags = deploymentBucketObject.tags; const deploymentBucketLogicalId = this.provider.naming.getDeploymentBucketLogicalId(); - const bucketTags = _.map(_.keys(tags), (key) => ({ + const bucketTags = _.map(_.keys(tags), key => ({ Key: key, Value: tags[key], })); - Object.assign(this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[deploymentBucketLogicalId].Properties, { + Object.assign( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + deploymentBucketLogicalId + ].Properties, + { Tags: bucketTags, - }); + } + ); } const isS3TransferAccelerationSupported = this.provider.isS3TransferAccelerationSupported(); @@ -64,48 +66,58 @@ module.exports = { } this.bucketName = bucketName; this.serverless.service.package.deploymentBucket = bucketName; - this.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.ServerlessDeploymentBucketName.Value = bucketName; + this.serverless.service.provider.compiledCloudFormationTemplate.Outputs.ServerlessDeploymentBucketName.Value = bucketName; - delete this.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ServerlessDeploymentBucket; + delete this.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ServerlessDeploymentBucket; }); } if (isS3TransferAccelerationEnabled && isS3TransferAccelerationSupported) { // enable acceleration via CloudFormation - Object.assign(this.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ServerlessDeploymentBucket.Properties, { + Object.assign( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ServerlessDeploymentBucket.Properties, + { AccelerateConfiguration: { AccelerationStatus: 'Enabled', }, - }); + } + ); // keep track of acceleration status via CloudFormation Output - this.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.ServerlessDeploymentBucketAccelerated = { Value: true }; + this.serverless.service.provider.compiledCloudFormationTemplate.Outputs.ServerlessDeploymentBucketAccelerated = { + Value: true, + }; } else if (isS3TransferAccelerationDisabled && isS3TransferAccelerationSupported) { // explicitly disable acceleration via CloudFormation - Object.assign(this.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ServerlessDeploymentBucket.Properties, { + Object.assign( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ServerlessDeploymentBucket.Properties, + { AccelerateConfiguration: { AccelerationStatus: 'Suspended', }, - }); + } + ); } const coreTemplateFileName = this.provider.naming.getCoreTemplateFileName(); - const coreTemplateFilePath = path.join(this.serverless.config.servicePath, + const coreTemplateFilePath = path.join( + this.serverless.config.servicePath, '.serverless', - coreTemplateFileName); + coreTemplateFileName + ); - this.serverless.utils.writeFileSync(coreTemplateFilePath, - this.serverless.service.provider.compiledCloudFormationTemplate); + this.serverless.utils.writeFileSync( + coreTemplateFilePath, + this.serverless.service.provider.compiledCloudFormationTemplate + ); - this.serverless.service.provider.coreCloudFormationTemplate = - _.cloneDeep(this.serverless.service.provider.compiledCloudFormationTemplate); + this.serverless.service.provider.coreCloudFormationTemplate = _.cloneDeep( + this.serverless.service.provider.compiledCloudFormationTemplate + ); return BbPromise.resolve(); }, - }; diff --git a/lib/plugins/aws/package/lib/generateCoreTemplate.test.js b/lib/plugins/aws/package/lib/generateCoreTemplate.test.js index 5d487b32d..acb0b2ea6 100644 --- a/lib/plugins/aws/package/lib/generateCoreTemplate.test.js +++ b/lib/plugins/aws/package/lib/generateCoreTemplate.test.js @@ -51,25 +51,16 @@ describe('#generateCoreTemplate()', () => { awsPlugin.serverless.service.provider.deploymentBucket = bucketName; const coreCloudFormationTemplate = awsPlugin.serverless.utils.readFileSync( - path.join( - __dirname, - 'core-cloudformation-template.json' - ) + path.join(__dirname, 'core-cloudformation-template.json') ); - awsPlugin.serverless.service.provider - .compiledCloudFormationTemplate = coreCloudFormationTemplate; + awsPlugin.serverless.service.provider.compiledCloudFormationTemplate = coreCloudFormationTemplate; - return expect(awsPlugin.generateCoreTemplate()).to.be.fulfilled - .then(() => { - const template = awsPlugin.serverless.service.provider.compiledCloudFormationTemplate; - expect( - template.Outputs.ServerlessDeploymentBucketName.Value - ).to.equal(bucketName); - // eslint-disable-next-line no-unused-expressions - expect( - template.Resources.ServerlessDeploymentBucket - ).to.not.exist; - }); + return expect(awsPlugin.generateCoreTemplate()).to.be.fulfilled.then(() => { + const template = awsPlugin.serverless.service.provider.compiledCloudFormationTemplate; + expect(template.Outputs.ServerlessDeploymentBucketName.Value).to.equal(bucketName); + // eslint-disable-next-line no-unused-expressions + expect(template.Resources.ServerlessDeploymentBucket).to.not.exist; + }); }); it('should add resource tags to the bucket if present', () => { @@ -84,8 +75,8 @@ describe('#generateCoreTemplate()', () => { return expect(awsPlugin.generateCoreTemplate()).to.be.fulfilled.then(() => { expect( - awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ServerlessDeploymentBucket + awsPlugin.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ServerlessDeploymentBucket ).to.be.deep.equal({ Type: 'AWS::S3::Bucket', Properties: { @@ -98,10 +89,7 @@ describe('#generateCoreTemplate()', () => { }, ], }, - Tags: [ - { Key: 'FOO', Value: 'bar' }, - { Key: 'BAZ', Value: 'qux' }, - ], + Tags: [{ Key: 'FOO', Value: 'bar' }, { Key: 'BAZ', Value: 'qux' }], }, }); }); @@ -114,36 +102,25 @@ describe('#generateCoreTemplate()', () => { awsPlugin.provider.options['aws-s3-accelerate'] = true; const coreCloudFormationTemplate = awsPlugin.serverless.utils.readFileSync( - path.join( - __dirname, - 'core-cloudformation-template.json' - ) + path.join(__dirname, 'core-cloudformation-template.json') ); - awsPlugin.serverless.service.provider - .compiledCloudFormationTemplate = coreCloudFormationTemplate; + awsPlugin.serverless.service.provider.compiledCloudFormationTemplate = coreCloudFormationTemplate; - return expect(awsPlugin.generateCoreTemplate()).to.be.fulfilled - .then(() => { - const template = awsPlugin.serverless.service.provider.compiledCloudFormationTemplate; - expect( - template.Outputs.ServerlessDeploymentBucketName.Value - ).to.equal(bucketName); - // eslint-disable-next-line no-unused-expressions - expect( - template.Resources.ServerlessDeploymentBucket - ).to.not.exist; - // eslint-disable-next-line no-unused-expressions - expect( - template.Outputs.ServerlessDeploymentBucketAccelerated - ).to.not.exist; - }); + return expect(awsPlugin.generateCoreTemplate()).to.be.fulfilled.then(() => { + const template = awsPlugin.serverless.service.provider.compiledCloudFormationTemplate; + expect(template.Outputs.ServerlessDeploymentBucketName.Value).to.equal(bucketName); + // eslint-disable-next-line no-unused-expressions + expect(template.Resources.ServerlessDeploymentBucket).to.not.exist; + // eslint-disable-next-line no-unused-expressions + expect(template.Outputs.ServerlessDeploymentBucketAccelerated).to.not.exist; + }); }); it('should use a auto generated bucket if you does not specify deploymentBucket', () => expect(awsPlugin.generateCoreTemplate()).to.be.fulfilled.then(() => { expect( - awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ServerlessDeploymentBucket + awsPlugin.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ServerlessDeploymentBucket ).to.be.deep.equal({ Type: 'AWS::S3::Bucket', Properties: { @@ -158,19 +135,17 @@ describe('#generateCoreTemplate()', () => { }, }, }); - }) - ); + })); it('should add a deployment bucket to the CF template, if not provided', () => { sinon.stub(awsPlugin.provider, 'request').resolves(); sinon.stub(serverless.utils, 'writeFileSync').resolves(); serverless.config.servicePath = './'; - return awsPlugin.generateCoreTemplate() - .then(() => { - const template = serverless.service.provider.coreCloudFormationTemplate; - expect(template).to.not.equal(null); - }); + return awsPlugin.generateCoreTemplate().then(() => { + const template = serverless.service.provider.coreCloudFormationTemplate; + expect(template).to.not.equal(null); + }); }); it('should add a custom output if S3 Transfer Acceleration is enabled', () => { @@ -179,12 +154,11 @@ describe('#generateCoreTemplate()', () => { serverless.config.servicePath = './'; awsPlugin.provider.options['aws-s3-accelerate'] = true; - return awsPlugin.generateCoreTemplate() - .then(() => { - const template = serverless.service.provider.coreCloudFormationTemplate; - expect(template.Outputs.ServerlessDeploymentBucketAccelerated).to.not.equal(null); - expect(template.Outputs.ServerlessDeploymentBucketAccelerated.Value).to.equal(true); - }); + return awsPlugin.generateCoreTemplate().then(() => { + const template = serverless.service.provider.coreCloudFormationTemplate; + expect(template.Outputs.ServerlessDeploymentBucketAccelerated).to.not.equal(null); + expect(template.Outputs.ServerlessDeploymentBucketAccelerated.Value).to.equal(true); + }); }); it('should explicitly disable S3 Transfer Acceleration, if requested', () => { @@ -193,27 +167,26 @@ describe('#generateCoreTemplate()', () => { serverless.config.servicePath = './'; awsPlugin.provider.options['no-aws-s3-accelerate'] = true; - return awsPlugin.generateCoreTemplate() - .then(() => { - const template = serverless.service.provider.coreCloudFormationTemplate; - expect(template.Resources.ServerlessDeploymentBucket).to.be.deep.equal({ - Type: 'AWS::S3::Bucket', - Properties: { - AccelerateConfiguration: { - AccelerationStatus: 'Suspended', - }, - BucketEncryption: { - ServerSideEncryptionConfiguration: [ - { - ServerSideEncryptionByDefault: { - SSEAlgorithm: 'AES256', - }, - }, - ], - }, + return awsPlugin.generateCoreTemplate().then(() => { + const template = serverless.service.provider.coreCloudFormationTemplate; + expect(template.Resources.ServerlessDeploymentBucket).to.be.deep.equal({ + Type: 'AWS::S3::Bucket', + Properties: { + AccelerateConfiguration: { + AccelerationStatus: 'Suspended', }, - }); + BucketEncryption: { + ServerSideEncryptionConfiguration: [ + { + ServerSideEncryptionByDefault: { + SSEAlgorithm: 'AES256', + }, + }, + ], + }, + }, }); + }); }); it('should exclude AccelerateConfiguration for govcloud region', () => { @@ -222,24 +195,23 @@ describe('#generateCoreTemplate()', () => { serverless.config.servicePath = './'; awsPlugin.provider.options.region = 'us-gov-west-1'; - return awsPlugin.generateCoreTemplate() - .then(() => { - const template = serverless.service.provider.coreCloudFormationTemplate; - expect(template.Resources.ServerlessDeploymentBucket).to.be.deep.equal({ - Type: 'AWS::S3::Bucket', - Properties: { - BucketEncryption: { - ServerSideEncryptionConfiguration: [ - { - ServerSideEncryptionByDefault: { - SSEAlgorithm: 'AES256', - }, + return awsPlugin.generateCoreTemplate().then(() => { + const template = serverless.service.provider.coreCloudFormationTemplate; + expect(template.Resources.ServerlessDeploymentBucket).to.be.deep.equal({ + Type: 'AWS::S3::Bucket', + Properties: { + BucketEncryption: { + ServerSideEncryptionConfiguration: [ + { + ServerSideEncryptionByDefault: { + SSEAlgorithm: 'AES256', }, - ], - }, + }, + ], }, - }); + }, }); + }); }); it('should explode if transfer acceleration is both enabled and disabled', () => { @@ -249,8 +221,9 @@ describe('#generateCoreTemplate()', () => { awsPlugin.provider.options['aws-s3-accelerate'] = true; awsPlugin.provider.options['no-aws-s3-accelerate'] = true; - return expect( - awsPlugin.generateCoreTemplate() - ).to.be.rejectedWith(serverless.classes.Error, /at the same time/); + return expect(awsPlugin.generateCoreTemplate()).to.be.rejectedWith( + serverless.classes.Error, + /at the same time/ + ); }); }); diff --git a/lib/plugins/aws/package/lib/iam-role-lambda-execution-template.json b/lib/plugins/aws/package/lib/iam-role-lambda-execution-template.json index d75bf0450..c2ca81b2c 100644 --- a/lib/plugins/aws/package/lib/iam-role-lambda-execution-template.json +++ b/lib/plugins/aws/package/lib/iam-role-lambda-execution-template.json @@ -7,13 +7,9 @@ { "Effect": "Allow", "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] + "Service": ["lambda.amazonaws.com"] }, - "Action": [ - "sts:AssumeRole" - ] + "Action": ["sts:AssumeRole"] } ] }, @@ -25,16 +21,12 @@ "Statement": [ { "Effect": "Allow", - "Action": [ - "logs:CreateLogStream" - ], + "Action": ["logs:CreateLogStream"], "Resource": [] }, { "Effect": "Allow", - "Action": [ - "logs:PutLogEvents" - ], + "Action": ["logs:PutLogEvents"], "Resource": [] } ] diff --git a/lib/plugins/aws/package/lib/mergeCustomProviderResources.test.js b/lib/plugins/aws/package/lib/mergeCustomProviderResources.test.js index 885cf5b34..8ecfc7b97 100644 --- a/lib/plugins/aws/package/lib/mergeCustomProviderResources.test.js +++ b/lib/plugins/aws/package/lib/mergeCustomProviderResources.test.js @@ -14,40 +14,33 @@ describe('mergeCustomProviderResources', () => { serverless = new Serverless(); awsPackage = new AwsPackage(serverless, {}); - coreCloudFormationTemplate = awsPackage - .serverless.utils.readFileSync( - path.join( - __dirname, - '..', - 'lib', - 'core-cloudformation-template.json' - ) - ); + coreCloudFormationTemplate = awsPackage.serverless.utils.readFileSync( + path.join(__dirname, '..', 'lib', 'core-cloudformation-template.json') + ); - awsPackage.serverless.service.provider - .compiledCloudFormationTemplate = coreCloudFormationTemplate; + awsPackage.serverless.service.provider.compiledCloudFormationTemplate = coreCloudFormationTemplate; }); describe('#mergeCustomProviderResources()', () => { it('should set an empty resources.Resources object if it is not present', () => { - awsPackage.serverless.service.provider - .compiledCloudFormationTemplate.Resources = {}; // reset the core CloudFormation template + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources = {}; // reset the core CloudFormation template awsPackage.serverless.service.resources.Resources = null; return awsPackage.mergeCustomProviderResources().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources) - .to.deep.equal({}); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources + ).to.deep.equal({}); }); }); it('should set an empty resources.Outputs object if it is not present', () => { - awsPackage.serverless.service.provider - .compiledCloudFormationTemplate.Outputs = {}; // reset the core CloudFormation template + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Outputs = {}; // reset the core CloudFormation template awsPackage.serverless.service.resources.Outputs = null; return awsPackage.mergeCustomProviderResources().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Outputs) - .to.deep.equal({}); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Outputs + ).to.deep.equal({}); }); }); @@ -59,8 +52,9 @@ describe('mergeCustomProviderResources', () => { awsPackage.serverless.service.resources = customResourcesMock; return awsPackage.mergeCustomProviderResources().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Description) - .to.equal(customResourcesMock.Description); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Description + ).to.equal(customResourcesMock.Description); }); }); @@ -91,8 +85,9 @@ describe('mergeCustomProviderResources', () => { awsPackage.serverless.service.resources = customResourcesMock; return awsPackage.mergeCustomProviderResources().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ServerlessDeploymentBucket + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ServerlessDeploymentBucket ).to.deep.equal(customResourcesMock.Resources.ServerlessDeploymentBucket); }); }); @@ -124,16 +119,23 @@ describe('mergeCustomProviderResources', () => { awsPackage.serverless.service.resources = customResourcesMock; return awsPackage.mergeCustomProviderResources().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FakeResource1).to.deep.equal(customResourcesMock.Resources.FakeResource1); - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources.FakeResource2).to.deep.equal(customResourcesMock.Resources.FakeResource2); - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.FakeOutput1).to.deep.equal(customResourcesMock.Outputs.FakeOutput1); - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.FakeOutput2).to.deep.equal(customResourcesMock.Outputs.FakeOutput2); - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .CustomDefinition).to.deep.equal(customResourcesMock.CustomDefinition); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FakeResource1 + ).to.deep.equal(customResourcesMock.Resources.FakeResource1); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FakeResource2 + ).to.deep.equal(customResourcesMock.Resources.FakeResource2); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Outputs.FakeOutput1 + ).to.deep.equal(customResourcesMock.Outputs.FakeOutput1); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Outputs.FakeOutput2 + ).to.deep.equal(customResourcesMock.Outputs.FakeOutput2); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.CustomDefinition + ).to.deep.equal(customResourcesMock.CustomDefinition); }); }); @@ -151,28 +153,21 @@ describe('mergeCustomProviderResources', () => { expect( awsPackage.serverless.service.provider.compiledCloudFormationTemplate .AWSTemplateFormatVersion - ).to.equal( - coreCloudFormationTemplate.AWSTemplateFormatVersion - ); + ).to.equal(coreCloudFormationTemplate.AWSTemplateFormatVersion); expect( awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Description - ).to.equal( - coreCloudFormationTemplate.Description - ); - - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources.ServerlessDeploymentBucket - ).to.deep.equal( - coreCloudFormationTemplate.Resources.ServerlessDeploymentBucket - ); + ).to.equal(coreCloudFormationTemplate.Description); expect( - awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Outputs.ServerlessDeploymentBucketName - ).to.deep.equal( - coreCloudFormationTemplate.Outputs.ServerlessDeploymentBucketName - ); + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources + .ServerlessDeploymentBucket + ).to.deep.equal(coreCloudFormationTemplate.Resources.ServerlessDeploymentBucket); + + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Outputs + .ServerlessDeploymentBucketName + ).to.deep.equal(coreCloudFormationTemplate.Outputs.ServerlessDeploymentBucketName); }); }); }); diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.js b/lib/plugins/aws/package/lib/mergeIamTemplates.js index 030751af7..945312b69 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.js @@ -18,10 +18,9 @@ module.exports = { } // create log group resources - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObject = this.serverless.service.getFunction(functionName); - const logGroupLogicalId = this.provider.naming - .getLogGroupLogicalId(functionName); + const logGroupLogicalId = this.provider.naming.getLogGroupLogicalId(functionName); const newLogGroup = { [logGroupLogicalId]: { Type: 'AWS::Logs::LogGroup', @@ -37,14 +36,15 @@ module.exports = { if (_.isInteger(retentionInDays) && retentionInDays > 0) { newLogGroup[logGroupLogicalId].Properties.RetentionInDays = retentionInDays; } else { - const errorMessage = - `logRetentionInDays should be an integer over 0 but ${rawRetentionInDays}`; + const errorMessage = `logRetentionInDays should be an integer over 0 but ${rawRetentionInDays}`; throw new this.serverless.classes.Error(errorMessage); } } - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newLogGroup); + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newLogGroup + ); }); // resolve early if provider level role is provided @@ -54,7 +54,7 @@ module.exports = { // resolve early if all functions contain a custom role const customRolesProvided = []; - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObject = this.serverless.service.getFunction(functionName); customRolesProvided.push('role' in functionObject); }); @@ -64,86 +64,76 @@ module.exports = { // merge in the iamRoleLambdaTemplate const iamRoleLambdaExecutionTemplate = this.serverless.utils.readFileSync( - path.join(this.serverless.config.serverlessPath, + path.join( + this.serverless.config.serverlessPath, 'plugins', 'aws', 'package', 'lib', - 'iam-role-lambda-execution-template.json') + 'iam-role-lambda-execution-template.json' + ) ); iamRoleLambdaExecutionTemplate.Properties.Path = this.provider.naming.getRolePath(); iamRoleLambdaExecutionTemplate.Properties.RoleName = this.provider.naming.getRoleName(); - iamRoleLambdaExecutionTemplate.Properties.Policies[0] - .PolicyName = this.provider.naming.getPolicyName(); + iamRoleLambdaExecutionTemplate.Properties.Policies[0].PolicyName = this.provider.naming.getPolicyName(); - _.merge( - this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - { - [this.provider.naming.getRoleLogicalId()]: iamRoleLambdaExecutionTemplate, - } - ); + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { + [this.provider.naming.getRoleLogicalId()]: iamRoleLambdaExecutionTemplate, + }); - const canonicalFunctionNamePrefix = - `${this.provider.serverless.service.service}-${this.provider.getStage()}`; - const logGroupsPrefix = this.provider.naming - .getLogGroupName(canonicalFunctionNamePrefix); + const canonicalFunctionNamePrefix = `${ + this.provider.serverless.service.service + }-${this.provider.getStage()}`; + const logGroupsPrefix = this.provider.naming.getLogGroupName(canonicalFunctionNamePrefix); const policyDocumentStatements = this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument + .Resources[this.provider.naming.getRoleLogicalId()].Properties.Policies[0].PolicyDocument .Statement; // Ensure general polices for functions with default name resolution - policyDocumentStatements[0] - .Resource - .push({ - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + - `:log-group:${logGroupsPrefix}*:*`, - }); + policyDocumentStatements[0].Resource.push({ + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${logGroupsPrefix}*:*`, + }); - policyDocumentStatements[1] - .Resource - .push({ - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + - `:log-group:${logGroupsPrefix}*:*:*`, - }); + policyDocumentStatements[1].Resource.push({ + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${logGroupsPrefix}*:*:*`, + }); // Ensure policies for functions with custom name resolution - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const { name: resolvedFunctionName } = this.serverless.service.getFunction(functionName); if (!resolvedFunctionName || resolvedFunctionName.startsWith(canonicalFunctionNamePrefix)) { return; } - const customFunctionNamelogGroupsPrefix = - this.provider.naming.getLogGroupName(resolvedFunctionName); + const customFunctionNamelogGroupsPrefix = this.provider.naming.getLogGroupName( + resolvedFunctionName + ); - policyDocumentStatements[0] - .Resource - .push({ - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + - `:log-group:${customFunctionNamelogGroupsPrefix}:*`, - }); + policyDocumentStatements[0].Resource.push({ + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${customFunctionNamelogGroupsPrefix}:*`, + }); - policyDocumentStatements[1] - .Resource - .push({ - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + - `:log-group:${customFunctionNamelogGroupsPrefix}:*:*`, - }); + policyDocumentStatements[1].Resource.push({ + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${customFunctionNamelogGroupsPrefix}:*:*`, + }); }); if (this.serverless.service.provider.iamRoleStatements) { // add custom iam role statements - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement = policyDocumentStatements - .concat(this.serverless.service.provider.iamRoleStatements); + this.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + this.provider.naming.getRoleLogicalId() + ].Properties.Policies[0].PolicyDocument.Statement = policyDocumentStatements.concat( + this.serverless.service.provider.iamRoleStatements + ); } if (this.serverless.service.provider.iamManagedPolicies) { @@ -156,7 +146,7 @@ module.exports = { // check if one of the functions contains vpc configuration const vpcConfigProvided = []; - this.serverless.service.getAllFunctions().forEach((functionName) => { + this.serverless.service.getAllFunctions().forEach(functionName => { const functionObject = this.serverless.service.getFunction(functionName); if ('vpc' in functionObject) { vpcConfigProvided.push(true); @@ -165,24 +155,27 @@ module.exports = { if (_.includes(vpcConfigProvided, true) || this.serverless.service.provider.vpc) { // add managed iam policy to allow ENI management - this.mergeManagedPolicies([{ - 'Fn::Join': ['', - [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', + this.mergeManagedPolicies([ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', + ], ], - ], - }]); + }, + ]); } return BbPromise.resolve(); }, mergeManagedPolicies(managedPolicies) { - const resource = this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.provider.naming.getRoleLogicalId()] - .Properties; + const resource = this.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + this.provider.naming.getRoleLogicalId() + ].Properties; if (!_.has(resource, 'ManagedPolicyArns') || _.isEmpty(resource.ManagedPolicyArns)) { resource.ManagedPolicyArns = []; } @@ -201,9 +194,11 @@ module.exports = { } else { const descriptions = statements.map((statement, i) => { const missing = ['Effect', 'Action', 'Resource'].filter( - prop => statement[prop] === undefined); - return missing.length === 0 ? null : - `statement ${i} is missing the following properties: ${missing.join(', ')}`; + prop => statement[prop] === undefined + ); + return missing.length === 0 + ? null + : `statement ${i} is missing the following properties: ${missing.join(', ')}`; }); const flawed = descriptions.filter(curr => curr); if (flawed.length) { diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js index c659ab00f..09447f641 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js @@ -39,106 +39,93 @@ describe('#mergeIamTemplates()', () => { it('should not merge if there are no functions', () => { awsPackage.serverless.service.functions = {}; - return awsPackage.mergeIamTemplates() - .then(() => { - const resources = awsPackage.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + return awsPackage.mergeIamTemplates().then(() => { + const resources = + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources; - return expect( - resources[awsPackage.provider.naming.getRoleLogicalId()] - ).to.not.exist; - }); + return expect(resources[awsPackage.provider.naming.getRoleLogicalId()]).to.not.exist; + }); }); - it('should merge the IamRoleLambdaExecution template into the CloudFormation template', - () => awsPackage.mergeIamTemplates() - .then(() => { - const canonicalFunctionsPrefix = - `${awsPackage.serverless.service.service}-${awsPackage.provider.getStage()}`; + it('should merge the IamRoleLambdaExecution template into the CloudFormation template', () => + awsPackage.mergeIamTemplates().then(() => { + const canonicalFunctionsPrefix = `${ + awsPackage.serverless.service.service + }-${awsPackage.provider.getStage()}`; - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - ).to.deep.equal({ - Type: 'AWS::IAM::Role', - Properties: { - AssumeRolePolicyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Principal: { - Service: [ - 'lambda.amazonaws.com', - ], - }, - Action: [ - 'sts:AssumeRole', - ], - }, - ], - }, - Path: '/', - Policies: [ + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ] + ).to.deep.equal({ + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ { - PolicyName: { - 'Fn::Join': [ - '-', - [ - awsPackage.provider.getStage(), - awsPackage.serverless.service.service, - 'lambda', - ], - ], - }, - PolicyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Action: [ - 'logs:CreateLogStream', - ], - Resource: [ - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*`, - }, - ], - }, - { - Effect: 'Allow', - Action: [ - 'logs:PutLogEvents', - ], - Resource: [ - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*:*`, - }, - ], - }, - ], + Effect: 'Allow', + Principal: { + Service: ['lambda.amazonaws.com'], }, + Action: ['sts:AssumeRole'], }, ], - RoleName: { - 'Fn::Join': [ - '-', - [ - awsPackage.serverless.service.service, - awsPackage.provider.getStage(), - { - Ref: 'AWS::Region', - }, - 'lambdaRole', - ], - ], - }, }, - }); - }) - ); - + Path: '/', + Policies: [ + { + PolicyName: { + 'Fn::Join': [ + '-', + [awsPackage.provider.getStage(), awsPackage.serverless.service.service, 'lambda'], + ], + }, + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: ['logs:CreateLogStream'], + Resource: [ + { + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*`, + }, + ], + }, + { + Effect: 'Allow', + Action: ['logs:PutLogEvents'], + Resource: [ + { + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*:*`, + }, + ], + }, + ], + }, + }, + ], + RoleName: { + 'Fn::Join': [ + '-', + [ + awsPackage.serverless.service.service, + awsPackage.provider.getStage(), + { + Ref: 'AWS::Region', + }, + 'lambdaRole', + ], + ], + }, + }, + }); + })); it('should ensure IAM policies for custom named functions', () => { const customFunctionName = 'foo-bar'; @@ -151,123 +138,112 @@ describe('#mergeIamTemplates()', () => { }; serverless.service.setFunctionNames(); // Ensure to resolve function names - return awsPackage.mergeIamTemplates() - .then(() => { - const canonicalFunctionsPrefix = - `${awsPackage.serverless.service.service}-${awsPackage.provider.getStage()}`; + return awsPackage.mergeIamTemplates().then(() => { + const canonicalFunctionsPrefix = `${ + awsPackage.serverless.service.service + }-${awsPackage.provider.getStage()}`; - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - ).to.deep.equal({ - Type: 'AWS::IAM::Role', - Properties: { - AssumeRolePolicyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Principal: { - Service: [ - 'lambda.amazonaws.com', - ], - }, - Action: [ - 'sts:AssumeRole', - ], - }, - ], - }, - Path: '/', - Policies: [ + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ] + ).to.deep.equal({ + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ { - PolicyName: { - 'Fn::Join': [ - '-', - [ - awsPackage.provider.getStage(), - awsPackage.serverless.service.service, - 'lambda', - ], - ], - }, - PolicyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Action: [ - 'logs:CreateLogStream', - ], - Resource: [ - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*`, - }, - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + `log-group:/aws/lambda/${customFunctionName}:*`, - }, - ], - }, - { - Effect: 'Allow', - Action: [ - 'logs:PutLogEvents', - ], - Resource: [ - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*:*`, - }, - { - 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' - + `log-group:/aws/lambda/${customFunctionName}:*:*`, - }, - ], - }, - ], + Effect: 'Allow', + Principal: { + Service: ['lambda.amazonaws.com'], }, + Action: ['sts:AssumeRole'], }, ], - RoleName: { - 'Fn::Join': [ - '-', - [ - awsPackage.serverless.service.service, - awsPackage.provider.getStage(), - { - Ref: 'AWS::Region', - }, - 'lambdaRole', - ], - ], - }, }, - }); + Path: '/', + Policies: [ + { + PolicyName: { + 'Fn::Join': [ + '-', + [awsPackage.provider.getStage(), awsPackage.serverless.service.service, 'lambda'], + ], + }, + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: ['logs:CreateLogStream'], + Resource: [ + { + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*`, + }, + { + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + `log-group:/aws/lambda/${customFunctionName}:*`, + }, + ], + }, + { + Effect: 'Allow', + Action: ['logs:PutLogEvents'], + Resource: [ + { + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + `log-group:/aws/lambda/${canonicalFunctionsPrefix}*:*:*`, + }, + { + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + `log-group:/aws/lambda/${customFunctionName}:*:*`, + }, + ], + }, + ], + }, + }, + ], + RoleName: { + 'Fn::Join': [ + '-', + [ + awsPackage.serverless.service.service, + awsPackage.provider.getStage(), + { + Ref: 'AWS::Region', + }, + 'lambdaRole', + ], + ], + }, + }, }); + }); }); it('should add custom IAM policy statements', () => { awsPackage.serverless.service.provider.iamRoleStatements = [ { Effect: 'Allow', - Action: [ - 'something:SomethingElse', - ], + Action: ['something:SomethingElse'], Resource: 'some:aws:arn:xxx:*:*', }, ]; - return awsPackage.mergeIamTemplates() - .then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - .Properties - .Policies[0] - .PolicyDocument - .Statement[2] - ).to.deep.equal(awsPackage.serverless.service.provider.iamRoleStatements[0]); - }); + return awsPackage.mergeIamTemplates().then(() => { + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ].Properties.Policies[0].PolicyDocument.Statement[2] + ).to.deep.equal(awsPackage.serverless.service.provider.iamRoleStatements[0]); + }); }); it('should add managed policy arns', () => { @@ -276,14 +252,13 @@ describe('#mergeIamTemplates()', () => { 'someOther:aws:arn:xxx:*:*', { 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] }, ]; - return awsPackage.mergeIamTemplates() - .then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - .Properties - .ManagedPolicyArns - ).to.deep.equal(awsPackage.serverless.service.provider.iamManagedPolicies); - }); + return awsPackage.mergeIamTemplates().then(() => { + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ].Properties.ManagedPolicyArns + ).to.deep.equal(awsPackage.serverless.service.provider.iamManagedPolicies); + }); }); it('should merge managed policy arns when vpc config supplied', () => { @@ -301,23 +276,24 @@ describe('#mergeIamTemplates()', () => { 'some:aws:arn:xxx:*:*', 'someOther:aws:arn:xxx:*:*', { 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] }, - { 'Fn::Join': ['', - [ - 'arn:', - { Ref: 'AWS::Partition' }, - ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', + { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', + ], ], - ], }, ]; - return awsPackage.mergeIamTemplates() - .then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - .Properties - .ManagedPolicyArns - ).to.deep.equal(expectedManagedPolicyArns); - }); + return awsPackage.mergeIamTemplates().then(() => { + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ].Properties.ManagedPolicyArns + ).to.deep.equal(expectedManagedPolicyArns); + }); }); it('should throw error if custom IAM policy statements is not an array', () => { @@ -326,9 +302,7 @@ describe('#mergeIamTemplates()', () => { statments: [ { Effect: 'Allow', - Action: [ - 'something:SomethingElse', - ], + Action: ['something:SomethingElse'], Resource: 'some:aws:arn:xxx:*:*', }, ], @@ -338,33 +312,42 @@ describe('#mergeIamTemplates()', () => { }); it('should throw error if a custom IAM policy statement does not have an Effect field', () => { - awsPackage.serverless.service.provider.iamRoleStatements = [{ - Action: ['something:SomethingElse'], - Resource: '*', - }]; + awsPackage.serverless.service.provider.iamRoleStatements = [ + { + Action: ['something:SomethingElse'], + Resource: '*', + }, + ]; expect(() => awsPackage.mergeIamTemplates()).to.throw( - 'missing the following properties: Effect'); + 'missing the following properties: Effect' + ); }); it('should throw error if a custom IAM policy statement does not have an Action field', () => { - awsPackage.serverless.service.provider.iamRoleStatements = [{ - Effect: 'Allow', - Resource: '*', - }]; + awsPackage.serverless.service.provider.iamRoleStatements = [ + { + Effect: 'Allow', + Resource: '*', + }, + ]; expect(() => awsPackage.mergeIamTemplates()).to.throw( - 'missing the following properties: Action'); + 'missing the following properties: Action' + ); }); it('should throw error if a custom IAM policy statement does not have a Resource field', () => { - awsPackage.serverless.service.provider.iamRoleStatements = [{ - Action: ['something:SomethingElse'], - Effect: 'Allow', - }]; + awsPackage.serverless.service.provider.iamRoleStatements = [ + { + Action: ['something:SomethingElse'], + Effect: 'Allow', + }, + ]; expect(() => awsPackage.mergeIamTemplates()).to.throw( - 'missing the following properties: Resource'); + 'missing the following properties: Resource' + ); }); it('should throw an error describing all problematics custom IAM policy statements', () => { @@ -383,68 +366,69 @@ describe('#mergeIamTemplates()', () => { }, ]; - expect(() => awsPackage.mergeIamTemplates()) - .to.throw(/statement 0 is missing.*Resource; statement 2 is missing.*Effect, Action/); + expect(() => awsPackage.mergeIamTemplates()).to.throw( + /statement 0 is missing.*Resource; statement 2 is missing.*Effect, Action/ + ); }); it('should throw error if managed policies is not an array', () => { awsPackage.serverless.service.provider.iamManagedPolicies = 'a string'; - expect(() => awsPackage.mergeIamTemplates()) - .to.throw('iamManagedPolicies should be an array of arns'); + expect(() => awsPackage.mergeIamTemplates()).to.throw( + 'iamManagedPolicies should be an array of arns' + ); }); it('should add a CloudWatch LogGroup resource', () => { const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName); return awsPackage.mergeIamTemplates().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[normalizedName] - ).to.deep.equal( - { - Type: 'AWS::Logs::LogGroup', - Properties: { - LogGroupName: awsPackage.provider.naming.getLogGroupName(resolvedFunctionName), - }, - } - ); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + normalizedName + ] + ).to.deep.equal({ + Type: 'AWS::Logs::LogGroup', + Properties: { + LogGroupName: awsPackage.provider.naming.getLogGroupName(resolvedFunctionName), + }, + }); }); }); - it('should add RetentionInDays to a CloudWatch LogGroup resource if logRetentionInDays is given' - , () => - Promise.all([5, '5'].map((logRetentionInDays) => { + it('should add RetentionInDays to a CloudWatch LogGroup resource if logRetentionInDays is given', () => + Promise.all( + [5, '5'].map(logRetentionInDays => { awsPackage.serverless.service.provider.logRetentionInDays = logRetentionInDays; const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName); return awsPackage.mergeIamTemplates().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[normalizedName] - ).to.deep.equal( - { - Type: 'AWS::Logs::LogGroup', - Properties: { - LogGroupName: awsPackage.provider.naming.getLogGroupName(resolvedFunctionName), - RetentionInDays: 5, - }, - } - ); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + normalizedName + ] + ).to.deep.equal({ + Type: 'AWS::Logs::LogGroup', + Properties: { + LogGroupName: awsPackage.provider.naming.getLogGroupName(resolvedFunctionName), + RetentionInDays: 5, + }, + }); }); - })) - ); + }) + )); - it('should throw error if RetentionInDays is 0 or not an integer' - , () => { - awsPackage.serverless.service.provider.logRetentionInDays = 0; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - awsPackage.serverless.service.provider.logRetentionInDays = 'string'; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - awsPackage.serverless.service.provider.logRetentionInDays = []; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - awsPackage.serverless.service.provider.logRetentionInDays = {}; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - awsPackage.serverless.service.provider.logRetentionInDays = undefined; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - awsPackage.serverless.service.provider.logRetentionInDays = null; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - }); + it('should throw error if RetentionInDays is 0 or not an integer', () => { + awsPackage.serverless.service.provider.logRetentionInDays = 0; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + awsPackage.serverless.service.provider.logRetentionInDays = 'string'; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + awsPackage.serverless.service.provider.logRetentionInDays = []; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + awsPackage.serverless.service.provider.logRetentionInDays = {}; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + awsPackage.serverless.service.provider.logRetentionInDays = undefined; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + awsPackage.serverless.service.provider.logRetentionInDays = null; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + }); it('should add a CloudWatch LogGroup resource if all functions use custom roles', () => { awsPackage.serverless.service.functions[functionName].role = 'something'; @@ -464,26 +448,26 @@ describe('#mergeIamTemplates()', () => { awsPackage.provider.naming.getLogGroupLogicalId(f.func1.name), ]; return awsPackage.mergeIamTemplates().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[normalizedNames[0]] - ).to.deep.equal( - { - Type: 'AWS::Logs::LogGroup', - Properties: { - LogGroupName: awsPackage.provider.naming.getLogGroupName(f.func0.name), - }, - } - ); - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[normalizedNames[1]] - ).to.deep.equal( - { - Type: 'AWS::Logs::LogGroup', - Properties: { - LogGroupName: awsPackage.provider.naming.getLogGroupName(f.func1.name), - }, - } - ); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + normalizedNames[0] + ] + ).to.deep.equal({ + Type: 'AWS::Logs::LogGroup', + Properties: { + LogGroupName: awsPackage.provider.naming.getLogGroupName(f.func0.name), + }, + }); + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + normalizedNames[1] + ] + ).to.deep.equal({ + Type: 'AWS::Logs::LogGroup', + Properties: { + LogGroupName: awsPackage.provider.naming.getLogGroupName(f.func1.name), + }, + }); }); }); @@ -501,10 +485,15 @@ describe('#mergeIamTemplates()', () => { }, }; - return awsPackage.mergeIamTemplates() - .then(() => expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - ).to.exist + return awsPackage + .mergeIamTemplates() + .then( + () => + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ] + ).to.exist ); }); @@ -521,10 +510,16 @@ describe('#mergeIamTemplates()', () => { }, }; - return awsPackage.mergeIamTemplates() - .then(() => expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - ).to.not.exist); + return awsPackage + .mergeIamTemplates() + .then( + () => + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ] + ).to.not.exist + ); }); it('should not add the default role if all functions have an ARN role', () => { @@ -541,10 +536,15 @@ describe('#mergeIamTemplates()', () => { }, }; - return awsPackage.mergeIamTemplates() - .then(() => expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - ).to.not.exist + return awsPackage + .mergeIamTemplates() + .then( + () => + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ] + ).to.not.exist ); }); @@ -557,10 +557,15 @@ describe('#mergeIamTemplates()', () => { }, }; - return awsPackage.mergeIamTemplates() - .then(() => expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()].Properties.ManagedPolicyArns - ).to.not.exist + return awsPackage + .mergeIamTemplates() + .then( + () => + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ].Properties.ManagedPolicyArns + ).to.not.exist ); }); @@ -570,20 +575,24 @@ describe('#mergeIamTemplates()', () => { subnetIds: ['xxx'], }; - return awsPackage.mergeIamTemplates() - .then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()].Properties.ManagedPolicyArns - ).to.deep.equal([{ - 'Fn::Join': ['', + return awsPackage.mergeIamTemplates().then(() => { + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ].Properties.ManagedPolicyArns + ).to.deep.equal([ + { + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', ], ], - }]); - }); + }, + ]); + }); }); it('should be added if vpc config is defined on function level', () => { @@ -602,20 +611,24 @@ describe('#mergeIamTemplates()', () => { }, }; - return awsPackage.mergeIamTemplates() - .then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()].Properties.ManagedPolicyArns - ).to.deep.equal([{ - 'Fn::Join': ['', + return awsPackage.mergeIamTemplates().then(() => { + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ].Properties.ManagedPolicyArns + ).to.deep.equal([ + { + 'Fn::Join': [ + '', [ 'arn:', { Ref: 'AWS::Partition' }, ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', ], ], - }]); - }); + }, + ]); + }); }); it('should not be added if vpc config is defined with role on function level', () => { @@ -631,10 +644,15 @@ describe('#mergeIamTemplates()', () => { }, }; - return awsPackage.mergeIamTemplates() - .then(() => expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - ).to.not.exist + return awsPackage + .mergeIamTemplates() + .then( + () => + expect( + awsPackage.serverless.service.provider.compiledCloudFormationTemplate.Resources[ + awsPackage.provider.naming.getRoleLogicalId() + ] + ).to.not.exist ); }); }); diff --git a/lib/plugins/aws/package/lib/saveCompiledTemplate.js b/lib/plugins/aws/package/lib/saveCompiledTemplate.js index 17e60630e..23ddc689d 100644 --- a/lib/plugins/aws/package/lib/saveCompiledTemplate.js +++ b/lib/plugins/aws/package/lib/saveCompiledTemplate.js @@ -13,8 +13,10 @@ module.exports = { compiledTemplateFileName ); - this.serverless.utils.writeFileSync(compiledTemplateFilePath, - this.serverless.service.provider.compiledCloudFormationTemplate); + this.serverless.utils.writeFileSync( + compiledTemplateFilePath, + this.serverless.service.provider.compiledCloudFormationTemplate + ); return BbPromise.resolve(); }, diff --git a/lib/plugins/aws/package/lib/saveCompiledTemplate.test.js b/lib/plugins/aws/package/lib/saveCompiledTemplate.test.js index ad9f57371..9d3b44b42 100644 --- a/lib/plugins/aws/package/lib/saveCompiledTemplate.test.js +++ b/lib/plugins/aws/package/lib/saveCompiledTemplate.test.js @@ -27,8 +27,7 @@ describe('#saveCompiledTemplate()', () => { getCompiledTemplateFileNameStub = sinon .stub(awsPackage.provider.naming, 'getCompiledTemplateFileName') .returns('compiled.json'); - writeFileSyncStub = sinon - .stub(awsPackage.serverless.utils, 'writeFileSync').returns(); + writeFileSyncStub = sinon.stub(awsPackage.serverless.utils, 'writeFileSync').returns(); }); afterEach(() => { diff --git a/lib/plugins/aws/package/lib/saveServiceState.js b/lib/plugins/aws/package/lib/saveServiceState.js index 7d6afe2fb..ab3ab9a84 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.js +++ b/lib/plugins/aws/package/lib/saveServiceState.js @@ -16,13 +16,12 @@ module.exports = { ); const artifact = _.last( - _.split( - _.get(this.serverless.service, 'package.artifact', ''), path.sep - ) + _.split(_.get(this.serverless.service, 'package.artifact', ''), path.sep) ); const strippedService = _.assign( - {}, _.omit(this.serverless.service, ['serverless', 'package']) + {}, + _.omit(this.serverless.service, ['serverless', 'package']) ); const selfReferences = findReferences(strippedService, this.serverless.service); _.forEach(selfReferences, refPath => _.set(strippedService, refPath, '${self:}')); diff --git a/lib/plugins/aws/package/lib/saveServiceState.test.js b/lib/plugins/aws/package/lib/saveServiceState.test.js index 064fb861c..41800ab2f 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.test.js +++ b/lib/plugins/aws/package/lib/saveServiceState.test.js @@ -32,8 +32,7 @@ describe('#saveServiceState()', () => { getServiceStateFileNameStub = sinon .stub(awsPackage.provider.naming, 'getServiceStateFileName') .returns('service-state.json'); - writeFileSyncStub = sinon - .stub(awsPackage.serverless.utils, 'writeFileSync').returns(); + writeFileSyncStub = sinon.stub(awsPackage.serverless.utils, 'writeFileSync').returns(); }); afterEach(() => { @@ -63,8 +62,9 @@ describe('#saveServiceState()', () => { }; expect(getServiceStateFileNameStub.calledOnce).to.equal(true); - expect(writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent, true)) - .to.equal(true); + expect( + writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent, true) + ).to.equal(true); }); }); @@ -97,8 +97,9 @@ describe('#saveServiceState()', () => { }; expect(getServiceStateFileNameStub.calledOnce).to.equal(true); - expect(writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent, true)) - .to.equal(true); + expect( + writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent, true) + ).to.equal(true); }); }); }); diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index f58ed2a62..e87d32fc3 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -29,18 +29,18 @@ const impl = { * @param credentials The credentials to test for validity * @return {boolean} Whether the given credentials were valid */ - validCredentials: (credentials) => { + validCredentials: credentials => { let result = false; if (credentials) { if ( - ( // valid credentials loaded - credentials.accessKeyId && credentials.accessKeyId !== 'undefined' && - credentials.secretAccessKey && credentials.secretAccessKey !== 'undefined' - ) || ( - // a role to assume has been successfully loaded, the associated STS request has been - // sent, and the temporary credentials will be asynchronously delivered. - credentials.roleArn - ) + // valid credentials loaded + (credentials.accessKeyId && + credentials.accessKeyId !== 'undefined' && + credentials.secretAccessKey && + credentials.secretAccessKey !== 'undefined') || + // a role to assume has been successfully loaded, the associated STS request has been + // sent, and the temporary credentials will be asynchronously delivered. + credentials.roleArn ) { result = true; } @@ -83,16 +83,20 @@ const impl = { // Setup a MFA callback for asking the code from the user. params.tokenCodeFn = (mfaSerial, callback) => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); - rl.question(`Enter MFA code for ${mfaSerial}: `, (answer) => { + rl.question(`Enter MFA code for ${mfaSerial}: `, answer => { rl.close(); callback(null, answer); }); }; const profileCredentials = new AWS.SharedIniFileCredentials(params); - if (!(profileCredentials.accessKeyId - || profileCredentials.sessionToken - || profileCredentials.roleArn)) { + if ( + !( + profileCredentials.accessKeyId || + profileCredentials.sessionToken || + profileCredentials.roleArn + ) + ) { throw new Error(`Profile ${profile} does not exist`); } @@ -137,20 +141,19 @@ class AwsProvider { } // Use HTTPS Proxy (Optional) - const proxy = process.env.proxy - || process.env.HTTP_PROXY - || process.env.http_proxy - || process.env.HTTPS_PROXY - || process.env.https_proxy; + const proxy = + process.env.proxy || + process.env.HTTP_PROXY || + process.env.http_proxy || + process.env.HTTPS_PROXY || + process.env.https_proxy; const proxyOptions = {}; if (proxy) { Object.assign(proxyOptions, url.parse(proxy)); } - const ca = process.env.ca - || process.env.HTTPS_CA - || process.env.https_ca; + const ca = process.env.ca || process.env.HTTPS_CA || process.env.https_ca; let caCerts = []; @@ -161,9 +164,7 @@ class AwsProvider { caCerts = caCerts.concat(caArr.map(cert => cert.replace(/\\n/g, '\n'))); } - const cafile = process.env.cafile - || process.env.HTTPS_CAFILE - || process.env.https_cafile; + const cafile = process.env.cafile || process.env.HTTPS_CAFILE || process.env.https_cafile; if (cafile) { // Can be a single certificate file path or multiple paths, comma separated. @@ -228,27 +229,33 @@ class AwsProvider { }); const paramsHash = objectHash.sha1(paramsWithRegion); const MAX_TRIES = 4; - const persistentRequest = (f) => new BbPromise((resolve, reject) => { - const doCall = (numTry) => { - f() - // We're resembling if/else logic, therefore single `then` instead of `then`/`catch` pair - .then(resolve, e => { - if (numTry < MAX_TRIES && - ((e.providerError && e.providerError.retryable) || e.statusCode === 429)) { - that.serverless.cli.log( - _.join([ - `Recoverable error occurred (${e.message}), sleeping for 5 seconds.`, - `Try ${numTry + 1} of ${MAX_TRIES}`, - ], ' ') - ); - setTimeout(doCall, 5000, numTry + 1); - } else { - reject(e); - } - }); - }; - return doCall(0); - }); + const persistentRequest = f => + new BbPromise((resolve, reject) => { + const doCall = numTry => { + f() + // We're resembling if/else logic, therefore single `then` instead of `then`/`catch` pair + .then(resolve, e => { + if ( + numTry < MAX_TRIES && + ((e.providerError && e.providerError.retryable) || e.statusCode === 429) + ) { + that.serverless.cli.log( + _.join( + [ + `Recoverable error occurred (${e.message}), sleeping for 5 seconds.`, + `Try ${numTry + 1} of ${MAX_TRIES}`, + ], + ' ' + ) + ); + setTimeout(doCall, 5000, numTry + 1); + } else { + reject(e); + } + }); + }; + return doCall(0); + }); // Emit a warning for misuses of the old signature including stage and region // TODO: Determine calling module and log that @@ -268,49 +275,54 @@ class AwsProvider { } } - const request = this.requestQueue.add(() => persistentRequest(() => { - if (options && !_.isUndefined(options.region)) { - credentials.region = options.region; - } - const awsService = new that.sdk[service](credentials); - const req = awsService[method](params); - - // TODO: Add listeners, put Debug statments here... - // req.on('send', function (r) {console.log(r)}); - - const promise = req.promise ? req.promise() : BbPromise.fromCallback(cb => { - req.send(cb); - }); - return promise.catch(err => { - let message = err.message !== null ? err.message : err.code; - if (err.message === 'Missing credentials in config') { - const errorMessage = [ - 'AWS provider credentials not found.', - ' Learn how to set up AWS provider credentials', - ` in our docs here: <${chalk.green('http://bit.ly/aws-creds-setup')}>.`, - ].join(''); - message = errorMessage; - userStats.track('user_awsCredentialsNotFound'); - // We do not want to trigger the retry mechanism for credential errors - return BbPromise.reject(Object.assign( - new this.serverless.classes.Error(message, err.statusCode), - { providerError: _.assign({}, err, { retryable: false }) } - )); + const request = this.requestQueue.add(() => + persistentRequest(() => { + if (options && !_.isUndefined(options.region)) { + credentials.region = options.region; } + const awsService = new that.sdk[service](credentials); + const req = awsService[method](params); - return BbPromise.reject(Object.assign( - new this.serverless.classes.Error(message, err.statusCode), - { providerError: err } - )); - }); - }) - .then(data => { + // TODO: Add listeners, put Debug statments here... + // req.on('send', function (r) {console.log(r)}); + + const promise = req.promise + ? req.promise() + : BbPromise.fromCallback(cb => { + req.send(cb); + }); + return promise.catch(err => { + let message = err.message !== null ? err.message : err.code; + if (err.message === 'Missing credentials in config') { + const errorMessage = [ + 'AWS provider credentials not found.', + ' Learn how to set up AWS provider credentials', + ` in our docs here: <${chalk.green('http://bit.ly/aws-creds-setup')}>.`, + ].join(''); + message = errorMessage; + userStats.track('user_awsCredentialsNotFound'); + // We do not want to trigger the retry mechanism for credential errors + return BbPromise.reject( + Object.assign(new this.serverless.classes.Error(message, err.statusCode), { + providerError: _.assign({}, err, { retryable: false }), + }) + ); + } + + return BbPromise.reject( + Object.assign(new this.serverless.classes.Error(message, err.statusCode), { + providerError: err, + }) + ); + }); + }).then(data => { const result = BbPromise.resolve(data); if (shouldCache) { _.set(this.requestCache, `${service}.${method}.${paramsHash}`, result); } return result; - })); + }) + ); if (shouldCache) { _.set(this.requestCache, `${service}.${method}.${paramsHash}`, request); @@ -348,8 +360,11 @@ class AwsProvider { result.region = this.getRegion(); const deploymentBucketObject = this.serverless.service.provider.deploymentBucketObject; - if (deploymentBucketObject && deploymentBucketObject.serverSideEncryption - && deploymentBucketObject.serverSideEncryption === 'aws:kms') { + if ( + deploymentBucketObject && + deploymentBucketObject.serverSideEncryption && + deploymentBucketObject.serverSideEncryption === 'aws:kms' + ) { result.signatureVersion = 'v4'; } @@ -360,9 +375,11 @@ class AwsProvider { canUseS3TransferAcceleration(service, method) { // TODO enable more S3 APIs? - return service === 'S3' - && ['upload', 'putObject'].indexOf(method) !== -1 - && this.isS3TransferAccelerationEnabled(); + return ( + service === 'S3' && + ['upload', 'putObject'].indexOf(method) !== -1 && + this.isS3TransferAccelerationEnabled() + ); } // This function will be used to block the addition of transfer acceleration options @@ -388,7 +405,7 @@ class AwsProvider { enableS3TransferAcceleration(credentials) { this.serverless.cli.log('Using S3 Transfer Acceleration Endpoint...'); - credentials.useAccelerateEndpoint = true; // eslint-disable-line no-param-reassign + credentials.useAccelerateEndpoint = true; // eslint-disable-line no-param-reassign } getValues(source, paths) { @@ -433,13 +450,10 @@ class AwsProvider { if (this.serverless.service.provider.deploymentBucket) { return BbPromise.resolve(this.serverless.service.provider.deploymentBucket); } - return this.request('CloudFormation', - 'describeStackResource', - { - StackName: this.naming.getStackName(), - LogicalResourceId: this.naming.getDeploymentBucketLogicalId(), - } - ).then((result) => result.StackResourceDetail.PhysicalResourceId); + return this.request('CloudFormation', 'describeStackResource', { + StackName: this.naming.getStackName(), + LogicalResourceId: this.naming.getDeploymentBucketLogicalId(), + }).then(result => result.StackResourceDetail.PhysicalResourceId); } getDeploymentPrefix() { @@ -461,31 +475,31 @@ class AwsProvider { } getAccountId() { - return this.getAccountInfo() - .then((result) => result.accountId); + return this.getAccountInfo().then(result => result.accountId); } getAccountInfo() { - return this.request('STS', 'getCallerIdentity', {}) - .then((result) => { - const arn = result.Arn; - const accountId = result.Account; - const partition = _.nth(_.split(arn, ':'), 1); // ex: arn:aws:iam:acctId:user/xyz - return { - accountId, - partition, - arn: result.Arn, - userId: result.UserId, - }; - }); + return this.request('STS', 'getCallerIdentity', {}).then(result => { + const arn = result.Arn; + const accountId = result.Account; + const partition = _.nth(_.split(arn, ':'), 1); // ex: arn:aws:iam:acctId:user/xyz + return { + accountId, + partition, + arn: result.Arn, + userId: result.UserId, + }; + }); } /** * Get API Gateway Rest API ID from serverless config */ getApiGatewayRestApiId() { - if (this.serverless.service.provider.apiGateway - && this.serverless.service.provider.apiGateway.restApiId) { + if ( + this.serverless.service.provider.apiGateway && + this.serverless.service.provider.apiGateway.restApiId + ) { return this.serverless.service.provider.apiGateway.restApiId; } @@ -493,8 +507,10 @@ class AwsProvider { } getApiGatewayDescription() { - if (this.serverless.service.provider.apiGateway - && this.serverless.service.provider.apiGateway.description) { + if ( + this.serverless.service.provider.apiGateway && + this.serverless.service.provider.apiGateway.description + ) { return this.serverless.service.provider.apiGateway.description; } return undefined; @@ -513,8 +529,10 @@ class AwsProvider { * Get Rest API Root Resource ID from serverless config */ getApiGatewayRestApiRootResourceId() { - if (this.serverless.service.provider.apiGateway - && this.serverless.service.provider.apiGateway.restApiRootResourceId) { + if ( + this.serverless.service.provider.apiGateway && + this.serverless.service.provider.apiGateway.restApiRootResourceId + ) { return this.serverless.service.provider.apiGateway.restApiRootResourceId; } return { 'Fn::GetAtt': [this.naming.getRestApiLogicalId(), 'RootResourceId'] }; @@ -524,8 +542,10 @@ class AwsProvider { * Get Rest API Predefined Resources from serverless config */ getApiGatewayPredefinedResources() { - if (!this.serverless.service.provider.apiGateway - || !this.serverless.service.provider.apiGateway.restApiResources) { + if ( + !this.serverless.service.provider.apiGateway || + !this.serverless.service.provider.apiGateway.restApiResources + ) { return []; } @@ -537,19 +557,20 @@ class AwsProvider { throw new Error('REST API resource must be an array of object'); } - return Object.keys(this.serverless.service.provider.apiGateway.restApiResources) - .map((key) => ({ - path: key, - resourceId: this.serverless.service.provider.apiGateway.restApiResources[key], - })); + return Object.keys(this.serverless.service.provider.apiGateway.restApiResources).map(key => ({ + path: key, + resourceId: this.serverless.service.provider.apiGateway.restApiResources[key], + })); } /** * Get API Gateway websocket API ID from serverless config */ getApiGatewayWebsocketApiId() { - if (this.serverless.service.provider.apiGateway - && this.serverless.service.provider.apiGateway.websocketApiId) { + if ( + this.serverless.service.provider.apiGateway && + this.serverless.service.provider.apiGateway.websocketApiId + ) { return this.serverless.service.provider.apiGateway.websocketApiId; } @@ -564,10 +585,7 @@ class AwsProvider { if (!resources) resources = []; if (next) params.NextToken = next; - return this.request('CloudFormation', - 'listStackResources', - params - ).then((res) => { + return this.request('CloudFormation', 'listStackResources', params).then(res => { const allResources = resources.concat(res.StackResourceSummaries); if (!res.NextToken) { return allResources; diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index c89524c42..593482273 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -39,7 +39,6 @@ describe('AwsProvider', () => { }); afterEach(() => restoreEnv()); - describe('#getProviderName()', () => { it('should return the provider name', () => { expect(AwsProvider.getProviderName()).to.equal('aws'); @@ -85,12 +84,7 @@ describe('AwsProvider', () => { }); describe('stage name validation', () => { - const stages = [ - 'myStage', - 'my-stage', - 'my_stage', - '${opt:stage, \'prod\'}', - ]; + const stages = ['myStage', 'my-stage', 'my_stage', "${opt:stage, 'prod'}"]; stages.forEach(stage => { it(`should not throw an error before variable population even if http event is present and stage is ${stage}`, () => { @@ -206,8 +200,9 @@ describe('AwsProvider', () => { const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket) - .to.equal('my.deployment.bucket'); + expect(newAwsProvider.serverless.service.provider.deploymentBucket).to.equal( + 'my.deployment.bucket' + ); }); it('should save a given object and use name from it', () => { @@ -219,10 +214,12 @@ describe('AwsProvider', () => { const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket) - .to.equal('my.deployment.bucket'); - expect(newAwsProvider.serverless.service.provider.deploymentBucketObject) - .to.deep.equal(deploymentBucketObject); + expect(newAwsProvider.serverless.service.provider.deploymentBucket).to.equal( + 'my.deployment.bucket' + ); + expect(newAwsProvider.serverless.service.provider.deploymentBucketObject).to.deep.equal( + deploymentBucketObject + ); }); it('should save a given object and nullify the name if one is not provided', () => { @@ -233,10 +230,10 @@ describe('AwsProvider', () => { const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket) - .to.equal(null); - expect(newAwsProvider.serverless.service.provider.deploymentBucketObject) - .to.deep.equal(deploymentBucketObject); + expect(newAwsProvider.serverless.service.provider.deploymentBucket).to.equal(null); + expect(newAwsProvider.serverless.service.provider.deploymentBucketObject).to.deep.equal( + deploymentBucketObject + ); }); }); }); @@ -244,8 +241,9 @@ describe('AwsProvider', () => { describe('#request()', () => { beforeEach(() => { const originalSetTimeout = setTimeout; - sinon.stub(global, 'setTimeout').callsFake((cb, timeout) => - originalSetTimeout(cb, Math.min(timeout || 0, 10))); + sinon + .stub(global, 'setTimeout') + .callsFake((cb, timeout) => originalSetTimeout(cb, Math.min(timeout || 0, 10))); }); afterEach(() => { @@ -261,7 +259,7 @@ describe('AwsProvider', () => { putObject() { return { - send: (cb) => cb(null, { called: true }), + send: cb => cb(null, { called: true }), }; } } @@ -318,7 +316,6 @@ describe('AwsProvider', () => { }); }); - it('should request to the specified region if region in options set', () => { // mocking S3 for testing class FakeCloudForamtion { @@ -328,9 +325,10 @@ describe('AwsProvider', () => { describeStacks() { return { - send: (cb) => cb(null, { - region: this.config.region, - }), + send: cb => + cb(null, { + region: this.config.region, + }), }; } } @@ -351,10 +349,12 @@ describe('AwsProvider', () => { expect(awsProvider.getCredentials().region).to.eql(options.region); return awsProvider - .request('CloudFormation', + .request( + 'CloudFormation', 'describeStacks', { StackName: 'foo' }, - { region: 'ap-northeast-1' }) + { region: 'ap-northeast-1' } + ) .then(data => { expect(data).to.eql({ region: 'ap-northeast-1' }); // Requesting different region should not affect region in credentials @@ -362,7 +362,7 @@ describe('AwsProvider', () => { }); }); - it('should retry if error code is 429', (done) => { + it('should retry if error code is 429', done => { const error = { statusCode: 429, retryable: true, @@ -385,7 +385,8 @@ describe('AwsProvider', () => { awsProvider.sdk = { S3: FakeS3, }; - awsProvider.request('S3', 'error', {}) + awsProvider + .request('S3', 'error', {}) .then(data => { expect(data).to.exist; expect(sendFake.send).to.have.been.calledTwice; @@ -394,7 +395,7 @@ describe('AwsProvider', () => { .catch(done); }); - it('should retry if error code is 429 and retryable is set to false', (done) => { + it('should retry if error code is 429 and retryable is set to false', done => { const error = { statusCode: 429, retryable: false, @@ -417,7 +418,8 @@ describe('AwsProvider', () => { awsProvider.sdk = { S3: FakeS3, }; - awsProvider.request('S3', 'error', {}) + awsProvider + .request('S3', 'error', {}) .then(data => { expect(data).to.exist; expect(sendFake.send).to.have.been.calledTwice; @@ -426,7 +428,7 @@ describe('AwsProvider', () => { .catch(done); }); - it('should reject errors', (done) => { + it('should reject errors', done => { const error = { statusCode: 500, message: 'Some error message', @@ -447,12 +449,13 @@ describe('AwsProvider', () => { awsProvider.sdk = { S3: FakeS3, }; - awsProvider.request('S3', 'error', {}) + awsProvider + .request('S3', 'error', {}) .then(() => done('Should not succeed')) .catch(() => done()); }); - it('should use error message if it exists', (done) => { + it('should use error message if it exists', done => { const awsErrorResponse = { message: 'Something went wrong...', code: 'Forbidden', @@ -481,16 +484,17 @@ describe('AwsProvider', () => { awsProvider.sdk = { S3: FakeS3, }; - awsProvider.request('S3', 'error', {}) + awsProvider + .request('S3', 'error', {}) .then(() => done('Should not succeed')) - .catch((err) => { + .catch(err => { expect(err.message).to.eql(awsErrorResponse.message); done(); }) .catch(done); }); - it('should default to error code if error message is non-existent', (done) => { + it('should default to error code if error message is non-existent', done => { const awsErrorResponse = { message: null, code: 'Forbidden', @@ -519,16 +523,17 @@ describe('AwsProvider', () => { awsProvider.sdk = { S3: FakeS3, }; - awsProvider.request('S3', 'error', {}) + awsProvider + .request('S3', 'error', {}) .then(() => done('Should not succeed')) - .catch((err) => { + .catch(err => { expect(err.message).to.eql(awsErrorResponse.code); done(); }) .catch(done); }); - it('should return ref to docs for missing credentials', (done) => { + it('should return ref to docs for missing credentials', done => { const error = { statusCode: 403, message: 'Missing credentials in config', @@ -549,16 +554,17 @@ describe('AwsProvider', () => { awsProvider.sdk = { S3: FakeS3, }; - awsProvider.request('S3', 'error', {}) + awsProvider + .request('S3', 'error', {}) .then(() => done('Should not succeed')) - .catch((err) => { + .catch(err => { expect(err.message).to.contain('in our docs here:'); done(); }) .catch(done); }); - it('should not retry for missing credentials', (done) => { + it('should not retry for missing credentials', done => { const error = { statusCode: 403, message: 'Missing credentials in config', @@ -578,9 +584,10 @@ describe('AwsProvider', () => { awsProvider.sdk = { S3: FakeS3, }; - awsProvider.request('S3', 'error', {}) + awsProvider + .request('S3', 'error', {}) .then(() => done('Should not succeed')) - .catch((err) => { + .catch(err => { expect(sendFake.send).to.have.been.calledOnce; expect(err.message).to.contain('in our docs here:'); done(); @@ -597,7 +604,7 @@ describe('AwsProvider', () => { putObject() { return { - send: (cb) => cb(null, { called: true }), + send: cb => cb(null, { called: true }), }; } } @@ -617,7 +624,8 @@ describe('AwsProvider', () => { }; const enableS3TransferAccelerationStub = sinon - .stub(awsProvider, 'enableS3TransferAcceleration').resolves(); + .stub(awsProvider, 'enableS3TransferAcceleration') + .resolves(); awsProvider.options['aws-s3-accelerate'] = true; return awsProvider.request('S3', 'putObject', {}).then(() => { @@ -635,7 +643,7 @@ describe('AwsProvider', () => { describeStacks() { return { - send: (cb) => cb(null, { called: true }), + send: cb => cb(null, { called: true }), }; } } @@ -654,12 +662,8 @@ describe('AwsProvider', () => { }, }; - return awsProvider.request( - 'CloudFormation', - 'describeStacks', - {}, - { useCache: true } - ) + return awsProvider + .request('CloudFormation', 'describeStacks', {}, { useCache: true }) .then(data => { expect(data.called).to.equal(true); }); @@ -683,15 +687,16 @@ describe('AwsProvider', () => { awsProvider.sdk = { CloudFormation: FakeCF, }; - const executeRequestWithRegion = (region) => awsProvider.request( - 'CloudFormation', - 'describeStacks', - { StackName: 'same-stack' }, - { - useCache: true, - region, - } - ); + const executeRequestWithRegion = region => + awsProvider.request( + 'CloudFormation', + 'describeStacks', + { StackName: 'same-stack' }, + { + useCache: true, + region, + } + ); const requests = []; requests.push(BbPromise.try(() => executeRequestWithRegion('us-east-1'))); requests.push(BbPromise.try(() => executeRequestWithRegion('ap-northeast-1'))); @@ -741,12 +746,8 @@ describe('AwsProvider', () => { }; const numTests = 1000; - const executeRequest = () => awsProvider.request( - 'CloudFormation', - 'describeStacks', - {}, - { useCache: true } - ); + const executeRequest = () => + awsProvider.request('CloudFormation', 'describeStacks', {}, { useCache: true }); const requests = []; for (let n = 0; n < numTests; n++) { requests.push(BbPromise.try(() => executeRequest())); @@ -782,12 +783,12 @@ describe('AwsProvider', () => { serverless.utils.writeFileSync( relevantEnvironment.AWS_SHARED_CREDENTIALS_FILE, '[default]\n' + - 'aws_access_key_id = 1111\n' + - 'aws_secret_access_key = 22222\n' + - '\n' + - '[async]\n' + - 'role_arn = arn:123\n' + - 'source_profile = default' + 'aws_access_key_id = 1111\n' + + 'aws_secret_access_key = 22222\n' + + '\n' + + '[async]\n' + + 'role_arn = arn:123\n' + + 'source_profile = default' ); newAwsProvider = new AwsProvider(serverless, options); }); @@ -813,7 +814,7 @@ describe('AwsProvider', () => { describeStacks() { return { - send: (cb) => cb(null, {}), + send: cb => cb(null, {}), }; } } @@ -823,10 +824,12 @@ describe('AwsProvider', () => { }; return newAwsProvider - .request('CloudFormation', + .request( + 'CloudFormation', 'describeStacks', { StackName: 'foo' }, - { region: 'ap-northeast-1' }) + { region: 'ap-northeast-1' } + ) .then(() => { // STS token is resolved after SDK call const actualToken = newAwsProvider.getCredentials().credentials.sessionToken; @@ -843,7 +846,6 @@ describe('AwsProvider', () => { 'aws-sdk': awsStub, }); - // add environment variables here if you want them cleared prior to your test and restored // after it has completed. Any environment variable that might alter credentials loading // should be added here @@ -884,17 +886,17 @@ describe('AwsProvider', () => { serverless.utils.writeFileSync( relevantEnvironment.AWS_SHARED_CREDENTIALS_FILE, '[notDefault]\n' + - `aws_access_key_id = ${fakeCredentials.accessKeyId}\n` + - `aws_secret_access_key = ${fakeCredentials.secretAccessKey}\n` + - '\n' + - '[notDefaultTemporary]\n' + - `aws_access_key_id = ${fakeCredentials.accessKeyId}\n` + - `aws_secret_access_key = ${fakeCredentials.secretAccessKey}\n` + - `aws_session_token = ${fakeCredentials.sessionToken}\n` + - '\n' + - '[notDefaultAsync]\n' + - `role_arn = ${fakeCredentials.roleArn}\n` + - `source_profile = ${fakeCredentials.sourceProfile}\n` + `aws_access_key_id = ${fakeCredentials.accessKeyId}\n` + + `aws_secret_access_key = ${fakeCredentials.secretAccessKey}\n` + + '\n' + + '[notDefaultTemporary]\n' + + `aws_access_key_id = ${fakeCredentials.accessKeyId}\n` + + `aws_secret_access_key = ${fakeCredentials.secretAccessKey}\n` + + `aws_session_token = ${fakeCredentials.sessionToken}\n` + + '\n' + + '[notDefaultAsync]\n' + + `role_arn = ${fakeCredentials.roleArn}\n` + + `source_profile = ${fakeCredentials.sourceProfile}\n` ); newAwsProvider = new AwsProviderProxyquired(serverless, newOptions); }); @@ -1047,7 +1049,8 @@ describe('AwsProvider', () => { expect(credentials.credentials.profile).to.equal('notDefault'); }); - it('should get credentials when profile is provied via --aws-profile option even if profile is defined in serverless.yml', () => { // eslint-disable-line max-len + it('should get credentials when profile is provied via --aws-profile option even if profile is defined in serverless.yml', () => { + // eslint-disable-line max-len process.env.AWS_PROFILE = 'notDefaultTemporary'; newAwsProvider.options['aws-profile'] = 'notDefault'; @@ -1057,7 +1060,8 @@ describe('AwsProvider', () => { expect(credentials.credentials.profile).to.equal('notDefault'); }); - it('should get credentials when profile is provied via process.env.AWS_PROFILE even if profile is defined in serverless.yml', () => { // eslint-disable-line max-len + it('should get credentials when profile is provied via process.env.AWS_PROFILE even if profile is defined in serverless.yml', () => { + // eslint-disable-line max-len process.env.AWS_PROFILE = 'notDefault'; serverless.service.provider.profile = 'notDefaultTemporary'; @@ -1086,11 +1090,7 @@ describe('AwsProvider', () => { }, }, }; - const paths = [ - ['a'], - ['c', 'd'], - ['c', 'f', 'g'], - ]; + const paths = [['a'], ['c', 'd'], ['c', 'f', 'g']]; const getExpected = [ { path: paths[0], value: obj.a }, { path: paths[1], value: obj.c.d }, @@ -1102,13 +1102,13 @@ describe('AwsProvider', () => { }); }); describe('#firstValue', () => { - it('should ignore entries without a \'value\' attribute', () => { + it("should ignore entries without a 'value' attribute", () => { const input = _.cloneDeep(getExpected); delete input[0].value; delete input[2].value; expect(awsProvider.firstValue(input)).to.eql(getExpected[1]); }); - it('should ignore entries with an undefined \'value\' attribute', () => { + it("should ignore entries with an undefined 'value' attribute", () => { const input = _.cloneDeep(getExpected); input[0].value = undefined; input[2].value = undefined; @@ -1230,47 +1230,39 @@ describe('AwsProvider', () => { describe('#getServerlessDeploymentBucketName()', () => { it('should return the name of the serverless deployment bucket', () => { - const describeStackResourcesStub = sinon - .stub(awsProvider, 'request') - .resolves({ - StackResourceDetail: { - PhysicalResourceId: 'serverlessDeploymentBucketName', - }, - }); + const describeStackResourcesStub = sinon.stub(awsProvider, 'request').resolves({ + StackResourceDetail: { + PhysicalResourceId: 'serverlessDeploymentBucketName', + }, + }); - return awsProvider.getServerlessDeploymentBucketName() - .then((bucketName) => { - expect(bucketName).to.equal('serverlessDeploymentBucketName'); - expect(describeStackResourcesStub.calledOnce).to.be.equal(true); - expect(describeStackResourcesStub.calledWithExactly( - 'CloudFormation', - 'describeStackResource', - { - StackName: awsProvider.naming.getStackName(), - LogicalResourceId: awsProvider.naming.getDeploymentBucketLogicalId(), - } - )).to.be.equal(true); - awsProvider.request.restore(); - }); + return awsProvider.getServerlessDeploymentBucketName().then(bucketName => { + expect(bucketName).to.equal('serverlessDeploymentBucketName'); + expect(describeStackResourcesStub.calledOnce).to.be.equal(true); + expect( + describeStackResourcesStub.calledWithExactly('CloudFormation', 'describeStackResource', { + StackName: awsProvider.naming.getStackName(), + LogicalResourceId: awsProvider.naming.getDeploymentBucketLogicalId(), + }) + ).to.be.equal(true); + awsProvider.request.restore(); + }); }); it('should return the name of the custom deployment bucket', () => { awsProvider.serverless.service.provider.deploymentBucket = 'custom-bucket'; - const describeStackResourcesStub = sinon - .stub(awsProvider, 'request') - .resolves({ - StackResourceDetail: { - PhysicalResourceId: 'serverlessDeploymentBucketName', - }, - }); + const describeStackResourcesStub = sinon.stub(awsProvider, 'request').resolves({ + StackResourceDetail: { + PhysicalResourceId: 'serverlessDeploymentBucketName', + }, + }); - return awsProvider.getServerlessDeploymentBucketName() - .then((bucketName) => { - expect(describeStackResourcesStub.called).to.be.equal(false); - expect(bucketName).to.equal('custom-bucket'); - awsProvider.request.restore(); - }); + return awsProvider.getServerlessDeploymentBucketName().then(bucketName => { + expect(describeStackResourcesStub.called).to.be.equal(false); + expect(bucketName).to.equal('custom-bucket'); + awsProvider.request.restore(); + }); }); }); @@ -1278,8 +1270,9 @@ describe('AwsProvider', () => { it('should return custom deployment prefix if defined', () => { serverless.service.provider.deploymentPrefix = 'providerPrefix'; - expect(awsProvider.getDeploymentPrefix()) - .to.equal(serverless.service.provider.deploymentPrefix); + expect(awsProvider.getDeploymentPrefix()).to.equal( + serverless.service.provider.deploymentPrefix + ); }); it('should use the default serverless if not defined', () => { @@ -1341,22 +1334,19 @@ describe('AwsProvider', () => { const accountId = '12345678'; const partition = 'aws'; - const stsGetCallerIdentityStub = sinon - .stub(awsProvider, 'request') - .resolves({ - ResponseMetadata: { RequestId: '12345678-1234-1234-1234-123456789012' }, - UserId: 'ABCDEFGHIJKLMNOPQRSTU:VWXYZ', - Account: accountId, - Arn: 'arn:aws:sts::123456789012:assumed-role/ROLE-NAME/VWXYZ', - }); + const stsGetCallerIdentityStub = sinon.stub(awsProvider, 'request').resolves({ + ResponseMetadata: { RequestId: '12345678-1234-1234-1234-123456789012' }, + UserId: 'ABCDEFGHIJKLMNOPQRSTU:VWXYZ', + Account: accountId, + Arn: 'arn:aws:sts::123456789012:assumed-role/ROLE-NAME/VWXYZ', + }); - return awsProvider.getAccountInfo() - .then((result) => { - expect(stsGetCallerIdentityStub.calledOnce).to.equal(true); - expect(result.accountId).to.equal(accountId); - expect(result.partition).to.equal(partition); - awsProvider.request.restore(); - }); + return awsProvider.getAccountInfo().then(result => { + expect(stsGetCallerIdentityStub.calledOnce).to.equal(true); + expect(result.accountId).to.equal(accountId); + expect(result.partition).to.equal(partition); + awsProvider.request.restore(); + }); }); }); @@ -1364,67 +1354,60 @@ describe('AwsProvider', () => { it('should return the AWS account id', () => { const accountId = '12345678'; - const stsGetCallerIdentityStub = sinon - .stub(awsProvider, 'request') - .resolves({ - ResponseMetadata: { RequestId: '12345678-1234-1234-1234-123456789012' }, - UserId: 'ABCDEFGHIJKLMNOPQRSTU:VWXYZ', - Account: accountId, - Arn: 'arn:aws:sts::123456789012:assumed-role/ROLE-NAME/VWXYZ', - }); + const stsGetCallerIdentityStub = sinon.stub(awsProvider, 'request').resolves({ + ResponseMetadata: { RequestId: '12345678-1234-1234-1234-123456789012' }, + UserId: 'ABCDEFGHIJKLMNOPQRSTU:VWXYZ', + Account: accountId, + Arn: 'arn:aws:sts::123456789012:assumed-role/ROLE-NAME/VWXYZ', + }); - return awsProvider.getAccountId() - .then((result) => { - expect(stsGetCallerIdentityStub.calledOnce).to.equal(true); - expect(result).to.equal(accountId); - awsProvider.request.restore(); - }); + return awsProvider.getAccountId().then(result => { + expect(stsGetCallerIdentityStub.calledOnce).to.equal(true); + expect(result).to.equal(accountId); + awsProvider.request.restore(); + }); }); }); describe('#isS3TransferAccelerationEnabled()', () => { it('should return false by default', () => { awsProvider.options['aws-s3-accelerate'] = undefined; - return expect(awsProvider.isS3TransferAccelerationEnabled()) - .to.equal(false); + return expect(awsProvider.isS3TransferAccelerationEnabled()).to.equal(false); }); it('should return true when CLI option is provided', () => { awsProvider.options['aws-s3-accelerate'] = true; - return expect(awsProvider.isS3TransferAccelerationEnabled()) - .to.equal(true); + return expect(awsProvider.isS3TransferAccelerationEnabled()).to.equal(true); }); }); describe('#canUseS3TransferAcceleration()', () => { it('should return false by default with any input', () => { awsProvider.options['aws-s3-accelerate'] = undefined; - return expect(awsProvider.canUseS3TransferAcceleration('lambda', 'updateFunctionCode')) - .to.equal(false); + return expect( + awsProvider.canUseS3TransferAcceleration('lambda', 'updateFunctionCode') + ).to.equal(false); }); it('should return false by default with S3.upload too', () => { awsProvider.options['aws-s3-accelerate'] = undefined; - return expect(awsProvider.canUseS3TransferAcceleration('S3', 'upload')) - .to.equal(false); + return expect(awsProvider.canUseS3TransferAcceleration('S3', 'upload')).to.equal(false); }); it('should return false by default with S3.putObject too', () => { awsProvider.options['aws-s3-accelerate'] = undefined; - return expect(awsProvider.canUseS3TransferAcceleration('S3', 'putObject')) - .to.equal(false); + return expect(awsProvider.canUseS3TransferAcceleration('S3', 'putObject')).to.equal(false); }); it('should return false when CLI option is provided but not an S3 upload', () => { awsProvider.options['aws-s3-accelerate'] = true; - return expect(awsProvider.canUseS3TransferAcceleration('lambda', 'updateFunctionCode')) - .to.equal(false); + return expect( + awsProvider.canUseS3TransferAcceleration('lambda', 'updateFunctionCode') + ).to.equal(false); }); it('should return true when CLI option is provided for S3.upload', () => { awsProvider.options['aws-s3-accelerate'] = true; - return expect(awsProvider.canUseS3TransferAcceleration('S3', 'upload')) - .to.equal(true); + return expect(awsProvider.canUseS3TransferAcceleration('S3', 'upload')).to.equal(true); }); it('should return true when CLI option is provided for S3.putObject', () => { awsProvider.options['aws-s3-accelerate'] = true; - return expect(awsProvider.canUseS3TransferAcceleration('S3', 'putObject')) - .to.equal(true); + return expect(awsProvider.canUseS3TransferAcceleration('S3', 'putObject')).to.equal(true); }); }); diff --git a/lib/plugins/aws/remove/index.js b/lib/plugins/aws/remove/index.js index e7ca3ca5b..2007f68b6 100644 --- a/lib/plugins/aws/remove/index.js +++ b/lib/plugins/aws/remove/index.js @@ -15,11 +15,12 @@ class AwsRemove { Object.assign(this, validate, emptyS3Bucket, removeStack, monitorStack); this.hooks = { - 'remove:remove': () => BbPromise.bind(this) - .then(this.validate) - .then(this.emptyS3Bucket) - .then(this.removeStack) - .then((cfData) => this.monitorStack('removal', cfData)), + 'remove:remove': () => + BbPromise.bind(this) + .then(this.validate) + .then(this.emptyS3Bucket) + .then(this.removeStack) + .then(cfData => this.monitorStack('removal', cfData)), }; } } diff --git a/lib/plugins/aws/remove/index.test.js b/lib/plugins/aws/remove/index.test.js index 6ee7e20b2..b9f802b48 100644 --- a/lib/plugins/aws/remove/index.test.js +++ b/lib/plugins/aws/remove/index.test.js @@ -27,27 +27,22 @@ describe('AwsRemove', () => { }); it('should run promise chain in order', () => { - const validateStub = sinon - .stub(awsRemove, 'validate').resolves(); - const emptyS3BucketStub = sinon - .stub(awsRemove, 'emptyS3Bucket').resolves(); - const removeStackStub = sinon - .stub(awsRemove, 'removeStack').resolves(); - const monitorStackStub = sinon - .stub(awsRemove, 'monitorStack').resolves(); + const validateStub = sinon.stub(awsRemove, 'validate').resolves(); + const emptyS3BucketStub = sinon.stub(awsRemove, 'emptyS3Bucket').resolves(); + const removeStackStub = sinon.stub(awsRemove, 'removeStack').resolves(); + const monitorStackStub = sinon.stub(awsRemove, 'monitorStack').resolves(); - return awsRemove.hooks['remove:remove']() - .then(() => { - expect(validateStub.calledOnce).to.be.equal(true); - expect(emptyS3BucketStub.calledAfter(validateStub)).to.be.equal(true); - expect(removeStackStub.calledAfter(emptyS3BucketStub)).to.be.equal(true); - expect(monitorStackStub.calledAfter(emptyS3BucketStub)).to.be.equal(true); + return awsRemove.hooks['remove:remove']().then(() => { + expect(validateStub.calledOnce).to.be.equal(true); + expect(emptyS3BucketStub.calledAfter(validateStub)).to.be.equal(true); + expect(removeStackStub.calledAfter(emptyS3BucketStub)).to.be.equal(true); + expect(monitorStackStub.calledAfter(emptyS3BucketStub)).to.be.equal(true); - awsRemove.validate.restore(); - awsRemove.emptyS3Bucket.restore(); - awsRemove.removeStack.restore(); - awsRemove.monitorStack.restore(); - }); + awsRemove.validate.restore(); + awsRemove.emptyS3Bucket.restore(); + awsRemove.removeStack.restore(); + awsRemove.monitorStack.restore(); + }); }); }); }); diff --git a/lib/plugins/aws/remove/lib/bucket.js b/lib/plugins/aws/remove/lib/bucket.js index b67abf82e..f5d483321 100644 --- a/lib/plugins/aws/remove/lib/bucket.js +++ b/lib/plugins/aws/remove/lib/bucket.js @@ -4,10 +4,9 @@ const BbPromise = require('bluebird'); module.exports = { setServerlessDeploymentBucketName() { - return this.provider.getServerlessDeploymentBucketName() - .then((bucketName) => { - this.bucketName = bucketName; - }); + return this.provider.getServerlessDeploymentBucketName().then(bucketName => { + this.bucketName = bucketName; + }); }, listObjects() { @@ -16,19 +15,21 @@ module.exports = { this.serverless.cli.log('Getting all objects in S3 bucket...'); const serviceStage = `${this.serverless.service.service}/${this.provider.getStage()}`; - return this.provider.request('S3', 'listObjectsV2', { - Bucket: this.bucketName, - Prefix: `${this.provider.getDeploymentPrefix()}/${serviceStage}`, - }).then((result) => { - if (result) { - result.Contents.forEach((object) => { - this.objectsInBucket.push({ - Key: object.Key, + return this.provider + .request('S3', 'listObjectsV2', { + Bucket: this.bucketName, + Prefix: `${this.provider.getDeploymentPrefix()}/${serviceStage}`, + }) + .then(result => { + if (result) { + result.Contents.forEach(object => { + this.objectsInBucket.push({ + Key: object.Key, + }); }); - }); - } - return BbPromise.resolve(); - }); + } + return BbPromise.resolve(); + }); }, deleteObjects() { diff --git a/lib/plugins/aws/remove/lib/bucket.test.js b/lib/plugins/aws/remove/lib/bucket.test.js index 8714e96fc..703ab1750 100644 --- a/lib/plugins/aws/remove/lib/bucket.test.js +++ b/lib/plugins/aws/remove/lib/bucket.test.js @@ -39,49 +39,40 @@ describe('emptyS3Bucket', () => { describe('#listObjects()', () => { it('should resolve if no objects are present', () => { - const listObjectsStub = sinon.stub(awsRemove.provider, 'request') - .resolves(); + const listObjectsStub = sinon.stub(awsRemove.provider, 'request').resolves(); const stage = awsRemove.provider.getStage(); const prefix = awsRemove.provider.getDeploymentPrefix(); return awsRemove.listObjects().then(() => { expect(listObjectsStub.calledOnce).to.be.equal(true); - expect(listObjectsStub.calledWithExactly( - 'S3', - 'listObjectsV2', - { + expect( + listObjectsStub.calledWithExactly('S3', 'listObjectsV2', { Bucket: awsRemove.bucketName, Prefix: `${prefix}/${serverless.service.service}/${stage}`, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(awsRemove.objectsInBucket.length).to.equal(0); awsRemove.provider.request.restore(); }); }); it('should push objects to the array if present', () => { - const listObjectsStub = sinon.stub(awsRemove.provider, 'request') - .resolves({ - Contents: [ - { Key: 'object1' }, - { Key: 'object2' }, - ], - }); + const listObjectsStub = sinon.stub(awsRemove.provider, 'request').resolves({ + Contents: [{ Key: 'object1' }, { Key: 'object2' }], + }); const stage = awsRemove.provider.getStage(); const prefix = awsRemove.provider.getDeploymentPrefix(); return awsRemove.listObjects().then(() => { expect(listObjectsStub.calledOnce).to.be.equal(true); - expect(listObjectsStub.calledWithExactly( - 'S3', - 'listObjectsV2', - { + expect( + listObjectsStub.calledWithExactly('S3', 'listObjectsV2', { Bucket: awsRemove.bucketName, Prefix: `${prefix}/${serverless.service.service}/${stage}`, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); expect(awsRemove.objectsInBucket[0]).to.deep.equal({ Key: 'object1' }); expect(awsRemove.objectsInBucket[1]).to.deep.equal({ Key: 'object2' }); awsRemove.provider.request.restore(); @@ -93,26 +84,23 @@ describe('emptyS3Bucket', () => { it('should delete all objects in the S3 bucket', () => { awsRemove.objectsInBucket = [{ Key: 'foo' }]; - const deleteObjectsStub = sinon.stub(awsRemove.provider, 'request') - .resolves(); + const deleteObjectsStub = sinon.stub(awsRemove.provider, 'request').resolves(); return awsRemove.deleteObjects().then(() => { expect(deleteObjectsStub.calledOnce).to.be.equal(true); - expect(deleteObjectsStub.calledWithExactly( - 'S3', - 'deleteObjects', - { + expect( + deleteObjectsStub.calledWithExactly('S3', 'deleteObjects', { Bucket: awsRemove.bucketName, Delete: { Objects: awsRemove.objectsInBucket, }, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsRemove.provider.request.restore(); }); }); - it('should resolve if objectsInBucket is empty', (done) => { + it('should resolve if objectsInBucket is empty', done => { awsRemove.objectsInBucket = []; awsRemove.deleteObjects().then(() => { @@ -124,16 +112,16 @@ describe('emptyS3Bucket', () => { describe('#emptyS3Bucket()', () => { it('should run promise chain in order', () => { const setServerlessDeploymentBucketNameStub = sinon - .stub(awsRemove, 'setServerlessDeploymentBucketName').resolves(); - const listObjectsStub = sinon - .stub(awsRemove, 'listObjects').resolves(); - const deleteObjectsStub = sinon - .stub(awsRemove, 'deleteObjects').resolves(); + .stub(awsRemove, 'setServerlessDeploymentBucketName') + .resolves(); + const listObjectsStub = sinon.stub(awsRemove, 'listObjects').resolves(); + const deleteObjectsStub = sinon.stub(awsRemove, 'deleteObjects').resolves(); return awsRemove.emptyS3Bucket().then(() => { expect(setServerlessDeploymentBucketNameStub.calledOnce).to.be.equal(true); - expect(listObjectsStub.calledAfter(setServerlessDeploymentBucketNameStub)) - .to.be.equal(true); + expect(listObjectsStub.calledAfter(setServerlessDeploymentBucketNameStub)).to.be.equal( + true + ); expect(deleteObjectsStub.calledAfter(listObjectsStub)).to.be.equal(true); awsRemove.setServerlessDeploymentBucketName.restore(); diff --git a/lib/plugins/aws/remove/lib/stack.js b/lib/plugins/aws/remove/lib/stack.js index fe712b1da..8b36720e3 100644 --- a/lib/plugins/aws/remove/lib/stack.js +++ b/lib/plugins/aws/remove/lib/stack.js @@ -18,14 +18,10 @@ module.exports = { StackId: stackName, }; - return this.provider.request('CloudFormation', - 'deleteStack', - params) - .then(() => cfData); + return this.provider.request('CloudFormation', 'deleteStack', params).then(() => cfData); }, removeStack() { - return BbPromise.bind(this) - .then(this.remove); + return BbPromise.bind(this).then(this.remove); }, }; diff --git a/lib/plugins/aws/remove/lib/stack.test.js b/lib/plugins/aws/remove/lib/stack.test.js index 1916a84db..b90ddd8e0 100644 --- a/lib/plugins/aws/remove/lib/stack.test.js +++ b/lib/plugins/aws/remove/lib/stack.test.js @@ -25,24 +25,24 @@ describe('removeStack', () => { }); describe('#remove()', () => { - it('should remove a stack', () => awsRemove.remove().then(() => { - expect(removeStackStub.calledOnce).to.be.equal(true); - expect(removeStackStub.calledWithExactly( - 'CloudFormation', - 'deleteStack', - { - StackName: `${serverless.service.service}-${awsRemove.provider.getStage()}`, - } - )).to.be.equal(true); - awsRemove.provider.request.restore(); - })); + it('should remove a stack', () => + awsRemove.remove().then(() => { + expect(removeStackStub.calledOnce).to.be.equal(true); + expect( + removeStackStub.calledWithExactly('CloudFormation', 'deleteStack', { + StackName: `${serverless.service.service}-${awsRemove.provider.getStage()}`, + }) + ).to.be.equal(true); + awsRemove.provider.request.restore(); + })); it('should use CloudFormation service role if it is specified', () => { awsRemove.serverless.service.provider.cfnRole = 'arn:aws:iam::123456789012:role/myrole'; return awsRemove.remove().then(() => { - expect(removeStackStub.args[0][2].RoleARN) - .to.equal('arn:aws:iam::123456789012:role/myrole'); + expect(removeStackStub.args[0][2].RoleARN).to.equal( + 'arn:aws:iam::123456789012:role/myrole' + ); awsRemove.provider.request.restore(); }); }); @@ -50,8 +50,7 @@ describe('removeStack', () => { describe('#removeStack()', () => { it('should run promise chain in order', () => { - const removeStub = sinon - .stub(awsRemove, 'remove').resolves(); + const removeStub = sinon.stub(awsRemove, 'remove').resolves(); return awsRemove.removeStack().then(() => { expect(removeStub.calledOnce).to.be.equal(true); diff --git a/lib/plugins/aws/rollback/index.js b/lib/plugins/aws/rollback/index.js index 47ec0a101..acc314bb8 100644 --- a/lib/plugins/aws/rollback/index.js +++ b/lib/plugins/aws/rollback/index.js @@ -14,24 +14,19 @@ class AwsRollback { this.options = options; this.provider = this.serverless.getProvider('aws'); - Object.assign( - this, - validate, - setBucketName, - updateStack, - monitorStack - ); + Object.assign(this, validate, setBucketName, updateStack, monitorStack); this.hooks = { - 'before:rollback:initialize': () => BbPromise.bind(this) - .then(this.validate), + 'before:rollback:initialize': () => BbPromise.bind(this).then(this.validate), 'rollback:rollback': () => { if (!this.options.timestamp) { const command = this.serverless.pluginManager.spawn('deploy:list'); - this.serverless.cli.log([ - 'Use a timestamp from the deploy list below to rollback to a specific version.', - 'Run `sls rollback -t YourTimeStampHere`', - ].join('\n')); + this.serverless.cli.log( + [ + 'Use a timestamp from the deploy list below to rollback to a specific version.', + 'Run `sls rollback -t YourTimeStampHere`', + ].join('\n') + ); return command; } @@ -50,17 +45,16 @@ class AwsRollback { const deploymentPrefix = this.provider.getDeploymentPrefix(); const prefix = `${deploymentPrefix}/${serviceName}/${stage}`; - return this.provider.request('S3', - 'listObjectsV2', - { + return this.provider + .request('S3', 'listObjectsV2', { Bucket: this.bucketName, Prefix: prefix, }) - .then((response) => { + .then(response => { const deployments = findAndGroupDeployments(response, deploymentPrefix, serviceName, stage); if (deployments.length === 0) { - const msg = 'Couldn\'t find any existing deployments.'; + const msg = "Couldn't find any existing deployments."; const hint = 'Please verify that stage and region are correct.'; return BbPromise.reject(`${msg} ${hint}`); } @@ -73,12 +67,12 @@ class AwsRollback { } const dateString = `${date.getTime().toString()}-${date.toISOString()}`; - const exists = _.some(deployments, (deployment) => ( + const exists = _.some(deployments, deployment => _.some(deployment, { directory: dateString, file: 'compiled-cloudformation-template.json', }) - )); + ); if (!exists) { const msg = `Couldn't find a deployment for the timestamp: ${this.options.timestamp}.`; diff --git a/lib/plugins/aws/rollback/index.test.js b/lib/plugins/aws/rollback/index.test.js index 6813ae909..301e15c8e 100644 --- a/lib/plugins/aws/rollback/index.test.js +++ b/lib/plugins/aws/rollback/index.test.js @@ -14,7 +14,7 @@ describe('AwsRollback', () => { let serverless; let provider; - const createInstance = (options) => { + const createInstance = options => { serverless = new Serverless(); provider = new AwsProvider(serverless, options); serverless.setProvider('aws', provider); @@ -59,20 +59,14 @@ describe('AwsRollback', () => { }); it('should run "rollback:rollback" promise chain in order', () => { - const setBucketNameStub = sinon - .stub(awsRollback, 'setBucketName').resolves(); - const setStackToUpdateStub = sinon - .stub(awsRollback, 'setStackToUpdate').resolves(); - const updateStackStub = sinon - .stub(awsRollback, 'updateStack').resolves(); + const setBucketNameStub = sinon.stub(awsRollback, 'setBucketName').resolves(); + const setStackToUpdateStub = sinon.stub(awsRollback, 'setStackToUpdate').resolves(); + const updateStackStub = sinon.stub(awsRollback, 'updateStack').resolves(); return awsRollback.hooks['rollback:rollback']().then(() => { - expect(setBucketNameStub.calledOnce) - .to.be.equal(true); - expect(setStackToUpdateStub.calledAfter(setBucketNameStub)) - .to.be.equal(true); - expect(updateStackStub.calledAfter(setStackToUpdateStub)) - .to.be.equal(true); + expect(setBucketNameStub.calledOnce).to.be.equal(true); + expect(setStackToUpdateStub.calledAfter(setBucketNameStub)).to.be.equal(true); + expect(updateStackStub.calledAfter(setStackToUpdateStub)).to.be.equal(true); }); }); @@ -97,7 +91,8 @@ describe('AwsRollback', () => { const s3Objects = [ { // eslint-disable-next-line max-len - Key: 'serverless/rollback/dev/1476779096930-2016-10-18T08:24:56.930Z/compiled-cloudformation-template.json', + Key: + 'serverless/rollback/dev/1476779096930-2016-10-18T08:24:56.930Z/compiled-cloudformation-template.json', }, { Key: 'serverless/rollback/dev/1476779096930-2016-10-18T08:24:56.930Z/test.zip', @@ -107,41 +102,37 @@ describe('AwsRollback', () => { Contents: s3Objects, }; - sinon.stub(awsRollback.provider, 'request') - .resolves(s3Response); + sinon.stub(awsRollback.provider, 'request').resolves(s3Response); - return awsRollback.setStackToUpdate() - .then(() => { - expect(awsRollback.serverless.service.package.artifactDirectoryName) - .to.be.equal('serverless/rollback/dev/1476779096930-2016-10-18T08:24:56.930Z'); + return awsRollback.setStackToUpdate().then(() => { + expect(awsRollback.serverless.service.package.artifactDirectoryName).to.be.equal( + 'serverless/rollback/dev/1476779096930-2016-10-18T08:24:56.930Z' + ); - awsRollback.provider.request.restore(); - }); + awsRollback.provider.request.restore(); + }); }); - it('should reject in case no deployments are available', () => { const s3Response = { Contents: [], }; - const listObjectsStub = sinon.stub(awsRollback.provider, 'request') - .resolves(s3Response); + const listObjectsStub = sinon.stub(awsRollback.provider, 'request').resolves(s3Response); - return awsRollback.setStackToUpdate() + return awsRollback + .setStackToUpdate() .then(() => { assert.isNotOk(true, 'setStackToUpdate should not resolve'); }) - .catch((errorMessage) => { - expect(errorMessage).to.include('Couldn\'t find any existing deployments'); + .catch(errorMessage => { + expect(errorMessage).to.include("Couldn't find any existing deployments"); expect(listObjectsStub.calledOnce).to.be.equal(true); - expect(listObjectsStub.calledWithExactly( - 'S3', - 'listObjectsV2', - { + expect( + listObjectsStub.calledWithExactly('S3', 'listObjectsV2', { Bucket: awsRollback.bucketName, Prefix: `${s3Key}`, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsRollback.provider.request.restore(); }); }); @@ -150,7 +141,8 @@ describe('AwsRollback', () => { const s3Objects = [ { // eslint-disable-next-line max-len - Key: 'serverless/rollback/dev/2000000000000-2016-10-18T08:24:56.930Z/compiled-cloudformation-template.json', + Key: + 'serverless/rollback/dev/2000000000000-2016-10-18T08:24:56.930Z/compiled-cloudformation-template.json', }, { Key: 'serverless/rollback/dev/2000000000000-2016-10-18T08:24:56.930Z/test.zip', @@ -160,24 +152,22 @@ describe('AwsRollback', () => { Contents: s3Objects, }; - const listObjectsStub = sinon.stub(awsRollback.provider, 'request') - .resolves(s3Response); + const listObjectsStub = sinon.stub(awsRollback.provider, 'request').resolves(s3Response); - return awsRollback.setStackToUpdate() + return awsRollback + .setStackToUpdate() .then(() => { assert.isNotOk(true, 'setStackToUpdate should not resolve'); }) - .catch((errorMessage) => { - expect(errorMessage).to.include('Couldn\'t find a deployment for the timestamp'); + .catch(errorMessage => { + expect(errorMessage).to.include("Couldn't find a deployment for the timestamp"); expect(listObjectsStub.calledOnce).to.be.equal(true); - expect(listObjectsStub.calledWithExactly( - 'S3', - 'listObjectsV2', - { + expect( + listObjectsStub.calledWithExactly('S3', 'listObjectsV2', { Bucket: awsRollback.bucketName, Prefix: `${s3Key}`, - } - )).to.be.equal(true); + }) + ).to.be.equal(true); awsRollback.provider.request.restore(); }); }); @@ -186,7 +176,8 @@ describe('AwsRollback', () => { const s3Objects = [ { // eslint-disable-next-line max-len - Key: 'serverless/rollback/dev/1476779096930-2016-10-18T08:24:56.930Z/compiled-cloudformation-template.json', + Key: + 'serverless/rollback/dev/1476779096930-2016-10-18T08:24:56.930Z/compiled-cloudformation-template.json', }, { Key: 'serverless/rollback/dev/1476779096930-2016-10-18T08:24:56.930Z/test.zip', @@ -196,24 +187,21 @@ describe('AwsRollback', () => { Contents: s3Objects, }; - const listObjectsStub = sinon.stub(awsRollback.provider, 'request') - .resolves(s3Response); + const listObjectsStub = sinon.stub(awsRollback.provider, 'request').resolves(s3Response); - return awsRollback.setStackToUpdate() - .then(() => { - expect(awsRollback.serverless.service.package.artifactDirectoryName) - .to.be.equal('serverless/rollback/dev/1476779096930-2016-10-18T08:24:56.930Z'); - expect(listObjectsStub.calledOnce).to.be.equal(true); - expect(listObjectsStub.calledWithExactly( - 'S3', - 'listObjectsV2', - { - Bucket: awsRollback.bucketName, - Prefix: `${s3Key}`, - } - )).to.be.equal(true); - awsRollback.provider.request.restore(); - }); + return awsRollback.setStackToUpdate().then(() => { + expect(awsRollback.serverless.service.package.artifactDirectoryName).to.be.equal( + 'serverless/rollback/dev/1476779096930-2016-10-18T08:24:56.930Z' + ); + expect(listObjectsStub.calledOnce).to.be.equal(true); + expect( + listObjectsStub.calledWithExactly('S3', 'listObjectsV2', { + Bucket: awsRollback.bucketName, + Prefix: `${s3Key}`, + }) + ).to.be.equal(true); + awsRollback.provider.request.restore(); + }); }); }); }); diff --git a/lib/plugins/aws/rollbackFunction/index.js b/lib/plugins/aws/rollbackFunction/index.js index 98fe9820a..6306a94ea 100644 --- a/lib/plugins/aws/rollbackFunction/index.js +++ b/lib/plugins/aws/rollbackFunction/index.js @@ -17,9 +17,7 @@ class AwsRollbackFunction { commands: { function: { usage: 'Rollback the function to a specific version', - lifecycleEvents: [ - 'rollback', - ], + lifecycleEvents: ['rollback'], options: { function: { usage: 'Name of the function', @@ -46,11 +44,12 @@ class AwsRollbackFunction { }; this.hooks = { - 'rollback:function:rollback': () => BbPromise.bind(this) - .then(this.validate) - .then(this.getFunctionToBeRestored) - .then(this.fetchFunctionCode) - .then(this.restoreFunction), + 'rollback:function:rollback': () => + BbPromise.bind(this) + .then(this.validate) + .then(this.getFunctionToBeRestored) + .then(this.fetchFunctionCode) + .then(this.restoreFunction), }; } @@ -70,30 +69,27 @@ class AwsRollbackFunction { Qualifier: funcVersion, }; - return this.provider.request( - 'Lambda', - 'getFunction', - params - ) - .then((func) => func) - .catch((error) => { - if (error.message.match(/not found/)) { - const errorMessage = [ - `Function "${funcName}" with version "${funcVersion}" not found.`, - ` Please check if you've deployed "${funcName}"`, - ` and version "${funcVersion}" is available for this function.`, - ' Please check the docs for more info.', - ].join(''); - throw new Error(errorMessage); - } - throw new Error(error.message); - }); + return this.provider + .request('Lambda', 'getFunction', params) + .then(func => func) + .catch(error => { + if (error.message.match(/not found/)) { + const errorMessage = [ + `Function "${funcName}" with version "${funcVersion}" not found.`, + ` Please check if you've deployed "${funcName}"`, + ` and version "${funcVersion}" is available for this function.`, + ' Please check the docs for more info.', + ].join(''); + throw new Error(errorMessage); + } + throw new Error(error.message); + }); } fetchFunctionCode(func) { const codeUrl = func.Code.Location; - return fetch(codeUrl).then((response) => response.buffer()); + return fetch(codeUrl).then(response => response.buffer()); } restoreFunction(zipBuffer) { @@ -108,11 +104,7 @@ class AwsRollbackFunction { ZipFile: zipBuffer, }; - return this.provider.request( - 'Lambda', - 'updateFunctionCode', - params - ).then(() => { + return this.provider.request('Lambda', 'updateFunctionCode', params).then(() => { this.serverless.cli.log(`Successfully rolled back function "${this.options.function}"`); }); } diff --git a/lib/plugins/aws/rollbackFunction/index.test.js b/lib/plugins/aws/rollbackFunction/index.test.js index 25a5d54a7..6dc24b9fa 100644 --- a/lib/plugins/aws/rollbackFunction/index.test.js +++ b/lib/plugins/aws/rollbackFunction/index.test.js @@ -49,14 +49,12 @@ describe('AwsRollbackFunction', () => { let restoreFunctionStub; beforeEach(() => { - validateStub = sinon - .stub(awsRollbackFunction, 'validate').resolves(); + validateStub = sinon.stub(awsRollbackFunction, 'validate').resolves(); getFunctionToBeRestoredStub = sinon - .stub(awsRollbackFunction, 'getFunctionToBeRestored').resolves(); - fetchFunctionCodeStub = sinon - .stub(awsRollbackFunction, 'fetchFunctionCode').resolves(); - restoreFunctionStub = sinon - .stub(awsRollbackFunction, 'restoreFunction').resolves(); + .stub(awsRollbackFunction, 'getFunctionToBeRestored') + .resolves(); + fetchFunctionCodeStub = sinon.stub(awsRollbackFunction, 'fetchFunctionCode').resolves(); + restoreFunctionStub = sinon.stub(awsRollbackFunction, 'restoreFunction').resolves(); }); afterEach(() => { @@ -78,14 +76,13 @@ describe('AwsRollbackFunction', () => { expect(awsRollbackFunctionWithEmptyOptions.options).to.deep.equal({}); }); - it('should run promise chain in order', () => awsRollbackFunction - .hooks['rollback:function:rollback']().then(() => { + it('should run promise chain in order', () => + awsRollbackFunction.hooks['rollback:function:rollback']().then(() => { expect(validateStub.calledOnce).to.equal(true); expect(getFunctionToBeRestoredStub.calledAfter(validateStub)).to.equal(true); expect(fetchFunctionCodeStub.calledAfter(getFunctionToBeRestoredStub)).to.equal(true); expect(restoreFunctionStub.calledAfter(fetchFunctionCodeStub)).to.equal(true); - }) - ); + })); }); describe('#getFunctionToBeRestored()', () => { @@ -106,16 +103,14 @@ describe('AwsRollbackFunction', () => { awsRollbackFunction.options.function = 'hello'; awsRollbackFunction.options.version = '4711'; - return awsRollbackFunction.getFunctionToBeRestored().then((result) => { + return awsRollbackFunction.getFunctionToBeRestored().then(result => { expect(getFunctionStub.calledOnce).to.equal(true); - expect(getFunctionStub.calledWithExactly( - 'Lambda', - 'getFunction', - { + expect( + getFunctionStub.calledWithExactly('Lambda', 'getFunction', { FunctionName: 'service-dev-hello', Qualifier: '4711', - } - )).to.equal(true); + }) + ).to.equal(true); expect(consoleLogStub.called).to.equal(true); expect(result).to.deep.equal({ function: 'hello' }); }); @@ -139,17 +134,15 @@ describe('AwsRollbackFunction', () => { awsRollbackFunction.options.function = 'hello'; awsRollbackFunction.options.version = '4711'; - return awsRollbackFunction.getFunctionToBeRestored().catch((error) => { + return awsRollbackFunction.getFunctionToBeRestored().catch(error => { expect(error.message.match(/Function "hello" with version "4711" not found/)); expect(getFunctionStub.calledOnce).to.equal(true); - expect(getFunctionStub.calledWithExactly( - 'Lambda', - 'getFunction', - { + expect( + getFunctionStub.calledWithExactly('Lambda', 'getFunction', { FunctionName: 'service-dev-hello', Qualifier: '4711', - } - )).to.equal(true); + }) + ).to.equal(true); expect(consoleLogStub.called).to.equal(true); }); }); @@ -172,17 +165,15 @@ describe('AwsRollbackFunction', () => { awsRollbackFunction.options.function = 'hello'; awsRollbackFunction.options.version = '4711'; - return awsRollbackFunction.getFunctionToBeRestored().catch((error) => { + return awsRollbackFunction.getFunctionToBeRestored().catch(error => { expect(error.message.match(/something went wrong/)); expect(getFunctionStub.calledOnce).to.equal(true); - expect(getFunctionStub.calledWithExactly( - 'Lambda', - 'getFunction', - { + expect( + getFunctionStub.calledWithExactly('Lambda', 'getFunction', { FunctionName: 'service-dev-hello', Qualifier: '4711', - } - )).to.equal(true); + }) + ).to.equal(true); expect(consoleLogStub.called).to.equal(true); }); }); @@ -207,8 +198,7 @@ describe('AwsRollbackFunction', () => { let updateFunctionCodeStub; beforeEach(() => { - updateFunctionCodeStub = sinon - .stub(awsRollbackFunction.provider, 'request').resolves(); + updateFunctionCodeStub = sinon.stub(awsRollbackFunction.provider, 'request').resolves(); }); afterEach(() => { @@ -221,14 +211,12 @@ describe('AwsRollbackFunction', () => { return awsRollbackFunction.restoreFunction(zipBuffer).then(() => { expect(updateFunctionCodeStub.calledOnce).to.equal(true); - expect(updateFunctionCodeStub.calledWithExactly( - 'Lambda', - 'updateFunctionCode', - { + expect( + updateFunctionCodeStub.calledWithExactly('Lambda', 'updateFunctionCode', { FunctionName: 'service-dev-hello', ZipFile: zipBuffer, - } - )).to.equal(true); + }) + ).to.equal(true); expect(consoleLogStub.called).to.equal(true); }); }); diff --git a/lib/plugins/aws/utils/findAndGroupDeployments.js b/lib/plugins/aws/utils/findAndGroupDeployments.js index aef6e7d36..fd8262d59 100644 --- a/lib/plugins/aws/utils/findAndGroupDeployments.js +++ b/lib/plugins/aws/utils/findAndGroupDeployments.js @@ -5,8 +5,8 @@ const _ = require('lodash'); module.exports = (s3Response, prefix, service, stage) => { if (s3Response.Contents.length) { const regex = new RegExp(`${prefix}/${service}/${stage}/(.+-.+-.+-.+)/(.+)`); - const s3Objects = s3Response.Contents.filter((s3Object) => s3Object.Key.match(regex)); - const names = s3Objects.map((s3Object) => { + const s3Objects = s3Response.Contents.filter(s3Object => s3Object.Key.match(regex)); + const names = s3Objects.map(s3Object => { const match = s3Object.Key.match(regex); return { directory: match[1], @@ -14,7 +14,7 @@ module.exports = (s3Response, prefix, service, stage) => { }; }); const grouped = _.groupBy(names, 'directory'); - return _.map(grouped, (value) => value); + return _.map(grouped, value => value); } return []; }; diff --git a/lib/plugins/aws/utils/findAndGroupDeployments.test.js b/lib/plugins/aws/utils/findAndGroupDeployments.test.js index 17406cf84..2986ed854 100644 --- a/lib/plugins/aws/utils/findAndGroupDeployments.test.js +++ b/lib/plugins/aws/utils/findAndGroupDeployments.test.js @@ -16,21 +16,24 @@ describe('#findAndGroupDeployments()', () => { const s3Objects = [ { // eslint-disable-next-line max-len - Key: 'serverless/test/dev/1476779096930-2016-10-18T08:24:56.930Z/compiled-cloudformation-template.json', + Key: + 'serverless/test/dev/1476779096930-2016-10-18T08:24:56.930Z/compiled-cloudformation-template.json', }, { Key: 'serverless/test/dev/1476779096930-2016-10-18T08:24:56.930Z/test.zip', }, { // eslint-disable-next-line max-len - Key: 'serverless/test/dev/1476779278222-2016-10-18T08:27:58.222Z/compiled-cloudformation-template.json', + Key: + 'serverless/test/dev/1476779278222-2016-10-18T08:27:58.222Z/compiled-cloudformation-template.json', }, { Key: 'serverless/test/dev/1476779278222-2016-10-18T08:27:58.222Z/test.zip', }, { // eslint-disable-next-line max-len - Key: 'serverless/test/dev/1476781042481-2016-10-18T08:57:22.481Z/compiled-cloudformation-template.json', + Key: + 'serverless/test/dev/1476781042481-2016-10-18T08:57:22.481Z/compiled-cloudformation-template.json', }, { Key: 'serverless/test/dev/1476781042481-2016-10-18T08:57:22.481Z/test.zip', @@ -73,7 +76,8 @@ describe('#findAndGroupDeployments()', () => { ], ]; - expect(findAndGroupDeployments(s3Response, 'serverless', 'test', 'dev')) - .to.deep.equal(expected); + expect(findAndGroupDeployments(s3Response, 'serverless', 'test', 'dev')).to.deep.equal( + expected + ); }); }); diff --git a/lib/plugins/aws/utils/findReferences.test.js b/lib/plugins/aws/utils/findReferences.test.js index cc994712d..1e3681c00 100644 --- a/lib/plugins/aws/utils/findReferences.test.js +++ b/lib/plugins/aws/utils/findReferences.test.js @@ -9,8 +9,12 @@ describe('#findReferences()', () => { const withoutArgs = findReferences(); const nullArgs = findReferences(null); - expect(withoutArgs).to.be.a('Array').to.have.lengthOf(0); - expect(nullArgs).to.be.a('Array').have.lengthOf(0); + expect(withoutArgs) + .to.be.a('Array') + .to.have.lengthOf(0); + expect(nullArgs) + .to.be.a('Array') + .have.lengthOf(0); }); it('should return paths', () => { @@ -45,7 +49,9 @@ describe('#findReferences()', () => { ]; const paths = findReferences(testObject, 'hit'); - expect(paths).to.be.a('Array').to.have.lengthOf(4); + expect(paths) + .to.be.a('Array') + .to.have.lengthOf(4); expect(_.every(paths, path => _.includes(expectedResult, path))).to.equal(true); }); @@ -82,7 +88,9 @@ describe('#findReferences()', () => { ]; const paths = findReferences(testObject, 'hit'); - expect(paths).to.be.a('Array').to.have.lengthOf(4); + expect(paths) + .to.be.a('Array') + .to.have.lengthOf(4); expect(_.every(paths, path => _.includes(expectedResult, path))).to.equal(true); }); }); diff --git a/lib/plugins/aws/utils/formatLambdaLogEvent.js b/lib/plugins/aws/utils/formatLambdaLogEvent.js index a4aeee14f..1359ddb0b 100644 --- a/lib/plugins/aws/utils/formatLambdaLogEvent.js +++ b/lib/plugins/aws/utils/formatLambdaLogEvent.js @@ -4,7 +4,7 @@ const moment = require('moment'); const chalk = require('chalk'); const os = require('os'); -module.exports = (msgParam) => { +module.exports = msgParam => { let msg = msgParam; const dateFormat = 'YYYY-MM-DD HH:mm:ss.SSS (Z)'; @@ -27,10 +27,10 @@ module.exports = (msgParam) => { let date = ''; let reqId = ''; let level = ''; - if (!isNaN((new Date(splitted[0])).getTime())) { + if (!isNaN(new Date(splitted[0]).getTime())) { date = splitted[0]; reqId = splitted[1]; - } else if (!isNaN((new Date(splitted[1])).getTime())) { + } else if (!isNaN(new Date(splitted[1]).getTime())) { date = splitted[1]; reqId = splitted[2]; level = `${splitted[0]}\t`; diff --git a/lib/plugins/aws/utils/formatLambdaLogEvent.test.js b/lib/plugins/aws/utils/formatLambdaLogEvent.test.js index a5d4639df..dacc6cc0f 100644 --- a/lib/plugins/aws/utils/formatLambdaLogEvent.test.js +++ b/lib/plugins/aws/utils/formatLambdaLogEvent.test.js @@ -8,7 +8,8 @@ const formatLambdaLogEvent = require('./formatLambdaLogEvent'); describe('#formatLambdaLogEvent()', () => { it('should format invocation report', () => { - const msg = 'REPORT\tRequestId: 99c30000-b01a-11e5-93f7-b8e85631a00e\tDuration: 0.40 ms\tBilled Duration: 100 ms\tMemory Size: 512 MB\tMax Memory Used: 30 MB'; // eslint-disable-line + const msg = + 'REPORT\tRequestId: 99c30000-b01a-11e5-93f7-b8e85631a00e\tDuration: 0.40 ms\tBilled Duration: 100 ms\tMemory Size: 512 MB\tMax Memory Used: 30 MB'; // eslint-disable-line expect(formatLambdaLogEvent(msg)).to.equal(chalk.grey(msg + os.EOL)); }); @@ -31,7 +32,8 @@ describe('#formatLambdaLogEvent()', () => { }); it('should format lambda python logger lines', () => { - const pythonLoggerLine = '[INFO]\t2016-01-01T12:00:00Z\t99c30000-b01a-11e5-93f7-b8e85631a00e\ttest'; // eslint-disable-line + const pythonLoggerLine = + '[INFO]\t2016-01-01T12:00:00Z\t99c30000-b01a-11e5-93f7-b8e85631a00e\ttest'; // eslint-disable-line let expectedLogMessage = ''; const momentDate = moment('2016-01-01T12:00:00Z').format('YYYY-MM-DD HH:mm:ss.SSS (Z)'); diff --git a/lib/plugins/aws/utils/getS3ObjectsFromStacks.js b/lib/plugins/aws/utils/getS3ObjectsFromStacks.js index 1a98dc822..8d55d0f7f 100644 --- a/lib/plugins/aws/utils/getS3ObjectsFromStacks.js +++ b/lib/plugins/aws/utils/getS3ObjectsFromStacks.js @@ -2,8 +2,7 @@ const _ = require('lodash'); -module.exports = (stacks, prefix, service, stage) => ( - _.flatten(stacks).map((entry) => ( - { Key: `${prefix}/${service}/${stage}/${entry.directory}/${entry.file}` }) - ) -); +module.exports = (stacks, prefix, service, stage) => + _.flatten(stacks).map(entry => ({ + Key: `${prefix}/${service}/${stage}/${entry.directory}/${entry.file}`, + })); diff --git a/lib/plugins/aws/utils/getS3ObjectsFromStacks.test.js b/lib/plugins/aws/utils/getS3ObjectsFromStacks.test.js index df6f4beda..1b1d2dbee 100644 --- a/lib/plugins/aws/utils/getS3ObjectsFromStacks.test.js +++ b/lib/plugins/aws/utils/getS3ObjectsFromStacks.test.js @@ -34,10 +34,16 @@ describe('#getS3ObjectsFromStacks()', () => { const expected = [ // eslint-disable-next-line max-len - { Key: 'serverless/test/dev/1476779096930-2016-10-18T08:24:56.930Z/compiled-cloudformation-template.json' }, + { + Key: + 'serverless/test/dev/1476779096930-2016-10-18T08:24:56.930Z/compiled-cloudformation-template.json', + }, { Key: 'serverless/test/dev/1476779096930-2016-10-18T08:24:56.930Z/test.zip' }, // eslint-disable-next-line max-len - { Key: 'serverless/test/dev/1476779278222-2016-10-18T08:27:58.222Z/compiled-cloudformation-template.json' }, + { + Key: + 'serverless/test/dev/1476779278222-2016-10-18T08:27:58.222Z/compiled-cloudformation-template.json', + }, { Key: 'serverless/test/dev/1476779278222-2016-10-18T08:27:58.222Z/test.zip' }, ]; diff --git a/lib/plugins/config/config.js b/lib/plugins/config/config.js index 357e4e0f0..35cf6bfd4 100644 --- a/lib/plugins/config/config.js +++ b/lib/plugins/config/config.js @@ -4,10 +4,7 @@ const BbPromise = require('bluebird'); const userStats = require('../../utils/userStats'); // class wide constants -const validProviders = [ - 'aws', - 'spotinst', -]; +const validProviders = ['aws', 'spotinst']; // TODO: update to look like the list in the "create" plugin // once more than one provider is supported @@ -25,9 +22,7 @@ class Config { commands: { credentials: { usage: 'Configures a new provider profile for the Serverless Framework', - lifecycleEvents: [ - 'config', - ], + lifecycleEvents: ['config'], options: { provider: { usage: `Name of the provider. Supported providers: ${humanReadableProvidersList}`, diff --git a/lib/plugins/config/config.test.js b/lib/plugins/config/config.test.js index 1ef172c50..7ea4d175b 100644 --- a/lib/plugins/config/config.test.js +++ b/lib/plugins/config/config.test.js @@ -29,9 +29,7 @@ describe('Config', () => { }); it('should have the lifecycle event "config" for the "credentials" sub-command', () => { - expect(config.commands.config.commands.credentials.lifecycleEvents).to.deep.equal([ - 'config', - ]); + expect(config.commands.config.commands.credentials.lifecycleEvents).to.deep.equal(['config']); }); it('should have a required option "provider" for the "credentials" sub-command', () => { @@ -44,8 +42,7 @@ describe('Config', () => { }); it('should run promise chain in order for "before:config:credentials:config" hook', () => { - const configStub = sinon - .stub(config, 'validate').resolves(); + const configStub = sinon.stub(config, 'validate').resolves(); return config.hooks['before:config:credentials:config']().then(() => { expect(configStub.calledOnce).to.equal(true); @@ -61,7 +58,7 @@ describe('Config', () => { expect(() => config.validate()).to.throw(Error); }); - it('should resolve if user passed supported "provider" option', (done) => { + it('should resolve if user passed supported "provider" option', done => { config.options.provider = 'aws'; // aws is one example for a valid provider config.validate().then(() => done()); diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js index fcc4d9b4a..0da256897 100644 --- a/lib/plugins/create/create.js +++ b/lib/plugins/create/create.js @@ -65,8 +65,10 @@ const validTemplates = [ 'hello-world', ]; -const humanReadableTemplateList = `${validTemplates.slice(0, -1) - .map((template) => `"${template}"`).join(', ')} and "${validTemplates.slice(-1)}"`; +const humanReadableTemplateList = `${validTemplates + .slice(0, -1) + .map(template => `"${template}"`) + .join(', ')} and "${validTemplates.slice(-1)}"`; class Create { constructor(serverless, options) { @@ -76,11 +78,9 @@ class Create { this.commands = { create: { usage: 'Create new Serverless service', - lifecycleEvents: [ - 'create', - ], + lifecycleEvents: ['create'], options: { - template: { + 'template': { usage: `Template for the service. Available templates: ${humanReadableTemplateList}`, shortcut: 't', }, @@ -91,11 +91,11 @@ class Create { 'template-path': { usage: 'Template local path for the service.', }, - path: { + 'path': { usage: 'The path where the service should be created (e.g. --path my-service)', shortcut: 'p', }, - name: { + 'name': { usage: 'Name for the service. Overwrites the default name of the created service.', shortcut: 'n', }, @@ -104,8 +104,7 @@ class Create { }; this.hooks = { - 'create:create': () => BbPromise.bind(this) - .then(this.create), + 'create:create': () => BbPromise.bind(this).then(this.create), }; } @@ -115,16 +114,20 @@ class Create { if ('template' in this.options) { this.createFromTemplate(); } else if ('template-url' in this.options) { - return download.downloadTemplateFromRepo( - this.options['template-url'], - this.options.name, - this.options.path - ) + return download + .downloadTemplateFromRepo( + this.options['template-url'], + this.options.name, + this.options.path + ) .then(serviceName => { const message = [ `Successfully installed "${serviceName}" `, - `${this.options.name && - this.options.name !== serviceName ? `as "${this.options.name}"` : ''}`, + `${ + this.options.name && this.options.name !== serviceName + ? `as "${this.options.name}"` + : '' + }`, ].join(''); this.serverless.cli.log(message); @@ -176,8 +179,13 @@ class Create { // store the custom options for the service if given const boilerplatePath = _.toString(this.options.path); const serviceName = _.toString(this.options.name); - const templateSrcDir = path.join(this.serverless.config.serverlessPath, - 'plugins', 'create', 'templates', this.options.template); + const templateSrcDir = path.join( + this.serverless.config.serverlessPath, + 'plugins', + 'create', + 'templates', + this.options.template + ); // create (if not yet present) and chdir into the directory for the service if (boilerplatePath) { @@ -224,8 +232,10 @@ class Create { // NPM renames .gitignore to .npmignore on publish so we have to rename it. if (fse.existsSync(path.join(process.cwd(), 'gitignore'))) { - fse.renameSync(path.join(process.cwd(), 'gitignore'), - path.join(process.cwd(), '.gitignore')); + fse.renameSync( + path.join(process.cwd(), 'gitignore'), + path.join(process.cwd(), '.gitignore') + ); } } catch (err) { const errorMessage = [ @@ -249,15 +259,16 @@ class Create { }); this.serverless.cli.asciiGreeting(); - this.serverless.cli - .log(`Successfully generated boilerplate for template: "${this.options.template}"`); + this.serverless.cli.log( + `Successfully generated boilerplate for template: "${this.options.template}"` + ); if (!(boilerplatePath || serviceName) && notPlugin) { - this.serverless.cli - .log('NOTE: Please update the "service" property in serverless.yml with your service name'); + this.serverless.cli.log( + 'NOTE: Please update the "service" property in serverless.yml with your service name' + ); } } - } module.exports = Create; diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index 9d61db345..683dc8b24 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -38,8 +38,7 @@ describe('Create', () => { it('should have hooks', () => expect(create.hooks).to.be.not.empty); it('should run promise chain in order for "create:create" hook', () => { - const createStub = sinon - .stub(create, 'create').resolves(); + const createStub = sinon.stub(create, 'create').resolves(); return create.hooks['create:create']().then(() => { expect(createStub.calledOnce).to.be.equal(true); @@ -79,7 +78,7 @@ describe('Create', () => { downloadStub.rejects(new Error('Wrong')); create.options['template-url'] = 'https://github.com/serverless/serverless'; - return create.create().catch((error) => { + return create.create().catch(error => { expect(error).to.match(/Wrong/); expect(downloadStub).to.have.been.calledOnce; // eslint-disable-line }); @@ -90,8 +89,9 @@ describe('Create', () => { create.options['template-url'] = 'https://github.com/serverless/serverless'; return create.create().then(() => { - const installationMessage = - logSpy.args.filter(arg => arg[0].includes('installed "serverless"')); + const installationMessage = logSpy.args.filter(arg => + arg[0].includes('installed "serverless"') + ); expect(downloadStub).to.have.been.calledOnce; // eslint-disable-line expect(installationMessage[0]).to.have.lengthOf(1); @@ -104,8 +104,9 @@ describe('Create', () => { create.options.name = 'sls'; return create.create().then(() => { - const installationMessage = - logSpy.args.filter(arg => arg[0].includes('installed "serverless" as "sls"')); + const installationMessage = logSpy.args.filter(arg => + arg[0].includes('installed "serverless" as "sls"') + ); expect(downloadStub).to.have.been.calledOnce; // eslint-disable-line expect(installationMessage[0]).to.have.lengthOf(1); @@ -124,9 +125,7 @@ describe('Create', () => { create.options.name = 'my_service'; return create.create().then(() => - create.serverless.yamlParser.parse( - path.join(tmpDir, 'serverless.yml') - ).then((obj) => { + create.serverless.yamlParser.parse(path.join(tmpDir, 'serverless.yml')).then(obj => { expect(obj.service).to.equal('my_service'); }) ); @@ -154,21 +153,18 @@ describe('Create', () => { create.options.template = 'aws-clojure-gradle'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('build.gradle'); expect(dirContent).to.include('gradlew'); expect(dirContent).to.include('gradlew.bat'); - expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); - expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); - expect(dirContent).to.include(path.join('src', 'main', 'resources', - 'log4j.properties')); - expect(dirContent).to.include(path.join('src', 'main', 'clojure', - 'hello.clj')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', 'gradle-wrapper.jar')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', 'gradle-wrapper.properties')); + expect(dirContent).to.include(path.join('src', 'main', 'resources', 'log4j.properties')); + expect(dirContent).to.include(path.join('src', 'main', 'clojure', 'hello.clj')); expect(dirContent).to.include('.gitignore'); }); }); @@ -178,19 +174,19 @@ describe('Create', () => { create.options.template = 'aws-clojurescript-gradle'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('build.gradle'); expect(dirContent).to.include('gradlew'); expect(dirContent).to.include('gradlew.bat'); - expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); - expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); - expect(dirContent).to.include(path.join('src', 'main', 'clojurescript', - 'serverless', 'functions.cljs')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', 'gradle-wrapper.jar')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', 'gradle-wrapper.properties')); + expect(dirContent).to.include( + path.join('src', 'main', 'clojurescript', 'serverless', 'functions.cljs') + ); expect(dirContent).to.include(path.join('scripts', 'node_repl.clj')); expect(dirContent).to.include('.gitignore'); }); @@ -314,18 +310,22 @@ describe('Create', () => { create.options.template = 'aws-java-maven'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('pom.xml'); expect(dirContent).to.include(path.join('src', 'main', 'resources', 'log4j2.xml')); - expect(dirContent).to.include(path.join('src', 'main', 'java', 'com', 'serverless', - 'Handler.java')); - expect(dirContent).to.include(path.join('src', 'main', 'java', 'com', 'serverless', - 'ApiGatewayResponse.java')); - expect(dirContent).to.include(path.join('src', 'main', 'java', 'com', 'serverless', - 'Response.java')); + expect(dirContent).to.include( + path.join('src', 'main', 'java', 'com', 'serverless', 'Handler.java') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'java', 'com', 'serverless', 'ApiGatewayResponse.java') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'java', 'com', 'serverless', 'Response.java') + ); expect(dirContent).to.include(path.join('.gitignore')); }); }); @@ -335,17 +335,21 @@ describe('Create', () => { create.options.template = 'aws-kotlin-jvm-maven'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('pom.xml'); - expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', - 'Handler.kt')); - expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', - 'ApiGatewayResponse.kt')); - expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', - 'Response.kt')); + expect(dirContent).to.include( + path.join('src', 'main', 'kotlin', 'com', 'serverless', 'Handler.kt') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'kotlin', 'com', 'serverless', 'ApiGatewayResponse.kt') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'kotlin', 'com', 'serverless', 'Response.kt') + ); expect(dirContent).to.include(path.join('src', 'test', 'kotlin', '.gitkeep')); expect(dirContent).to.include(path.join('.gitignore')); }); @@ -356,17 +360,21 @@ describe('Create', () => { create.options.template = 'aws-kotlin-jvm-gradle'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('build.gradle'); - expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', - 'Handler.kt')); - expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', - 'ApiGatewayResponse.kt')); - expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', - 'Response.kt')); + expect(dirContent).to.include( + path.join('src', 'main', 'kotlin', 'com', 'serverless', 'Handler.kt') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'kotlin', 'com', 'serverless', 'ApiGatewayResponse.kt') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'kotlin', 'com', 'serverless', 'Response.kt') + ); expect(dirContent).to.include(path.join('src', 'test', 'kotlin', '.gitkeep')); expect(dirContent).to.include(path.join('.gitignore')); }); @@ -377,24 +385,26 @@ describe('Create', () => { create.options.template = 'aws-kotlin-nodejs-gradle'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('build.gradle'); expect(dirContent).to.include('gradlew'); expect(dirContent).to.include('gradlew.bat'); expect(dirContent).to.include('package.json'); - expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); - expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); - expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', - 'Handler.kt')); - expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', - 'ApiGatewayResponse.kt')); - expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', - 'Response.kt')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', 'gradle-wrapper.jar')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', 'gradle-wrapper.properties')); + expect(dirContent).to.include( + path.join('src', 'main', 'kotlin', 'com', 'serverless', 'Handler.kt') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'kotlin', 'com', 'serverless', 'ApiGatewayResponse.kt') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'kotlin', 'com', 'serverless', 'Response.kt') + ); expect(dirContent).to.include(path.join('src', 'test', 'kotlin', '.gitkeep')); expect(dirContent).to.include(path.join('.gitignore')); }); @@ -405,25 +415,26 @@ describe('Create', () => { create.options.template = 'aws-java-gradle'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('build.gradle'); expect(dirContent).to.include('gradlew'); expect(dirContent).to.include('gradlew.bat'); - expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); - expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); - expect(dirContent).to.include(path.join('src', 'main', 'resources', - 'log4j.properties')); - expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'Handler.java')); - expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'ApiGatewayResponse.java')); - expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'Response.java')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', 'gradle-wrapper.jar')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', 'gradle-wrapper.properties')); + expect(dirContent).to.include(path.join('src', 'main', 'resources', 'log4j.properties')); + expect(dirContent).to.include( + path.join('src', 'main', 'java', 'com', 'serverless', 'Handler.java') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'java', 'com', 'serverless', 'ApiGatewayResponse.java') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'java', 'com', 'serverless', 'Response.java') + ); expect(dirContent).to.include(path.join('.gitignore')); }); }); @@ -433,25 +444,26 @@ describe('Create', () => { create.options.template = 'aws-groovy-gradle'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('build.gradle'); expect(dirContent).to.include('gradlew'); expect(dirContent).to.include('gradlew.bat'); - expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); - expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); - expect(dirContent).to.include(path.join('src', 'main', 'resources', - 'log4j.properties')); - expect(dirContent).to.include(path.join('src', 'main', 'groovy', - 'com', 'serverless', 'Handler.groovy')); - expect(dirContent).to.include(path.join('src', 'main', 'groovy', - 'com', 'serverless', 'ApiGatewayResponse.groovy')); - expect(dirContent).to.include(path.join('src', 'main', 'groovy', - 'com', 'serverless', 'Response.groovy')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', 'gradle-wrapper.jar')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', 'gradle-wrapper.properties')); + expect(dirContent).to.include(path.join('src', 'main', 'resources', 'log4j.properties')); + expect(dirContent).to.include( + path.join('src', 'main', 'groovy', 'com', 'serverless', 'Handler.groovy') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'groovy', 'com', 'serverless', 'ApiGatewayResponse.groovy') + ); + expect(dirContent).to.include( + path.join('src', 'main', 'groovy', 'com', 'serverless', 'Response.groovy') + ); expect(dirContent).to.include('.gitignore'); }); }); @@ -461,17 +473,15 @@ describe('Create', () => { create.options.template = 'aws-scala-sbt'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('build.sbt'); - expect(dirContent).to.include(path.join('src', 'main', 'scala', - 'hello', 'Handler.scala')); - expect(dirContent).to.include(path.join('src', 'main', 'scala', - 'hello', 'Request.scala')); - expect(dirContent).to.include(path.join('src', 'main', 'scala', - 'hello', 'Response.scala')); + expect(dirContent).to.include(path.join('src', 'main', 'scala', 'hello', 'Handler.scala')); + expect(dirContent).to.include(path.join('src', 'main', 'scala', 'hello', 'Request.scala')); + expect(dirContent).to.include(path.join('src', 'main', 'scala', 'hello', 'Response.scala')); expect(dirContent).to.include('.gitignore'); }); }); @@ -481,13 +491,16 @@ describe('Create', () => { create.options.template = 'openwhisk-java-maven'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('pom.xml'); - expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'example', 'FunctionApp.java')); - expect(dirContent).to.include(path.join('src', 'test', 'java', - 'com', 'example', 'FunctionAppTest.java')); + expect(dirContent).to.include( + path.join('src', 'main', 'java', 'com', 'example', 'FunctionApp.java') + ); + expect(dirContent).to.include( + path.join('src', 'test', 'java', 'com', 'example', 'FunctionAppTest.java') + ); expect(dirContent).to.include('.gitignore'); expect(dirContent).to.include('serverless.yml'); }); @@ -726,14 +739,16 @@ describe('Create', () => { create.options.template = 'spotinst-java8'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('package.json'); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('pom.xml'); - expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'Handler.java')); + expect(dirContent).to.include( + path.join('src', 'main', 'java', 'com', 'serverless', 'Handler.java') + ); expect(dirContent).to.include('.gitignore'); }); }); @@ -743,8 +758,9 @@ describe('Create', () => { create.options.template = 'fn-nodejs'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('package.json'); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('.gitignore'); @@ -759,8 +775,9 @@ describe('Create', () => { create.options.template = 'fn-go'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('package.json'); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('.gitignore'); @@ -825,9 +842,10 @@ describe('Create', () => { // check if the service was renamed const serverlessYmlfileContent = fse - .readFileSync(path.join(serviceDir, 'serverless.yml')).toString(); + .readFileSync(path.join(serviceDir, 'serverless.yml')) + .toString(); - expect((/service: my-new-service/).test(serverlessYmlfileContent)).to.equal(true); + expect(/service: my-new-service/.test(serverlessYmlfileContent)).to.equal(true); }); }); @@ -850,31 +868,35 @@ describe('Create', () => { }); }); - it('should create a custom renamed service in the directory if using ' + - 'the "path" and "name" option', () => { - process.chdir(tmpDir); + it( + 'should create a custom renamed service in the directory if using ' + + 'the "path" and "name" option', + () => { + process.chdir(tmpDir); - create.options.path = 'my-new-service'; - create.options.name = 'my-custom-new-service'; + create.options.path = 'my-new-service'; + create.options.name = 'my-custom-new-service'; - // using the nodejs template (this test is completely be independent from the template) - create.options.template = 'aws-nodejs'; + // using the nodejs template (this test is completely be independent from the template) + create.options.template = 'aws-nodejs'; - return create.create().then(() => { - const serviceDir = path.join(tmpDir, create.options.path); - const dirContent = fs.readdirSync(serviceDir); + return create.create().then(() => { + const serviceDir = path.join(tmpDir, create.options.path); + const dirContent = fs.readdirSync(serviceDir); - // check if files are created in the correct directory - expect(dirContent).to.include('serverless.yml'); - expect(dirContent).to.include('handler.js'); + // check if files are created in the correct directory + expect(dirContent).to.include('serverless.yml'); + expect(dirContent).to.include('handler.js'); - // check if the service was renamed - const serverlessYmlfileContent = fse - .readFileSync(path.join(serviceDir, 'serverless.yml')).toString(); + // check if the service was renamed + const serverlessYmlfileContent = fse + .readFileSync(path.join(serviceDir, 'serverless.yml')) + .toString(); - expect((/service: my-custom-new-service/).test(serverlessYmlfileContent)).to.equal(true); - }); - }); + expect(/service: my-custom-new-service/.test(serverlessYmlfileContent)).to.equal(true); + }); + } + ); it('should throw error if there are existing template files in cwd', () => { process.chdir(tmpDir); @@ -882,8 +904,16 @@ describe('Create', () => { // create existing files from nodejs template create.options.template = 'aws-nodejs'; create.options.path = ''; - create.serverless.utils.copyDirContentsSync(path.join(create.serverless.config.serverlessPath, - 'plugins', 'create', 'templates', create.options.template), tmpDir); + create.serverless.utils.copyDirContentsSync( + path.join( + create.serverless.config.serverlessPath, + 'plugins', + 'create', + 'templates', + create.options.template + ), + tmpDir + ); const dirContent = fs.readdirSync(tmpDir); @@ -918,9 +948,10 @@ describe('Create', () => { // check if the service was renamed const serverlessYmlfileContent = fse - .readFileSync(path.join(distDir, 'serverless.yml')).toString(); + .readFileSync(path.join(distDir, 'serverless.yml')) + .toString(); - expect((/service: aws-nodejs/).test(serverlessYmlfileContent)).to.equal(true); + expect(/service: aws-nodejs/.test(serverlessYmlfileContent)).to.equal(true); }); }); @@ -939,9 +970,10 @@ describe('Create', () => { // check if the service was renamed const serverlessYmlfileContent = fse - .readFileSync(path.join(tmpDir, 'my-awesome-service', 'serverless.yml')).toString(); + .readFileSync(path.join(tmpDir, 'my-awesome-service', 'serverless.yml')) + .toString(); - expect((/service: my-awesome-service/).test(serverlessYmlfileContent)).to.equal(true); + expect(/service: my-awesome-service/.test(serverlessYmlfileContent)).to.equal(true); }); }); @@ -950,8 +982,9 @@ describe('Create', () => { create.options.template = 'aws-go'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include(path.join('hello', 'main.go')); @@ -966,8 +999,9 @@ describe('Create', () => { create.options.template = 'aws-go-dep'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include(path.join('hello', 'main.go')); @@ -983,8 +1017,9 @@ describe('Create', () => { create.options.template = 'aws-go-mod'; return create.create().then(() => { - const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + const dirContent = walkDirSync(tmpDir).map(elem => + elem.replace(path.join(tmpDir, path.sep), '') + ); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include(path.join('hello', 'main.go')); @@ -1000,12 +1035,15 @@ describe('Create', () => { create.options.template = 'aws-ruby'; return create.create().then(() => { - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.yml'))) - .to.be.equal(true); - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'handler.rb'))) - .to.be.equal(true); - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, '.gitignore'))) - .to.be.equal(true); + expect( + create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.yml')) + ).to.be.equal(true); + expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'handler.rb'))).to.be.equal( + true + ); + expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, '.gitignore'))).to.be.equal( + true + ); }); }); @@ -1014,14 +1052,18 @@ describe('Create', () => { create.options.template = 'aws-provided'; return create.create().then(() => { - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.yml'))) - .to.be.equal(true); - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'handler.sh'))) - .to.be.equal(true); - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, '.gitignore'))) - .to.be.equal(true); - expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'bootstrap'))) - .to.be.equal(true); + expect( + create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.yml')) + ).to.be.equal(true); + expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'handler.sh'))).to.be.equal( + true + ); + expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, '.gitignore'))).to.be.equal( + true + ); + expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'bootstrap'))).to.be.equal( + true + ); }); }); }); diff --git a/lib/plugins/create/templates/aws-alexa-typescript/package.json b/lib/plugins/create/templates/aws-alexa-typescript/package.json index ba84ae3bd..58904e451 100644 --- a/lib/plugins/create/templates/aws-alexa-typescript/package.json +++ b/lib/plugins/create/templates/aws-alexa-typescript/package.json @@ -18,7 +18,6 @@ "typescript": "^3.2.4", "webpack": "^4.29.0" }, - "author": - "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", + "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", "license": "MIT" } diff --git a/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml b/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml index 6dde48d3a..ec37bc364 100644 --- a/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml +++ b/lib/plugins/create/templates/aws-alexa-typescript/serverless.yml @@ -16,7 +16,7 @@ custom: # Step 1: Run `sls alexa auth` to authenticate # Step 2: Run `sls alexa create --name "Serverless Alexa Typescript" --locale en-GB --type custom` to create a new skill skills: - # Step 3: Paste the skill id returned by the create command here: + # Step 3: Paste the skill id returned by the create command here: - id: amzn1.ask.skill.xxxx-xxxx-xxxx-xxxx-xxxx manifest: publishingInformation: diff --git a/lib/plugins/create/templates/aws-alexa-typescript/tsconfig.json b/lib/plugins/create/templates/aws-alexa-typescript/tsconfig.json index 20be6a1c6..583da3154 100644 --- a/lib/plugins/create/templates/aws-alexa-typescript/tsconfig.json +++ b/lib/plugins/create/templates/aws-alexa-typescript/tsconfig.json @@ -2,12 +2,8 @@ "compilerOptions": { "sourceMap": true, "target": "es6", - "lib": [ - "esnext" - ], + "lib": ["esnext"], "moduleResolution": "node" }, - "exclude": [ - "node_modules" - ] + "exclude": ["node_modules"] } diff --git a/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml b/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml index b45dbccfc..71b728895 100644 --- a/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-clojure-gradle/serverless.yml @@ -60,7 +60,6 @@ functions: handler: hello::handler tags: VERSION: ${file(build/build.json):version} - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-clojurescript-gradle/README.md b/lib/plugins/create/templates/aws-clojurescript-gradle/README.md index be8cfdeb6..1b08984a5 100644 --- a/lib/plugins/create/templates/aws-clojurescript-gradle/README.md +++ b/lib/plugins/create/templates/aws-clojurescript-gradle/README.md @@ -1,4 +1,3 @@ - # AWS Clojurescript Gradle Template This project compiles **Clojurescript** to a [NodeJS](https://nodejs.org/en/) module using the [Gradle Clojure Plugin](https://gradle-clojure.github.io/gradle-clojure/index.html). @@ -12,6 +11,7 @@ See [functions.cljs](./src/main/clojurescript/serverless/functions.cljs) as an e To include **NodeJS** dependencies, modify [build.gradle](./build.gradle) and add the module to the `closurescript .. npmDeps` section. ### Prerequisites + - Create an [Amazon Web Services](https://aws.amazon.com) account - Install and set-up [Serverless Framework CLI](https://serverless.com) - Install [Java 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) @@ -20,12 +20,12 @@ To include **NodeJS** dependencies, modify [build.gradle](./build.gradle) and ad - Install [Gradle](https://gradle.org/install/) ### Build and Deploy + - To build, run `./gradlew clean build` - To deploy, run `serverless deploy` - ### Using the Repl in IntelliJ Cursive IDE This project contains a [script](./scripts/node_repl.clj) the must be initialized in order to use the **Repl** in **IntelliJ**. -![](http://share.rowellbelen.com/5WvFH2+) \ No newline at end of file +![](http://share.rowellbelen.com/5WvFH2+) diff --git a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml index 978252814..b71681702 100644 --- a/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-clojurescript-gradle/serverless.yml @@ -59,17 +59,16 @@ functions: hello: handler: build/clojurescript/main/functions.hello events: - - http: - path: hello - method: get + - http: + path: hello + method: get now: handler: build/clojurescript/main/functions.now events: - - http: - path: now - method: get - + - http: + path: now + method: get # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-csharp/serverless.yml b/lib/plugins/create/templates/aws-csharp/serverless.yml index 9e0b29bff..f4f250a5f 100644 --- a/lib/plugins/create/templates/aws-csharp/serverless.yml +++ b/lib/plugins/create/templates/aws-csharp/serverless.yml @@ -60,7 +60,6 @@ functions: # exclude: # - exclude-me.js # - exclude-me-dir/** - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-fsharp/serverless.yml b/lib/plugins/create/templates/aws-fsharp/serverless.yml index c185b184f..292a7053b 100644 --- a/lib/plugins/create/templates/aws-fsharp/serverless.yml +++ b/lib/plugins/create/templates/aws-fsharp/serverless.yml @@ -57,7 +57,6 @@ package: functions: hello: handler: FsharpHandlers::AwsDotnetFsharp.Handler::hello - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-go-dep/serverless.yml b/lib/plugins/create/templates/aws-go-dep/serverless.yml index f19abe668..d9dd8badd 100644 --- a/lib/plugins/create/templates/aws-go-dep/serverless.yml +++ b/lib/plugins/create/templates/aws-go-dep/serverless.yml @@ -18,7 +18,7 @@ service: aws-go-dep # NOTE: update this with your service name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details # frameworkVersion: "=X.X.X" -frameworkVersion: ">=1.28.0 <2.0.0" +frameworkVersion: '>=1.28.0 <2.0.0' provider: name: aws @@ -49,10 +49,10 @@ provider: # variable1: value1 package: - exclude: - - ./** - include: - - ./bin/** + exclude: + - ./** + include: + - ./bin/** functions: hello: @@ -67,7 +67,6 @@ functions: - http: path: world method: get - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-go-mod/serverless.yml b/lib/plugins/create/templates/aws-go-mod/serverless.yml index f46024cc4..137088cd1 100644 --- a/lib/plugins/create/templates/aws-go-mod/serverless.yml +++ b/lib/plugins/create/templates/aws-go-mod/serverless.yml @@ -18,7 +18,7 @@ service: aws-go-mod # NOTE: update this with your service name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details # frameworkVersion: "=X.X.X" -frameworkVersion: ">=1.28.0 <2.0.0" +frameworkVersion: '>=1.28.0 <2.0.0' provider: name: aws @@ -49,10 +49,10 @@ provider: # variable1: value1 package: - exclude: - - ./** - include: - - ./bin/** + exclude: + - ./** + include: + - ./bin/** functions: hello: @@ -67,7 +67,6 @@ functions: - http: path: world method: get - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-go/serverless.yml b/lib/plugins/create/templates/aws-go/serverless.yml index 6df5cee6c..f2e34e0fa 100644 --- a/lib/plugins/create/templates/aws-go/serverless.yml +++ b/lib/plugins/create/templates/aws-go/serverless.yml @@ -18,7 +18,7 @@ service: aws-go # NOTE: update this with your service name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details # frameworkVersion: "=X.X.X" -frameworkVersion: ">=1.28.0 <2.0.0" +frameworkVersion: '>=1.28.0 <2.0.0' provider: name: aws @@ -49,10 +49,10 @@ provider: # variable1: value1 package: - exclude: - - ./** - include: - - ./bin/** + exclude: + - ./** + include: + - ./bin/** functions: hello: @@ -67,7 +67,6 @@ functions: - http: path: world method: get - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml index db6d38f11..553248644 100644 --- a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml @@ -54,7 +54,6 @@ package: functions: hello: handler: com.serverless.Handler - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-java-gradle/serverless.yml b/lib/plugins/create/templates/aws-java-gradle/serverless.yml index 8d0efe8e1..439b9431c 100644 --- a/lib/plugins/create/templates/aws-java-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-java-gradle/serverless.yml @@ -54,7 +54,6 @@ package: functions: hello: handler: com.serverless.Handler - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.yml b/lib/plugins/create/templates/aws-java-maven/serverless.yml index 09f619491..25f50aa50 100644 --- a/lib/plugins/create/templates/aws-java-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-java-maven/serverless.yml @@ -54,7 +54,6 @@ package: functions: hello: handler: com.serverless.Handler - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml index 157032257..f19577ea3 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml @@ -54,7 +54,6 @@ package: functions: hello: handler: com.serverless.Handler - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml index 37eb728b0..d1187e2ba 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml @@ -54,7 +54,6 @@ package: functions: hello: handler: com.serverless.Handler - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml index e1ce95c7f..ef5683baf 100644 --- a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml @@ -50,7 +50,6 @@ provider: functions: hello: handler: build/index.Handler - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/first.js b/lib/plugins/create/templates/aws-nodejs-ecma-script/first.js index 8b8e34077..81886dd5c 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/first.js +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/first.js @@ -1,12 +1,12 @@ // eslint-disable-next-line import/prefer-default-export export const hello = (event, context, callback) => { - const p = new Promise((resolve) => { + const p = new Promise(resolve => { resolve('success'); }); - p - .then(() => callback(null, { + p.then(() => + callback(null, { message: 'Go Serverless Webpack (Ecma Script) v1.0! First module!', event, - })) - .catch(e => callback(e)); + }) + ).catch(e => callback(e)); }; diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/package.json b/lib/plugins/create/templates/aws-nodejs-ecma-script/package.json index bcfad7dd9..79f22191f 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/package.json +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/package.json @@ -16,4 +16,4 @@ }, "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", "license": "MIT" -} \ No newline at end of file +} diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/second.js b/lib/plugins/create/templates/aws-nodejs-ecma-script/second.js index a932eac05..9517398c3 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/second.js +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/second.js @@ -1,16 +1,18 @@ // eslint-disable-next-line import/prefer-default-export export const hello = (event, context, cb) => { - const p = new Promise((resolve) => { + const p = new Promise(resolve => { resolve('success'); }); const response = { statusCode: 200, - body: JSON.stringify({ - message: 'Go Serverless Webpack (Ecma Script) v1.0! Second module!', - input: event, - }, null, 2), + body: JSON.stringify( + { + message: 'Go Serverless Webpack (Ecma Script) v1.0! Second module!', + input: event, + }, + null, + 2 + ), }; - p - .then(() => cb(null, response)) - .catch(e => cb(e)); + p.then(() => cb(null, response)).catch(e => cb(e)); }; diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js b/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js index 4d173e306..da20b575a 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js @@ -6,12 +6,14 @@ module.exports = { entry: slsw.lib.entries, target: 'node', module: { - loaders: [{ - test: /\.js$/, - loaders: ['babel-loader'], - include: __dirname, - exclude: /node_modules/, - }], + loaders: [ + { + test: /\.js$/, + loaders: ['babel-loader'], + include: __dirname, + exclude: /node_modules/, + }, + ], }, output: { libraryTarget: 'commonjs', diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/package.json b/lib/plugins/create/templates/aws-nodejs-typescript/package.json index a4ef1a31d..002a8fadf 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/package.json +++ b/lib/plugins/create/templates/aws-nodejs-typescript/package.json @@ -17,7 +17,6 @@ "typescript": "^3.2.4", "webpack": "^4.29.0" }, - "author": - "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", + "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", "license": "MIT" } diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json b/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json index de011adf2..7aa5aad6a 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json +++ b/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json @@ -1,8 +1,6 @@ { "compilerOptions": { - "lib": [ - "es2017" - ], + "lib": ["es2017"], "moduleResolution": "node", "noUnusedLocals": true, "noUnusedParameters": true, @@ -10,7 +8,5 @@ "target": "es2017", "outDir": "lib" }, - "exclude": [ - "node_modules" - ] + "exclude": ["node_modules"] } diff --git a/lib/plugins/create/templates/aws-nodejs/handler.js b/lib/plugins/create/templates/aws-nodejs/handler.js index 8d524e278..13dd03fcd 100644 --- a/lib/plugins/create/templates/aws-nodejs/handler.js +++ b/lib/plugins/create/templates/aws-nodejs/handler.js @@ -1,12 +1,16 @@ 'use strict'; -module.exports.hello = async (event) => { +module.exports.hello = async event => { return { statusCode: 200, - body: JSON.stringify({ - message: 'Go Serverless v1.0! Your function executed successfully!', - input: event, - }, null, 2), + body: JSON.stringify( + { + message: 'Go Serverless v1.0! Your function executed successfully!', + input: event, + }, + null, + 2 + ), }; // Use this code if you don't use the http event with the LAMBDA-PROXY integration diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index 443554742..655fa0f54 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -59,7 +59,6 @@ provider: functions: hello: handler: handler.hello - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-provided/serverless.yml b/lib/plugins/create/templates/aws-provided/serverless.yml index 3411b1de9..bcdbc3122 100644 --- a/lib/plugins/create/templates/aws-provided/serverless.yml +++ b/lib/plugins/create/templates/aws-provided/serverless.yml @@ -59,7 +59,6 @@ provider: functions: hello: handler: handler.hello - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-python/serverless.yml b/lib/plugins/create/templates/aws-python/serverless.yml index 5aef41a5d..67cbc5448 100644 --- a/lib/plugins/create/templates/aws-python/serverless.yml +++ b/lib/plugins/create/templates/aws-python/serverless.yml @@ -59,7 +59,6 @@ provider: functions: hello: handler: handler.hello - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-python3/serverless.yml b/lib/plugins/create/templates/aws-python3/serverless.yml index bb1db67af..81974682d 100644 --- a/lib/plugins/create/templates/aws-python3/serverless.yml +++ b/lib/plugins/create/templates/aws-python3/serverless.yml @@ -59,7 +59,6 @@ provider: functions: hello: handler: handler.hello - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-ruby/serverless.yml b/lib/plugins/create/templates/aws-ruby/serverless.yml index 19a1b810b..d5b816f26 100644 --- a/lib/plugins/create/templates/aws-ruby/serverless.yml +++ b/lib/plugins/create/templates/aws-ruby/serverless.yml @@ -59,7 +59,6 @@ provider: functions: hello: handler: handler.hello - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml index f7fbacd3d..2ebdc16ce 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml +++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml @@ -56,7 +56,6 @@ package: functions: hello: handler: hello.Handler - # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/lib/plugins/create/templates/azure-nodejs/README.md b/lib/plugins/create/templates/azure-nodejs/README.md index 8f1ff8f98..e3f75575f 100644 --- a/lib/plugins/create/templates/azure-nodejs/README.md +++ b/lib/plugins/create/templates/azure-nodejs/README.md @@ -2,11 +2,11 @@ ## Pre-requisites -1. Node.js `v6.5.0` or later. *(v6.5.0 is the minimum runtime version supported by Azure Functions)* +1. Node.js `v6.5.0` or later. _(v6.5.0 is the minimum runtime version supported by Azure Functions)_ 2. Serverless CLI `v1.9.0` or later. You can run -`npm install -g serverless` to install it. + `npm install -g serverless` to install it. 3. Azure plugin that allows you to work with Azure Functions `npm install -g serverless-azure-functions` -4. An Azure account. If you don't already have one, you can sign up for a [free trial](https://azure.microsoft.com/en-us/free/) that includes $200 of free credit. +4. An Azure account. If you don't already have one, you can sign up for a [free trial](https://azure.microsoft.com/en-us/free/) that includes \$200 of free credit. 5. **Set-up your [Provider Credentials](./credentials.md)**. ## Create a new service @@ -47,46 +47,46 @@ Note: The file `{function name}/function.json` is included in the template for t 1. **Deploy the Service:** - 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. +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. - ```bash - serverless deploy - ``` +```bash +serverless deploy +``` - > Note: Once you've authenticated, a new Azure "service principal" will be - created, and used for subsequent deployments. This prevents you from needing to - manually login again. See [below](#advanced-authentication) if you'd prefer to - use a custom service principal instead. +> Note: Once you've authenticated, a new Azure "service principal" will be +> created, and used for subsequent deployments. This prevents you from needing to +> manually login again. See [below](#advanced-authentication) if you'd prefer to +> use a custom service principal instead. 2. **Deploy the Function** - Use this to quickly upload and overwrite your function code,allowing you to - develop faster. If you're working on a single function, you can simply deploy - the specified function instead of the entire service. +Use this to quickly upload and overwrite your function code,allowing you to +develop faster. If you're working on a single function, you can simply deploy +the specified function instead of the entire service. - ```bash - serverless deploy function -f hello - ``` +```bash +serverless deploy function -f hello +``` 3. **Invoke the Function** - Invoke a function, in order to test that it works: +Invoke a function, in order to test that it works: - ```bash - serverless invoke -f hello - ``` +```bash +serverless invoke -f hello +``` 4. **Fetch the Function Logs** - Open up a separate tab in your console and stream all logs for a specific - Function using this command. +Open up a separate tab in your console and stream all logs for a specific +Function using this command. - ```bash - serverless logs -f hello -t - ``` +```bash +serverless logs -f hello -t +``` ## Cleanup @@ -109,6 +109,7 @@ yourself, you can indicate that this plugin should use its credentials instead, by setting the following environment variables: **Bash** + ```bash export azureSubId='' export azureServicePrincipalTenantId='' @@ -117,6 +118,7 @@ export azureServicePrincipalPassword='' ``` **Powershell** + ```powershell $env:azureSubId='' $env:azureServicePrincipalTenantId='' @@ -124,7 +126,6 @@ $env:azureServicePrincipalClientId='' $env:azureServicePrincipalPassword='' ``` - ## Issues / Feedback / Feature Requests? If you have any issues, comments or want to see new features, please file an issue in the project repository: diff --git a/lib/plugins/create/templates/azure-nodejs/goodbye.js b/lib/plugins/create/templates/azure-nodejs/goodbye.js index cd73874f4..cb7634b33 100644 --- a/lib/plugins/create/templates/azure-nodejs/goodbye.js +++ b/lib/plugins/create/templates/azure-nodejs/goodbye.js @@ -1,18 +1,17 @@ 'use strict'; -module.exports.handler = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); +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: "Goodbye " + (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" - }; - } -}; \ No newline at end of file + if (req.query.name || (req.body && req.body.name)) { + context.res = { + // status: 200, /* Defaults to 200 */ + body: 'Goodbye ' + (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', + }; + } +}; diff --git a/lib/plugins/create/templates/azure-nodejs/hello.js b/lib/plugins/create/templates/azure-nodejs/hello.js index a63058384..2c3ea26e6 100644 --- a/lib/plugins/create/templates/azure-nodejs/hello.js +++ b/lib/plugins/create/templates/azure-nodejs/hello.js @@ -1,18 +1,17 @@ 'use strict'; -module.exports.handler = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); +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: "Hello " + (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" - }; - } -}; \ No newline at end of file + if (req.query.name || (req.body && req.body.name)) { + context.res = { + // status: 200, /* Defaults to 200 */ + body: 'Hello ' + (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', + }; + } +}; diff --git a/lib/plugins/create/templates/azure-nodejs/package.json b/lib/plugins/create/templates/azure-nodejs/package.json index 8db9f41a9..49b66beb9 100644 --- a/lib/plugins/create/templates/azure-nodejs/package.json +++ b/lib/plugins/create/templates/azure-nodejs/package.json @@ -14,4 +14,4 @@ "devDependencies": { "serverless-azure-functions": ">=0.7.0" } -} \ No newline at end of file +} diff --git a/lib/plugins/create/templates/azure-nodejs/serverless.yml b/lib/plugins/create/templates/azure-nodejs/serverless.yml index 054455f33..c3e84ff1a 100644 --- a/lib/plugins/create/templates/azure-nodejs/serverless.yml +++ b/lib/plugins/create/templates/azure-nodejs/serverless.yml @@ -26,12 +26,12 @@ plugins: # you can add packaging information here package: -# include: -# - include-me.js -# - include-me-dir/** + # include: + # - include-me.js + # - include-me-dir/** exclude: -# - exclude-me.js -# - exclude-me-dir/** + # - exclude-me.js + # - exclude-me-dir/** - local.settings.json - .vscode/** @@ -41,7 +41,7 @@ functions: events: - http: true x-azure-settings: - authLevel : anonymous + authLevel: anonymous - http: true x-azure-settings: direction: out @@ -51,12 +51,11 @@ functions: events: - http: true x-azure-settings: - authLevel : anonymous + authLevel: anonymous - http: true x-azure-settings: direction: out name: res - # The following are a few examples of other events you can configure: # # events: diff --git a/lib/plugins/create/templates/cloudflare-workers-enterprise/bar.js b/lib/plugins/create/templates/cloudflare-workers-enterprise/bar.js index 14359baea..0d537fadf 100644 --- a/lib/plugins/create/templates/cloudflare-workers-enterprise/bar.js +++ b/lib/plugins/create/templates/cloudflare-workers-enterprise/bar.js @@ -1,7 +1,7 @@ addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request)) - }) - - async function handleRequest(request) { - return new Response("Foo is not Bar") - } + event.respondWith(handleRequest(event.request)); +}); + +async function handleRequest(request) { + return new Response('Foo is not Bar'); +} diff --git a/lib/plugins/create/templates/cloudflare-workers-enterprise/helloWorld.js b/lib/plugins/create/templates/cloudflare-workers-enterprise/helloWorld.js index dbede8a62..8e780d2b0 100644 --- a/lib/plugins/create/templates/cloudflare-workers-enterprise/helloWorld.js +++ b/lib/plugins/create/templates/cloudflare-workers-enterprise/helloWorld.js @@ -1,7 +1,7 @@ addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request)) - }) - - async function handleRequest(request) { - return new Response("Hello world") - } + event.respondWith(handleRequest(event.request)); +}); + +async function handleRequest(request) { + return new Response('Hello world'); +} diff --git a/lib/plugins/create/templates/cloudflare-workers-enterprise/serverless.yml b/lib/plugins/create/templates/cloudflare-workers-enterprise/serverless.yml index ff9776602..1b1d3b94d 100644 --- a/lib/plugins/create/templates/cloudflare-workers-enterprise/serverless.yml +++ b/lib/plugins/create/templates/cloudflare-workers-enterprise/serverless.yml @@ -1,5 +1,5 @@ service: - name: hello-world + name: hello-world provider: name: cloudflare diff --git a/lib/plugins/create/templates/cloudflare-workers-rust/helloWorld.js b/lib/plugins/create/templates/cloudflare-workers-rust/helloWorld.js index 2423e9165..7f7d962b5 100644 --- a/lib/plugins/create/templates/cloudflare-workers-rust/helloWorld.js +++ b/lib/plugins/create/templates/cloudflare-workers-rust/helloWorld.js @@ -1,6 +1,6 @@ addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request)) -}) + event.respondWith(handleRequest(event.request)); +}); async function handleRequest(request) { try { diff --git a/lib/plugins/create/templates/cloudflare-workers-rust/serverless.yml b/lib/plugins/create/templates/cloudflare-workers-rust/serverless.yml index ea7091b8d..459553e35 100644 --- a/lib/plugins/create/templates/cloudflare-workers-rust/serverless.yml +++ b/lib/plugins/create/templates/cloudflare-workers-rust/serverless.yml @@ -1,5 +1,5 @@ service: - name: hello-world + name: hello-world provider: name: cloudflare @@ -27,4 +27,3 @@ functions: wasm: - variable: WASM file: rust-wasm/pkg/rust_wasm_bg.wasm - diff --git a/lib/plugins/create/templates/cloudflare-workers-rust/webpack.config.js b/lib/plugins/create/templates/cloudflare-workers-rust/webpack.config.js index 41aaf8755..cbf4211f1 100644 --- a/lib/plugins/create/templates/cloudflare-workers-rust/webpack.config.js +++ b/lib/plugins/create/templates/cloudflare-workers-rust/webpack.config.js @@ -1,29 +1,32 @@ -const path = require("path"); -const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); +const path = require('path'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); const wasmDir = 'rust-wasm'; module.exports = { entry: { - helloWorld: [path.join(__dirname, `./${wasmDir}/pkg/rust_wasm.js`), path.join(__dirname, './helloWorld.js')] + helloWorld: [ + path.join(__dirname, `./${wasmDir}/pkg/rust_wasm.js`), + path.join(__dirname, './helloWorld.js'), + ], }, resolve: { - extensions: ['.js'] + extensions: ['.js'], }, output: { filename: '[name].js', - path: path.join(__dirname, 'dist') + path: path.join(__dirname, 'dist'), }, plugins: [ new WasmPackPlugin({ crateDirectory: path.resolve(__dirname, wasmDir), - extraArgs: "--no-typescript --target no-modules" - }) + extraArgs: '--no-typescript --target no-modules', + }), ], //devtool: 'inline-source-map', target: 'web', - mode: process.env.NODE_ENV || 'production' + mode: process.env.NODE_ENV || 'production', }; diff --git a/lib/plugins/create/templates/cloudflare-workers/helloWorld.js b/lib/plugins/create/templates/cloudflare-workers/helloWorld.js index dbede8a62..8e780d2b0 100644 --- a/lib/plugins/create/templates/cloudflare-workers/helloWorld.js +++ b/lib/plugins/create/templates/cloudflare-workers/helloWorld.js @@ -1,7 +1,7 @@ addEventListener('fetch', event => { - event.respondWith(handleRequest(event.request)) - }) - - async function handleRequest(request) { - return new Response("Hello world") - } + event.respondWith(handleRequest(event.request)); +}); + +async function handleRequest(request) { + return new Response('Hello world'); +} diff --git a/lib/plugins/create/templates/cloudflare-workers/serverless.yml b/lib/plugins/create/templates/cloudflare-workers/serverless.yml index efa295a7d..803b8ddc6 100644 --- a/lib/plugins/create/templates/cloudflare-workers/serverless.yml +++ b/lib/plugins/create/templates/cloudflare-workers/serverless.yml @@ -1,5 +1,5 @@ service: - name: hello-world + name: hello-world provider: name: cloudflare @@ -13,7 +13,7 @@ plugins: functions: hello: name: hello - script: helloWorld # there must be a file called helloWorld.js + script: helloWorld # there must be a file called helloWorld.js events: - http: url: example.com/hello/* diff --git a/lib/plugins/create/templates/fn-go/hello/test.json b/lib/plugins/create/templates/fn-go/hello/test.json index 391d9b42f..485898b57 100644 --- a/lib/plugins/create/templates/fn-go/hello/test.json +++ b/lib/plugins/create/templates/fn-go/hello/test.json @@ -1,26 +1,26 @@ { - "tests": [ - { - "input": { - "body": { - "name": "Johnny" - } - }, - "output": { - "body": { - "message": "Hello Johnny" - } - } - }, - { - "input": { - "body": "" - }, - "output": { - "body": { - "message": "Hello World" - } - } + "tests": [ + { + "input": { + "body": { + "name": "Johnny" } - ] + }, + "output": { + "body": { + "message": "Hello Johnny" + } + } + }, + { + "input": { + "body": "" + }, + "output": { + "body": { + "message": "Hello World" + } + } + } + ] } diff --git a/lib/plugins/create/templates/fn-go/serverless.yml b/lib/plugins/create/templates/fn-go/serverless.yml index b338b2a69..67e2e93d8 100644 --- a/lib/plugins/create/templates/fn-go/serverless.yml +++ b/lib/plugins/create/templates/fn-go/serverless.yml @@ -6,7 +6,7 @@ # The `service` block is the name of the service service: - name: hello-world + name: hello-world # config: # some: 'val' @@ -25,5 +25,5 @@ functions: format: json runtime: go events: - - http: - path: /hellogo + - http: + path: /hellogo diff --git a/lib/plugins/create/templates/fn-nodejs/hello/func.js b/lib/plugins/create/templates/fn-nodejs/hello/func.js index 3d7d26bd7..911c30efc 100644 --- a/lib/plugins/create/templates/fn-nodejs/hello/func.js +++ b/lib/plugins/create/templates/fn-nodejs/hello/func.js @@ -1,11 +1,11 @@ const fdk = require('@fnproject/fdk'); -fdk.handle((input) => { - let name = 'World'; - if (input.name) { - name = input.name; - } - const response = { message: `Hello ${name}` }; - console.error(`I show up in the logs name was: ${name}`); - return response; +fdk.handle(input => { + let name = 'World'; + if (input.name) { + name = input.name; + } + const response = { message: `Hello ${name}` }; + console.error(`I show up in the logs name was: ${name}`); + return response; }); diff --git a/lib/plugins/create/templates/fn-nodejs/hello/package.json b/lib/plugins/create/templates/fn-nodejs/hello/package.json index cfd79b538..377a1fe33 100644 --- a/lib/plugins/create/templates/fn-nodejs/hello/package.json +++ b/lib/plugins/create/templates/fn-nodejs/hello/package.json @@ -1,11 +1,11 @@ { - "name": "hellofn", - "version": "1.0.0", - "description": "example function", - "main": "func.js", - "author": "", - "license": "Apache-2.0", - "dependencies": { - "@fnproject/fdk": "0.x" - } + "name": "hellofn", + "version": "1.0.0", + "description": "example function", + "main": "func.js", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@fnproject/fdk": "0.x" + } } diff --git a/lib/plugins/create/templates/fn-nodejs/hello/test.json b/lib/plugins/create/templates/fn-nodejs/hello/test.json index 391d9b42f..485898b57 100644 --- a/lib/plugins/create/templates/fn-nodejs/hello/test.json +++ b/lib/plugins/create/templates/fn-nodejs/hello/test.json @@ -1,26 +1,26 @@ { - "tests": [ - { - "input": { - "body": { - "name": "Johnny" - } - }, - "output": { - "body": { - "message": "Hello Johnny" - } - } - }, - { - "input": { - "body": "" - }, - "output": { - "body": { - "message": "Hello World" - } - } + "tests": [ + { + "input": { + "body": { + "name": "Johnny" } - ] + }, + "output": { + "body": { + "message": "Hello Johnny" + } + } + }, + { + "input": { + "body": "" + }, + "output": { + "body": { + "message": "Hello World" + } + } + } + ] } diff --git a/lib/plugins/create/templates/fn-nodejs/serverless.yml b/lib/plugins/create/templates/fn-nodejs/serverless.yml index bd51faf04..de49bb131 100644 --- a/lib/plugins/create/templates/fn-nodejs/serverless.yml +++ b/lib/plugins/create/templates/fn-nodejs/serverless.yml @@ -6,7 +6,7 @@ # The `service` block is the name of the service service: - name: hello-world + name: hello-world # config: # some: 'val' @@ -25,9 +25,9 @@ functions: idletimeout: 45 format: json memory: 256 -# config: -# another: value + # config: + # another: value runtime: node events: - - http: - path: /hello + - http: + path: /hello diff --git a/lib/plugins/create/templates/google-go/serverless.yml b/lib/plugins/create/templates/google-go/serverless.yml index 5ba3875e1..2f8aae90c 100644 --- a/lib/plugins/create/templates/google-go/serverless.yml +++ b/lib/plugins/create/templates/google-go/serverless.yml @@ -25,18 +25,15 @@ functions: handler: http events: - http: path - # NOTE: the following uses an "event" event (pubSub event in this case). # Please create the corresponding resources in the Google Cloud # before deploying this service through Serverless - #second: # handler: event # events: # - event: # eventType: providers/cloud.pubsub/eventTypes/topic.publish # resource: projects/*/topics/my-topic - # you can define resources, templates etc. the same way you would in a # Google Cloud deployment configuration #resources: diff --git a/lib/plugins/create/templates/google-nodejs/serverless.yml b/lib/plugins/create/templates/google-nodejs/serverless.yml index 2f72ae814..d1693d5de 100644 --- a/lib/plugins/create/templates/google-nodejs/serverless.yml +++ b/lib/plugins/create/templates/google-nodejs/serverless.yml @@ -37,7 +37,6 @@ functions: # - event: # eventType: providers/cloud.pubsub/eventTypes/topic.publish # resource: projects/*/topics/my-topic - # you can define resources, templates etc. the same way you would in a # Google Cloud deployment configuration #resources: diff --git a/lib/plugins/create/templates/google-python/serverless.yml b/lib/plugins/create/templates/google-python/serverless.yml index 36ea8c5a3..7c99ff3dd 100644 --- a/lib/plugins/create/templates/google-python/serverless.yml +++ b/lib/plugins/create/templates/google-python/serverless.yml @@ -37,7 +37,6 @@ functions: # - event: # eventType: providers/cloud.pubsub/eventTypes/topic.publish # resource: projects/*/topics/my-topic - # you can define resources, templates etc. the same way you would in a # Google Cloud deployment configuration #resources: diff --git a/lib/plugins/create/templates/openwhisk-java-maven/serverless.yml b/lib/plugins/create/templates/openwhisk-java-maven/serverless.yml index d3541cdf8..8b929cf78 100644 --- a/lib/plugins/create/templates/openwhisk-java-maven/serverless.yml +++ b/lib/plugins/create/templates/openwhisk-java-maven/serverless.yml @@ -23,7 +23,7 @@ provider: # you can add packaging information here package: - artifact: target/demo-function.jar + artifact: target/demo-function.jar functions: demo: @@ -32,8 +32,7 @@ functions: # extend the framework using plugins listed here: # https://github.com/serverless/plugins plugins: - - "serverless-openwhisk" - + - 'serverless-openwhisk' # you can define custom triggers and trigger feeds using the resources section. # #resources: diff --git a/lib/plugins/create/templates/openwhisk-nodejs/README.md b/lib/plugins/create/templates/openwhisk-nodejs/README.md index 6d3525c4d..7c804970f 100644 --- a/lib/plugins/create/templates/openwhisk-nodejs/README.md +++ b/lib/plugins/create/templates/openwhisk-nodejs/README.md @@ -8,17 +8,17 @@ This is a template Node.js service for the OpenWhisk platform. Before you can de Before you can deploy your service to OpenWhisk, you need to have an account registered with the platform. -- *Want to run the platform locally?* Please read the project's [*Quick Start*](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. -- *Want to use a hosted provider?* Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). +- _Want to run the platform locally?_ Please read the project's [_Quick Start_](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. +- _Want to use a hosted provider?_ Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). Account credentials for OpenWhisk can be provided through a configuration file or environment variables. This plugin requires the API endpoint, namespace and authentication credentials. -**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. +**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. **Do you want to use environment variables for credentials?** Use the following environment variables to be pass in account credentials. These values override anything extracted from the configuration file. -- *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` -- *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy +- _OW_APIHOST_ - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` +- _OW_AUTH_ - Authentication key, e.g. `xxxxxx:yyyyy ### Have you installed the provider plugin? @@ -38,8 +38,6 @@ Use the `serverless` command to deploy your service. The sample `handler.js` fil serverless deploy ``` - - ### Issues / Feedback / Feature Requests? If you have any issues, comments or want to see new features, please file an issue in the project repository: diff --git a/lib/plugins/create/templates/openwhisk-php/README.md b/lib/plugins/create/templates/openwhisk-php/README.md index 853d41590..acfb9171d 100644 --- a/lib/plugins/create/templates/openwhisk-php/README.md +++ b/lib/plugins/create/templates/openwhisk-php/README.md @@ -8,17 +8,17 @@ This is a template PHP service for the OpenWhisk platform. Before you can deploy Before you can deploy your service to OpenWhisk, you need to have an account registered with the platform. -- *Want to run the platform locally?* Please read the project's [*Quick Start*](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. -- *Want to use a hosted provider?* Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). +- _Want to run the platform locally?_ Please read the project's [_Quick Start_](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. +- _Want to use a hosted provider?_ Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). Account credentials for OpenWhisk can be provided through a configuration file or environment variables. This plugin requires the API endpoint, namespace and authentication credentials. -**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. +**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. **Do you want to use environment variables for credentials?** Use the following environment variables to be pass in account credentials. These values override anything extracted from the configuration file. -- *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` -- *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy +- _OW_APIHOST_ - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` +- _OW_AUTH_ - Authentication key, e.g. `xxxxxx:yyyyy ### Have you installed the provider plugin? @@ -38,8 +38,6 @@ Use the `serverless` command to deploy your service. The sample `handler.js` fil serverless deploy ``` - - ### Issues / Feedback / Feature Requests? If you have any issues, comments or want to see new features, please file an issue in the project repository: diff --git a/lib/plugins/create/templates/openwhisk-python/README.md b/lib/plugins/create/templates/openwhisk-python/README.md index d29e60d36..26748320b 100644 --- a/lib/plugins/create/templates/openwhisk-python/README.md +++ b/lib/plugins/create/templates/openwhisk-python/README.md @@ -8,17 +8,17 @@ This is a template Python service for the OpenWhisk platform. Before you can dep Before you can deploy your service to OpenWhisk, you need to have an account registered with the platform. -- *Want to run the platform locally?* Please read the project's [*Quick Start*](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. -- *Want to use a hosted provider?* Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). +- _Want to run the platform locally?_ Please read the project's [_Quick Start_](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. +- _Want to use a hosted provider?_ Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). Account credentials for OpenWhisk can be provided through a configuration file or environment variables. This plugin requires the API endpoint, namespace and authentication credentials. -**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. +**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. **Do you want to use environment variables for credentials?** Use the following environment variables to be pass in account credentials. These values override anything extracted from the configuration file. -- *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` -- *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy` +- _OW_APIHOST_ - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` +- _OW_AUTH_ - Authentication key, e.g. `xxxxxx:yyyyy` ### Have you installed the provider plugin? @@ -38,8 +38,6 @@ Use the `serverless` command to deploy your service. The sample `handler.js` fil serverless deploy ``` - - ### Issues / Feedback / Feature Requests? If you have any issues, comments or want to see new features, please file an issue in the project repository: diff --git a/lib/plugins/create/templates/openwhisk-ruby/README.md b/lib/plugins/create/templates/openwhisk-ruby/README.md index 19954a6b1..0d989c786 100644 --- a/lib/plugins/create/templates/openwhisk-ruby/README.md +++ b/lib/plugins/create/templates/openwhisk-ruby/README.md @@ -8,17 +8,17 @@ This is a template Ruby service for the OpenWhisk platform. Before you can deplo Before you can deploy your service to OpenWhisk, you need to have an account registered with the platform. -- *Want to run the platform locally?* Please read the project's [*Quick Start*](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. -- *Want to use a hosted provider?* Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). +- _Want to run the platform locally?_ Please read the project's [_Quick Start_](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. +- _Want to use a hosted provider?_ Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). Account credentials for OpenWhisk can be provided through a configuration file or environment variables. This plugin requires the API endpoint, namespace and authentication credentials. -**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. +**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. **Do you want to use environment variables for credentials?** Use the following environment variables to be pass in account credentials. These values override anything extracted from the configuration file. -- *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` -- *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy +- _OW_APIHOST_ - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` +- _OW_AUTH_ - Authentication key, e.g. `xxxxxx:yyyyy ### Have you installed the provider plugin? @@ -38,8 +38,6 @@ Use the `serverless` command to deploy your service. The sample `handler.js` fil serverless deploy ``` - - ### Issues / Feedback / Feature Requests? If you have any issues, comments or want to see new features, please file an issue in the project repository: diff --git a/lib/plugins/create/templates/openwhisk-swift/README.md b/lib/plugins/create/templates/openwhisk-swift/README.md index ab7f9328b..596eb2520 100644 --- a/lib/plugins/create/templates/openwhisk-swift/README.md +++ b/lib/plugins/create/templates/openwhisk-swift/README.md @@ -8,17 +8,17 @@ This is a template Swift service for the OpenWhisk platform. Before you can depl Before you can deploy your service to OpenWhisk, you need to have an account registered with the platform. -- *Want to run the platform locally?* Please read the project's [*Quick Start*](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. -- *Want to use a hosted provider?* Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). +- _Want to run the platform locally?_ Please read the project's [_Quick Start_](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. +- _Want to use a hosted provider?_ Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). Account credentials for OpenWhisk can be provided through a configuration file or environment variables. This plugin requires the API endpoint, namespace and authentication credentials. -**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. +**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. **Do you want to use environment variables for credentials?** Use the following environment variables to be pass in account credentials. These values override anything extracted from the configuration file. -- *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` -- *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy +- _OW_APIHOST_ - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` +- _OW_AUTH_ - Authentication key, e.g. `xxxxxx:yyyyy ### Have you installed the provider plugin? @@ -38,8 +38,6 @@ Use the `serverless` command to deploy your service. The sample `handler.js` fil serverless deploy ``` - - ### Issues / Feedback / Feature Requests? If you have any issues, comments or want to see new features, please file an issue in the project repository: diff --git a/lib/plugins/create/templates/plugin/index.js b/lib/plugins/create/templates/plugin/index.js index ddd6d4d6c..c4df77127 100644 --- a/lib/plugins/create/templates/plugin/index.js +++ b/lib/plugins/create/templates/plugin/index.js @@ -8,15 +8,12 @@ class ServerlessPlugin { this.commands = { welcome: { usage: 'Helps you start your first Serverless plugin', - lifecycleEvents: [ - 'hello', - 'world', - ], + lifecycleEvents: ['hello', 'world'], options: { message: { usage: - 'Specify the message you want to deploy ' - + '(e.g. "--message \'My Message\'" or "-m \'My Message\'")', + 'Specify the message you want to deploy ' + + '(e.g. "--message \'My Message\'" or "-m \'My Message\'")', required: true, shortcut: 'm', }, diff --git a/lib/plugins/create/templates/spotinst-nodejs/handler.js b/lib/plugins/create/templates/spotinst-nodejs/handler.js index 30832c0f6..a7e482e5c 100644 --- a/lib/plugins/create/templates/spotinst-nodejs/handler.js +++ b/lib/plugins/create/templates/spotinst-nodejs/handler.js @@ -12,12 +12,12 @@ * headers: {"Content-Type": "application/json"} * }) * -*/ + */ -module.exports.main = function main (event, context, callback) { - callback(null, { - statusCode: 200, - body: '{"hello":"from NodeJS8.3 function"}', - headers: {"Content-Type": "application/json"} - }); +module.exports.main = function main(event, context, callback) { + callback(null, { + statusCode: 200, + body: '{"hello":"from NodeJS8.3 function"}', + headers: { 'Content-Type': 'application/json' }, + }); }; diff --git a/lib/plugins/deploy/deploy.js b/lib/plugins/deploy/deploy.js index 2f215ecd0..ee85e5ae4 100644 --- a/lib/plugins/deploy/deploy.js +++ b/lib/plugins/deploy/deploy.js @@ -23,30 +23,30 @@ class Deploy { 'finalize', ], options: { - conceal: { + 'conceal': { usage: 'Hide secrets from the output (e.g. API Gateway key values)', }, - stage: { + 'stage': { usage: 'Stage of the service', shortcut: 's', }, - region: { + 'region': { usage: 'Region of the service', shortcut: 'r', }, - package: { + 'package': { usage: 'Path of the deployment package', shortcut: 'p', }, - verbose: { + 'verbose': { usage: 'Show all stack events during deployment', shortcut: 'v', }, - force: { + 'force': { usage: 'Forces a deployment to take place', }, - function: { - usage: 'Function name. Deploys a single function (see \'deploy function\')', + 'function': { + usage: "Function name. Deploys a single function (see 'deploy function')", shortcut: 'f', }, 'aws-s3-accelerate': { @@ -56,45 +56,38 @@ class Deploy { commands: { function: { usage: 'Deploy a single function from the service', - lifecycleEvents: [ - 'initialize', - 'packageFunction', - 'deploy', - ], + lifecycleEvents: ['initialize', 'packageFunction', 'deploy'], options: { - function: { + 'function': { usage: 'Name of the function', shortcut: 'f', required: true, }, - stage: { + 'stage': { usage: 'Stage of the function', shortcut: 's', }, - region: { + 'region': { usage: 'Region of the function', shortcut: 'r', }, - force: { + 'force': { usage: 'Forces a deployment to take place', }, 'update-config': { - usage: 'Updates function configuration, e.g. Timeout or Memory Size without deploying code', // eslint-disable-line max-len + usage: + 'Updates function configuration, e.g. Timeout or Memory Size without deploying code', // eslint-disable-line max-len shortcut: 'u', }, }, }, list: { usage: 'List deployed version of your Serverless Service', - lifecycleEvents: [ - 'log', - ], + lifecycleEvents: ['log'], commands: { functions: { usage: 'List all the deployed functions and their versions', - lifecycleEvents: [ - 'log', - ], + lifecycleEvents: ['log'], }, }, }, @@ -103,8 +96,8 @@ class Deploy { }; this.hooks = { - 'before:deploy:deploy': () => BbPromise.bind(this) - .then(() => { + 'before:deploy:deploy': () => + BbPromise.bind(this).then(() => { const provider = this.serverless.service.provider.name; if (!this.serverless.getProvider(provider)) { const errorMessage = `The specified provider "${provider}" does not exist.`; @@ -114,9 +107,9 @@ class Deploy { // If the user has given a function parameter, spawn a function deploy // and terminate execution right afterwards. We did not enter the // deploy lifecycle yet, so nothing has to be cleaned up. - return this.serverless.pluginManager.spawn( - 'deploy:function', { terminateLifecycleAfterExecution: true } - ); + return this.serverless.pluginManager.spawn('deploy:function', { + terminateLifecycleAfterExecution: true, + }); } if (!this.options.package && !this.serverless.service.package.path) { return this.serverless.pluginManager.spawn('package'); diff --git a/lib/plugins/deploy/deploy.test.js b/lib/plugins/deploy/deploy.test.js index 81d54b397..0ef4a0a55 100644 --- a/lib/plugins/deploy/deploy.test.js +++ b/lib/plugins/deploy/deploy.test.js @@ -29,7 +29,9 @@ describe('Deploy', () => { it('should have hooks', () => expect(deploy.hooks).to.be.not.empty); it('should work without options', () => { const noOptionDeploy = new Deploy(serverless); - expect(noOptionDeploy).to.have.property('options').to.be.eql({}); + expect(noOptionDeploy) + .to.have.property('options') + .to.be.eql({}); }); }); @@ -52,28 +54,31 @@ describe('Deploy', () => { deploy.options.package = false; deploy.serverless.service.package.path = 'some_path'; - return expect(deploy.hooks['before:deploy:deploy']()).to.be.fulfilled - .then(() => expect(spawnPackageStub).to.be.not.called); + return expect(deploy.hooks['before:deploy:deploy']()).to.be.fulfilled.then( + () => expect(spawnPackageStub).to.be.not.called + ); }); it('should resolve if the service package path is set', () => { deploy.options.package = 'some_path'; deploy.serverless.service.package.path = false; - return expect(deploy.hooks['before:deploy:deploy']()).to.be.fulfilled - .then(() => expect(spawnPackageStub).to.be.not.called); + return expect(deploy.hooks['before:deploy:deploy']()).to.be.fulfilled.then( + () => expect(spawnPackageStub).to.be.not.called + ); }); it('should use the default packaging mechanism if no packaging config is provided', () => { deploy.options.package = false; deploy.serverless.service.package.path = false; - return expect(deploy.hooks['before:deploy:deploy']()).to.be.fulfilled - .then(() => BbPromise.all([ + return expect(deploy.hooks['before:deploy:deploy']()).to.be.fulfilled.then(() => + BbPromise.all([ expect(spawnDeployFunctionStub).to.not.be.called, expect(spawnPackageStub).to.be.calledOnce, expect(spawnPackageStub).to.be.calledWithExactly('package'), - ])); + ]) + ); }); it('should execute deploy function if a function option is given', () => { @@ -81,16 +86,15 @@ describe('Deploy', () => { deploy.options.function = 'myfunc'; deploy.serverless.service.package.path = false; - return expect(deploy.hooks['before:deploy:deploy']()).to.be.fulfilled - .then(() => BbPromise.all([ - expect(spawnPackageStub).to.not.be.called, - expect(spawnDeployFunctionStub).to.be.calledOnce, - expect(spawnDeployFunctionStub).to.be - .calledWithExactly('deploy:function', { + return expect(deploy.hooks['before:deploy:deploy']()).to.be.fulfilled.then(() => + BbPromise.all([ + expect(spawnPackageStub).to.not.be.called, + expect(spawnDeployFunctionStub).to.be.calledOnce, + expect(spawnDeployFunctionStub).to.be.calledWithExactly('deploy:function', { terminateLifecycleAfterExecution: true, - } - ), - ])); + }), + ]) + ); }); it('should throw an error if provider does not exist', () => { diff --git a/lib/plugins/info/info.js b/lib/plugins/info/info.js index 6306d98db..ef5b17bbc 100644 --- a/lib/plugins/info/info.js +++ b/lib/plugins/info/info.js @@ -11,9 +11,7 @@ class Info { info: { usage: 'Display information about the service', configDependent: true, - lifecycleEvents: [ - 'info', - ], + lifecycleEvents: ['info'], options: { stage: { usage: 'Stage of the service', diff --git a/lib/plugins/info/info.test.js b/lib/plugins/info/info.test.js index 7e7febac7..b6d4de8ea 100644 --- a/lib/plugins/info/info.test.js +++ b/lib/plugins/info/info.test.js @@ -34,8 +34,8 @@ describe('Info', () => { }); it('should run the validation', () => - expect(info.hooks['after:info:info']()) - .to.be.fulfilled.then(() => expect(trackStub).to.be.called) - ); + expect(info.hooks['after:info:info']()).to.be.fulfilled.then( + () => expect(trackStub).to.be.called + )); }); }); diff --git a/lib/plugins/install/install.js b/lib/plugins/install/install.js index 1d82642d9..b692ea30c 100644 --- a/lib/plugins/install/install.js +++ b/lib/plugins/install/install.js @@ -13,9 +13,7 @@ class Install { this.commands = { install: { usage: 'Install a Serverless service from GitHub or a plugin from the Serverless registry', - lifecycleEvents: [ - 'install', - ], + lifecycleEvents: ['install'], options: { url: { usage: 'URL of the Serverless service on GitHub', @@ -31,21 +29,25 @@ class Install { }; this.hooks = { - 'install:install': () => BbPromise.bind(this) - .then(this.install), + 'install:install': () => BbPromise.bind(this).then(this.install), }; } install() { - return download.downloadTemplateFromRepo(this.options.url, this.options.name) + return download + .downloadTemplateFromRepo(this.options.url, this.options.name) .then(serviceName => { const message = [ `Successfully installed "${serviceName}" `, - `${this.options.name && - this.options.name !== serviceName ? `as "${this.options.name}"` : ''}`, + `${ + this.options.name && this.options.name !== serviceName + ? `as "${this.options.name}"` + : '' + }`, ].join(''); userStats.track('service_installed', { - data: { // will be updated with core analtyics lib + data: { + // will be updated with core analtyics lib url: this.options.url, }, }); diff --git a/lib/plugins/install/install.test.js b/lib/plugins/install/install.test.js index 78e87a706..cdc00acdc 100644 --- a/lib/plugins/install/install.test.js +++ b/lib/plugins/install/install.test.js @@ -50,8 +50,7 @@ describe('Install', () => { let installStub; beforeEach(() => { - installStub = sinon - .stub(install, 'install').resolves(); + installStub = sinon.stub(install, 'install').resolves(); }); afterEach(() => { @@ -62,8 +61,8 @@ describe('Install', () => { it('should have hooks', () => expect(install.hooks).to.be.not.empty); - it('should run promise chain in order for "install:install" hook', () => install - .hooks['install:install']().then(() => { + it('should run promise chain in order for "install:install" hook', () => + install.hooks['install:install']().then(() => { expect(installStub.calledOnce).to.be.equal(true); })); }); @@ -105,8 +104,9 @@ describe('Install', () => { downloadStub.resolves('remote-service'); return install.install().then(() => { - const installationMessage = - logSpy.args.filter(arg => arg[0].includes('installed "remote-service"')); + const installationMessage = logSpy.args.filter(arg => + arg[0].includes('installed "remote-service"') + ); expect(downloadStub).to.have.been.calledOnce; // eslint-disable-line expect(installationMessage[0]).to.have.lengthOf(1); @@ -119,8 +119,9 @@ describe('Install', () => { downloadStub.resolves('remote-service'); return install.install().then(() => { - const installationMessage = - logSpy.args.filter(arg => arg[0].includes('installed "remote-service" as "remote"')); + const installationMessage = logSpy.args.filter(arg => + arg[0].includes('installed "remote-service" as "remote"') + ); expect(downloadStub).to.have.been.calledOnce; // eslint-disable-line expect(installationMessage[0]).to.have.lengthOf(1); diff --git a/lib/plugins/invoke/invoke.js b/lib/plugins/invoke/invoke.js index f9c5405b6..7121feb06 100644 --- a/lib/plugins/invoke/invoke.js +++ b/lib/plugins/invoke/invoke.js @@ -13,9 +13,7 @@ class Invoke { invoke: { usage: 'Invoke a deployed function', configDependent: true, - lifecycleEvents: [ - 'invoke', - ], + lifecycleEvents: ['invoke'], options: { function: { usage: 'The function name', @@ -49,50 +47,45 @@ class Invoke { raw: { usage: 'Flag to pass input data as a raw string', }, - }, commands: { local: { usage: 'Invoke function locally', - lifecycleEvents: [ - 'loadEnvVars', - 'invoke', - ], + lifecycleEvents: ['loadEnvVars', 'invoke'], options: { - function: { + 'function': { usage: 'Name of the function', shortcut: 'f', required: true, }, - path: { + 'path': { usage: 'Path to JSON or YAML file holding input data', shortcut: 'p', }, - data: { + 'data': { usage: 'input data', shortcut: 'd', }, - raw: { + 'raw': { usage: 'Flag to pass input data as a raw string', }, - context: { + 'context': { usage: 'Context of the service', shortcut: 'c', }, - contextPath: { + 'contextPath': { usage: 'Path to JSON or YAML file holding context data', shortcut: 'x', }, - env: { + 'env': { usage: 'Override environment variables. e.g. --env VAR1=val1 --env VAR2=val2', shortcut: 'e', }, - docker: { usage: 'Flag to turn on docker use for node/python/ruby/java' }, + 'docker': { usage: 'Flag to turn on docker use for node/python/ruby/java' }, 'docker-arg': { usage: 'Arguments to docker run command. e.g. --docker-arg "-p 9229:9229"', }, }, - }, }, }, @@ -127,11 +120,10 @@ class Invoke { // Turn zero or more --env options into an array // ...then split --env NAME=value and put into process.env. - _.concat(this.options.env || []) - .forEach(itm => { - const splitItm = _.split(itm, '='); - process.env[splitItm[0]] = splitItm[1] || ''; - }); + _.concat(this.options.env || []).forEach(itm => { + const splitItm = _.split(itm, '='); + process.env[splitItm[0]] = splitItm[1] || ''; + }); return BbPromise.resolve(); } diff --git a/lib/plugins/invoke/invoke.test.js b/lib/plugins/invoke/invoke.test.js index b7c891d06..71ac1b4f2 100644 --- a/lib/plugins/invoke/invoke.test.js +++ b/lib/plugins/invoke/invoke.test.js @@ -47,16 +47,17 @@ describe('Invoke', () => { it('should accept a single env option', () => { invoke.options = { env: 'NAME=value' }; - return expect(invoke.hooks['invoke:local:loadEnvVars']()).to.be.fulfilled - .then(() => expect(process.env.NAME).to.equal('value')); + return expect(invoke.hooks['invoke:local:loadEnvVars']()).to.be.fulfilled.then(() => + expect(process.env.NAME).to.equal('value') + ); }); it('should accept multiple env options', () => { invoke.options = { env: ['NAME1=val1', 'NAME2=val2'] }; - return expect(invoke.hooks['invoke:local:loadEnvVars']()).to.be.fulfilled - .then(() => expect(process.env.NAME1).to.equal('val1')) - .then(() => expect(process.env.NAME2).to.equal('val2')); + return expect(invoke.hooks['invoke:local:loadEnvVars']()) + .to.be.fulfilled.then(() => expect(process.env.NAME1).to.equal('val1')) + .then(() => expect(process.env.NAME2).to.equal('val2')); }); }); }); diff --git a/lib/plugins/logs/logs.js b/lib/plugins/logs/logs.js index a237c22eb..7c12aeb6d 100644 --- a/lib/plugins/logs/logs.js +++ b/lib/plugins/logs/logs.js @@ -12,9 +12,7 @@ class Logs { logs: { usage: 'Output the logs of a deployed function', configDependent: true, - lifecycleEvents: [ - 'logs', - ], + lifecycleEvents: ['logs'], options: { function: { usage: 'The function name', @@ -56,7 +54,7 @@ class Logs { const sls = this.serverless; if (sls && sls.processedInput && sls.processedInput.options) { const opts = sls.processedInput.options; - const type = (opts.tail) ? 'service_logsTailed' : 'service_logsViewed'; + const type = opts.tail ? 'service_logsTailed' : 'service_logsViewed'; this.userStats.track(type); } return BbPromise.resolve(); diff --git a/lib/plugins/logs/logs.test.js b/lib/plugins/logs/logs.test.js index 2348ab92e..14ebf4e06 100644 --- a/lib/plugins/logs/logs.test.js +++ b/lib/plugins/logs/logs.test.js @@ -30,8 +30,7 @@ describe('Logs', () => { const newLogs = new Logs(serverless); newLogs.userStats = userStats; - return newLogs.track() - .then(() => { + return newLogs.track().then(() => { expect(userStats.track.called).to.be.equal(false); }); }); @@ -44,10 +43,8 @@ describe('Logs', () => { const newLogs = new Logs(serverless); newLogs.userStats = userStats; - return newLogs.track() - .then(() => { - expect(userStats.track.calledWithExactly('service_logsViewed')) - .to.be.equal(true); + return newLogs.track().then(() => { + expect(userStats.track.calledWithExactly('service_logsViewed')).to.be.equal(true); }); }); @@ -57,10 +54,8 @@ describe('Logs', () => { const newLogs = new Logs(serverless); newLogs.userStats = userStats; - return newLogs.track() - .then(() => { - expect(userStats.track.calledWithExactly('service_logsTailed')) - .to.be.equal(true); + return newLogs.track().then(() => { + expect(userStats.track.calledWithExactly('service_logsTailed')).to.be.equal(true); }); }); }); diff --git a/lib/plugins/metrics/metrics.js b/lib/plugins/metrics/metrics.js index 747222069..ae2cd864a 100644 --- a/lib/plugins/metrics/metrics.js +++ b/lib/plugins/metrics/metrics.js @@ -12,9 +12,7 @@ class Metrics { metrics: { usage: 'Show metrics for a specific function', configDependent: true, - lifecycleEvents: [ - 'metrics', - ], + lifecycleEvents: ['metrics'], options: { function: { usage: 'The function name', diff --git a/lib/plugins/metrics/metrics.test.js b/lib/plugins/metrics/metrics.test.js index f96cd08d1..d8f6a5c1f 100644 --- a/lib/plugins/metrics/metrics.test.js +++ b/lib/plugins/metrics/metrics.test.js @@ -21,9 +21,7 @@ describe('Metrics', () => { }); it('should have a lifecycle event "metrics"', () => { - expect(metrics.commands.metrics.lifecycleEvents).to.deep.equal([ - 'metrics', - ]); + expect(metrics.commands.metrics.lifecycleEvents).to.deep.equal(['metrics']); }); }); }); diff --git a/lib/plugins/package/lib/packageService.js b/lib/plugins/package/lib/packageService.js index 22792f78a..0c7388f56 100644 --- a/lib/plugins/package/lib/packageService.js +++ b/lib/plugins/package/lib/packageService.js @@ -28,12 +28,16 @@ module.exports = { .then(configFilePath => { const packageExcludes = this.serverless.service.package.exclude || []; // add local service plugins Path - const pluginsLocalPath = this.serverless.pluginManager - .parsePluginsObject(this.serverless.service.plugins).localPath; + const pluginsLocalPath = this.serverless.pluginManager.parsePluginsObject( + this.serverless.service.plugins + ).localPath; const localPathExcludes = pluginsLocalPath ? [pluginsLocalPath] : []; // add layer paths - const layerExcludes = excludeLayers ? this.serverless.service.getAllLayers().map( - (layer) => `${this.serverless.service.getLayer(layer).path}/**`) : []; + const layerExcludes = excludeLayers + ? this.serverless.service + .getAllLayers() + .map(layer => `${this.serverless.service.getLayer(layer).path}/**`) + : []; // add defaults for exclude const serverlessConfigFileExclude = configFilePath ? [path.basename(configFilePath)] : []; @@ -63,22 +67,23 @@ module.exports = { if (functionObject.package.artifact) { return BbPromise.resolve(); } - if (functionObject.package.individually || this.serverless.service - .package.individually) { + if (functionObject.package.individually || this.serverless.service.package.individually) { return this.packageFunction(functionName); } shouldPackageService = true; return BbPromise.resolve(); }); const allLayers = this.serverless.service.getAllLayers(); - packagePromises = packagePromises.concat(_.map(allLayers, layerName => { - const layerObject = this.serverless.service.getLayer(layerName); - layerObject.package = layerObject.package || {}; - if (layerObject.package.artifact) { - return BbPromise.resolve(); - } - return this.packageLayer(layerName); - })); + packagePromises = packagePromises.concat( + _.map(allLayers, layerName => { + const layerObject = this.serverless.service.getLayer(layerName); + layerObject.package = layerObject.package || {}; + if (layerObject.package.artifact) { + return BbPromise.resolve(); + } + return this.packageLayer(layerName); + }) + ); return BbPromise.all(packagePromises).then(() => { if (shouldPackageService && !this.serverless.service.package.artifact) { @@ -95,13 +100,20 @@ module.exports = { * This is nearly imposible to actually set on a windows machine, so find all the Go handler * files and pass them into zipFiles as files to add with the execute bit in the zip file */ - const filesToChmodPlusX = process.platform !== 'win32' ? [] : - Object.values(this.serverless.service.functions) - .map(f => Object.assign({ - runtime: this.serverless.service.provider.runtime || 'node8.10', - }, f)) - .filter(f => f.runtime && f.runtime.startsWith('go')) - .map(f => f.handler); + const filesToChmodPlusX = + process.platform !== 'win32' + ? [] + : Object.values(this.serverless.service.functions) + .map(f => + Object.assign( + { + runtime: this.serverless.service.provider.runtime || 'node8.10', + }, + f + ) + ) + .filter(f => f.runtime && f.runtime.startsWith('go')) + .map(f => f.handler); return this.resolveFilePathsAll().then(filePaths => this.zipFiles(filePaths, zipFileName, undefined, filesToChmodPlusX).then(filePath => { @@ -130,8 +142,10 @@ module.exports = { // use the artifact in service config if provided // and if the function is not set to be packaged individually if (this.serverless.service.package.artifact && !funcPackageConfig.individually) { - const filePath = path.join(this.serverless.config.servicePath, - this.serverless.service.package.artifact); + const filePath = path.join( + this.serverless.config.servicePath, + this.serverless.service.package.artifact + ); funcPackageConfig.artifact = filePath; return BbPromise.resolve(filePath); } @@ -139,8 +153,8 @@ module.exports = { const zipFileName = `${functionName}.zip`; const filesToChmodPlusX = []; if (process.platform === 'win32') { - const runtime = functionName.runtime || this.serverless.service.provider.runtime - || 'node8.10'; + const runtime = + functionName.runtime || this.serverless.service.provider.runtime || 'node8.10'; if (runtime.startsWith('go')) { filesToChmodPlusX.push(functionObject.handler); } @@ -212,7 +226,7 @@ module.exports = { resolveFilePathsFromPatterns(params, prefix) { const patterns = []; - params.exclude.forEach((pattern) => { + params.exclude.forEach(pattern => { if (pattern.charAt(0) !== '!') { patterns.push(`!${pattern}`); } else { @@ -222,7 +236,7 @@ module.exports = { // push the include globs to the end of the array // (files and folders will be re-added again even if they were excluded beforehand) - params.include.forEach((pattern) => { + params.include.forEach(pattern => { patterns.push(pattern); }); @@ -243,12 +257,13 @@ module.exports = { .forEach(p => { const exclude = p.startsWith('!'); const pattern = exclude ? p.slice(1) : p; - nanomatch(allFilePaths, [pattern], { dot: true }) - .forEach(key => { - filePathStates[key] = !exclude; - }); + nanomatch(allFilePaths, [pattern], { dot: true }).forEach(key => { + filePathStates[key] = !exclude; + }); }); - const filePaths = _.toPairs(filePathStates).filter(r => r[1] === true).map(r => r[0]); + const filePaths = _.toPairs(filePathStates) + .filter(r => r[1] === true) + .map(r => r[0]); if (filePaths.length !== 0) return filePaths; throw new this.serverless.classes.Error('No file matches include / exclude patterns'); }); diff --git a/lib/plugins/package/lib/packageService.test.js b/lib/plugins/package/lib/packageService.test.js index f03b5e8c3..098367a7e 100644 --- a/lib/plugins/package/lib/packageService.test.js +++ b/lib/plugins/package/lib/packageService.test.js @@ -39,33 +39,22 @@ describe('#packageService()', () => { }); it('should merge package includes', () => { - const packageIncludes = [ - 'dir', 'file.js', - ]; + const packageIncludes = ['dir', 'file.js']; serverless.service.package.include = packageIncludes; const include = packagePlugin.getIncludes(); - expect(include).to.deep.equal([ - 'dir', 'file.js', - ]); + expect(include).to.deep.equal(['dir', 'file.js']); }); it('should merge package and func includes', () => { - const funcIncludes = [ - 'lib', 'other.js', - ]; - const packageIncludes = [ - 'dir', 'file.js', - ]; + const funcIncludes = ['lib', 'other.js']; + const packageIncludes = ['dir', 'file.js']; serverless.service.package.include = packageIncludes; const include = packagePlugin.getIncludes(funcIncludes); - expect(include).to.deep.equal([ - 'dir', 'file.js', - 'lib', 'other.js', - ]); + expect(include).to.deep.equal(['dir', 'file.js', 'lib', 'other.js']); }); }); @@ -84,96 +73,95 @@ describe('#packageService()', () => { }); it('should exclude defaults and serverless config file being used', () => - expect(packagePlugin.getExcludes()).to.be.fulfilled - .then(exclude => BbPromise.join( + expect(packagePlugin.getExcludes()).to.be.fulfilled.then(exclude => + BbPromise.join( expect(getServerlessConfigFilePathStub).to.be.calledOnce, expect(exclude).to.deep.equal( _.union(packagePlugin.defaultExcludes, [serverlessConfigFileName]) ) - )) - ); + ) + )); it('should exclude plugins localPath defaults', () => { const localPath = './myplugins'; serverless.service.plugins = { localPath }; - return expect(packagePlugin.getExcludes()).to.be.fulfilled - .then(exclude => - expect(exclude).to.deep.equal( - _.union(packagePlugin.defaultExcludes, [serverlessConfigFileName], [localPath]) - ) - ); + return expect(packagePlugin.getExcludes()).to.be.fulfilled.then(exclude => + expect(exclude).to.deep.equal( + _.union(packagePlugin.defaultExcludes, [serverlessConfigFileName], [localPath]) + ) + ); }); it('should not exclude plugins localPath if it is empty', () => { const localPath = ''; serverless.service.plugins = { localPath }; - return expect(packagePlugin.getExcludes()).to.be.fulfilled - .then(exclude => - expect(exclude).to.deep.equal( - _.union(packagePlugin.defaultExcludes, [serverlessConfigFileName]) - ) - ); + return expect(packagePlugin.getExcludes()).to.be.fulfilled.then(exclude => + expect(exclude).to.deep.equal( + _.union(packagePlugin.defaultExcludes, [serverlessConfigFileName]) + ) + ); }); it('should not exclude plugins localPath if it is not a string', () => { const localPath = {}; serverless.service.plugins = { localPath }; - return expect(packagePlugin.getExcludes()).to.be.fulfilled - .then(exclude => - expect(exclude).to.deep.equal( - _.union(packagePlugin.defaultExcludes, [serverlessConfigFileName]) - ) - ); + return expect(packagePlugin.getExcludes()).to.be.fulfilled.then(exclude => + expect(exclude).to.deep.equal( + _.union(packagePlugin.defaultExcludes, [serverlessConfigFileName]) + ) + ); }); it('should not exclude serverlessConfigFilePath if is not found', () => { getServerlessConfigFilePathStub.returns(BbPromise.resolve(null)); - return expect(packagePlugin.getExcludes()).to.be.fulfilled - .then(exclude => - expect(exclude).to.deep.equal(packagePlugin.defaultExcludes) - ); + return expect(packagePlugin.getExcludes()).to.be.fulfilled.then(exclude => + expect(exclude).to.deep.equal(packagePlugin.defaultExcludes) + ); }); it('should merge defaults with plugin localPath and excludes', () => { const localPath = './myplugins'; serverless.service.plugins = { localPath }; - const packageExcludes = [ - 'dir', 'file.js', - ]; + const packageExcludes = ['dir', 'file.js']; serverless.service.package.exclude = packageExcludes; - return expect(packagePlugin.getExcludes()).to.be.fulfilled - .then(exclude => - expect(exclude).to.deep.equal(_.union(packagePlugin.defaultExcludes, - [serverlessConfigFileName], [localPath], packageExcludes) + return expect(packagePlugin.getExcludes()).to.be.fulfilled.then(exclude => + expect(exclude).to.deep.equal( + _.union( + packagePlugin.defaultExcludes, + [serverlessConfigFileName], + [localPath], + packageExcludes ) - ); + ) + ); }); it('should merge defaults with plugin localPath package and func excludes', () => { const localPath = './myplugins'; serverless.service.plugins = { localPath }; - const packageExcludes = [ - 'dir', 'file.js', - ]; + const packageExcludes = ['dir', 'file.js']; serverless.service.package.exclude = packageExcludes; - const funcExcludes = [ - 'lib', 'other.js', - ]; + const funcExcludes = ['lib', 'other.js']; - return expect(packagePlugin.getExcludes(funcExcludes)).to.be.fulfilled - .then(exclude => - expect(exclude).to.deep.equal(_.union(packagePlugin.defaultExcludes, - [serverlessConfigFileName], [localPath], packageExcludes, funcExcludes) + return expect(packagePlugin.getExcludes(funcExcludes)).to.be.fulfilled.then(exclude => + expect(exclude).to.deep.equal( + _.union( + packagePlugin.defaultExcludes, + [serverlessConfigFileName], + [localPath], + packageExcludes, + funcExcludes ) - ); + ) + ); }); }); @@ -181,11 +169,11 @@ describe('#packageService()', () => { it('should package all functions', () => { serverless.service.package.individually = false; - const packageAllStub = sinon - .stub(packagePlugin, 'packageAll').resolves(); + const packageAllStub = sinon.stub(packagePlugin, 'packageAll').resolves(); - return expect(packagePlugin.packageService()).to.be.fulfilled - .then(() => expect(packageAllStub).to.be.calledOnce); + return expect(packagePlugin.packageService()).to.be.fulfilled.then( + () => expect(packageAllStub).to.be.calledOnce + ); }); it('should package functions individually', () => { @@ -200,10 +188,12 @@ describe('#packageService()', () => { }; const packageFunctionStub = sinon - .stub(packagePlugin, 'packageFunction').resolves((func) => func.name); + .stub(packagePlugin, 'packageFunction') + .resolves(func => func.name); - return expect(packagePlugin.packageService()).to.be.fulfilled - .then(() => expect(packageFunctionStub).to.be.calledTwice); + return expect(packagePlugin.packageService()).to.be.fulfilled.then( + () => expect(packageFunctionStub).to.be.calledTwice + ); }); it('should package single function individually', () => { @@ -220,15 +210,16 @@ describe('#packageService()', () => { }; const packageFunctionStub = sinon - .stub(packagePlugin, 'packageFunction').resolves((func) => func.name); - const packageAllStub = sinon - .stub(packagePlugin, 'packageAll').resolves((func) => func.name); + .stub(packagePlugin, 'packageFunction') + .resolves(func => func.name); + const packageAllStub = sinon.stub(packagePlugin, 'packageAll').resolves(func => func.name); - return expect(packagePlugin.packageService()).to.be.fulfilled - .then(() => BbPromise.join( - expect(packageFunctionStub).to.be.calledOnce, - expect(packageAllStub).to.be.calledOnce - )); + return expect(packagePlugin.packageService()).to.be.fulfilled.then(() => + BbPromise.join( + expect(packageFunctionStub).to.be.calledOnce, + expect(packageAllStub).to.be.calledOnce + ) + ); }); it('should not package functions if package artifact specified', () => { @@ -236,8 +227,9 @@ describe('#packageService()', () => { const packageAllStub = sinon.stub(packagePlugin, 'packageAll').resolves(); - return expect(packagePlugin.packageService()).to.be.fulfilled - .then(() => expect(packageAllStub).to.not.be.called); + return expect(packagePlugin.packageService()).to.be.fulfilled.then( + () => expect(packageAllStub).to.not.be.called + ); }); it('should package functions individually if package artifact specified', () => { @@ -253,15 +245,16 @@ describe('#packageService()', () => { }; const packageFunctionStub = sinon - .stub(packagePlugin, 'packageFunction').resolves((func) => func.name); - const packageAllStub = sinon - .stub(packagePlugin, 'packageAll').resolves((func) => func.name); + .stub(packagePlugin, 'packageFunction') + .resolves(func => func.name); + const packageAllStub = sinon.stub(packagePlugin, 'packageAll').resolves(func => func.name); - return expect(packagePlugin.packageService()).to.be.fulfilled - .then(() => BbPromise.join( - expect(packageFunctionStub).to.be.calledTwice, - expect(packageAllStub).to.not.be.called - )); + return expect(packagePlugin.packageService()).to.be.fulfilled.then(() => + BbPromise.join( + expect(packageFunctionStub).to.be.calledTwice, + expect(packageAllStub).to.not.be.called + ) + ); }); it('should package single functions individually if package artifact specified', () => { @@ -279,15 +272,16 @@ describe('#packageService()', () => { }; const packageFunctionStub = sinon - .stub(packagePlugin, 'packageFunction').resolves((func) => func.name); - const packageAllStub = sinon - .stub(packagePlugin, 'packageAll').resolves((func) => func.name); + .stub(packagePlugin, 'packageFunction') + .resolves(func => func.name); + const packageAllStub = sinon.stub(packagePlugin, 'packageAll').resolves(func => func.name); - return expect(packagePlugin.packageService()).to.be.fulfilled - .then(() => BbPromise.join( + return expect(packagePlugin.packageService()).to.be.fulfilled.then(() => + BbPromise.join( expect(packageFunctionStub).to.be.calledOnce, expect(packageAllStub).to.not.be.called - )); + ) + ); }); }); @@ -303,13 +297,13 @@ describe('#packageService()', () => { beforeEach(() => { getExcludesStub = sinon - .stub(packagePlugin, 'getExcludes').returns(BbPromise.resolve(exclude)); - getIncludesStub = sinon - .stub(packagePlugin, 'getIncludes').returns(include); + .stub(packagePlugin, 'getExcludes') + .returns(BbPromise.resolve(exclude)); + getIncludesStub = sinon.stub(packagePlugin, 'getIncludes').returns(include); resolveFilePathsFromPatternsStub = sinon - .stub(packagePlugin, 'resolveFilePathsFromPatterns').returns(files); - zipFilesStub = sinon - .stub(packagePlugin, 'zipFiles').resolves(artifactFilePath); + .stub(packagePlugin, 'resolveFilePathsFromPatterns') + .returns(files); + zipFilesStub = sinon.stub(packagePlugin, 'zipFiles').resolves(artifactFilePath); }); afterEach(() => { @@ -325,65 +319,61 @@ describe('#packageService()', () => { serverless.config.servicePath = servicePath; - return expect(packagePlugin.packageService()).to.be.fulfilled - .then(() => BbPromise.all([ - expect(getExcludesStub).to.be.calledOnce, - expect(getIncludesStub).to.be.calledOnce, - expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, - expect(zipFilesStub).to.be.calledOnce, - expect(zipFilesStub).to.have.been.calledWithExactly( - files, - zipFileName, - undefined, - [] - ), - ])); + return expect(packagePlugin.packageService()).to.be.fulfilled.then(() => + BbPromise.all([ + expect(getExcludesStub).to.be.calledOnce, + expect(getIncludesStub).to.be.calledOnce, + expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, + expect(zipFilesStub).to.be.calledOnce, + expect(zipFilesStub).to.have.been.calledWithExactly(files, zipFileName, undefined, []), + ]) + ); }); (process.platfrom === 'win32' ? it : it.skip)( - 'should call zipService with settings & binaries to chmod for GoLang on win32', () => { + 'should call zipService with settings & binaries to chmod for GoLang on win32', + () => { const servicePath = 'test'; const zipFileName = `${serverless.service.service}.zip`; serverless.config.servicePath = servicePath; serverless.service.provider.runtime = 'go1.x'; - return expect(packagePlugin.packageService()).to.be.fulfilled - .then(() => BbPromise.all([ - expect(getExcludesStub).to.be.calledOnce, - expect(getIncludesStub).to.be.calledOnce, - expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, - expect(zipFilesStub).to.be.calledOnce, - expect(zipFilesStub).to.have.been.calledWithExactly( - files, - zipFileName, - undefined, - ['foo'] - ), - ])); - }); + return expect(packagePlugin.packageService()).to.be.fulfilled.then(() => + BbPromise.all([ + expect(getExcludesStub).to.be.calledOnce, + expect(getIncludesStub).to.be.calledOnce, + expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, + expect(zipFilesStub).to.be.calledOnce, + expect(zipFilesStub).to.have.been.calledWithExactly(files, zipFileName, undefined, [ + 'foo', + ]), + ]) + ); + } + ); (process.platfrom === 'win32' ? it : it.skip)( - 'should call zipService with settings & no binaries to chmod for non-go on win32', () => { + 'should call zipService with settings & no binaries to chmod for non-go on win32', + () => { const servicePath = 'test'; const zipFileName = `${serverless.service.service}.zip`; serverless.config.servicePath = servicePath; - return expect(packagePlugin.packageService()).to.be.fulfilled - .then(() => BbPromise.all([ - expect(getExcludesStub).to.be.calledOnce, - expect(getIncludesStub).to.be.calledOnce, - expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, - expect(zipFilesStub).to.be.calledOnce, - expect(zipFilesStub).to.have.been.calledWithExactly( - files, - zipFileName, - undefined, - ['foo'] - ), - ])); - }); + return expect(packagePlugin.packageService()).to.be.fulfilled.then(() => + BbPromise.all([ + expect(getExcludesStub).to.be.calledOnce, + expect(getIncludesStub).to.be.calledOnce, + expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, + expect(zipFilesStub).to.be.calledOnce, + expect(zipFilesStub).to.have.been.calledWithExactly(files, zipFileName, undefined, [ + 'foo', + ]), + ]) + ); + } + ); }); describe('#packageFunction()', () => { @@ -398,13 +388,13 @@ describe('#packageService()', () => { beforeEach(() => { getExcludesStub = sinon - .stub(packagePlugin, 'getExcludes').returns(BbPromise.resolve(exclude)); - getIncludesStub = sinon - .stub(packagePlugin, 'getIncludes').returns(include); + .stub(packagePlugin, 'getExcludes') + .returns(BbPromise.resolve(exclude)); + getIncludesStub = sinon.stub(packagePlugin, 'getIncludes').returns(include); resolveFilePathsFromPatternsStub = sinon - .stub(packagePlugin, 'resolveFilePathsFromPatterns').returns(files); - zipFilesStub = sinon - .stub(packagePlugin, 'zipFiles').resolves(artifactFilePath); + .stub(packagePlugin, 'resolveFilePathsFromPatterns') + .returns(files); + zipFilesStub = sinon.stub(packagePlugin, 'zipFiles').resolves(artifactFilePath); }); afterEach(() => { @@ -424,20 +414,18 @@ describe('#packageService()', () => { serverless.service.functions = {}; serverless.service.functions[funcName] = { name: `test-proj-${funcName}` }; - return expect(packagePlugin.packageFunction(funcName)).to.eventually.equal(artifactFilePath) - .then(() => BbPromise.all([ - expect(getExcludesStub).to.be.calledOnce, - expect(getIncludesStub).to.be.calledOnce, - expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, + return expect(packagePlugin.packageFunction(funcName)) + .to.eventually.equal(artifactFilePath) + .then(() => + BbPromise.all([ + expect(getExcludesStub).to.be.calledOnce, + expect(getIncludesStub).to.be.calledOnce, + expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, - expect(zipFilesStub).to.be.calledOnce, - expect(zipFilesStub).to.have.been.calledWithExactly( - files, - zipFileName, - undefined, - [] - ), - ])); + expect(zipFilesStub).to.be.calledOnce, + expect(zipFilesStub).to.have.been.calledWithExactly(files, zipFileName, undefined, []), + ]) + ); }); it('should return function artifact file path', () => { @@ -453,13 +441,15 @@ describe('#packageService()', () => { }, }; - return expect(packagePlugin.packageFunction(funcName)).to.eventually - .equal(path.join('test/artifact.zip')) - .then(() => BbPromise.all([ - expect(getExcludesStub).to.not.have.been.called, - expect(getIncludesStub).to.not.have.been.called, - expect(zipFilesStub).to.not.have.been.called, - ])); + return expect(packagePlugin.packageFunction(funcName)) + .to.eventually.equal(path.join('test/artifact.zip')) + .then(() => + BbPromise.all([ + expect(getExcludesStub).to.not.have.been.called, + expect(getIncludesStub).to.not.have.been.called, + expect(zipFilesStub).to.not.have.been.called, + ]) + ); }); it('should return service artifact file path', () => { @@ -475,13 +465,15 @@ describe('#packageService()', () => { name: `test-proj-${funcName}`, }; - return expect(packagePlugin.packageFunction(funcName)).to.eventually - .equal(path.join('test/artifact.zip')) - .then(() => BbPromise.all([ - expect(getExcludesStub).to.not.have.been.called, - expect(getIncludesStub).to.not.have.been.called, - expect(zipFilesStub).to.not.have.been.called, - ])); + return expect(packagePlugin.packageFunction(funcName)) + .to.eventually.equal(path.join('test/artifact.zip')) + .then(() => + BbPromise.all([ + expect(getExcludesStub).to.not.have.been.called, + expect(getIncludesStub).to.not.have.been.called, + expect(zipFilesStub).to.not.have.been.called, + ]) + ); }); it('should call zipService with settings if packaging individually without artifact', () => { @@ -500,20 +492,18 @@ describe('#packageService()', () => { package: { individually: true }, }; - return expect(packagePlugin.packageFunction(funcName)).to.eventually.equal(artifactFilePath) - .then(() => BbPromise.all([ - expect(getExcludesStub).to.be.calledOnce, - expect(getIncludesStub).to.be.calledOnce, - expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, + return expect(packagePlugin.packageFunction(funcName)) + .to.eventually.equal(artifactFilePath) + .then(() => + BbPromise.all([ + expect(getExcludesStub).to.be.calledOnce, + expect(getIncludesStub).to.be.calledOnce, + expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, - expect(zipFilesStub).to.be.calledOnce, - expect(zipFilesStub).to.have.been.calledWithExactly( - files, - zipFileName, - undefined, - [] - ), - ])); + expect(zipFilesStub).to.be.calledOnce, + expect(zipFilesStub).to.have.been.calledWithExactly(files, zipFileName, undefined, []), + ]) + ); }); }); @@ -529,13 +519,13 @@ describe('#packageService()', () => { beforeEach(() => { getExcludesStub = sinon - .stub(packagePlugin, 'getExcludes').returns(BbPromise.resolve(exclude)); - getIncludesStub = sinon - .stub(packagePlugin, 'getIncludes').returns(include); + .stub(packagePlugin, 'getExcludes') + .returns(BbPromise.resolve(exclude)); + getIncludesStub = sinon.stub(packagePlugin, 'getIncludes').returns(include); resolveFilePathsFromPatternsStub = sinon - .stub(packagePlugin, 'resolveFilePathsFromPatterns').returns(files); - zipFilesStub = sinon - .stub(packagePlugin, 'zipFiles').resolves(artifactFilePath); + .stub(packagePlugin, 'resolveFilePathsFromPatterns') + .returns(files); + zipFilesStub = sinon.stub(packagePlugin, 'zipFiles').resolves(artifactFilePath); }); afterEach(() => { @@ -555,19 +545,22 @@ describe('#packageService()', () => { serverless.service.layers = {}; serverless.service.layers[layerName] = { path: './foobar' }; - return expect(packagePlugin.packageLayer(layerName)).to.eventually.equal(artifactFilePath) - .then(() => BbPromise.all([ - expect(getExcludesStub).to.be.calledOnce, - expect(getIncludesStub).to.be.calledOnce, - expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, + return expect(packagePlugin.packageLayer(layerName)) + .to.eventually.equal(artifactFilePath) + .then(() => + BbPromise.all([ + expect(getExcludesStub).to.be.calledOnce, + expect(getIncludesStub).to.be.calledOnce, + expect(resolveFilePathsFromPatternsStub).to.be.calledOnce, - expect(zipFilesStub).to.be.calledOnce, - expect(zipFilesStub).to.have.been.calledWithExactly( - files, - zipFileName, - path.resolve('./foobar') - ), - ])); + expect(zipFilesStub).to.be.calledOnce, + expect(zipFilesStub).to.have.been.calledWithExactly( + files, + zipFileName, + path.resolve('./foobar') + ), + ]) + ); }); }); }); diff --git a/lib/plugins/package/lib/zipService.js b/lib/plugins/package/lib/zipService.js index 2d53538bc..739d40b9b 100644 --- a/lib/plugins/package/lib/zipService.js +++ b/lib/plugins/package/lib/zipService.js @@ -37,7 +37,7 @@ module.exports = { return BbPromise.bind(this) .then(() => excludeNodeDevDependencies(servicePath)) - .then((exAndInNode) => { + .then(exAndInNode => { params.exclude = _.union(params.exclude, exAndInNode.exclude); //eslint-disable-line params.include = _.union(params.include, exAndInNode.include); //eslint-disable-line return params; @@ -50,7 +50,8 @@ module.exports = { zip(params) { return this.resolveFilePathsFromPatterns(params).then(filePaths => - this.zipFiles(filePaths, params.zipFileName)); + this.zipFiles(filePaths, params.zipFileName) + ); }, /** @@ -69,7 +70,8 @@ module.exports = { const zip = archiver.create('zip'); // Create artifact in temp path and move it to the package path (if any) later - const artifactFilePath = path.join(this.serverless.config.servicePath, + const artifactFilePath = path.join( + this.serverless.config.servicePath, '.serverless', zipFileName ); @@ -79,8 +81,8 @@ module.exports = { return new BbPromise((resolve, reject) => { output.on('close', () => resolve(artifactFilePath)); - output.on('error', (err) => reject(err)); - zip.on('error', (err) => reject(err)); + output.on('error', err => reject(err)); + zip.on('error', err => reject(err)); output.on('open', () => { zip.pipe(output); @@ -88,12 +90,15 @@ module.exports = { const normalizedFiles = _.uniq(files.map(file => path.normalize(file))); return BbPromise.all(normalizedFiles.map(this.getFileContentAndStat.bind(this))) - .then((contents) => { - _.forEach(_.sortBy(contents, ['filePath']), (file) => { + .then(contents => { + _.forEach(_.sortBy(contents, ['filePath']), file => { const name = file.filePath.slice(prefix ? `${prefix}${path.sep}`.length : 0); let mode = file.stat.mode; - if (filesToChmodPlusX && _.includes(filesToChmodPlusX, name) - && file.stat.mode % 2 === 0) { + if ( + filesToChmodPlusX && + _.includes(filesToChmodPlusX, name) && + file.stat.mode % 2 === 0 + ) { mode += 1; } zip.append(file.data, { @@ -104,21 +109,20 @@ module.exports = { }); zip.finalize(); - }).catch(reject); + }) + .catch(reject); }); }); }, getFileContentAndStat(filePath) { - const fullPath = path.resolve( - this.serverless.config.servicePath, - filePath - ); + const fullPath = path.resolve(this.serverless.config.servicePath, filePath); - return BbPromise.all([ // Get file contents and stat in parallel + return BbPromise.all([ + // Get file contents and stat in parallel this.getFileContent(fullPath), fs.statAsync(fullPath), - ]).then((result) => ({ + ]).then(result => ({ data: result[0], stat: result[1], filePath, @@ -145,19 +149,22 @@ function excludeNodeDevDependencies(servicePath) { const nodeProdDepFile = path.join(tmpDir, `node-dependencies-${randHash}-prod`); try { - const packageJsonFilePaths = globby.sync([ - '**/package.json', - // TODO add glob for node_modules filtering - ], { - cwd: servicePath, - dot: true, - silent: true, - follow: true, - nosort: true, - }); + const packageJsonFilePaths = globby.sync( + [ + '**/package.json', + // TODO add glob for node_modules filtering + ], + { + cwd: servicePath, + dot: true, + silent: true, + follow: true, + nosort: true, + } + ); // filter out non node_modules file paths - const packageJsonPaths = _.filter(packageJsonFilePaths, (filePath) => { + const packageJsonPaths = _.filter(packageJsonFilePaths, filePath => { const isNodeModulesDir = !!filePath.match(/node_modules/); return !isNodeModulesDir; }); @@ -167,82 +174,93 @@ function excludeNodeDevDependencies(servicePath) { } // NOTE: using mapSeries here for a sequential computation (w/o race conditions) - return BbPromise.mapSeries(packageJsonPaths, (packageJsonPath) => { - // the path where the package.json file lives - const fullPath = path.join(servicePath, packageJsonPath); - const dirWithPackageJson = fullPath.replace(path.join(path.sep, 'package.json'), ''); + return ( + BbPromise.mapSeries(packageJsonPaths, packageJsonPath => { + // the path where the package.json file lives + const fullPath = path.join(servicePath, packageJsonPath); + const dirWithPackageJson = fullPath.replace(path.join(path.sep, 'package.json'), ''); - // we added a catch which resolves so that npm commands with an exit code of 1 - // (e.g. if the package.json is invalid) won't crash the dev dependency exclusion process - return BbPromise.map(['dev', 'prod'], (env) => { - const depFile = env === 'dev' ? nodeDevDepFile : nodeProdDepFile; - return childProcess.execAsync( - `npm ls --${env}=true --parseable=true --long=false --silent >> ${depFile}`, - { cwd: dirWithPackageJson } - ).catch(() => BbPromise.resolve()); - }); - }) - // NOTE: using mapSeries here for a sequential computation (w/o race conditions) - .then(() => BbPromise.mapSeries(['dev', 'prod'], (env) => { - const depFile = env === 'dev' ? nodeDevDepFile : nodeProdDepFile; - return fs.readFileAsync(depFile) - .then((fileContent) => _.compact( - (_.uniq(_.split(fileContent.toString('utf8'), '\n'))), - elem => elem.length > 0 - )).catch(() => BbPromise.resolve()); - })) - .then((devAndProDependencies) => { - const devDependencies = devAndProDependencies[0]; - const prodDependencies = devAndProDependencies[1]; + // we added a catch which resolves so that npm commands with an exit code of 1 + // (e.g. if the package.json is invalid) won't crash the dev dependency exclusion process + return BbPromise.map(['dev', 'prod'], env => { + const depFile = env === 'dev' ? nodeDevDepFile : nodeProdDepFile; + return childProcess + .execAsync( + `npm ls --${env}=true --parseable=true --long=false --silent >> ${depFile}`, + { cwd: dirWithPackageJson } + ) + .catch(() => BbPromise.resolve()); + }); + }) + // NOTE: using mapSeries here for a sequential computation (w/o race conditions) + .then(() => + BbPromise.mapSeries(['dev', 'prod'], env => { + const depFile = env === 'dev' ? nodeDevDepFile : nodeProdDepFile; + return fs + .readFileAsync(depFile) + .then(fileContent => + _.compact( + _.uniq(_.split(fileContent.toString('utf8'), '\n')), + elem => elem.length > 0 + ) + ) + .catch(() => BbPromise.resolve()); + }) + ) + .then(devAndProDependencies => { + const devDependencies = devAndProDependencies[0]; + const prodDependencies = devAndProDependencies[1]; - // NOTE: the order for _.difference is important - const dependencies = _.difference(devDependencies, prodDependencies); - const nodeModulesRegex = new RegExp(`${path.join('node_modules', path.sep)}.*`, 'g'); + // NOTE: the order for _.difference is important + const dependencies = _.difference(devDependencies, prodDependencies); + const nodeModulesRegex = new RegExp(`${path.join('node_modules', path.sep)}.*`, 'g'); - if (!_.isEmpty(dependencies)) { - return BbPromise - .map(dependencies, (item) => item.replace(path.join(servicePath, path.sep), '')) - .filter((item) => item.length > 0 && item.match(nodeModulesRegex)) - .reduce((globs, item) => { - const packagePath = path.join(servicePath, item, 'package.json'); - return fs.readFileAsync(packagePath, 'utf-8').then((packageJsonFile) => { - const lastIndex = item.lastIndexOf(path.sep) + 1; - const moduleName = item.substr(lastIndex); - const modulePath = item.substr(0, lastIndex); + if (!_.isEmpty(dependencies)) { + return BbPromise.map(dependencies, item => + item.replace(path.join(servicePath, path.sep), '') + ) + .filter(item => item.length > 0 && item.match(nodeModulesRegex)) + .reduce((globs, item) => { + const packagePath = path.join(servicePath, item, 'package.json'); + return fs.readFileAsync(packagePath, 'utf-8').then(packageJsonFile => { + const lastIndex = item.lastIndexOf(path.sep) + 1; + const moduleName = item.substr(lastIndex); + const modulePath = item.substr(0, lastIndex); - const packageJson = JSON.parse(packageJsonFile); - const bin = packageJson.bin; + const packageJson = JSON.parse(packageJsonFile); + const bin = packageJson.bin; - const baseGlobs = [path.join(item, '**')]; + const baseGlobs = [path.join(item, '**')]; - // NOTE: pkg.bin can be object, string, or undefined - if (typeof bin === 'object') { - _.each(_.keys(bin), (executable) => { - baseGlobs.push(path.join(modulePath, '.bin', executable)); + // NOTE: pkg.bin can be object, string, or undefined + if (typeof bin === 'object') { + _.each(_.keys(bin), executable => { + baseGlobs.push(path.join(modulePath, '.bin', executable)); + }); + // only 1 executable with same name as lib + } else if (typeof bin === 'string') { + baseGlobs.push(path.join(modulePath, '.bin', moduleName)); + } + + return globs.concat(baseGlobs); }); - // only 1 executable with same name as lib - } else if (typeof bin === 'string') { - baseGlobs.push(path.join(modulePath, '.bin', moduleName)); - } + }, []) + .then(globs => { + exAndIn.exclude = exAndIn.exclude.concat(globs); + return exAndIn; + }); + } - return globs.concat(baseGlobs); - }); - }, []) - .then((globs) => { - exAndIn.exclude = exAndIn.exclude.concat(globs); - return exAndIn; - }); - } - - return exAndIn; - }) - .then(() => { - // cleanup - fs.unlinkSync(nodeDevDepFile); - fs.unlinkSync(nodeProdDepFile); - return exAndIn; - }) - .catch(() => exAndIn); + return exAndIn; + }) + .then(() => { + // cleanup + fs.unlinkSync(nodeDevDepFile); + fs.unlinkSync(nodeProdDepFile); + return exAndIn; + }) + .catch(() => exAndIn) + ); } catch (e) { // fail silently } diff --git a/lib/plugins/package/lib/zipService.test.js b/lib/plugins/package/lib/zipService.test.js index 82823f69d..b7050dd64 100644 --- a/lib/plugins/package/lib/zipService.test.js +++ b/lib/plugins/package/lib/zipService.test.js @@ -60,11 +60,12 @@ describe('zipService', () => { const include = params.include; const zipFileName = params.zipFileName; - return expect(packagePlugin.zipService(exclude, include, zipFileName)).to.be - .fulfilled.then(() => { + return expect(packagePlugin.zipService(exclude, include, zipFileName)).to.be.fulfilled.then( + () => { expect(excludeDevDependenciesStub).to.have.been.calledOnce; expect(zipStub).to.have.been.calledOnce; - }); + } + ); }); }); @@ -82,10 +83,9 @@ describe('zipService', () => { fs.writeFileSync(filePath, buf); - return expect(packagePlugin.getFileContentAndStat(filePath)).to.be.fulfilled - .then((result) => { - expect(result.data).to.deep.equal(buf); - }); + return expect(packagePlugin.getFileContentAndStat(filePath)).to.be.fulfilled.then(result => { + expect(result.data).to.deep.equal(buf); + }); }); }); @@ -103,10 +103,9 @@ describe('zipService', () => { fs.writeFileSync(filePath, buf); - return expect(packagePlugin.getFileContent(filePath)).to.be.fulfilled - .then((result) => { - expect(result).to.deep.equal(buf); - }); + return expect(packagePlugin.getFileContent(filePath)).to.be.fulfilled.then(result => { + expect(result).to.deep.equal(buf); + }); }); }); @@ -114,10 +113,11 @@ describe('zipService', () => { it('should resolve when opted out of dev dependency exclusion', () => { packagePlugin.serverless.service.package.excludeDevDependencies = false; - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(updatedParams).to.deep.equal(params); - }); + } + ); }); describe('when dealing with Node.js runtimes', () => { @@ -144,27 +144,23 @@ describe('zipService', () => { globbySyncStub.returns(filePaths); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.have.been.calledOnce; expect(execAsyncStub).to.not.have.been.called; expect(readFileAsyncStub).to.not.have.been.called; - expect(globbySyncStub).to.have.been - .calledWithExactly(['**/package.json'], { - cwd: packagePlugin.serverless.config.servicePath, - dot: true, - silent: true, - follow: true, - nosort: true, - }); - expect(updatedParams.exclude).to.deep.equal([ - 'user-defined-exclude-me', - ]); - expect(updatedParams.include).to.deep.equal([ - 'user-defined-include-me', - ]); + expect(globbySyncStub).to.have.been.calledWithExactly(['**/package.json'], { + cwd: packagePlugin.serverless.config.servicePath, + dot: true, + silent: true, + follow: true, + nosort: true, + }); + expect(updatedParams.exclude).to.deep.equal(['user-defined-exclude-me']); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); it('should do nothing if no dependencies are found', () => { @@ -175,53 +171,46 @@ describe('zipService', () => { const depPaths = ''; readFileAsyncStub.resolves(depPaths); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.have.been.calledOnce; expect(execAsyncStub).to.have.been.calledTwice; expect(readFileAsyncStub).to.have.been.calledTwice; - expect(globbySyncStub).to.have.been - .calledWithExactly(['**/package.json'], { - cwd: packagePlugin.serverless.config.servicePath, - dot: true, - silent: true, - follow: true, - nosort: true, - }); - expect(execAsyncStub.args[0][0]).to - .match(/npm ls --dev=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[0][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[1][0]).to - .match(/npm ls --prod=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[1][1].cwd).to - .match(/.+/); - expect(updatedParams.exclude).to.deep.equal([ - 'user-defined-exclude-me', - ]); - expect(updatedParams.include).to.deep.equal([ - 'user-defined-include-me', - ]); + expect(globbySyncStub).to.have.been.calledWithExactly(['**/package.json'], { + cwd: packagePlugin.serverless.config.servicePath, + dot: true, + silent: true, + follow: true, + nosort: true, + }); + expect(execAsyncStub.args[0][0]).to.match( + /npm ls --dev=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[0][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[1][0]).to.match( + /npm ls --prod=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[1][1].cwd).to.match(/.+/); + expect(updatedParams.exclude).to.deep.equal(['user-defined-exclude-me']); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); it('should return excludes and includes if an error is thrown in the global scope', () => { globbySyncStub.throws(); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.have.been.calledOnce; expect(execAsyncStub).to.not.have.been.called; expect(readFileAsyncStub).to.not.have.been.called; - expect(updatedParams.exclude).to.deep.equal([ - 'user-defined-exclude-me', - ]); - expect(updatedParams.include).to.deep.equal([ - 'user-defined-include-me', - ]); + expect(updatedParams.exclude).to.deep.equal(['user-defined-exclude-me']); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); it('should return excludes and includes if a exec Promise is rejected', () => { @@ -232,19 +221,16 @@ describe('zipService', () => { execAsyncStub.onCall(1).rejects(); readFileAsyncStub.resolves(); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.been.calledOnce; expect(execAsyncStub).to.have.been.calledTwice; expect(readFileAsyncStub).to.have.been.calledTwice; - expect(updatedParams.exclude).to.deep.equal([ - 'user-defined-exclude-me', - ]); - expect(updatedParams.include).to.deep.equal([ - 'user-defined-include-me', - ]); + expect(updatedParams.exclude).to.deep.equal(['user-defined-exclude-me']); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); it('should return excludes and includes if a readFile Promise is rejected', () => { @@ -256,25 +242,23 @@ describe('zipService', () => { readFileAsyncStub.onCall(0).resolves(); readFileAsyncStub.onCall(1).rejects(); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.been.calledOnce; expect(execAsyncStub).to.have.been.calledTwice; expect(readFileAsyncStub).to.have.been.calledTwice; - expect(updatedParams.exclude).to.deep.equal([ - 'user-defined-exclude-me', - ]); - expect(updatedParams.include).to.deep.equal([ - 'user-defined-include-me', - ]); + expect(updatedParams.exclude).to.deep.equal(['user-defined-exclude-me']); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); it('should fail silently and continue if "npm ls" call throws an error', () => { const filePaths = [ // root of the service - 'package.json', 'node_modules', + 'package.json', + 'node_modules', // nested-dir // NOTE: reading the dependencies in this directory will fail in this tests path.join('1st', 'package.json'), @@ -304,65 +288,54 @@ describe('zipService', () => { readFileAsyncStub.onCall(4).resolves('{}'); readFileAsyncStub.onCall(5).resolves('{}'); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.have.been.calledOnce; expect(execAsyncStub.callCount).to.equal(6); expect(readFileAsyncStub).to.have.callCount(6); - expect(readFileAsyncStub).to.have.been - .calledWith(path.join(servicePath, 'node_modules', 'module-1', 'package.json')); - expect(readFileAsyncStub).to.have.been - .calledWith(path.join(servicePath, 'node_modules', 'module-2', 'package.json')); - expect(readFileAsyncStub).to.have.been - .calledWith(path.join( - servicePath, - '1st', - '2nd', - 'node_modules', - 'module-1', - 'package.json' - )); - expect(readFileAsyncStub).to.have.been - .calledWith(path.join( - servicePath, - '1st', - '2nd', - 'node_modules', - 'module-1', - 'package.json' - )); - expect(globbySyncStub).to.have.been - .calledWithExactly(['**/package.json'], { - cwd: packagePlugin.serverless.config.servicePath, - dot: true, - silent: true, - follow: true, - nosort: true, - }); - expect(execAsyncStub.args[0][0]).to - .match(/npm ls --dev=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[0][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[1][0]).to - .match(/npm ls --prod=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[1][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[2][0]).to - .match(/npm ls --dev=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[2][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[3][0]).to - .match(/npm ls --prod=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[3][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[4][0]).to - .match(/npm ls --dev=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[4][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[5][0]).to - .match(/npm ls --prod=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[5][1].cwd).to - .match(/.+/); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, 'node_modules', 'module-1', 'package.json') + ); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, 'node_modules', 'module-2', 'package.json') + ); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, '1st', '2nd', 'node_modules', 'module-1', 'package.json') + ); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, '1st', '2nd', 'node_modules', 'module-1', 'package.json') + ); + expect(globbySyncStub).to.have.been.calledWithExactly(['**/package.json'], { + cwd: packagePlugin.serverless.config.servicePath, + dot: true, + silent: true, + follow: true, + nosort: true, + }); + expect(execAsyncStub.args[0][0]).to.match( + /npm ls --dev=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[0][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[1][0]).to.match( + /npm ls --prod=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[1][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[2][0]).to.match( + /npm ls --dev=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[2][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[3][0]).to.match( + /npm ls --prod=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[3][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[4][0]).to.match( + /npm ls --dev=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[4][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[5][0]).to.match( + /npm ls --prod=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[5][1].cwd).to.match(/.+/); expect(updatedParams.exclude).to.deep.equal([ 'user-defined-exclude-me', path.join('node_modules', 'module-1', '**'), @@ -370,12 +343,10 @@ describe('zipService', () => { path.join('1st', '2nd', 'node_modules', 'module-1', '**'), path.join('1st', '2nd', 'node_modules', 'module-2', '**'), ]); - expect(updatedParams.include).to - .deep.equal([ - 'user-defined-include-me', - ]); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); it('should exclude dev dependencies in the services root directory', () => { @@ -392,47 +363,48 @@ describe('zipService', () => { readFileAsyncStub.onCall(2).resolves('{}'); readFileAsyncStub.onCall(3).resolves('{}'); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.have.been.calledOnce; expect(execAsyncStub).to.have.been.calledTwice; expect(readFileAsyncStub).to.have.callCount(4); - expect(readFileAsyncStub).to.have.been - .calledWith(path.join(servicePath, 'node_modules', 'module-1', 'package.json')); - expect(readFileAsyncStub).to.have.been - .calledWith(path.join(servicePath, 'node_modules', 'module-2', 'package.json')); - expect(globbySyncStub).to.have.been - .calledWithExactly(['**/package.json'], { - cwd: packagePlugin.serverless.config.servicePath, - dot: true, - silent: true, - follow: true, - nosort: true, - }); - expect(execAsyncStub.args[0][0]).to - .match(/npm ls --dev=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[0][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[1][0]).to - .match(/npm ls --prod=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[1][1].cwd).to - .match(/.+/); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, 'node_modules', 'module-1', 'package.json') + ); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, 'node_modules', 'module-2', 'package.json') + ); + expect(globbySyncStub).to.have.been.calledWithExactly(['**/package.json'], { + cwd: packagePlugin.serverless.config.servicePath, + dot: true, + silent: true, + follow: true, + nosort: true, + }); + expect(execAsyncStub.args[0][0]).to.match( + /npm ls --dev=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[0][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[1][0]).to.match( + /npm ls --prod=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[1][1].cwd).to.match(/.+/); expect(updatedParams.exclude).to.deep.equal([ 'user-defined-exclude-me', path.join('node_modules', 'module-1', '**'), path.join('node_modules', 'module-2', '**'), ]); - expect(updatedParams.include).to.deep.equal([ - 'user-defined-include-me', - ]); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); it('should exclude dev dependencies in deeply nested services directories', () => { const filePaths = [ // root of the service - 'package.json', 'node_modules', + 'package.json', + 'node_modules', // nested-dir path.join('1st', 'package.json'), path.join('1st', 'node_modules'), @@ -460,43 +432,42 @@ describe('zipService', () => { readFileAsyncStub.onCall(6).resolves('{}'); readFileAsyncStub.onCall(7).resolves('{}'); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.have.been.calledOnce; expect(execAsyncStub.callCount).to.equal(6); expect(readFileAsyncStub).to.have.callCount(8); - expect(globbySyncStub).to.have.been - .calledWithExactly(['**/package.json'], { - cwd: packagePlugin.serverless.config.servicePath, - dot: true, - silent: true, - follow: true, - nosort: true, - }); - expect(execAsyncStub.args[0][0]).to - .match(/npm ls --dev=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[0][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[1][0]).to - .match(/npm ls --prod=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[1][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[2][0]).to - .match(/npm ls --dev=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[2][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[3][0]).to - .match(/npm ls --prod=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[3][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[4][0]).to - .match(/npm ls --dev=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[4][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[5][0]).to - .match(/npm ls --prod=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[5][1].cwd).to - .match(/.+/); + expect(globbySyncStub).to.have.been.calledWithExactly(['**/package.json'], { + cwd: packagePlugin.serverless.config.servicePath, + dot: true, + silent: true, + follow: true, + nosort: true, + }); + expect(execAsyncStub.args[0][0]).to.match( + /npm ls --dev=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[0][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[1][0]).to.match( + /npm ls --prod=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[1][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[2][0]).to.match( + /npm ls --dev=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[2][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[3][0]).to.match( + /npm ls --prod=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[3][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[4][0]).to.match( + /npm ls --dev=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[4][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[5][0]).to.match( + /npm ls --prod=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[5][1].cwd).to.match(/.+/); expect(updatedParams.exclude).to.deep.equal([ 'user-defined-exclude-me', path.join('node_modules', 'module-1', '**'), @@ -506,11 +477,10 @@ describe('zipService', () => { path.join('1st', '2nd', 'node_modules', 'module-1', '**'), path.join('1st', '2nd', 'node_modules', 'module-2', '**'), ]); - expect(updatedParams.include).to.deep.equal([ - 'user-defined-include-me', - ]); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); it('should not include packages if in both dependencies and devDependencies', () => { @@ -525,44 +495,41 @@ describe('zipService', () => { ].join('\n'); readFileAsyncStub.withArgs(sinon.match(/dev$/)).resolves(devDepPaths); - const prodDepPaths = [ - path.join(servicePath, 'node_modules', 'module-2'), - ]; + const prodDepPaths = [path.join(servicePath, 'node_modules', 'module-2')]; readFileAsyncStub.withArgs(sinon.match(/prod$/)).resolves(prodDepPaths); readFileAsyncStub.onCall(2).resolves('{}'); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.have.been.calledOnce; expect(execAsyncStub).to.have.been.calledTwice; expect(readFileAsyncStub).to.have.been.calledThrice; - expect(readFileAsyncStub).to.have.been - .calledWith(path.join(servicePath, 'node_modules', 'module-1', 'package.json')); - expect(globbySyncStub).to.have.been - .calledWithExactly(['**/package.json'], { - cwd: packagePlugin.serverless.config.servicePath, - dot: true, - silent: true, - follow: true, - nosort: true, - }); - expect(execAsyncStub.args[0][0]).to - .match(/npm ls --dev=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[0][1].cwd).to - .match(/.+/); - expect(execAsyncStub.args[1][0]).to - .match(/npm ls --prod=true --parseable=true --long=false --silent >> .+/); - expect(execAsyncStub.args[1][1].cwd).to - .match(/.+/); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, 'node_modules', 'module-1', 'package.json') + ); + expect(globbySyncStub).to.have.been.calledWithExactly(['**/package.json'], { + cwd: packagePlugin.serverless.config.servicePath, + dot: true, + silent: true, + follow: true, + nosort: true, + }); + expect(execAsyncStub.args[0][0]).to.match( + /npm ls --dev=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[0][1].cwd).to.match(/.+/); + expect(execAsyncStub.args[1][0]).to.match( + /npm ls --prod=true --parseable=true --long=false --silent >> .+/ + ); + expect(execAsyncStub.args[1][1].cwd).to.match(/.+/); expect(updatedParams.exclude).to.deep.equal([ 'user-defined-exclude-me', path.join('node_modules', 'module-1', '**'), ]); - expect(updatedParams.include).to.deep.equal([ - 'user-defined-include-me', - ]); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); it('should exclude dev dependency executables in node_modules/.bin', () => { @@ -573,19 +540,14 @@ describe('zipService', () => { 'node_modules/meowmix', ]; - const prodPaths = [ - 'node_modules/node-dude', - ]; + const prodPaths = ['node_modules/node-dude']; - const filePaths = [ - 'node_modules/', - 'package.json', - ].concat(devPaths).concat(prodPaths); + const filePaths = ['node_modules/', 'package.json'].concat(devPaths).concat(prodPaths); globbySyncStub.returns(filePaths); execAsyncStub.resolves(); - const mapper = (depPath) => path.join(`${servicePath}`, depPath); + const mapper = depPath => path.join(`${servicePath}`, depPath); const devDepPaths = devPaths.map(mapper).join('\n'); readFileAsyncStub.withArgs(sinon.match(/dev$/)).resolves(devDepPaths); @@ -593,9 +555,7 @@ describe('zipService', () => { const prodDepPaths = prodPaths.map(mapper).join('\n'); readFileAsyncStub.withArgs(sinon.match(/prod$/)).resolves(prodDepPaths); - readFileAsyncStub - .onCall(2) - .resolves('{"name": "bro-module", "bin": "main.js"}'); + readFileAsyncStub.onCall(2).resolves('{"name": "bro-module", "bin": "main.js"}'); readFileAsyncStub .onCall(3) .resolves('{"name": "lumo-clj", "bin": {"lumo": "./bin/lumo.js"}}'); @@ -604,18 +564,21 @@ describe('zipService', () => { // need to handle possibility of multiple executables provided by the lib .resolves('{"name": "meowmix", "bin": {"meow": "./bin/meow.js", "mix": "./bin/mix.js"}}'); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.been.calledOnce; expect(execAsyncStub).to.have.been.calledTwice; expect(readFileAsyncStub).to.have.callCount(5); - expect(readFileAsyncStub).to.have.been - .calledWith(path.join(servicePath, 'node_modules', 'bro-module', 'package.json')); - expect(readFileAsyncStub).to.have.been - .calledWith(path.join(servicePath, 'node_modules', 'lumo-clj', 'package.json')); - expect(readFileAsyncStub).to.have.been - .calledWith(path.join(servicePath, 'node_modules', 'meowmix', 'package.json')); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, 'node_modules', 'bro-module', 'package.json') + ); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, 'node_modules', 'lumo-clj', 'package.json') + ); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, 'node_modules', 'meowmix', 'package.json') + ); expect(updatedParams.exclude).to.deep.equal([ 'user-defined-exclude-me', @@ -627,16 +590,16 @@ describe('zipService', () => { path.join('node_modules', '.bin', 'meow'), path.join('node_modules', '.bin', 'mix'), ]); - expect(updatedParams.include).to.deep.equal([ - 'user-defined-include-me', - ]); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); it('should exclude .bin executables in deeply nested folders', () => { const filePaths = [ - 'package.json', 'node_modules', + 'package.json', + 'node_modules', path.join('1st', 'package.json'), path.join('1st', 'node_modules'), path.join('1st', '2nd', 'package.json'), @@ -653,7 +616,7 @@ describe('zipService', () => { '1st/2nd/node_modules/module-1', '1st/2nd/node_modules/module-2', ]; - const depPaths = deps.map((depPath) => path.join(`${servicePath}`, depPath)); + const depPaths = deps.map(depPath => path.join(`${servicePath}`, depPath)); readFileAsyncStub.withArgs(sinon.match(/dev$/)).resolves(depPaths.join('\n')); readFileAsyncStub.withArgs(sinon.match(/prod$/)).resolves([]); @@ -675,23 +638,23 @@ describe('zipService', () => { readFileAsyncStub.onCall(6).resolves(module1PackageJson); readFileAsyncStub.onCall(7).resolves(module2PackageJson); - return expect(packagePlugin.excludeDevDependencies(params)).to.be - .fulfilled.then((updatedParams) => { + return expect(packagePlugin.excludeDevDependencies(params)).to.be.fulfilled.then( + updatedParams => { expect(globbySyncStub).to.have.been.calledOnce; expect(execAsyncStub.callCount).to.equal(6); expect(readFileAsyncStub).to.have.callCount(8); for (const depPath of deps) { - expect(readFileAsyncStub).to.have.been - .calledWith(path.join(servicePath, depPath, 'package.json')); + expect(readFileAsyncStub).to.have.been.calledWith( + path.join(servicePath, depPath, 'package.json') + ); } - expect(globbySyncStub).to.have.been - .calledWithExactly(['**/package.json'], { - cwd: packagePlugin.serverless.config.servicePath, - dot: true, - silent: true, - follow: true, - nosort: true, - }); + expect(globbySyncStub).to.have.been.calledWithExactly(['**/package.json'], { + cwd: packagePlugin.serverless.config.servicePath, + dot: true, + silent: true, + follow: true, + nosort: true, + }); expect(updatedParams.exclude).to.deep.equal([ 'user-defined-exclude-me', @@ -708,11 +671,10 @@ describe('zipService', () => { path.join('1st', '2nd', 'node_modules', 'module-2', '**'), path.join('1st', '2nd', 'node_modules', '.bin', 'module-2'), ]); - expect(updatedParams.include).to.deep.equal([ - 'user-defined-include-me', - ]); + expect(updatedParams.include).to.deep.equal(['user-defined-include-me']); expect(updatedParams.zipFileName).to.equal(params.zipFileName); - }); + } + ); }); }); }); @@ -729,7 +691,7 @@ describe('zipService', () => { 'file-2': 'some content', }, // bin - bin: { + 'bin': { 'binary-777': { content: 'some content', permissions: 777, @@ -740,7 +702,7 @@ describe('zipService', () => { }, }, // lib - lib: { + 'lib': { 'file-1.js': 'some content', }, 'lib/directory-1': { @@ -758,7 +720,7 @@ describe('zipService', () => { }; function getTestArtifactFileName(testName) { - return `test-${testName}-${(new Date()).getTime().toString()}.zip`; + return `test-${testName}-${new Date().getTime().toString()}.zip`; } beforeEach(() => { @@ -789,120 +751,122 @@ describe('zipService', () => { it('should zip a whole service (without include / exclude usage)', () => { params.zipFileName = getTestArtifactFileName('whole-service'); - return expect(packagePlugin.zip(params)).to.eventually.be - .equal(path.join(serverless.config.servicePath, '.serverless', params.zipFileName)) - .then(artifact => { - const data = fs.readFileSync(artifact); - return expect(zip.loadAsync(data)).to.be.fulfilled; - }) - .then(unzippedData => { - const unzippedFileData = unzippedData.files; + return expect(packagePlugin.zip(params)) + .to.eventually.be.equal( + path.join(serverless.config.servicePath, '.serverless', params.zipFileName) + ) + .then(artifact => { + const data = fs.readFileSync(artifact); + return expect(zip.loadAsync(data)).to.be.fulfilled; + }) + .then(unzippedData => { + const unzippedFileData = unzippedData.files; - expect(Object.keys(unzippedFileData) - .filter(file => !unzippedFileData[file].dir)) - .to.be.lengthOf(12); + expect( + Object.keys(unzippedFileData).filter(file => !unzippedFileData[file].dir) + ).to.be.lengthOf(12); - // root directory - expect(unzippedFileData['event.json'].name) - .to.equal('event.json'); - expect(unzippedFileData['handler.js'].name) - .to.equal('handler.js'); - expect(unzippedFileData['file-1'].name) - .to.equal('file-1'); - expect(unzippedFileData['file-2'].name) - .to.equal('file-2'); + // root directory + expect(unzippedFileData['event.json'].name).to.equal('event.json'); + expect(unzippedFileData['handler.js'].name).to.equal('handler.js'); + expect(unzippedFileData['file-1'].name).to.equal('file-1'); + expect(unzippedFileData['file-2'].name).to.equal('file-2'); - // bin directory - expect(unzippedFileData['bin/binary-777'].name) - .to.equal('bin/binary-777'); - expect(unzippedFileData['bin/binary-444'].name) - .to.equal('bin/binary-444'); + // bin directory + expect(unzippedFileData['bin/binary-777'].name).to.equal('bin/binary-777'); + expect(unzippedFileData['bin/binary-444'].name).to.equal('bin/binary-444'); - // lib directory - expect(unzippedFileData['lib/file-1.js'].name) - .to.equal('lib/file-1.js'); - expect(unzippedFileData['lib/directory-1/file-1.js'].name) - .to.equal('lib/directory-1/file-1.js'); + // lib directory + expect(unzippedFileData['lib/file-1.js'].name).to.equal('lib/file-1.js'); + expect(unzippedFileData['lib/directory-1/file-1.js'].name).to.equal( + 'lib/directory-1/file-1.js' + ); - // node_modules directory - expect(unzippedFileData['node_modules/directory-1/file-1'].name) - .to.equal('node_modules/directory-1/file-1'); - expect(unzippedFileData['node_modules/directory-1/file-2'].name) - .to.equal('node_modules/directory-1/file-2'); - expect(unzippedFileData['node_modules/directory-2/file-1'].name) - .to.equal('node_modules/directory-2/file-1'); - expect(unzippedFileData['node_modules/directory-2/file-2'].name) - .to.equal('node_modules/directory-2/file-2'); - }); + // node_modules directory + expect(unzippedFileData['node_modules/directory-1/file-1'].name).to.equal( + 'node_modules/directory-1/file-1' + ); + expect(unzippedFileData['node_modules/directory-1/file-2'].name).to.equal( + 'node_modules/directory-1/file-2' + ); + expect(unzippedFileData['node_modules/directory-2/file-1'].name).to.equal( + 'node_modules/directory-2/file-1' + ); + expect(unzippedFileData['node_modules/directory-2/file-2'].name).to.equal( + 'node_modules/directory-2/file-2' + ); + }); }); it('should keep file permissions', () => { params.zipFileName = getTestArtifactFileName('file-permissions'); - return expect(packagePlugin.zip(params)).to.eventually.be - .equal(path.join(serverless.config.servicePath, '.serverless', params.zipFileName)) - .then(artifact => { - const data = fs.readFileSync(artifact); - return expect(zip.loadAsync(data)).to.be.fulfilled; - }).then(unzippedData => { - const unzippedFileData = unzippedData.files; + return expect(packagePlugin.zip(params)) + .to.eventually.be.equal( + path.join(serverless.config.servicePath, '.serverless', params.zipFileName) + ) + .then(artifact => { + const data = fs.readFileSync(artifact); + return expect(zip.loadAsync(data)).to.be.fulfilled; + }) + .then(unzippedData => { + const unzippedFileData = unzippedData.files; - if (os.platform() === 'win32') { - // chmod does not work right on windows. this is better than nothing? - expect(unzippedFileData['bin/binary-777'].unixPermissions) - .to.not.equal(unzippedFileData['bin/binary-444'].unixPermissions); - } else { - // binary file is set with chmod of 777 - expect(unzippedFileData['bin/binary-777'].unixPermissions) - .to.equal(Math.pow(2, 15) + 777); + if (os.platform() === 'win32') { + // chmod does not work right on windows. this is better than nothing? + expect(unzippedFileData['bin/binary-777'].unixPermissions).to.not.equal( + unzippedFileData['bin/binary-444'].unixPermissions + ); + } else { + // binary file is set with chmod of 777 + expect(unzippedFileData['bin/binary-777'].unixPermissions).to.equal( + Math.pow(2, 15) + 777 + ); - // read only file is set with chmod of 444 - expect(unzippedFileData['bin/binary-444'].unixPermissions) - .to.equal(Math.pow(2, 15) + 444); - } - }); + // read only file is set with chmod of 444 + expect(unzippedFileData['bin/binary-444'].unixPermissions).to.equal( + Math.pow(2, 15) + 444 + ); + } + }); }); it('should exclude with globs', () => { params.zipFileName = getTestArtifactFileName('exclude-with-globs'); - params.exclude = [ - 'event.json', - 'lib/**', - 'node_modules/directory-1/**', - ]; + params.exclude = ['event.json', 'lib/**', 'node_modules/directory-1/**']; - return expect(packagePlugin.zip(params)).to.eventually.be - .equal(path.join(serverless.config.servicePath, '.serverless', params.zipFileName)) - .then(artifact => { - const data = fs.readFileSync(artifact); - return expect(zip.loadAsync(data)).to.be.fulfilled; - }).then(unzippedData => { - const unzippedFileData = unzippedData.files; + return expect(packagePlugin.zip(params)) + .to.eventually.be.equal( + path.join(serverless.config.servicePath, '.serverless', params.zipFileName) + ) + .then(artifact => { + const data = fs.readFileSync(artifact); + return expect(zip.loadAsync(data)).to.be.fulfilled; + }) + .then(unzippedData => { + const unzippedFileData = unzippedData.files; - expect(Object.keys(unzippedFileData) - .filter(file => !unzippedFileData[file].dir)) - .to.be.lengthOf(7); + expect( + Object.keys(unzippedFileData).filter(file => !unzippedFileData[file].dir) + ).to.be.lengthOf(7); - // root directory - expect(unzippedFileData['handler.js'].name) - .to.equal('handler.js'); - expect(unzippedFileData['file-1'].name) - .to.equal('file-1'); - expect(unzippedFileData['file-2'].name) - .to.equal('file-2'); + // root directory + expect(unzippedFileData['handler.js'].name).to.equal('handler.js'); + expect(unzippedFileData['file-1'].name).to.equal('file-1'); + expect(unzippedFileData['file-2'].name).to.equal('file-2'); - // bin directory - expect(unzippedFileData['bin/binary-777'].name) - .to.equal('bin/binary-777'); - expect(unzippedFileData['bin/binary-444'].name) - .to.equal('bin/binary-444'); + // bin directory + expect(unzippedFileData['bin/binary-777'].name).to.equal('bin/binary-777'); + expect(unzippedFileData['bin/binary-444'].name).to.equal('bin/binary-444'); - // node_modules directory - expect(unzippedFileData['node_modules/directory-2/file-1'].name) - .to.equal('node_modules/directory-2/file-1'); - expect(unzippedFileData['node_modules/directory-2/file-2'].name) - .to.equal('node_modules/directory-2/file-2'); - }); + // node_modules directory + expect(unzippedFileData['node_modules/directory-2/file-1'].name).to.equal( + 'node_modules/directory-2/file-1' + ); + expect(unzippedFileData['node_modules/directory-2/file-2'].name).to.equal( + 'node_modules/directory-2/file-2' + ); + }); }); it('should re-include files using ! glob pattern', () => { @@ -916,118 +880,108 @@ describe('zipService', () => { '!lib/**', // re-include ]; - return expect(packagePlugin.zip(params)).to.eventually.be - .equal(path.join(serverless.config.servicePath, '.serverless', params.zipFileName)) - .then(artifact => { - const data = fs.readFileSync(artifact); - return expect(zip.loadAsync(data)).to.be.fulfilled; - }).then(unzippedData => { - const unzippedFileData = unzippedData.files; + return expect(packagePlugin.zip(params)) + .to.eventually.be.equal( + path.join(serverless.config.servicePath, '.serverless', params.zipFileName) + ) + .then(artifact => { + const data = fs.readFileSync(artifact); + return expect(zip.loadAsync(data)).to.be.fulfilled; + }) + .then(unzippedData => { + const unzippedFileData = unzippedData.files; - expect(Object.keys(unzippedFileData) - .filter(file => !unzippedFileData[file].dir)) - .to.be.lengthOf(10); + expect( + Object.keys(unzippedFileData).filter(file => !unzippedFileData[file].dir) + ).to.be.lengthOf(10); - // root directory - expect(unzippedFileData['event.json'].name) - .to.equal('event.json'); - expect(unzippedFileData['handler.js'].name) - .to.equal('handler.js'); - expect(unzippedFileData['file-1'].name) - .to.equal('file-1'); - expect(unzippedFileData['file-2'].name) - .to.equal('file-2'); + // root directory + expect(unzippedFileData['event.json'].name).to.equal('event.json'); + expect(unzippedFileData['handler.js'].name).to.equal('handler.js'); + expect(unzippedFileData['file-1'].name).to.equal('file-1'); + expect(unzippedFileData['file-2'].name).to.equal('file-2'); - // bin directory - expect(unzippedFileData['bin/binary-777'].name) - .to.equal('bin/binary-777'); - expect(unzippedFileData['bin/binary-444'].name) - .to.equal('bin/binary-444'); + // bin directory + expect(unzippedFileData['bin/binary-777'].name).to.equal('bin/binary-777'); + expect(unzippedFileData['bin/binary-444'].name).to.equal('bin/binary-444'); - // lib directory - expect(unzippedFileData['lib/file-1.js'].name) - .to.equal('lib/file-1.js'); - expect(unzippedFileData['lib/directory-1/file-1.js'].name) - .to.equal('lib/directory-1/file-1.js'); + // lib directory + expect(unzippedFileData['lib/file-1.js'].name).to.equal('lib/file-1.js'); + expect(unzippedFileData['lib/directory-1/file-1.js'].name).to.equal( + 'lib/directory-1/file-1.js' + ); - // node_modules directory - expect(unzippedFileData['node_modules/directory-2/file-1'].name) - .to.equal('node_modules/directory-2/file-1'); - expect(unzippedFileData['node_modules/directory-2/file-2'].name) - .to.equal('node_modules/directory-2/file-2'); - }); + // node_modules directory + expect(unzippedFileData['node_modules/directory-2/file-1'].name).to.equal( + 'node_modules/directory-2/file-1' + ); + expect(unzippedFileData['node_modules/directory-2/file-2'].name).to.equal( + 'node_modules/directory-2/file-2' + ); + }); }); it('should re-include files using include config', () => { params.zipFileName = getTestArtifactFileName('re-include-with-include'); - params.exclude = [ - 'event.json', - 'lib/**', - 'node_modules/directory-1/**', - ]; - params.include = [ - 'event.json', - 'lib/**', - ]; + params.exclude = ['event.json', 'lib/**', 'node_modules/directory-1/**']; + params.include = ['event.json', 'lib/**']; - return expect(packagePlugin.zip(params)).to.eventually.be - .equal(path.join(serverless.config.servicePath, '.serverless', params.zipFileName)) - .then(artifact => { - const data = fs.readFileSync(artifact); - return expect(zip.loadAsync(data)).to.be.fulfilled; - }).then(unzippedData => { - const unzippedFileData = unzippedData.files; + return expect(packagePlugin.zip(params)) + .to.eventually.be.equal( + path.join(serverless.config.servicePath, '.serverless', params.zipFileName) + ) + .then(artifact => { + const data = fs.readFileSync(artifact); + return expect(zip.loadAsync(data)).to.be.fulfilled; + }) + .then(unzippedData => { + const unzippedFileData = unzippedData.files; - expect(Object.keys(unzippedFileData) - .filter(file => !unzippedFileData[file].dir)) - .to.be.lengthOf(10); + expect( + Object.keys(unzippedFileData).filter(file => !unzippedFileData[file].dir) + ).to.be.lengthOf(10); - // root directory - expect(unzippedFileData['event.json'].name) - .to.equal('event.json'); - expect(unzippedFileData['handler.js'].name) - .to.equal('handler.js'); - expect(unzippedFileData['file-1'].name) - .to.equal('file-1'); - expect(unzippedFileData['file-2'].name) - .to.equal('file-2'); + // root directory + expect(unzippedFileData['event.json'].name).to.equal('event.json'); + expect(unzippedFileData['handler.js'].name).to.equal('handler.js'); + expect(unzippedFileData['file-1'].name).to.equal('file-1'); + expect(unzippedFileData['file-2'].name).to.equal('file-2'); - // bin directory - expect(unzippedFileData['bin/binary-777'].name) - .to.equal('bin/binary-777'); - expect(unzippedFileData['bin/binary-444'].name) - .to.equal('bin/binary-444'); + // bin directory + expect(unzippedFileData['bin/binary-777'].name).to.equal('bin/binary-777'); + expect(unzippedFileData['bin/binary-444'].name).to.equal('bin/binary-444'); - // lib directory - expect(unzippedFileData['lib/file-1.js'].name) - .to.equal('lib/file-1.js'); - expect(unzippedFileData['lib/directory-1/file-1.js'].name) - .to.equal('lib/directory-1/file-1.js'); + // lib directory + expect(unzippedFileData['lib/file-1.js'].name).to.equal('lib/file-1.js'); + expect(unzippedFileData['lib/directory-1/file-1.js'].name).to.equal( + 'lib/directory-1/file-1.js' + ); - // node_modules directory - expect(unzippedFileData['node_modules/directory-2/file-1'].name) - .to.equal('node_modules/directory-2/file-1'); - expect(unzippedFileData['node_modules/directory-2/file-2'].name) - .to.equal('node_modules/directory-2/file-2'); - }); + // node_modules directory + expect(unzippedFileData['node_modules/directory-2/file-1'].name).to.equal( + 'node_modules/directory-2/file-1' + ); + expect(unzippedFileData['node_modules/directory-2/file-2'].name).to.equal( + 'node_modules/directory-2/file-2' + ); + }); }); it('should include files even if outside working dir', () => { params.zipFileName = getTestArtifactFileName('include-outside-working-dir'); serverless.config.servicePath = path.join(serverless.config.servicePath, 'lib'); - params.exclude = [ - './**', - ]; - params.include = [ - '../bin/binary-**', - ]; + params.exclude = ['./**']; + params.include = ['../bin/binary-**']; - return expect(packagePlugin.zip(params)).to.eventually.be - .equal(path.join(serverless.config.servicePath, '.serverless', params.zipFileName)) + return expect(packagePlugin.zip(params)) + .to.eventually.be.equal( + path.join(serverless.config.servicePath, '.serverless', params.zipFileName) + ) .then(artifact => { const data = fs.readFileSync(artifact); return expect(zip.loadAsync(data)).to.be.fulfilled; - }).then(unzippedData => { + }) + .then(unzippedData => { const unzippedFileData = unzippedData.files; expect(Object.keys(unzippedFileData).sort()).to.deep.equal([ 'bin/binary-444', @@ -1039,19 +993,18 @@ describe('zipService', () => { it('should include files only once', () => { params.zipFileName = getTestArtifactFileName('include-outside-working-dir'); serverless.config.servicePath = path.join(serverless.config.servicePath, 'lib'); - params.exclude = [ - './**', - ]; - params.include = [ - '.././bin/**', - ]; + params.exclude = ['./**']; + params.include = ['.././bin/**']; - return expect(packagePlugin.zip(params)).to.eventually.be - .equal(path.join(serverless.config.servicePath, '.serverless', params.zipFileName)) + return expect(packagePlugin.zip(params)) + .to.eventually.be.equal( + path.join(serverless.config.servicePath, '.serverless', params.zipFileName) + ) .then(artifact => { const data = fs.readFileSync(artifact); return expect(zip.loadAsync(data)).to.be.fulfilled; - }).then(unzippedData => { + }) + .then(unzippedData => { const unzippedFileData = unzippedData.files; expect(Object.keys(unzippedFileData).sort()).to.deep.equal([ 'bin/binary-444', @@ -1065,15 +1018,18 @@ describe('zipService', () => { params.include = []; params.zipFileName = getTestArtifactFileName('empty'); - return expect(packagePlugin.zip(params)).to.be - .rejectedWith(Error, 'file matches include / exclude'); + return expect(packagePlugin.zip(params)).to.be.rejectedWith( + Error, + 'file matches include / exclude' + ); }); }); describe('#zipFiles()', () => { it('should throw an error if no files are provided', () => - expect(packagePlugin.zipFiles([], path.resolve(__dirname, 'tmp.zip'))).to.be - .rejectedWith(Error, 'No files to package') - ); + expect(packagePlugin.zipFiles([], path.resolve(__dirname, 'tmp.zip'))).to.be.rejectedWith( + Error, + 'No files to package' + )); }); }); diff --git a/lib/plugins/package/package.js b/lib/plugins/package/package.js index 7e8f89ad7..5a2430c8b 100644 --- a/lib/plugins/package/package.js +++ b/lib/plugins/package/package.js @@ -10,15 +10,12 @@ class Package { this.serverless = serverless; this.options = options; this.servicePath = this.serverless.config.servicePath || ''; - this.packagePath = this.options.package || + this.packagePath = + this.options.package || this.serverless.service.package.path || path.join(this.servicePath || '.', '.serverless'); - Object.assign( - this, - packageService, - zipService - ); + Object.assign(this, packageService, zipService); this.commands = { package: { @@ -51,17 +48,14 @@ class Package { commands: { function: { type: 'entrypoint', - lifecycleEvents: [ - 'package', - ], + lifecycleEvents: ['package'], }, }, }, }; this.hooks = { - 'package:createDeploymentArtifacts': () => BbPromise.bind(this) - .then(this.packageService), + 'package:createDeploymentArtifacts': () => BbPromise.bind(this).then(this.packageService), 'package:function:package': () => { if (this.options.function) { diff --git a/lib/plugins/package/package.test.js b/lib/plugins/package/package.test.js index 38608e0cf..175354eb5 100644 --- a/lib/plugins/package/package.test.js +++ b/lib/plugins/package/package.test.js @@ -54,25 +54,26 @@ describe('Package', () => { pkg.packageFunction.restore(); }); - it('should implement the package:createDeploymentArtifacts event', - () => expect(pkg.hooks).to.have.property('package:createDeploymentArtifacts')); + it('should implement the package:createDeploymentArtifacts event', () => + expect(pkg.hooks).to.have.property('package:createDeploymentArtifacts')); - it('should implement the package:function:package event', - () => expect(pkg.hooks).to.have.property('package:function:package')); + it('should implement the package:function:package event', () => + expect(pkg.hooks).to.have.property('package:function:package')); describe('package:createDeploymentArtifacts', () => { it('should call packageService', () => - expect(pkg.hooks['package:createDeploymentArtifacts']()).to.be.fulfilled - .then(() => expect(packageServiceStub).to.be.calledOnce) - ); + expect(pkg.hooks['package:createDeploymentArtifacts']()).to.be.fulfilled.then( + () => expect(packageServiceStub).to.be.calledOnce + )); }); describe('package:function:package', () => { it('should call packageFunction', () => { pkg.options.function = 'myFunction'; - return expect(pkg.hooks['package:function:package']()).to.be.fulfilled - .then(() => expect(packageFunctionStub).to.be.calledOnce); + return expect(pkg.hooks['package:function:package']()).to.be.fulfilled.then( + () => expect(packageFunctionStub).to.be.calledOnce + ); }); it('should fail without function option', () => { @@ -80,7 +81,7 @@ describe('Package', () => { return expect(pkg.hooks['package:function:package']()) .to.be.rejectedWith('Function name must be set') - .then(() => expect(packageFunctionStub).to.be.not.called); + .then(() => expect(packageFunctionStub).to.be.not.called); }); }); }); diff --git a/lib/plugins/plugin/install/install.js b/lib/plugins/plugin/install/install.js index 91714d089..c2df8ffa7 100644 --- a/lib/plugins/plugin/install/install.js +++ b/lib/plugins/plugin/install/install.js @@ -15,19 +15,14 @@ class PluginInstall { this.serverless = serverless; this.options = options; - Object.assign( - this, - pluginUtils - ); + Object.assign(this, pluginUtils); this.commands = { plugin: { commands: { install: { usage: 'Install and add a plugin to your service', - lifecycleEvents: [ - 'install', - ], + lifecycleEvents: ['install'], options: { name: { usage: 'The plugin name', @@ -40,9 +35,10 @@ class PluginInstall { }, }; this.hooks = { - 'plugin:install:install': () => BbPromise.bind(this) - .then(this.install) - .then(this.trackPluginInstall), + 'plugin:install:install': () => + BbPromise.bind(this) + .then(this.install) + .then(this.trackPluginInstall), }; } @@ -60,20 +56,20 @@ class PluginInstall { return BbPromise.bind(this) .then(this.validate) .then(this.getPlugins) - .then((plugins) => { - const plugin = plugins.find((item) => item.name === this.options.pluginName); + .then(plugins => { + const plugin = plugins.find(item => item.name === this.options.pluginName); if (plugin) { return BbPromise.bind(this) - .then(this.pluginInstall) - .then(this.addPluginToServerlessFile) - .then(this.installPeerDependencies) - .then(() => { - const message = [ - 'Successfully installed', - ` "${this.options.pluginName}@${this.options.pluginVersion}"`, - ].join(''); - this.serverless.cli.log(message); - }); + .then(this.pluginInstall) + .then(this.addPluginToServerlessFile) + .then(this.installPeerDependencies) + .then(() => { + const message = [ + 'Successfully installed', + ` "${this.options.pluginName}@${this.options.pluginVersion}"`, + ].join(''); + this.serverless.cli.log(message); + }); } const message = `Plugin "${this.options.pluginName}" not found. Did you spell it correct?`; throw new this.serverless.classes.Error(message); @@ -84,33 +80,33 @@ class PluginInstall { const servicePath = this.serverless.config.servicePath; const packageJsonFilePath = path.join(servicePath, 'package.json'); - return fileExists(packageJsonFilePath).then(exists => { - // check if package.json is already present. Otherwise create one - if (!exists) { - this.serverless.cli - .log('Creating an empty package.json file in your service directory'); + return fileExists(packageJsonFilePath) + .then(exists => { + // check if package.json is already present. Otherwise create one + if (!exists) { + this.serverless.cli.log('Creating an empty package.json file in your service directory'); - const packageJsonFileContent = { - name: this.serverless.service.service, - description: '', - version: '0.1.0', - dependencies: {}, - devDependencies: {}, - }; - return fse.writeJsonAsync(packageJsonFilePath, packageJsonFileContent); - } - return BbPromise.resolve(); - }) - .then(() => { - // install the package through npm - const pluginFullName = `${this.options.pluginName}@${this.options.pluginVersion}`; - const message = [ - `Installing plugin "${pluginFullName}"`, - ' (this might take a few seconds...)', - ].join(''); - this.serverless.cli.log(message); - return this.npmInstall(pluginFullName); - }); + const packageJsonFileContent = { + name: this.serverless.service.service, + description: '', + version: '0.1.0', + dependencies: {}, + devDependencies: {}, + }; + return fse.writeJsonAsync(packageJsonFilePath, packageJsonFileContent); + } + return BbPromise.resolve(); + }) + .then(() => { + // install the package through npm + const pluginFullName = `${this.options.pluginName}@${this.options.pluginVersion}`; + const message = [ + `Installing plugin "${pluginFullName}"`, + ' (this might take a few seconds...)', + ].join(''); + this.serverless.cli.log(message); + return this.npmInstall(pluginFullName); + }); } addPluginToServerlessFile() { @@ -123,7 +119,7 @@ class PluginInstall { return BbPromise.resolve(); } - const checkIsArrayPluginsObject = (pluginsObject) => + const checkIsArrayPluginsObject = pluginsObject => _.isNil(pluginsObject) || _.isArray(pluginsObject); // pluginsObject type determined based on the value loaded during the serverless init. if (_.last(_.split(serverlessFilePath, '.')) === 'json') { @@ -131,8 +127,9 @@ class PluginInstall { const newServerlessFileObj = serverlessFileObj; const isArrayPluginsObject = checkIsArrayPluginsObject(newServerlessFileObj.plugins); // null modules property is not supported - let plugins = isArrayPluginsObject ? newServerlessFileObj.plugins || [] : - newServerlessFileObj.plugins.modules; + let plugins = isArrayPluginsObject + ? newServerlessFileObj.plugins || [] + : newServerlessFileObj.plugins.modules; if (_.isNil(plugins)) { throw new Error('plugins modules property must be present'); @@ -153,16 +150,23 @@ class PluginInstall { return this.serverless.yamlParser .parse(serverlessFilePath) - .then((serverlessFileObj) => - yamlAstParser.addNewArrayItem(serverlessFilePath, - checkIsArrayPluginsObject(serverlessFileObj.plugins) ? - 'plugins' : 'plugins.modules', this.options.pluginName)); + .then(serverlessFileObj => + yamlAstParser.addNewArrayItem( + serverlessFilePath, + checkIsArrayPluginsObject(serverlessFileObj.plugins) ? 'plugins' : 'plugins.modules', + this.options.pluginName + ) + ); }); } installPeerDependencies() { - const pluginPackageJsonFilePath = path.join(this.serverless.config.servicePath, - 'node_modules', this.options.pluginName, 'package.json'); + const pluginPackageJsonFilePath = path.join( + this.serverless.config.servicePath, + 'node_modules', + this.options.pluginName, + 'package.json' + ); return fse.readJsonAsync(pluginPackageJsonFilePath).then(pluginPackageJson => { if (pluginPackageJson.peerDependencies) { const pluginsArray = []; @@ -176,10 +180,9 @@ class PluginInstall { } npmInstall(name) { - return childProcess - .execAsync(`npm install --save-dev ${name}`, { - stdio: 'ignore', - }); + return childProcess.execAsync(`npm install --save-dev ${name}`, { + stdio: 'ignore', + }); } trackPluginInstall() { diff --git a/lib/plugins/plugin/install/install.test.js b/lib/plugins/plugin/install/install.test.js index 9b5a59c17..d9666cc83 100644 --- a/lib/plugins/plugin/install/install.test.js +++ b/lib/plugins/plugin/install/install.test.js @@ -64,8 +64,7 @@ describe('PluginInstall', () => { let installStub; beforeEach(() => { - installStub = sinon - .stub(pluginInstall, 'install').returns(BbPromise.resolve()); + installStub = sinon.stub(pluginInstall, 'install').returns(BbPromise.resolve()); sinon.stub(userStats, 'track').resolves(); }); @@ -93,11 +92,10 @@ describe('PluginInstall', () => { expect(pluginInstall.hooks['plugin:install:install']).to.not.equal(undefined); }); - it('should run promise chain in order for "plugin:install:install" hook', - () => pluginInstall.hooks['plugin:install:install']().then(() => { + it('should run promise chain in order for "plugin:install:install" hook', () => + pluginInstall.hooks['plugin:install:install']().then(() => { expect(installStub.calledOnce).to.equal(true); - }) - ); + })); }); describe('#install()', () => { @@ -115,21 +113,15 @@ describe('PluginInstall', () => { pluginInstall.serverless.config.servicePath = servicePath; fse.ensureDirSync(servicePath); serverlessYmlFilePath = path.join(servicePath, 'serverless.yml'); - validateStub = sinon - .stub(pluginInstall, 'validate') - .returns(BbPromise.resolve()); - pluginInstallStub = sinon - .stub(pluginInstall, 'pluginInstall') - .returns(BbPromise.resolve()); + validateStub = sinon.stub(pluginInstall, 'validate').returns(BbPromise.resolve()); + pluginInstallStub = sinon.stub(pluginInstall, 'pluginInstall').returns(BbPromise.resolve()); addPluginToServerlessFileStub = sinon .stub(pluginInstall, 'addPluginToServerlessFile') .returns(BbPromise.resolve()); installPeerDependenciesStub = sinon .stub(pluginInstall, 'installPeerDependencies') .returns(BbPromise.resolve()); - getPluginsStub = sinon - .stub(pluginInstall, 'getPlugins') - .returns(BbPromise.resolve(plugins)); + getPluginsStub = sinon.stub(pluginInstall, 'getPlugins').returns(BbPromise.resolve(plugins)); // save the cwd so that we can restore it later savedCwd = process.cwd(); process.chdir(servicePath); @@ -150,8 +142,7 @@ describe('PluginInstall', () => { service: 'plugin-service', provider: 'aws', }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginInstall.options.name = 'serverless-plugin-1'; @@ -172,8 +163,7 @@ describe('PluginInstall', () => { service: 'plugin-service', provider: 'aws', }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginInstall.options.name = '@scope/serverless-plugin-1'; @@ -194,8 +184,7 @@ describe('PluginInstall', () => { service: 'plugin-service', provider: 'aws', }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginInstall.options.name = 'serverless-not-available-plugin'; return expect(pluginInstall.install()).to.be.rejected.then(() => { @@ -214,8 +203,7 @@ describe('PluginInstall', () => { service: 'plugin-service', provider: 'aws', }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginInstall.options.name = 'serverless-plugin-1'; return expect(pluginInstall.install()).to.be.fulfilled.then(() => { expect(pluginInstall.options.pluginName).to.be.equal('serverless-plugin-1'); @@ -223,28 +211,29 @@ describe('PluginInstall', () => { }); }); - it('should apply the latest version if you can not get the ' + - 'version from name option even if scoped', () => { - const serverlessYml = { - service: 'plugin-service', - provider: 'aws', - }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); - pluginInstall.options.name = '@scope/serverless-plugin-1'; - return expect(pluginInstall.install()).to.be.fulfilled.then(() => { - expect(pluginInstall.options.pluginName).to.be.equal('@scope/serverless-plugin-1'); - expect(pluginInstall.options.pluginVersion).to.be.equal('latest'); - }); - }); + it( + 'should apply the latest version if you can not get the ' + + 'version from name option even if scoped', + () => { + const serverlessYml = { + service: 'plugin-service', + provider: 'aws', + }; + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + pluginInstall.options.name = '@scope/serverless-plugin-1'; + return expect(pluginInstall.install()).to.be.fulfilled.then(() => { + expect(pluginInstall.options.pluginName).to.be.equal('@scope/serverless-plugin-1'); + expect(pluginInstall.options.pluginVersion).to.be.equal('latest'); + }); + } + ); it('should apply the specified version if you can get the version from name option', () => { const serverlessYml = { service: 'plugin-service', provider: 'aws', }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginInstall.options.name = 'serverless-plugin-1@1.0.0'; return expect(pluginInstall.install()).to.be.fulfilled.then(() => { expect(pluginInstall.options.pluginName).to.be.equal('serverless-plugin-1'); @@ -267,13 +256,11 @@ describe('PluginInstall', () => { fse.ensureDirSync(servicePath); packageJsonFilePath = path.join(servicePath, 'package.json'); npmInstallStub = sinon.stub(childProcess, 'execAsync').callsFake(() => { - const packageJson = - serverless.utils.readFileSync(packageJsonFilePath, 'utf8'); + const packageJson = serverless.utils.readFileSync(packageJsonFilePath, 'utf8'); packageJson.devDependencies = { 'serverless-plugin-1': 'latest', }; - serverless.utils - .writeFileSync(packageJsonFilePath, packageJson); + serverless.utils.writeFileSync(packageJsonFilePath, packageJson); return BbPromise.resolve(); }); @@ -296,31 +283,31 @@ describe('PluginInstall', () => { devDependencies: {}, }; - serverless.utils - .writeFileSync(packageJsonFilePath, packageJson); + serverless.utils.writeFileSync(packageJsonFilePath, packageJson); return expect(pluginInstall.pluginInstall()).to.be.fulfilled.then(() => Promise.all([ expect(consoleLogStub.called).to.equal(true), - expect(npmInstallStub.calledWithExactly( - 'npm install --save-dev serverless-plugin-1@latest', - { stdio: 'ignore' } - )).to.equal(true), + expect( + npmInstallStub.calledWithExactly('npm install --save-dev serverless-plugin-1@latest', { + stdio: 'ignore', + }) + ).to.equal(true), expect(serverlessErrorStub.calledOnce).to.equal(false), ]) ); }); - it('should generate a package.json file in the service directory if not present', - () => expect(pluginInstall.pluginInstall()).to.be.fulfilled.then(() => { + it('should generate a package.json file in the service directory if not present', () => + expect(pluginInstall.pluginInstall()).to.be.fulfilled.then(() => { expect(consoleLogStub.called).to.equal(true); - expect(npmInstallStub.calledWithExactly( - 'npm install --save-dev serverless-plugin-1@latest', - { stdio: 'ignore' } - )).to.equal(true); + expect( + npmInstallStub.calledWithExactly('npm install --save-dev serverless-plugin-1@latest', { + stdio: 'ignore', + }) + ).to.equal(true); expect(fs.existsSync(packageJsonFilePath)).to.equal(true); - }) - ); + })); }); describe('#addPluginToServerlessFile()', () => { @@ -340,16 +327,16 @@ describe('PluginInstall', () => { provider: 'aws', // no plugins array here }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginInstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')) - .to.deep.equal(_.assign({}, serverlessYml, { + expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')).to.deep.equal( + _.assign({}, serverlessYml, { plugins: ['serverless-plugin-1'], - })); + }) + ); }); }); @@ -360,16 +347,16 @@ describe('PluginInstall', () => { provider: 'aws', plugins: ['serverless-existing-plugin'], // one plugin was already added }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginInstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')) - .to.deep.equal(_.assign({}, serverlessYml, { + expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')).to.deep.equal( + _.assign({}, serverlessYml, { plugins: ['serverless-existing-plugin', 'serverless-plugin-1'], - })); + }) + ); }); }); @@ -379,12 +366,12 @@ describe('PluginInstall', () => { service: 'plugin-service', provider: 'aws', }; - serverless.utils - .writeFileSync(serverlessYamlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYamlFilePath, YAML.dump(serverlessYml)); pluginInstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYamlFilePath, 'utf8')) - .to.deep.equal(_.assign({}, serverlessYml, { plugins: ['serverless-plugin-1'] })); + expect(serverless.utils.readFileSync(serverlessYamlFilePath, 'utf8')).to.deep.equal( + _.assign({}, serverlessYml, { plugins: ['serverless-plugin-1'] }) + ); }); }); @@ -394,21 +381,22 @@ describe('PluginInstall', () => { service: 'plugin-service', provider: 'aws', }; - serverless.utils - .writeFileSync(serverlessJsonFilePath, serverlessJson); + serverless.utils.writeFileSync(serverlessJsonFilePath, serverlessJson); pluginInstall.options.pluginName = 'serverless-plugin-1'; - return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) - .to.deep.equal(_.assign({}, serverlessJson, { plugins: ['serverless-plugin-1'] })); - }) + return expect(pluginInstall.addPluginToServerlessFile()) + .to.be.fulfilled.then(() => { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')).to.deep.equal( + _.assign({}, serverlessJson, { plugins: ['serverless-plugin-1'] }) + ); + }) .then(() => { pluginInstall.options.pluginName = 'serverless-plugin-2'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) - .to.deep.equal(_.assign({}, serverlessJson, - { - plugins: ['serverless-plugin-1', 'serverless-plugin-2'], - })); + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')).to.deep.equal( + _.assign({}, serverlessJson, { + plugins: ['serverless-plugin-1', 'serverless-plugin-2'], + }) + ); }); }); }); @@ -420,14 +408,15 @@ describe('PluginInstall', () => { provider: 'aws', plugins: [], }; - serverless.utils - .writeFileSync(serverlessJsFilePath, `module.exports = ${JSON.stringify(serverlessJson)};`); + serverless.utils.writeFileSync( + serverlessJsFilePath, + `module.exports = ${JSON.stringify(serverlessJson)};` + ); pluginInstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { // use require to load serverless.js // eslint-disable-next-line global-require - expect(require(serverlessJsFilePath).plugins) - .to.be.deep.equal([]); + expect(require(serverlessJsFilePath).plugins).to.be.deep.equal([]); }); }); @@ -442,19 +431,19 @@ describe('PluginInstall', () => { modules: [], }, }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginInstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')) - .to.deep.equal(_.assign({}, serverlessYml, { + expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')).to.deep.equal( + _.assign({}, serverlessYml, { plugins: { localPath: 'test', modules: [pluginInstall.options.pluginName], }, - })); + }) + ); }); }); @@ -468,28 +457,30 @@ describe('PluginInstall', () => { modules: [], }, }; - serverless.utils - .writeFileSync(serverlessJsonFilePath, serverlessJson); + serverless.utils.writeFileSync(serverlessJsonFilePath, serverlessJson); pluginInstall.options.pluginName = 'serverless-plugin-1'; - return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) - .to.deep.equal(_.assign({}, serverlessJson, { - plugins: { - localPath: 'test', - modules: [pluginInstall.options.pluginName], - }, - })); - }) + return expect(pluginInstall.addPluginToServerlessFile()) + .to.be.fulfilled.then(() => { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')).to.deep.equal( + _.assign({}, serverlessJson, { + plugins: { + localPath: 'test', + modules: [pluginInstall.options.pluginName], + }, + }) + ); + }) .then(() => { pluginInstall.options.pluginName = 'serverless-plugin-2'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) - .to.deep.equal(_.assign({}, serverlessJson, { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')).to.deep.equal( + _.assign({}, serverlessJson, { plugins: { localPath: 'test', modules: ['serverless-plugin-1', 'serverless-plugin-2'], }, - })); + }) + ); }); }); }); @@ -515,13 +506,10 @@ describe('PluginInstall', () => { fse.writeJsonSync(servicePackageJsonFilePath, { devDependencies: {}, }); - pluginPath = path.join( - servicePath, 'node_modules', pluginName); + pluginPath = path.join(servicePath, 'node_modules', pluginName); fse.ensureDirSync(pluginPath); pluginPackageJsonFilePath = path.join(pluginPath, 'package.json'); - npmInstallStub = sinon - .stub(childProcess, 'execAsync') - .returns(BbPromise.resolve()); + npmInstallStub = sinon.stub(childProcess, 'execAsync').returns(BbPromise.resolve()); savedCwd = process.cwd(); process.chdir(servicePath); }); @@ -538,22 +526,23 @@ describe('PluginInstall', () => { }, }); return expect(pluginInstall.installPeerDependencies()).to.be.fulfilled.then(() => { - expect(npmInstallStub.calledWithExactly( - 'npm install --save-dev some-package@"*"', - { stdio: 'ignore' } - )).to.equal(true); + expect( + npmInstallStub.calledWithExactly('npm install --save-dev some-package@"*"', { + stdio: 'ignore', + }) + ).to.equal(true); }); }); it('should not install peerDependencies if an installed plugin does not have ones', () => { fse.writeJsonSync(pluginPackageJsonFilePath, {}); return expect(pluginInstall.installPeerDependencies()).to.be.fulfilled.then(() => { - expect(fse.readJsonSync(servicePackageJsonFilePath)) - .to.be.deep.equal({ devDependencies: {} }); - expect(npmInstallStub.calledWithExactly( - 'npm install', - { stdio: 'ignore' } - )).to.equal(false); + expect(fse.readJsonSync(servicePackageJsonFilePath)).to.be.deep.equal({ + devDependencies: {}, + }); + expect(npmInstallStub.calledWithExactly('npm install', { stdio: 'ignore' })).to.equal( + false + ); }); }); }); diff --git a/lib/plugins/plugin/lib/utils.js b/lib/plugins/plugin/lib/utils.js index 0f13b4573..f4eeffe3d 100644 --- a/lib/plugins/plugin/lib/utils.js +++ b/lib/plugins/plugin/lib/utils.js @@ -12,8 +12,9 @@ const fileExists = require('../../../utils/fs/fileExists'); module.exports = { validate() { if (!this.serverless.config.servicePath) { - throw new this.serverless.classes - .Error('This command can only be run inside a service directory'); + throw new this.serverless.classes.Error( + 'This command can only be run inside a service directory' + ); } return BbPromise.resolve(); @@ -31,7 +32,7 @@ module.exports = { yml: fileExists(ymlFilePath), yaml: fileExists(yamlFilePath), js: fileExists(jsFilePath), - }).then((exists) => { + }).then(exists => { if (exists.yml) { return ymlFilePath; } else if (exists.yaml) { @@ -42,10 +43,8 @@ module.exports = { return jsFilePath; } return BbPromise.reject( - new this.serverless.classes.Error( - 'Could not find any serverless service definition file.' - ) - ); + new this.serverless.classes.Error('Could not find any serverless service definition file.') + ); }); }, @@ -53,18 +52,21 @@ module.exports = { const endpoint = 'https://raw.githubusercontent.com/serverless/plugins/master/plugins.json'; // Use HTTPS Proxy (Optional) - const proxy = process.env.proxy - || process.env.HTTP_PROXY - || process.env.http_proxy - || process.env.HTTPS_PROXY - || process.env.https_proxy; + const proxy = + process.env.proxy || + process.env.HTTP_PROXY || + process.env.http_proxy || + process.env.HTTPS_PROXY || + process.env.https_proxy; const options = {}; if (proxy) { options.agent = new HttpsProxyAgent(url.parse(proxy)); } - return fetch(endpoint, options).then((result) => result.json()).then((json) => json); + return fetch(endpoint, options) + .then(result => result.json()) + .then(json => json); }, display(plugins) { @@ -72,7 +74,7 @@ module.exports = { if (plugins && plugins.length) { // order plugins by name const orderedPlugins = _.orderBy(plugins, ['name'], ['asc']); - orderedPlugins.forEach((plugin) => { + orderedPlugins.forEach(plugin => { message += `${chalk.yellow.underline(plugin.name)} - ${plugin.description}\n`; }); // remove last two newlines for a prettier output diff --git a/lib/plugins/plugin/lib/utils.test.js b/lib/plugins/plugin/lib/utils.test.js index 75dd046b1..1a797062f 100644 --- a/lib/plugins/plugin/lib/utils.test.js +++ b/lib/plugins/plugin/lib/utils.test.js @@ -53,10 +53,12 @@ describe('PluginUtils', () => { it('should throw an error if the the cwd is not a Serverless service', () => { pluginUtils.serverless.config.servicePath = false; - expect(() => { pluginUtils.validate(); }).to.throw(Error); + expect(() => { + pluginUtils.validate(); + }).to.throw(Error); }); - it('should resolve if the cwd is a Serverless service', (done) => { + it('should resolve if the cwd is a Serverless service', done => { pluginUtils.serverless.config.servicePath = true; pluginUtils.validate().then(() => done()); @@ -75,45 +77,50 @@ describe('PluginUtils', () => { const serverlessYmlFilePath = path.join(servicePath, 'serverless.yml'); fse.ensureFileSync(serverlessYmlFilePath); - return expect(pluginUtils.getServerlessFilePath()).to.be.fulfilled - .then(serverlessFilePath => { - expect(serverlessFilePath).to.equal(serverlessYmlFilePath); - }); + return expect(pluginUtils.getServerlessFilePath()).to.be.fulfilled.then( + serverlessFilePath => { + expect(serverlessFilePath).to.equal(serverlessYmlFilePath); + } + ); }); it('should return the correct serverless file path for a .yaml file', () => { const serverlessYamlFilePath = path.join(servicePath, 'serverless.yaml'); fse.ensureFileSync(serverlessYamlFilePath); - return expect(pluginUtils.getServerlessFilePath()).to.be.fulfilled - .then(serverlessFilePath => { - expect(serverlessFilePath).to.equal(serverlessYamlFilePath); - }); + return expect(pluginUtils.getServerlessFilePath()).to.be.fulfilled.then( + serverlessFilePath => { + expect(serverlessFilePath).to.equal(serverlessYamlFilePath); + } + ); }); it('should return the correct serverless file path for a .json file', () => { const serverlessJsonFilePath = path.join(servicePath, 'serverless.json'); fse.ensureFileSync(serverlessJsonFilePath); - return expect(pluginUtils.getServerlessFilePath()).to.be.fulfilled - .then(serverlessFilePath => { - expect(serverlessFilePath).to.equal(serverlessJsonFilePath); - }); + return expect(pluginUtils.getServerlessFilePath()).to.be.fulfilled.then( + serverlessFilePath => { + expect(serverlessFilePath).to.equal(serverlessJsonFilePath); + } + ); }); it('should return the correct serverless file path for a .js file', () => { const serverlessJsFilePath = path.join(servicePath, 'serverless.js'); fse.ensureFileSync(serverlessJsFilePath); - return expect(pluginUtils.getServerlessFilePath()).to.be.fulfilled - .then(serverlessFilePath => { - expect(serverlessFilePath).to.equal(serverlessJsFilePath); - }); + return expect(pluginUtils.getServerlessFilePath()).to.be.fulfilled.then( + serverlessFilePath => { + expect(serverlessFilePath).to.equal(serverlessJsFilePath); + } + ); }); it('should reject if no configuration file exists', () => - expect(pluginUtils.getServerlessFilePath()) - .to.be.rejectedWith('Could not find any serverless service definition file.')); + expect(pluginUtils.getServerlessFilePath()).to.be.rejectedWith( + 'Could not find any serverless service definition file.' + )); }); describe('#getPlugins()', () => { @@ -134,7 +141,7 @@ describe('PluginUtils', () => { it('should fetch and return the plugins from the plugins repository', () => { const endpoint = 'https://raw.githubusercontent.com/serverless/plugins/master/plugins.json'; - return pluginWithFetchStub.getPlugins().then((result) => { + return pluginWithFetchStub.getPlugins().then(result => { expect(fetchStub.calledOnce).to.equal(true); expect(fetchStub.args[0][0]).to.equal(endpoint); expect(result).to.deep.equal(plugins); @@ -152,7 +159,7 @@ describe('PluginUtils', () => { expectedMessage += `${chalk.yellow.underline('serverless-plugin-2')}`; expectedMessage += ' - Serverless Plugin 2\n'; expectedMessage = expectedMessage.slice(0, -2); - return expect(pluginUtils.display(plugins)).to.be.fulfilled.then((message) => { + return expect(pluginUtils.display(plugins)).to.be.fulfilled.then(message => { expect(consoleLogStub.calledTwice).to.equal(true); expect(message).to.equal(expectedMessage); }); @@ -161,7 +168,7 @@ describe('PluginUtils', () => { it('should print a message when no plugins are available to display', () => { const expectedMessage = 'There are no plugins available to display'; - return pluginUtils.display([]).then((message) => { + return pluginUtils.display([]).then(message => { expect(consoleLogStub.calledOnce).to.equal(true); expect(message).to.equal(expectedMessage); }); diff --git a/lib/plugins/plugin/list/list.js b/lib/plugins/plugin/list/list.js index 458f2300a..250324174 100644 --- a/lib/plugins/plugin/list/list.js +++ b/lib/plugins/plugin/list/list.js @@ -9,35 +9,31 @@ class PluginList { this.serverless = serverless; this.options = options; - Object.assign( - this, - pluginUtils - ); + Object.assign(this, pluginUtils); this.commands = { plugin: { commands: { list: { usage: 'Lists all available plugins', - lifecycleEvents: [ - 'list', - ], + lifecycleEvents: ['list'], }, }, }, }; this.hooks = { - 'plugin:list:list': () => BbPromise.bind(this) - .then(this.list) - .then(this.trackPluginList), + 'plugin:list:list': () => + BbPromise.bind(this) + .then(this.list) + .then(this.trackPluginList), }; } list() { return BbPromise.bind(this) .then(this.getPlugins) - .then((plugins) => this.display(plugins)); + .then(plugins => this.display(plugins)); } trackPluginList() { diff --git a/lib/plugins/plugin/list/list.test.js b/lib/plugins/plugin/list/list.test.js index 3799571b7..49091d355 100644 --- a/lib/plugins/plugin/list/list.test.js +++ b/lib/plugins/plugin/list/list.test.js @@ -25,8 +25,7 @@ describe('PluginList', () => { let listStub; beforeEach(() => { - listStub = sinon - .stub(pluginList, 'list').returns(BbPromise.resolve()); + listStub = sinon.stub(pluginList, 'list').returns(BbPromise.resolve()); sinon.stub(userStats, 'track').resolves(); }); @@ -40,9 +39,7 @@ describe('PluginList', () => { }); it('should have the lifecycle event "list" for the "list" sub-command', () => { - expect(pluginList.commands.plugin.commands.list.lifecycleEvents).to.deep.equal([ - 'list', - ]); + expect(pluginList.commands.plugin.commands.list.lifecycleEvents).to.deep.equal(['list']); }); it('should have no option for the "list" sub-command', () => { @@ -54,11 +51,10 @@ describe('PluginList', () => { expect(pluginList.hooks['plugin:list:list']).to.not.equal(undefined); }); - it('should run promise chain in order for "plugin:list:list" hook', - () => expect(pluginList.hooks['plugin:list:list']()).to.be.fulfilled.then(() => { + it('should run promise chain in order for "plugin:list:list" hook', () => + expect(pluginList.hooks['plugin:list:list']()).to.be.fulfilled.then(() => { expect(listStub.calledOnce).to.equal(true); - }) - ); + })); }); describe('#list()', () => { @@ -66,10 +62,8 @@ describe('PluginList', () => { let displayStub; beforeEach(() => { - getPluginsStub = sinon - .stub(pluginList, 'getPlugins').returns(BbPromise.resolve()); - displayStub = sinon - .stub(pluginList, 'display').returns(BbPromise.resolve()); + getPluginsStub = sinon.stub(pluginList, 'getPlugins').returns(BbPromise.resolve()); + displayStub = sinon.stub(pluginList, 'display').returns(BbPromise.resolve()); }); afterEach(() => { @@ -81,7 +75,6 @@ describe('PluginList', () => { pluginList.list().then(() => { expect(getPluginsStub.calledOnce).to.equal(true); expect(displayStub.calledOnce).to.equal(true); - }) - ); + })); }); }); diff --git a/lib/plugins/plugin/plugin.js b/lib/plugins/plugin/plugin.js index f6abc0461..56880843c 100644 --- a/lib/plugins/plugin/plugin.js +++ b/lib/plugins/plugin/plugin.js @@ -10,9 +10,7 @@ class Plugin { this.commands = { plugin: { usage: 'Plugin management for Serverless', - lifecycleEvents: [ - 'plugin', - ], + lifecycleEvents: ['plugin'], }, }; this.hooks = { diff --git a/lib/plugins/plugin/plugin.test.js b/lib/plugins/plugin/plugin.test.js index d99f59078..6afa304b8 100644 --- a/lib/plugins/plugin/plugin.test.js +++ b/lib/plugins/plugin/plugin.test.js @@ -25,7 +25,8 @@ describe('Plugin', () => { beforeEach(() => { generateCommandsHelpStub = sinon - .stub(plugin.serverless.cli, 'generateCommandsHelp').returns(BbPromise.resolve()); + .stub(plugin.serverless.cli, 'generateCommandsHelp') + .returns(BbPromise.resolve()); }); afterEach(() => { @@ -37,9 +38,7 @@ describe('Plugin', () => { }); it('should have the lifecycle event "plugin" for the "plugin" command', () => { - expect(plugin.commands.plugin.lifecycleEvents).to.deep.equal([ - 'plugin', - ]); + expect(plugin.commands.plugin.lifecycleEvents).to.deep.equal(['plugin']); }); it('should have no option for the "plugin" command', () => { @@ -51,10 +50,9 @@ describe('Plugin', () => { expect(plugin.hooks['plugin:plugin']).to.not.equal(undefined); }); - it('should run promise chain in order for "plugin:plugin" hook', - () => expect(plugin.hooks['plugin:plugin']()).to.be.fulfilled.then(() => { + it('should run promise chain in order for "plugin:plugin" hook', () => + expect(plugin.hooks['plugin:plugin']()).to.be.fulfilled.then(() => { expect(generateCommandsHelpStub.calledOnce).to.equal(true); - }) - ); + })); }); }); diff --git a/lib/plugins/plugin/search/search.js b/lib/plugins/plugin/search/search.js index f46fd364e..06a58dc26 100644 --- a/lib/plugins/plugin/search/search.js +++ b/lib/plugins/plugin/search/search.js @@ -10,19 +10,14 @@ class PluginSearch { this.serverless = serverless; this.options = options; - Object.assign( - this, - pluginUtils - ); + Object.assign(this, pluginUtils); this.commands = { plugin: { commands: { search: { usage: 'Search for plugins', - lifecycleEvents: [ - 'search', - ], + lifecycleEvents: ['search'], options: { query: { usage: 'Search query', @@ -36,21 +31,22 @@ class PluginSearch { }; this.hooks = { - 'plugin:search:search': () => BbPromise.bind(this) - .then(this.search) - .then(this.trackPluginSearch), + 'plugin:search:search': () => + BbPromise.bind(this) + .then(this.search) + .then(this.trackPluginSearch), }; } search() { return BbPromise.bind(this) .then(this.getPlugins) - .then((plugins) => { + .then(plugins => { // filter out plugins which match the query const regex = new RegExp(this.options.query); - const filteredPlugins = plugins.filter((plugin) => - (plugin.name.match(regex) || plugin.description.match(regex)) + const filteredPlugins = plugins.filter( + plugin => plugin.name.match(regex) || plugin.description.match(regex) ); // print a message with the search result @@ -61,7 +57,7 @@ class PluginSearch { return filteredPlugins; }) - .then((plugins) => { + .then(plugins => { this.display(plugins); }); } diff --git a/lib/plugins/plugin/search/search.test.js b/lib/plugins/plugin/search/search.test.js index 93e2afa54..f01ce0773 100644 --- a/lib/plugins/plugin/search/search.test.js +++ b/lib/plugins/plugin/search/search.test.js @@ -49,8 +49,7 @@ describe('PluginSearch', () => { let searchStub; beforeEach(() => { - searchStub = sinon - .stub(pluginSearch, 'search').returns(BbPromise.resolve()); + searchStub = sinon.stub(pluginSearch, 'search').returns(BbPromise.resolve()); sinon.stub(userStats, 'track').resolves(); }); @@ -78,11 +77,10 @@ describe('PluginSearch', () => { expect(pluginSearch.hooks['plugin:search:search']).to.not.equal(undefined); }); - it('should run promise chain in order for "plugin:search:search" hook', - () => expect(pluginSearch.hooks['plugin:search:search']()).to.be.fulfilled.then(() => { + it('should run promise chain in order for "plugin:search:search" hook', () => + expect(pluginSearch.hooks['plugin:search:search']()).to.be.fulfilled.then(() => { expect(searchStub.calledOnce).to.equal(true); - }) - ); + })); }); describe('#search()', () => { diff --git a/lib/plugins/plugin/uninstall/uninstall.js b/lib/plugins/plugin/uninstall/uninstall.js index 4d241d2e9..5d539c66a 100644 --- a/lib/plugins/plugin/uninstall/uninstall.js +++ b/lib/plugins/plugin/uninstall/uninstall.js @@ -14,19 +14,14 @@ class PluginUninstall { this.serverless = serverless; this.options = options; - Object.assign( - this, - pluginUtils - ); + Object.assign(this, pluginUtils); this.commands = { plugin: { commands: { uninstall: { usage: 'Uninstall and remove a plugin from your service', - lifecycleEvents: [ - 'uninstall', - ], + lifecycleEvents: ['uninstall'], options: { name: { usage: 'The plugin name', @@ -40,9 +35,10 @@ class PluginUninstall { }; this.hooks = { - 'plugin:uninstall:uninstall': () => BbPromise.bind(this) - .then(this.uninstall) - .then(this.trackPluginUninstall), + 'plugin:uninstall:uninstall': () => + BbPromise.bind(this) + .then(this.uninstall) + .then(this.trackPluginUninstall), }; } @@ -54,16 +50,16 @@ class PluginUninstall { .then(this.validate) .then(this.getPlugins) .then(plugins => { - const plugin = plugins.find((item) => item.name === this.options.pluginName); + const plugin = plugins.find(item => item.name === this.options.pluginName); if (plugin) { return BbPromise.bind(this) - .then(this.uninstallPeerDependencies) - .then(this.pluginUninstall) - .then(this.removePluginFromServerlessFile) - .then(() => { - this.serverless.cli.log(`Successfully uninstalled "${this.options.pluginName}"`); - return BbPromise.resolve(); - }); + .then(this.uninstallPeerDependencies) + .then(this.pluginUninstall) + .then(this.removePluginFromServerlessFile) + .then(() => { + this.serverless.cli.log(`Successfully uninstalled "${this.options.pluginName}"`); + return BbPromise.resolve(); + }); } const message = `Plugin "${this.options.pluginName}" not found. Did you spell it correct?`; throw new this.serverless.classes.Error(message); @@ -71,8 +67,9 @@ class PluginUninstall { } pluginUninstall() { - this.serverless.cli - .log(`Uninstalling plugin "${this.options.pluginName}" (this might take a few seconds...)`); + this.serverless.cli.log( + `Uninstalling plugin "${this.options.pluginName}" (this might take a few seconds...)` + ); return this.npmUninstall(this.options.pluginName); } @@ -89,9 +86,9 @@ class PluginUninstall { if (_.last(_.split(serverlessFilePath, '.')) === 'json') { return fse.readJsonAsync(serverlessFilePath).then(serverlessFileObj => { const isArrayPluginsObject = _.isArray(serverlessFileObj.plugins); - const plugins = isArrayPluginsObject ? - serverlessFileObj.plugins : - serverlessFileObj.plugins && serverlessFileObj.plugins.modules; + const plugins = isArrayPluginsObject + ? serverlessFileObj.plugins + : serverlessFileObj.plugins && serverlessFileObj.plugins.modules; if (plugins) { _.pull(plugins, this.options.pluginName); @@ -109,34 +106,43 @@ class PluginUninstall { } return this.serverless.yamlParser - .parse(serverlessFilePath) - .then((serverlessFileObj) => - yamlAstParser.removeExistingArrayItem( - serverlessFilePath, _.isArray(serverlessFileObj.plugins) ? - 'plugins' : 'plugins.modules', this.options.pluginName)); + .parse(serverlessFilePath) + .then(serverlessFileObj => + yamlAstParser.removeExistingArrayItem( + serverlessFilePath, + _.isArray(serverlessFileObj.plugins) ? 'plugins' : 'plugins.modules', + this.options.pluginName + ) + ); }); } uninstallPeerDependencies() { - const pluginPackageJsonFilePath = path.join(this.serverless.config.servicePath, - 'node_modules', this.options.pluginName, 'package.json'); - return fse.readJsonAsync(pluginPackageJsonFilePath).then(pluginPackageJson => { - if (pluginPackageJson.peerDependencies) { - const pluginsArray = []; - _.forEach(pluginPackageJson.peerDependencies, (v, k) => { - pluginsArray.push(k); - }); - return BbPromise.map(pluginsArray, this.npmUninstall); - } - return BbPromise.resolve(); - }).catch(() => BbPromise.resolve()); + const pluginPackageJsonFilePath = path.join( + this.serverless.config.servicePath, + 'node_modules', + this.options.pluginName, + 'package.json' + ); + return fse + .readJsonAsync(pluginPackageJsonFilePath) + .then(pluginPackageJson => { + if (pluginPackageJson.peerDependencies) { + const pluginsArray = []; + _.forEach(pluginPackageJson.peerDependencies, (v, k) => { + pluginsArray.push(k); + }); + return BbPromise.map(pluginsArray, this.npmUninstall); + } + return BbPromise.resolve(); + }) + .catch(() => BbPromise.resolve()); } npmUninstall(name) { - return childProcess - .execAsync(`npm uninstall --save-dev ${name}`, { - stdio: 'ignore', - }); + return childProcess.execAsync(`npm uninstall --save-dev ${name}`, { + stdio: 'ignore', + }); } trackPluginUninstall() { diff --git a/lib/plugins/plugin/uninstall/uninstall.test.js b/lib/plugins/plugin/uninstall/uninstall.test.js index d7e272e94..2c84c7c9b 100644 --- a/lib/plugins/plugin/uninstall/uninstall.test.js +++ b/lib/plugins/plugin/uninstall/uninstall.test.js @@ -58,8 +58,7 @@ describe('PluginUninstall', () => { let uninstallStub; beforeEach(() => { - uninstallStub = sinon - .stub(pluginUninstall, 'uninstall').returns(BbPromise.resolve()); + uninstallStub = sinon.stub(pluginUninstall, 'uninstall').returns(BbPromise.resolve()); sinon.stub(userStats, 'track').resolves(); }); @@ -87,12 +86,10 @@ describe('PluginUninstall', () => { expect(pluginUninstall.hooks['plugin:uninstall:uninstall']).to.not.equal(undefined); }); - it('should run promise chain in order for "plugin:uninstall:uninstall" hook', - () => expect(pluginUninstall.hooks['plugin:uninstall:uninstall']()) - .to.be.fulfilled.then(() => { - expect(uninstallStub.calledOnce).to.equal(true); - }) - ); + it('should run promise chain in order for "plugin:uninstall:uninstall" hook', () => + expect(pluginUninstall.hooks['plugin:uninstall:uninstall']()).to.be.fulfilled.then(() => { + expect(uninstallStub.calledOnce).to.equal(true); + })); }); describe('#uninstall()', () => { @@ -110,9 +107,7 @@ describe('PluginUninstall', () => { pluginUninstall.serverless.config.servicePath = servicePath; fse.ensureDirSync(servicePath); serverlessYmlFilePath = path.join(servicePath, 'serverless.yml'); - validateStub = sinon - .stub(pluginUninstall, 'validate') - .returns(BbPromise.resolve()); + validateStub = sinon.stub(pluginUninstall, 'validate').returns(BbPromise.resolve()); pluginUninstallStub = sinon .stub(pluginUninstall, 'pluginUninstall') .returns(BbPromise.resolve()); @@ -145,8 +140,7 @@ describe('PluginUninstall', () => { service: 'plugin-service', provider: 'aws', }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginUninstall.options.name = 'serverless-plugin-1'; @@ -167,8 +161,7 @@ describe('PluginUninstall', () => { service: 'plugin-service', provider: 'aws', }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginUninstall.options.name = 'serverless-not-available-plugin'; return expect(pluginUninstall.uninstall()).to.be.rejected.then(() => { @@ -187,8 +180,7 @@ describe('PluginUninstall', () => { service: 'plugin-service', provider: 'aws', }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginUninstall.options.name = 'serverless-plugin-1@1.0'; return expect(pluginUninstall.uninstall()).to.be.fulfilled.then(() => { expect(pluginUninstall.options.pluginName).to.be.equal('serverless-plugin-1'); @@ -208,8 +200,7 @@ describe('PluginUninstall', () => { pluginUninstall.serverless.config.servicePath = servicePath; fse.ensureDirSync(servicePath); packageJsonFilePath = path.join(servicePath, 'package.json'); - npmUninstallStub = sinon.stub(childProcess, 'execAsync') - .returns(BbPromise.resolve()); + npmUninstallStub = sinon.stub(childProcess, 'execAsync').returns(BbPromise.resolve()); // save the cwd so that we can restore it later savedCwd = process.cwd(); process.chdir(servicePath); @@ -231,15 +222,15 @@ describe('PluginUninstall', () => { }, }; - serverless.utils - .writeFileSync(packageJsonFilePath, packageJson); + serverless.utils.writeFileSync(packageJsonFilePath, packageJson); return expect(pluginUninstall.pluginUninstall()).to.be.fulfilled.then(() => { expect(consoleLogStub.called).to.equal(true); - expect(npmUninstallStub.calledWithExactly( - 'npm uninstall --save-dev serverless-plugin-1', - { stdio: 'ignore' } - )).to.equal(true); + expect( + npmUninstallStub.calledWithExactly('npm uninstall --save-dev serverless-plugin-1', { + stdio: 'ignore', + }) + ).to.equal(true); expect(serverlessErrorStub.calledOnce).to.equal(false); }); }); @@ -260,19 +251,16 @@ describe('PluginUninstall', () => { const serverlessYml = { service: 'plugin-service', provider: 'aws', - plugins: [ - 'serverless-existing-plugin', - 'serverless-plugin-1', - ], + plugins: ['serverless-existing-plugin', 'serverless-plugin-1'], }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginUninstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8').plugins) - .to.deep.equal(['serverless-existing-plugin']); + expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8').plugins).to.deep.equal([ + 'serverless-existing-plugin', + ]); }); }); @@ -281,18 +269,16 @@ describe('PluginUninstall', () => { const serverlessYml = { service: 'plugin-service', provider: 'aws', - plugins: [ - 'serverless-plugin-1', - ], + plugins: ['serverless-plugin-1'], }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginUninstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')) - .to.not.have.property('plugins'); + expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')).to.not.have.property( + 'plugins' + ); }); }); @@ -301,16 +287,14 @@ describe('PluginUninstall', () => { const serverlessYml = { service: 'plugin-service', provider: 'aws', - plugins: [ - 'serverless-plugin-1', - ], + plugins: ['serverless-plugin-1'], }; - serverless.utils - .writeFileSync(serverlessYamlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYamlFilePath, YAML.dump(serverlessYml)); pluginUninstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYamlFilePath, 'utf8')) - .to.not.have.property('plugins'); + expect(serverless.utils.readFileSync(serverlessYamlFilePath, 'utf8')).to.not.have.property( + 'plugins' + ); }); }); @@ -319,29 +303,31 @@ describe('PluginUninstall', () => { const serverlessJson = { service: 'plugin-service', provider: 'aws', - plugins: [ - 'serverless-plugin-1', - 'serverless-plugin-2', - ], + plugins: ['serverless-plugin-1', 'serverless-plugin-2'], }; - serverless.utils - .writeFileSync(serverlessJsonFilePath, serverlessJson); + serverless.utils.writeFileSync(serverlessJsonFilePath, serverlessJson); pluginUninstall.options.pluginName = 'serverless-plugin-1'; - return expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins) - .to.deep.equal(['serverless-plugin-2']); - pluginUninstall.options.pluginName = 'serverless-plugin-2'; - }) - .then(() => expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then( - () => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) - .to.not.have.property('plugins'); - })) - .then(() => expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then( - () => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) - .to.not.have.property('plugins'); - })); + return expect(pluginUninstall.removePluginFromServerlessFile()) + .to.be.fulfilled.then(() => { + expect( + serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins + ).to.deep.equal(['serverless-plugin-2']); + pluginUninstall.options.pluginName = 'serverless-plugin-2'; + }) + .then(() => + expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { + expect( + serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8') + ).to.not.have.property('plugins'); + }) + ) + .then(() => + expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { + expect( + serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8') + ).to.not.have.property('plugins'); + }) + ); }); it('should not modify serverless .js file', () => { @@ -350,10 +336,7 @@ describe('PluginUninstall', () => { const serverlessJson = { service: 'plugin-service', provider: 'aws', - plugins: [ - 'serverless-plugin-1', - 'serverless-plugin-2', - ], + plugins: ['serverless-plugin-1', 'serverless-plugin-2'], }; serverless.utils.writeFileSync( serverlessJsFilePath, @@ -363,8 +346,7 @@ describe('PluginUninstall', () => { return expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { // use require to load serverless.js // eslint-disable-next-line global-require - expect(require(serverlessJsFilePath).plugins) - .to.be.deep.equal(serverlessJson.plugins); + expect(require(serverlessJsFilePath).plugins).to.be.deep.equal(serverlessJson.plugins); }); }); }); @@ -377,25 +359,21 @@ describe('PluginUninstall', () => { provider: 'aws', plugins: { localPath: 'test', - modules: [ - 'serverless-existing-plugin', - 'serverless-plugin-1', - ], + modules: ['serverless-existing-plugin', 'serverless-plugin-1'], }, }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginUninstall.options.pluginName = 'serverless-plugin-1'; - return expect(pluginUninstall.removePluginFromServerlessFile()) - .to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8').plugins) - .to.deep.equal({ - localPath: 'test', - modules: ['serverless-existing-plugin'], - }); + return expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { + expect( + serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8').plugins + ).to.deep.equal({ + localPath: 'test', + modules: ['serverless-existing-plugin'], }); + }); }); it('should remove the plugin from the service if it is the only one', () => { @@ -405,21 +383,19 @@ describe('PluginUninstall', () => { provider: 'aws', plugins: { localPath: 'test', - modules: [ - 'serverless-plugin-1', - ], + modules: ['serverless-plugin-1'], }, }; - serverless.utils - .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + serverless.utils.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); pluginUninstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8').plugins) - .to.deep.equal({ - localPath: 'test', - }); + expect( + serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8').plugins + ).to.deep.equal({ + localPath: 'test', + }); }); }); @@ -430,39 +406,39 @@ describe('PluginUninstall', () => { provider: 'aws', plugins: { localPath: 'test', - modules: [ - 'serverless-plugin-1', - 'serverless-plugin-2', - ], + modules: ['serverless-plugin-1', 'serverless-plugin-2'], }, }; - serverless.utils - .writeFileSync(serverlessJsonFilePath, serverlessJson); + serverless.utils.writeFileSync(serverlessJsonFilePath, serverlessJson); pluginUninstall.options.pluginName = 'serverless-plugin-1'; - return expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins) - .to.deep.equal({ + return expect(pluginUninstall.removePluginFromServerlessFile()) + .to.be.fulfilled.then(() => { + expect( + serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins + ).to.deep.equal({ localPath: 'test', - modules: [ - 'serverless-plugin-2', - ], + modules: ['serverless-plugin-2'], }); - pluginUninstall.options.pluginName = 'serverless-plugin-2'; - }) - .then(() => expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then( - () => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins) - .to.deep.equal({ - localPath: 'test', - }); - })) - .then(() => expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then( - () => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins) - .to.deep.equal({ - localPath: 'test', - }); - })); + pluginUninstall.options.pluginName = 'serverless-plugin-2'; + }) + .then(() => + expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { + expect( + serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins + ).to.deep.equal({ + localPath: 'test', + }); + }) + ) + .then(() => + expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { + expect( + serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins + ).to.deep.equal({ + localPath: 'test', + }); + }) + ); }); }); }); @@ -480,13 +456,10 @@ describe('PluginUninstall', () => { pluginUninstall.options.pluginName = pluginName; servicePath = getTmpDirPath(); pluginUninstall.serverless.config.servicePath = servicePath; - pluginPath = path.join( - servicePath, 'node_modules', pluginName); + pluginPath = path.join(servicePath, 'node_modules', pluginName); fse.ensureDirSync(pluginPath); pluginPackageJsonFilePath = path.join(pluginPath, 'package.json'); - npmUninstallStub = sinon - .stub(childProcess, 'execAsync') - .returns(BbPromise.resolve()); + npmUninstallStub = sinon.stub(childProcess, 'execAsync').returns(BbPromise.resolve()); savedCwd = process.cwd(); process.chdir(servicePath); }); @@ -503,31 +476,32 @@ describe('PluginUninstall', () => { }, }); return expect(pluginUninstall.uninstallPeerDependencies()).to.be.fulfilled.then(() => { - expect(npmUninstallStub.calledWithExactly( - `npm uninstall --save-dev ${pluginName}`, - { stdio: 'ignore' } - )).to.equal(true); + expect( + npmUninstallStub.calledWithExactly(`npm uninstall --save-dev ${pluginName}`, { + stdio: 'ignore', + }) + ).to.equal(true); }); }); it('should not uninstall peerDependencies if an installed plugin does not have ones', () => { fse.writeJsonSync(pluginPackageJsonFilePath, {}); return expect(pluginUninstall.uninstallPeerDependencies()).to.be.fulfilled.then(() => { - expect(npmUninstallStub.calledWithExactly( - `npm uninstall --save-dev ${pluginName}`, - { stdio: 'ignore' } - )).to.equal(false); + expect( + npmUninstallStub.calledWithExactly(`npm uninstall --save-dev ${pluginName}`, { + stdio: 'ignore', + }) + ).to.equal(false); }); }); - it('should do nothing if an uninstalled plugin does not have package.json', - () => expect(pluginUninstall.uninstallPeerDependencies()).to.be.fulfilled.then( - () => { - expect(npmUninstallStub.calledWithExactly( - `npm uninstall --save-dev ${pluginName}`, - { stdio: 'ignore' } - )).to.equal(false); - }) - ); + it('should do nothing if an uninstalled plugin does not have package.json', () => + expect(pluginUninstall.uninstallPeerDependencies()).to.be.fulfilled.then(() => { + expect( + npmUninstallStub.calledWithExactly(`npm uninstall --save-dev ${pluginName}`, { + stdio: 'ignore', + }) + ).to.equal(false); + })); }); }); diff --git a/lib/plugins/print/print.js b/lib/plugins/print/print.js index d0dcffb32..650d52588 100644 --- a/lib/plugins/print/print.js +++ b/lib/plugins/print/print.js @@ -18,9 +18,7 @@ class Print { print: { usage: 'Print your compiled and resolved config file', configDependent: true, - lifecycleEvents: [ - 'print', - ], + lifecycleEvents: ['print'], options: { format: { usage: 'Print configuration in given format ("yaml", "json", "text"). Default: yaml', @@ -35,8 +33,7 @@ class Print { }, }; this.hooks = { - 'print:print': () => BbPromise.bind(this) - .then(this.print), + 'print:print': () => BbPromise.bind(this).then(this.print), }; } @@ -53,11 +50,14 @@ class Print { if (_.isString(this.cache.serviceProvider)) { service.provider = { name: this.cache.serviceProvider }; } - service.provider = _.merge({ - stage: 'dev', - region: 'us-east-1', - variableSyntax: '\\${([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}', - }, service.provider); + service.provider = _.merge( + { + stage: 'dev', + region: 'us-east-1', + variableSyntax: '\\${([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*]+?)}', + }, + service.provider + ); } strip(svc) { const service = svc; @@ -67,7 +67,8 @@ class Print { } if (_.isString(this.cache.serviceProvider)) { service.provider = this.cache.serviceProvider; - } else { // is object + } else { + // is object if (!this.cache.serviceProvider.stage) { delete service.provider.stage; } @@ -96,76 +97,73 @@ class Print { // the codebase. Avoiding that, this method must read the serverless.yml file itself, adorn it // as the Service class would and then populate it, reversing the adornments thereafter in // preparation for printing the service for the user. - return getServerlessConfigFile(this.serverless) - .then((svc) => { - const service = svc; - this.adorn(service); - // Need to delete variableSyntax to avoid self-matching errors - this.serverless.variables.loadVariableSyntax(); - delete service.provider.variableSyntax; // cached by adorn, restored by strip - this.serverless.variables.service = service; - return this.serverless.variables.populateObject(service) - .then((populated) => { - let conf = populated; - this.strip(conf); + return getServerlessConfigFile(this.serverless).then(svc => { + const service = svc; + this.adorn(service); + // Need to delete variableSyntax to avoid self-matching errors + this.serverless.variables.loadVariableSyntax(); + delete service.provider.variableSyntax; // cached by adorn, restored by strip + this.serverless.variables.service = service; + return this.serverless.variables.populateObject(service).then(populated => { + let conf = populated; + this.strip(conf); - // dig into the object - if (this.options.path) { - const steps = this.options.path.split('.'); - for (const step of steps) { - conf = conf[step]; + // dig into the object + if (this.options.path) { + const steps = this.options.path.split('.'); + for (const step of steps) { + conf = conf[step]; - if (conf === undefined) { - return BbPromise.reject( - new this.serverless.classes.Error(`Path "${this.options.path}" not found`) - ); - } - } - } - - // apply an optional filter - if (this.options.transform) { - if (this.options.transform === 'keys') { - conf = Object.keys(conf); - } else { - return BbPromise.reject( - new this.serverless.classes.Error('Transform can only be "keys"') - ); - } - } - - // print configuration in the specified format - const format = this.options.format || 'yaml'; - let out; - - if (format === 'text') { - if (_.isArray(conf)) { - out = conf.join(os.EOL); - } else { - if (_.isObject(conf)) { - return BbPromise.reject( - new this.serverless.classes.Error('Cannot print an object as "text"') - ); - } - - out = String(conf); - } - } else if (format === 'json') { - out = jc.stringify(conf, null, ' '); - } else if (format === 'yaml') { - out = YAML.dump(JSON.parse(jc.stringify(conf)), { noRefs: true }); - } else { + if (conf === undefined) { return BbPromise.reject( - new this.serverless.classes.Error('Format must be "yaml", "json" or "text"') + new this.serverless.classes.Error(`Path "${this.options.path}" not found`) + ); + } + } + } + + // apply an optional filter + if (this.options.transform) { + if (this.options.transform === 'keys') { + conf = Object.keys(conf); + } else { + return BbPromise.reject( + new this.serverless.classes.Error('Transform can only be "keys"') + ); + } + } + + // print configuration in the specified format + const format = this.options.format || 'yaml'; + let out; + + if (format === 'text') { + if (_.isArray(conf)) { + out = conf.join(os.EOL); + } else { + if (_.isObject(conf)) { + return BbPromise.reject( + new this.serverless.classes.Error('Cannot print an object as "text"') ); } - this.serverless.cli.consoleLog(out); - return BbPromise.resolve(); - }); - }); - } + out = String(conf); + } + } else if (format === 'json') { + out = jc.stringify(conf, null, ' '); + } else if (format === 'yaml') { + out = YAML.dump(JSON.parse(jc.stringify(conf)), { noRefs: true }); + } else { + return BbPromise.reject( + new this.serverless.classes.Error('Format must be "yaml", "json" or "text"') + ); + } + this.serverless.cli.consoleLog(out); + return BbPromise.resolve(); + }); + }); + } } module.exports = Print; diff --git a/lib/plugins/print/print.test.js b/lib/plugins/print/print.test.js index 3e6e8e7e6..979e16704 100644 --- a/lib/plugins/print/print.test.js +++ b/lib/plugins/print/print.test.js @@ -262,10 +262,10 @@ describe('Print', () => { provider: { name: 'aws', stage: '${{opt:stage}}', - variableSyntax: "\\${{([ ~:a-zA-Z0-9._@\\'\",\\-\\/\\(\\)*]+?)}}", + variableSyntax: '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)*]+?)}}', }, }; - serverless.service.provider.variableSyntax = "\\${{([ ~:a-zA-Z0-9._@\\'\",\\-\\/\\(\\)*]+?)}}"; + serverless.service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)*]+?)}}'; getServerlessConfigFileStub.resolves(conf); serverless.processedInput = { @@ -278,7 +278,7 @@ describe('Print', () => { provider: { name: 'aws', stage: 'dev', - variableSyntax: "\\${{([ ~:a-zA-Z0-9._@\\'\",\\-\\/\\(\\)*]+?)}}", + variableSyntax: '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)*]+?)}}', }, }; diff --git a/lib/plugins/remove/remove.js b/lib/plugins/remove/remove.js index 41b70f8ea..937e49760 100644 --- a/lib/plugins/remove/remove.js +++ b/lib/plugins/remove/remove.js @@ -11,9 +11,7 @@ class Remove { remove: { usage: 'Remove Serverless service and all resources', configDependent: true, - lifecycleEvents: [ - 'remove', - ], + lifecycleEvents: ['remove'], options: { stage: { usage: 'Stage of the service', diff --git a/lib/plugins/remove/remove.test.js b/lib/plugins/remove/remove.test.js index 9006a2518..9881ac61d 100644 --- a/lib/plugins/remove/remove.test.js +++ b/lib/plugins/remove/remove.test.js @@ -38,8 +38,9 @@ describe('Remove', () => { remove.track.restore(); }); - it('should track the execution', () => expect(remove.hooks['after:remove:remove']()) - .to.be.fulfilled.then(() => expect(trackStub).to.be.called) - ); + it('should track the execution', () => + expect(remove.hooks['after:remove:remove']()).to.be.fulfilled.then( + () => expect(trackStub).to.be.called + )); }); }); diff --git a/lib/plugins/rollback/index.js b/lib/plugins/rollback/index.js index a16b1e54e..93c402093 100644 --- a/lib/plugins/rollback/index.js +++ b/lib/plugins/rollback/index.js @@ -11,10 +11,7 @@ class Rollback { rollback: { usage: 'Rollback the Serverless service to a specific deployment', configDependent: true, - lifecycleEvents: [ - 'initialize', - 'rollback', - ], + lifecycleEvents: ['initialize', 'rollback'], options: { timestamp: { usage: 'Timestamp of the deployment (list deployments with `serverless deploy list`)', @@ -29,9 +26,7 @@ class Rollback { commands: { function: { usage: 'Rollback the function to the previous version', - lifecycleEvents: [ - 'rollback', - ], + lifecycleEvents: ['rollback'], options: { function: { usage: 'Name of the function', diff --git a/lib/plugins/slstats/slstats.js b/lib/plugins/slstats/slstats.js index 127432905..56d6c15e2 100644 --- a/lib/plugins/slstats/slstats.js +++ b/lib/plugins/slstats/slstats.js @@ -13,9 +13,7 @@ class SlStats { slstats: { usage: 'Enable or disable stats', configDependent: true, - lifecycleEvents: [ - 'slstats', - ], + lifecycleEvents: ['slstats'], options: { enable: { usage: 'Enable stats ("--enable")', @@ -39,24 +37,26 @@ class SlStats { const disabledStats = this.options.disable && !this.options.enable; const data = { force: true }; if (enableStats) { - return userStats.track('user_enabledTracking', data) + return userStats + .track('user_enabledTracking', data) .then(() => { // set .serverlessrc config config.set('trackingDisabled', false); this.serverless.cli.log('Stats successfully enabled'); }) - .catch((error) => { + .catch(error => { const message = error; return BbPromise.reject(`Enabling / Disabling of statistics failed: ${message}`); }); } else if (disabledStats) { - return userStats.track('user_disabledTracking', data) + return userStats + .track('user_disabledTracking', data) .then(() => { // set .serverlessrc config config.set('trackingDisabled', true); this.serverless.cli.log('Stats successfully disabled'); }) - .catch((error) => { + .catch(error => { const message = error; return BbPromise.reject(`Enabling / Disabling of statistics failed: ${message}`); }); diff --git a/lib/plugins/slstats/slstats.test.js b/lib/plugins/slstats/slstats.test.js index 3affbb65c..03314a8ab 100644 --- a/lib/plugins/slstats/slstats.test.js +++ b/lib/plugins/slstats/slstats.test.js @@ -49,16 +49,12 @@ describe('SlStats', () => { return slStats.toggleStats().then(() => { expect(trackStub.calledOnce).to.equal(true); - expect(trackStub.calledWithExactly( - 'user_disabledTracking', - { force: true } - )).to.equal(true); + expect(trackStub.calledWithExactly('user_disabledTracking', { force: true })).to.equal( + true + ); expect(setStub.calledOnce).to.equal(true); - expect(setStub.calledWithExactly( - 'trackingDisabled', - true - )).to.equal(true); + expect(setStub.calledWithExactly('trackingDisabled', true)).to.equal(true); }); }); @@ -69,16 +65,10 @@ describe('SlStats', () => { return slStats.toggleStats().then(() => { expect(trackStub.calledOnce).to.equal(true); - expect(trackStub.calledWithExactly( - 'user_enabledTracking', - { force: true } - )).to.equal(true); + expect(trackStub.calledWithExactly('user_enabledTracking', { force: true })).to.equal(true); expect(setStub.calledOnce).to.equal(true); - expect(setStub.calledWithExactly( - 'trackingDisabled', - false - )).to.equal(true); + expect(setStub.calledWithExactly('trackingDisabled', false)).to.equal(true); }); }); @@ -99,7 +89,7 @@ describe('SlStats', () => { trackStub.rejects('error while tracking'); slStats.options = { enable: true }; - return slStats.toggleStats().catch((error) => { + return slStats.toggleStats().catch(error => { expect(trackStub.calledOnce).to.equal(true); expect(setStub.calledOnce).to.equal(false); expect(error).to.match(/of statistics failed/); @@ -112,7 +102,7 @@ describe('SlStats', () => { trackStub.resolves(); slStats.options = { disable: true }; - return slStats.toggleStats().catch((error) => { + return slStats.toggleStats().catch(error => { expect(trackStub.calledOnce).to.equal(true); expect(setStub.calledOnce).to.equal(true); expect(error).to.match(/of statistics failed/); diff --git a/lib/utils/autocomplete.js b/lib/utils/autocomplete.js index 5381da6d0..47c8319a6 100644 --- a/lib/utils/autocomplete.js +++ b/lib/utils/autocomplete.js @@ -12,7 +12,7 @@ const name = path.basename(process.argv[0]); const tab = require('tabtab')({ name }); -const getSuggestions = (commands) => { +const getSuggestions = commands => { tab.on(name, (data, done) => { if (data.words === 1) { done(null, Object.keys(commands)); @@ -31,8 +31,10 @@ const getSuggestions = (commands) => { }; const cacheFileValid = (serverlessConfigFile, validationHash) => { - const serverlessConfigFileHash = crypto.createHash('sha256') - .update(JSON.stringify(serverlessConfigFile)).digest('hex'); + const serverlessConfigFileHash = crypto + .createHash('sha256') + .update(JSON.stringify(serverlessConfigFile)) + .digest('hex'); if (validationHash === serverlessConfigFileHash) { return true; } @@ -41,21 +43,23 @@ const cacheFileValid = (serverlessConfigFile, validationHash) => { const autocomplete = () => { const servicePath = process.cwd(); - return getServerlessConfigFile({ processedInput: { options: {} }, config: { servicePath } }) - .then((serverlessConfigFile) => getCacheFile(servicePath) - .then((cacheFile) => { - if (!cacheFile || !cacheFileValid(serverlessConfigFile, cacheFile.validationHash)) { - const serverless = new Serverless(); - return serverless.init().then(() => getCacheFile(servicePath)); - } - return cacheFile; - }) - .then((cacheFile) => { - if (!cacheFile || !cacheFileValid(serverlessConfigFile, cacheFile.validationHash)) { - return; - } - return getSuggestions(cacheFile.commands); // eslint-disable-line consistent-return - })); + return getServerlessConfigFile({ processedInput: { options: {} }, config: { servicePath } }).then( + serverlessConfigFile => + getCacheFile(servicePath) + .then(cacheFile => { + if (!cacheFile || !cacheFileValid(serverlessConfigFile, cacheFile.validationHash)) { + const serverless = new Serverless(); + return serverless.init().then(() => getCacheFile(servicePath)); + } + return cacheFile; + }) + .then(cacheFile => { + if (!cacheFile || !cacheFileValid(serverlessConfigFile, cacheFile.validationHash)) { + return; + } + return getSuggestions(cacheFile.commands); // eslint-disable-line consistent-return + }) + ); }; module.exports = autocomplete; diff --git a/lib/utils/config/index.js b/lib/utils/config/index.js index 786eb11de..0d256b851 100644 --- a/lib/utils/config/index.js +++ b/lib/utils/config/index.js @@ -27,7 +27,7 @@ function createConfig() { enterpriseDisabled: false, meta: { created_at: Math.round(+new Date() / 1000), // config file creation date - updated_at: null, // config file updated date + updated_at: null, // config file updated date }, }; diff --git a/lib/utils/downloadTemplateFromRepo.js b/lib/utils/downloadTemplateFromRepo.js index accd32314..49eaa7fe1 100644 --- a/lib/utils/downloadTemplateFromRepo.js +++ b/lib/utils/downloadTemplateFromRepo.js @@ -38,13 +38,9 @@ function getPathDirectory(length, parts) { * @param {String} repo */ function validateUrl(url, hostname, service, owner, repo) { - // validate if given url is a valid url + // validate if given url is a valid url if (url.hostname !== hostname || !owner || !repo) { - const errorMessage = `The URL must be a valid ${ - service - } URL in the following format: https://${ - hostname - }/serverless/serverless`; + const errorMessage = `The URL must be a valid ${service} URL in the following format: https://${hostname}/serverless/serverless`; throw new ServerlessError(errorMessage); } } @@ -64,7 +60,9 @@ function parseGitHubURL(url) { // validate if given url is a valid GitHub url validateUrl(url, 'github.com', 'GitHub', owner, repo); - const downloadUrl = `https://${url.auth ? `${url.auth}@` : ''}github.com/${owner}/${repo}/archive/${branch}.zip`; + const downloadUrl = `https://${ + url.auth ? `${url.auth}@` : '' + }github.com/${owner}/${repo}/archive/${branch}.zip`; return { owner, @@ -205,22 +203,25 @@ function downloadTemplateFromRepo(inputUrl, templateName, downloadPath) { log(`Downloading and installing "${serviceName}"...`); // download service - return download( - repoInformation.downloadUrl, - downloadServicePath, - { timeout: 30000, extract: true, strip: 1, mode: '755' } - ).then(() => { - // if it's a directory inside of git - if (repoInformation.isSubdirectory) { - const directory = path.join(downloadServicePath, repoInformation.pathToDirectory); - copyDirContentsSync(directory, servicePath); - fse.removeSync(downloadServicePath); - } - }).then(() => { - if (renamed) renameService(dirName, servicePath); + return download(repoInformation.downloadUrl, downloadServicePath, { + timeout: 30000, + extract: true, + strip: 1, + mode: '755', + }) + .then(() => { + // if it's a directory inside of git + if (repoInformation.isSubdirectory) { + const directory = path.join(downloadServicePath, repoInformation.pathToDirectory); + copyDirContentsSync(directory, servicePath); + fse.removeSync(downloadServicePath); + } + }) + .then(() => { + if (renamed) renameService(dirName, servicePath); - return BbPromise.resolve(serviceName); - }); + return BbPromise.resolve(serviceName); + }); } module.exports = { diff --git a/lib/utils/downloadTemplateFromRepo.test.js b/lib/utils/downloadTemplateFromRepo.test.js index 8fad468f5..b035d375a 100644 --- a/lib/utils/downloadTemplateFromRepo.test.js +++ b/lib/utils/downloadTemplateFromRepo.test.js @@ -58,7 +58,9 @@ describe('downloadTemplateFromRepo', () => { const serviceDirName = path.join(servicePath, 'existing-service'); fse.mkdirsSync(serviceDirName); - expect(() => downloadTemplateFromRepo('https://github.com/johndoe/existing-service')).to.throw(Error); + expect(() => + downloadTemplateFromRepo('https://github.com/johndoe/existing-service') + ).to.throw(Error); }); it('should download the service based on the GitHub URL', () => { @@ -83,16 +85,14 @@ describe('downloadTemplateFromRepo', () => { const name = 'new-service-name'; downloadStub.returns( - remove(newServicePath) - .then(() => { - const sp = downloadStub.args[0][1]; - const slsYml = path.join( - sp, - 'serverless.yml'); - writeFileSync(slsYml, 'service: service-name'); - })); + remove(newServicePath).then(() => { + const sp = downloadStub.args[0][1]; + const slsYml = path.join(sp, 'serverless.yml'); + writeFileSync(slsYml, 'service: service-name'); + }) + ); - return downloadTemplateFromRepo(url, name).then((serviceName) => { + return downloadTemplateFromRepo(url, name).then(serviceName => { expect(downloadStub.calledOnce).to.equal(true); expect(downloadStub.args[0][1]).to.contain(name); expect(downloadStub.args[0][0]).to.equal(`${url}/archive/master.zip`); @@ -107,17 +107,14 @@ describe('downloadTemplateFromRepo', () => { const name = 'new-service-name'; downloadStub.returns( - remove(newServicePath) - .then(() => { - const sp = downloadStub.args[0][1]; - const slsYml = path.join( - sp, - 'rest-api-with-dynamodb', - 'serverless.yml'); - writeFileSync(slsYml, 'service: service-name'); - })); + remove(newServicePath).then(() => { + const sp = downloadStub.args[0][1]; + const slsYml = path.join(sp, 'rest-api-with-dynamodb', 'serverless.yml'); + writeFileSync(slsYml, 'service: service-name'); + }) + ); - return downloadTemplateFromRepo(url, name).then((serviceName) => { + return downloadTemplateFromRepo(url, name).then(serviceName => { expect(downloadStub.calledOnce).to.equal(true); const yml = readFileSync(path.join(newServicePath, 'serverless.yml')); expect(yml.service).to.equal(name); @@ -187,7 +184,9 @@ describe('downloadTemplateFromRepo', () => { }); it('should parse a valid BitBucket URL with subdirectory', () => { - const output = parseRepoURL('https://bitbucket.org/atlassian/localstack/src/85870856fd6941ae75c0fa946a51cf756ff2f53a/localstack/dashboard/?at=mvn'); + const output = parseRepoURL( + 'https://bitbucket.org/atlassian/localstack/src/85870856fd6941ae75c0fa946a51cf756ff2f53a/localstack/dashboard/?at=mvn' + ); expect(output).to.deep.eq({ owner: 'atlassian', @@ -206,7 +205,8 @@ describe('downloadTemplateFromRepo', () => { owner: 'serverless', repo: 'serverless', branch: 'master', - downloadUrl: 'https://gitlab.com/serverless/serverless/-/archive/master/serverless-master.zip', + downloadUrl: + 'https://gitlab.com/serverless/serverless/-/archive/master/serverless-master.zip', isSubdirectory: false, pathToDirectory: '', }); diff --git a/lib/utils/fs/fileExists.js b/lib/utils/fs/fileExists.js index 7edf920e9..81a829ad5 100644 --- a/lib/utils/fs/fileExists.js +++ b/lib/utils/fs/fileExists.js @@ -3,8 +3,9 @@ const fse = require('./fse'); function fileExists(filePath) { - return fse.lstatAsync(filePath) - .then((stats) => stats.isFile()) + return fse + .lstatAsync(filePath) + .then(stats => stats.isFile()) .catch(() => false); } diff --git a/lib/utils/fs/fileExists.test.js b/lib/utils/fs/fileExists.test.js index 71bf001d1..159a78db6 100644 --- a/lib/utils/fs/fileExists.test.js +++ b/lib/utils/fs/fileExists.test.js @@ -11,10 +11,10 @@ const expect = require('chai').expect; describe('#fileExists()', () => { describe('When reading a file', () => { - it('should detect if a file exists', () => expect(fileExists(__filename)) - .to.eventually.equal(true)); + it('should detect if a file exists', () => + expect(fileExists(__filename)).to.eventually.equal(true)); - it('should detect if a file doesn\'t exist', () => expect(fileExists(path - .join(__dirname, 'XYZ.json'))).to.eventually.equal(false)); + it("should detect if a file doesn't exist", () => + expect(fileExists(path.join(__dirname, 'XYZ.json'))).to.eventually.equal(false)); }); }); diff --git a/lib/utils/fs/fileExistsSync.test.js b/lib/utils/fs/fileExistsSync.test.js index 34ab7fefe..e6e3b6e85 100644 --- a/lib/utils/fs/fileExistsSync.test.js +++ b/lib/utils/fs/fileExistsSync.test.js @@ -11,7 +11,7 @@ describe('#fileExistsSync()', () => { expect(file).to.equal(true); }); - it('should detect if a file doesn\'t exist', () => { + it("should detect if a file doesn't exist", () => { const noFile = fileExistsSync(path.join(__dirname, 'XYZ.json')); expect(noFile).to.equal(false); }); diff --git a/lib/utils/fs/parse.test.js b/lib/utils/fs/parse.test.js index 8473e925e..c0c0f72bd 100644 --- a/lib/utils/fs/parse.test.js +++ b/lib/utils/fs/parse.test.js @@ -112,7 +112,6 @@ describe('#parse()', () => { }); }); - it('should parse YAML without shorthand syntax', () => { const tmpFilePath = 'anything.yml'; const fileContents = 'Item:\n Fn::Join:\n - ""\n - - "arn:aws:s3::"\n - !Ref MyBucket'; diff --git a/lib/utils/fs/readFile.js b/lib/utils/fs/readFile.js index b494baff9..81f571ffb 100644 --- a/lib/utils/fs/readFile.js +++ b/lib/utils/fs/readFile.js @@ -4,8 +4,7 @@ const fse = require('./fse'); const parse = require('./parse'); function readFile(filePath) { - return fse.readFileAsync(filePath, 'utf8') - .then((contents) => parse(filePath, contents)); + return fse.readFileAsync(filePath, 'utf8').then(contents => parse(filePath, contents)); } module.exports = readFile; diff --git a/lib/utils/fs/readFile.test.js b/lib/utils/fs/readFile.test.js index 9809d808c..d2383b20d 100644 --- a/lib/utils/fs/readFile.test.js +++ b/lib/utils/fs/readFile.test.js @@ -14,27 +14,31 @@ describe('#readFile()', () => { it('should read a file asynchronously', () => { const tmpFilePath = getTmpFilePath('anything.json'); - return writeFile(tmpFilePath, { foo: 'bar' }) - .then(() => expect(readFile(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' })); + return writeFile(tmpFilePath, { foo: 'bar' }).then(() => + expect(readFile(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' }) + ); }); it('should read a filename extension .yml', () => { const tmpFilePath = getTmpFilePath('anything.yml'); - return writeFile(tmpFilePath, { foo: 'bar' }) - .then(() => expect(readFile(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' })); + return writeFile(tmpFilePath, { foo: 'bar' }).then(() => + expect(readFile(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' }) + ); }); it('should read a filename extension .yaml', () => { const tmpFilePath = getTmpFilePath('anything.yaml'); - return writeFile(tmpFilePath, { foo: 'bar' }) - .then(() => expect(readFile(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' })); + return writeFile(tmpFilePath, { foo: 'bar' }).then(() => + expect(readFile(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' }) + ); }); it('should throw YAMLException with filename if yml file is invalid format', () => { const tmpFilePath = getTmpFilePath('invalid.yml'); - return writeFile(tmpFilePath, ': a').then(() => readFile(tmpFilePath)) + return writeFile(tmpFilePath, ': a') + .then(() => readFile(tmpFilePath)) .catch(e => { expect(e.name).to.equal('YAMLException'); expect(e.message).to.match(new RegExp('.*invalid.yml')); diff --git a/lib/utils/fs/readFileIfExists.js b/lib/utils/fs/readFileIfExists.js index 9c7fdd70e..4446a45ca 100644 --- a/lib/utils/fs/readFileIfExists.js +++ b/lib/utils/fs/readFileIfExists.js @@ -4,14 +4,13 @@ const fileExists = require('./fileExists'); const readFile = require('./readFile'); const BbPromise = require('bluebird'); -const readFileIfExists = function (filePath) { - return fileExists(filePath) - .then((exists) => { - if (!exists) { - return BbPromise.resolve(false); - } - return readFile(filePath); - }); +const readFileIfExists = function(filePath) { + return fileExists(filePath).then(exists => { + if (!exists) { + return BbPromise.resolve(false); + } + return readFile(filePath); + }); }; module.exports = readFileIfExists; diff --git a/lib/utils/fs/readFileIfExists.test.js b/lib/utils/fs/readFileIfExists.test.js index 71d89e870..46a0c2d02 100644 --- a/lib/utils/fs/readFileIfExists.test.js +++ b/lib/utils/fs/readFileIfExists.test.js @@ -10,13 +10,13 @@ chai.use(require('sinon-chai')); const expect = require('chai').expect; describe('#readFileIfExists()', () => { - it('should resolve with file content if file exists', () => readFileIfExists(__filename) - .then(content => { + it('should resolve with file content if file exists', () => + readFileIfExists(__filename).then(content => { expect(content).to.not.equal(false); expect(content).to.not.equal(undefined); expect(typeof content).to.equal('string'); })); - it('should resolve with false if file does not exist', () => expect(readFileIfExists(path - .join(__dirname, 'XYZ.json'))).to.eventually.equal(false)); + it('should resolve with false if file does not exist', () => + expect(readFileIfExists(path.join(__dirname, 'XYZ.json'))).to.eventually.equal(false)); }); diff --git a/lib/utils/fs/walkDirSync.js b/lib/utils/fs/walkDirSync.js index 150e856b0..d0038cedd 100644 --- a/lib/utils/fs/walkDirSync.js +++ b/lib/utils/fs/walkDirSync.js @@ -4,12 +4,15 @@ const path = require('path'); const fs = require('fs'); function walkDirSync(dirPath, opts) { - const options = Object.assign({ - noLinks: false, - }, opts); + const options = Object.assign( + { + noLinks: false, + }, + opts + ); let filePaths = []; const list = fs.readdirSync(dirPath); - list.forEach((filePathParam) => { + list.forEach(filePathParam => { let filePath = filePathParam; filePath = path.join(dirPath, filePath); const stat = options.noLinks ? fs.lstatSync(filePath) : fs.statSync(filePath); diff --git a/lib/utils/fs/walkDirSync.test.js b/lib/utils/fs/walkDirSync.test.js index 4780e071a..04ffea2f0 100644 --- a/lib/utils/fs/walkDirSync.test.js +++ b/lib/utils/fs/walkDirSync.test.js @@ -31,7 +31,7 @@ describe('#walkDirSync()', () => { expect(filePaths).to.include(tmpFilePath3); }); - it('should check noLinks option', function () { + it('should check noLinks option', function() { const tmpDirPath = getTmpDirPath(); const realFile = path.join(tmpDirPath, 'real'); diff --git a/lib/utils/fs/writeFile.js b/lib/utils/fs/writeFile.js index abeef07e0..42defb6a7 100644 --- a/lib/utils/fs/writeFile.js +++ b/lib/utils/fs/writeFile.js @@ -8,25 +8,24 @@ const YAML = require('js-yaml'); function writeFile(filePath, conts, cycles) { let contents = conts || ''; - return fse.mkdirsAsync(path.dirname(filePath)) - .then(() => { - if (filePath.indexOf('.json') !== -1 && typeof contents !== 'string') { - if (cycles) { - contents = jc.stringify(contents, null, 2); - } else { - contents = JSON.stringify(contents, null, 2); - } + return fse.mkdirsAsync(path.dirname(filePath)).then(() => { + if (filePath.indexOf('.json') !== -1 && typeof contents !== 'string') { + if (cycles) { + contents = jc.stringify(contents, null, 2); + } else { + contents = JSON.stringify(contents, null, 2); } + } - const yamlFileExists = (filePath.indexOf('.yaml') !== -1); - const ymlFileExists = (filePath.indexOf('.yml') !== -1); + const yamlFileExists = filePath.indexOf('.yaml') !== -1; + const ymlFileExists = filePath.indexOf('.yml') !== -1; - if ((yamlFileExists || ymlFileExists) && typeof contents !== 'string') { - contents = YAML.dump(contents); - } + if ((yamlFileExists || ymlFileExists) && typeof contents !== 'string') { + contents = YAML.dump(contents); + } - return fse.writeFileAsync(filePath, contents); - }); + return fse.writeFileAsync(filePath, contents); + }); } module.exports = writeFile; diff --git a/lib/utils/fs/writeFile.test.js b/lib/utils/fs/writeFile.test.js index 01c4addda..35aefabba 100644 --- a/lib/utils/fs/writeFile.test.js +++ b/lib/utils/fs/writeFile.test.js @@ -12,7 +12,7 @@ chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); const expect = require('chai').expect; -describe('#writeFile()', function () { +describe('#writeFile()', function() { let serverless; this.timeout(0); @@ -22,25 +22,25 @@ describe('#writeFile()', function () { it('should write a .json file asynchronously', () => { const tmpFilePath = getTmpFilePath('anything.json'); - return writeFile(tmpFilePath, { foo: 'bar' }) - .then(() => expect(readFile(tmpFilePath)) - .to.eventually.deep.equal({ foo: 'bar' })); + return writeFile(tmpFilePath, { foo: 'bar' }).then(() => + expect(readFile(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' }) + ); }); it('should write a .yml file synchronously', () => { const tmpFilePath = getTmpFilePath('anything.yml'); - return writeFile(tmpFilePath, { foo: 'bar' }) - .then(() => expect(serverless.yamlParser.parse(tmpFilePath)) - .to.eventually.deep.equal({ foo: 'bar' })); + return writeFile(tmpFilePath, { foo: 'bar' }).then(() => + expect(serverless.yamlParser.parse(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' }) + ); }); it('should write a .yaml file synchronously', () => { const tmpFilePath = getTmpFilePath('anything.yaml'); - return writeFile(tmpFilePath, { foo: 'bar' }) - .then(() => expect(serverless.yamlParser.parse(tmpFilePath)) - .to.eventually.deep.equal({ foo: 'bar' })); + return writeFile(tmpFilePath, { foo: 'bar' }).then(() => + expect(serverless.yamlParser.parse(tmpFilePath)).to.eventually.deep.equal({ foo: 'bar' }) + ); }); it('should be able to write an object with circular references', () => { @@ -49,9 +49,8 @@ describe('#writeFile()', function () { bar.foo = bar; const expected = '{\n "foo": {\n "$ref": "$"\n }\n}'; - return writeFile(tmpFilePath, bar, true) - .then(() => - expect(fse.readFileAsync(tmpFilePath, 'utf8')).to.eventually.equal(expected) - ); + return writeFile(tmpFilePath, bar, true).then(() => + expect(fse.readFileAsync(tmpFilePath, 'utf8')).to.eventually.equal(expected) + ); }); }); diff --git a/lib/utils/fs/writeFileSync.js b/lib/utils/fs/writeFileSync.js index 6e58263c4..2119a88a7 100644 --- a/lib/utils/fs/writeFileSync.js +++ b/lib/utils/fs/writeFileSync.js @@ -18,8 +18,8 @@ function writeFileSync(filePath, conts, cycles) { } } - const yamlFileExists = (filePath.indexOf('.yaml') !== -1); - const ymlFileExists = (filePath.indexOf('.yml') !== -1); + const yamlFileExists = filePath.indexOf('.yaml') !== -1; + const ymlFileExists = filePath.indexOf('.yml') !== -1; if ((yamlFileExists || ymlFileExists) && typeof contents !== 'string') { contents = YAML.dump(contents); diff --git a/lib/utils/fs/writeFileSync.test.js b/lib/utils/fs/writeFileSync.test.js index c43337fb8..26a907a45 100644 --- a/lib/utils/fs/writeFileSync.test.js +++ b/lib/utils/fs/writeFileSync.test.js @@ -28,7 +28,7 @@ describe('#writeFileSync()', () => { writeFileSync(tmpFilePath, { foo: 'bar' }); - return serverless.yamlParser.parse(tmpFilePath).then((obj) => { + return serverless.yamlParser.parse(tmpFilePath).then(obj => { expect(obj.foo).to.equal('bar'); }); }); @@ -38,13 +38,15 @@ describe('#writeFileSync()', () => { writeFileSync(tmpFilePath, { foo: 'bar' }); - return serverless.yamlParser.parse(tmpFilePath).then((obj) => { + return serverless.yamlParser.parse(tmpFilePath).then(obj => { expect(obj.foo).to.equal('bar'); }); }); it('should throw error if invalid path is provided', () => { - expect(() => { writeFileSync(null); }).to.throw(Error); + expect(() => { + writeFileSync(null); + }).to.throw(Error); }); it('should be able to write an object with circular references', () => { @@ -55,7 +57,7 @@ describe('#writeFileSync()', () => { writeFileSync(tmpFilePath, bar, true); - return fse.readFileAsync(tmpFilePath, 'utf8').then((contents) => { + return fse.readFileAsync(tmpFilePath, 'utf8').then(contents => { expect(contents).to.equal(expected); }); }); diff --git a/lib/utils/getCacheFile.js b/lib/utils/getCacheFile.js index 8a51706b7..e44ffda69 100644 --- a/lib/utils/getCacheFile.js +++ b/lib/utils/getCacheFile.js @@ -4,15 +4,14 @@ const fileExists = require('./fs/fileExists'); const readFile = require('./fs/readFile'); const getCacheFilePath = require('./getCacheFilePath'); -const getCacheFile = function (servicePath) { +const getCacheFile = function(servicePath) { const cacheFilePath = getCacheFilePath(servicePath); - return fileExists(cacheFilePath) - .then((exists) => { - if (!exists) { - return false; - } - return readFile(cacheFilePath); - }); + return fileExists(cacheFilePath).then(exists => { + if (!exists) { + return false; + } + return readFile(cacheFilePath); + }); }; module.exports = getCacheFile; diff --git a/lib/utils/getCacheFilePath.js b/lib/utils/getCacheFilePath.js index adf6d5d25..72ec74097 100644 --- a/lib/utils/getCacheFilePath.js +++ b/lib/utils/getCacheFilePath.js @@ -4,9 +4,12 @@ const homedir = require('os').homedir(); const path = require('path'); const crypto = require('crypto'); -const getCacheFilePath = function (srvcPath) { +const getCacheFilePath = function(srvcPath) { const servicePath = srvcPath || process.cwd(); - const servicePathHash = crypto.createHash('sha256').update(servicePath).digest('hex'); + const servicePathHash = crypto + .createHash('sha256') + .update(servicePath) + .digest('hex'); return path.join(homedir, '.serverless', 'cache', servicePathHash, 'autocomplete.json'); }; diff --git a/lib/utils/getCommandSuggestion.js b/lib/utils/getCommandSuggestion.js index 3e4604ed8..85f22bf39 100644 --- a/lib/utils/getCommandSuggestion.js +++ b/lib/utils/getCommandSuggestion.js @@ -3,8 +3,8 @@ const _ = require('lodash'); const levenshtein = require('fast-levenshtein'); const getCollectCommandWords = (commandObject, commandWordsArray) => { - let wordsArray = _.isArray(commandWordsArray) - && !_.isEmpty(commandWordsArray) ? commandWordsArray : []; + let wordsArray = + _.isArray(commandWordsArray) && !_.isEmpty(commandWordsArray) ? commandWordsArray : []; _.forEach(commandObject, (commandChildObject, commandChildName) => { wordsArray.push(commandChildName); if (commandChildObject.commands) { diff --git a/lib/utils/getServerlessConfigFile.js b/lib/utils/getServerlessConfigFile.js index c185b1d1d..a6fa2c259 100644 --- a/lib/utils/getServerlessConfigFile.js +++ b/lib/utils/getServerlessConfigFile.js @@ -6,7 +6,7 @@ const path = require('path'); const fileExists = require('./fs/fileExists'); const readFile = require('./fs/readFile'); -const getServerlessConfigFilePath = (serverless) => { +const getServerlessConfigFilePath = serverless => { const servicePath = serverless.config.servicePath || process.cwd(); if (serverless.processedInput.options.config) { @@ -39,35 +39,36 @@ const getServerlessConfigFilePath = (serverless) => { }); }; -const handleJsConfigFile = (jsConfigFile) => BbPromise.try(() => { - // use require to load serverless.js - // eslint-disable-next-line global-require - const configExport = require(jsConfigFile); - // In case of a promise result, first resolve it. - return configExport; -}).then(config => { - if (_.isPlainObject(config)) { - return config; - } - throw new Error('serverless.js must export plain object'); -}); +const handleJsConfigFile = jsConfigFile => + BbPromise.try(() => { + // use require to load serverless.js + // eslint-disable-next-line global-require + const configExport = require(jsConfigFile); + // In case of a promise result, first resolve it. + return configExport; + }).then(config => { + if (_.isPlainObject(config)) { + return config; + } + throw new Error('serverless.js must export plain object'); + }); -const getServerlessConfigFile = _.memoize((serverless) => - getServerlessConfigFilePath(serverless) - .then(configFilePath => { - if (configFilePath !== null) { - const isJSConfigFile = _.last(_.split(configFilePath, '.')) === 'js'; +const getServerlessConfigFile = _.memoize( + serverless => + getServerlessConfigFilePath(serverless).then(configFilePath => { + if (configFilePath !== null) { + const isJSConfigFile = _.last(_.split(configFilePath, '.')) === 'js'; - if (isJSConfigFile) { - return handleJsConfigFile(configFilePath); + if (isJSConfigFile) { + return handleJsConfigFile(configFilePath); + } + + return readFile(configFilePath); } - return readFile(configFilePath); - } - - return ''; - }), - (serverless) => `${ - serverless.processedInput.options.config} - ${serverless.config.servicePath}`); + return ''; + }), + serverless => `${serverless.processedInput.options.config} - ${serverless.config.servicePath}` +); module.exports = { getServerlessConfigFile, getServerlessConfigFilePath }; diff --git a/lib/utils/getServerlessConfigFile.test.js b/lib/utils/getServerlessConfigFile.test.js index 87a84aab4..44be5b7b7 100644 --- a/lib/utils/getServerlessConfigFile.test.js +++ b/lib/utils/getServerlessConfigFile.test.js @@ -23,10 +23,12 @@ describe('#getServerlessConfigFile()', () => { const randomFilePath = path.join(tmpDirPath, 'not-a-serverless-file'); writeFileSync(randomFilePath, 'some content'); - return expect(getServerlessConfigFile({ - processedInput: { options: {} }, - config: { servicePath: tmpDirPath }, - })).to.be.fulfilled.then((result) => { + return expect( + getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + }) + ).to.be.fulfilled.then(result => { expect(result).to.equal(''); }); }); @@ -35,10 +37,12 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.yml'); writeFileSync(serverlessFilePath, 'service: my-yml-service'); - return expect(getServerlessConfigFile({ - processedInput: { options: {} }, - config: { servicePath: tmpDirPath }, - })).to.be.fulfilled.then((result) => { + return expect( + getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + }) + ).to.be.fulfilled.then(result => { expect(result).to.deep.equal({ service: 'my-yml-service' }); }); }); @@ -47,10 +51,12 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.yaml'); writeFileSync(serverlessFilePath, 'service: my-yaml-service'); - return expect(getServerlessConfigFile({ - processedInput: { options: {} }, - config: { servicePath: tmpDirPath }, - })).to.be.fulfilled.then((result) => { + return expect( + getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + }) + ).to.be.fulfilled.then(result => { expect(result).to.deep.equal({ service: 'my-yaml-service' }); }); }); @@ -59,42 +65,42 @@ describe('#getServerlessConfigFile()', () => { const serverlessFilePath = path.join(tmpDirPath, 'foobar.yaml'); writeFileSync(serverlessFilePath, 'service: my-yaml-service'); - return expect(getServerlessConfigFile({ - processedInput: { options: { config: 'foobar.yaml' } }, - config: { servicePath: tmpDirPath }, - })).to.be.fulfilled.then( - (result) => { - expect(result).to.deep.equal({ service: 'my-yaml-service' }); - }); + return expect( + getServerlessConfigFile({ + processedInput: { options: { config: 'foobar.yaml' } }, + config: { servicePath: tmpDirPath }, + }) + ).to.be.fulfilled.then(result => { + expect(result).to.deep.equal({ service: 'my-yaml-service' }); + }); }); it('should return the file content if a serverless.json file is found', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.json'); writeFileSync(serverlessFilePath, '{ "service": "my-json-service" }'); - return expect(getServerlessConfigFile({ - processedInput: { options: {} }, - config: { servicePath: tmpDirPath }, - })).to.be.fulfilled.then((result) => { + return expect( + getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + }) + ).to.be.fulfilled.then(result => { expect(result).to.deep.equal({ service: 'my-json-service' }); }); }); it('should return the file content if a serverless.js file found', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.js'); - writeFileSync( - serverlessFilePath, - 'module.exports = {"service": "my-json-service"};' - ); + writeFileSync(serverlessFilePath, 'module.exports = {"service": "my-json-service"};'); - return expect(getServerlessConfigFile({ - processedInput: { options: {} }, - config: { servicePath: tmpDirPath }, - })).to.be.fulfilled.then( - (result) => { - expect(result).to.deep.equal({ service: 'my-json-service' }); - } - ); + return expect( + getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + }) + ).to.be.fulfilled.then(result => { + expect(result).to.deep.equal({ service: 'my-json-service' }); + }); }); it('should return the resolved value if a promise-using serverless.js file found', () => { @@ -104,28 +110,26 @@ describe('#getServerlessConfigFile()', () => { 'module.exports = new Promise(resolve => { resolve({"service": "my-json-service"}); });' ); - return expect(getServerlessConfigFile( - { processedInput: { options: {} }, + return expect( + getServerlessConfigFile({ + processedInput: { options: {} }, config: { servicePath: tmpDirPath }, - })).to.be.fulfilled.then( - (result) => { - expect(result).to.deep.equal({ service: 'my-json-service' }); - } - ); + }) + ).to.be.fulfilled.then(result => { + expect(result).to.deep.equal({ service: 'my-json-service' }); + }); }); it('should throw an error, if serverless.js export not a plain object', () => { const serverlessFilePath = path.join(tmpDirPath, 'serverless.js'); - writeFileSync( - serverlessFilePath, - 'module.exports = function config() {};' - ); + writeFileSync(serverlessFilePath, 'module.exports = function config() {};'); - return expect(getServerlessConfigFile({ - processedInput: { options: {} }, - config: { servicePath: tmpDirPath }, - })) - .to.be.rejectedWith('serverless.js must export plain object'); + return expect( + getServerlessConfigFile({ + processedInput: { options: {} }, + config: { servicePath: tmpDirPath }, + }) + ).to.be.rejectedWith('serverless.js must export plain object'); }); it('should look in the current working directory if servicePath is undefined', () => { @@ -134,16 +138,20 @@ describe('#getServerlessConfigFile()', () => { writeFileSync(serverlessFilePath, 'service: my-yml-service'); const cwd = process.cwd(); process.chdir(tmpDirPath); - return expect(getServerlessConfigFile({ - processedInput: { options: {} }, - config: {}, - })).to.be.fulfilled - .then((result) => { + return expect( + getServerlessConfigFile({ + processedInput: { options: {} }, + config: {}, + }) + ).to.be.fulfilled.then( + result => { process.chdir(cwd); expect(result).to.deep.equal({ service: 'my-yml-service' }); - }, error => { + }, + error => { process.chdir(cwd); throw error; - }); + } + ); }); }); diff --git a/lib/utils/getTrackingConfigFileName.js b/lib/utils/getTrackingConfigFileName.js index 5bc85aedd..2bb98cc55 100644 --- a/lib/utils/getTrackingConfigFileName.js +++ b/lib/utils/getTrackingConfigFileName.js @@ -1,6 +1,6 @@ 'use strict'; -const getTrackingConfigFileName = function () { +const getTrackingConfigFileName = function() { return 'tracking-config.json'; }; diff --git a/lib/utils/log/consoleLog.js b/lib/utils/log/consoleLog.js index 4bbe99661..b256d8efb 100644 --- a/lib/utils/log/consoleLog.js +++ b/lib/utils/log/consoleLog.js @@ -1,6 +1,6 @@ 'use strict'; -const consoleLog = function (...args) { +const consoleLog = function(...args) { console.log(args); // eslint-disable-line no-console }; diff --git a/lib/utils/log/fileLog.js b/lib/utils/log/fileLog.js index 4b2c1f070..f47e5bf72 100644 --- a/lib/utils/log/fileLog.js +++ b/lib/utils/log/fileLog.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const fs = require('fs'); const path = require('path'); -const fileLog = function (...args) { +const fileLog = function(...args) { // TODO BRN: This does not guarentee order, is not multi process safe, // TODO BRN: and is not guarenteed to complete before exit. fs.appendFileSync(path.join(process.cwd(), 'sls.log'), `${_.join(args)}\n`); diff --git a/lib/utils/log/log.js b/lib/utils/log/log.js index c9561f026..adf689b0a 100644 --- a/lib/utils/log/log.js +++ b/lib/utils/log/log.js @@ -9,8 +9,8 @@ const loggers = [ fileLog, ]; -const log = function (...args) { - _.each(loggers, (logger) => logger(...args)); +const log = function(...args) { + _.each(loggers, logger => logger(...args)); }; module.exports = log; diff --git a/lib/utils/log/serverlessLog.js b/lib/utils/log/serverlessLog.js index 94ba61795..b1675b114 100644 --- a/lib/utils/log/serverlessLog.js +++ b/lib/utils/log/serverlessLog.js @@ -4,7 +4,7 @@ const chalk = require('chalk'); -const log = function (message) { +const log = function(message) { console.log(`Serverless: ${chalk.yellow(message)}`); }; diff --git a/lib/utils/renameService.js b/lib/utils/renameService.js index 69dbd4fcf..6f4b67a78 100644 --- a/lib/utils/renameService.js +++ b/lib/utils/renameService.js @@ -13,25 +13,22 @@ function renameService(name, servicePath) { const packageFile = path.join(servicePath, 'package.json'); if (!fileExistsSync(serviceFile)) { - const errorMessage = [ - 'serverless.yml not found in', - ` ${servicePath}`, - ].join(''); + const errorMessage = ['serverless.yml not found in', ` ${servicePath}`].join(''); throw new ServerlessError(errorMessage); } - const serverlessYml = - fse.readFileSync(serviceFile, 'utf-8') - .replace(/service\s*:.+/gi, (match) => { - const fractions = match.split('#'); - fractions[0] = `service: ${name}`; - return fractions.join(' #'); - }) - .replace(/service\s*:\n {2}name:.+/gi, (match) => { - const fractions = match.split('#'); - fractions[0] = `service:\n name: ${name}`; - return fractions.join(' #'); - }); + const serverlessYml = fse + .readFileSync(serviceFile, 'utf-8') + .replace(/service\s*:.+/gi, match => { + const fractions = match.split('#'); + fractions[0] = `service: ${name}`; + return fractions.join(' #'); + }) + .replace(/service\s*:\n {2}name:.+/gi, match => { + const fractions = match.split('#'); + fractions[0] = `service:\n name: ${name}`; + return fractions.join(' #'); + }); fse.writeFileSync(serviceFile, serverlessYml); diff --git a/lib/utils/renameService.test.js b/lib/utils/renameService.test.js index c6e50fdbe..cbfb52f2d 100644 --- a/lib/utils/renameService.test.js +++ b/lib/utils/renameService.test.js @@ -33,10 +33,8 @@ describe('renameService', () => { }); it('should set new service in serverless.yml and name in package.json', () => { - const defaultServiceYml = - 'service: service-name\n\nprovider:\n name: aws\n'; - const newServiceYml = - 'service: new-service-name\n\nprovider:\n name: aws\n'; + const defaultServiceYml = 'service: service-name\n\nprovider:\n name: aws\n'; + const newServiceYml = 'service: new-service-name\n\nprovider:\n name: aws\n'; const defaultServiceName = 'service-name'; const newServiceName = 'new-service-name'; @@ -93,10 +91,8 @@ describe('renameService', () => { }); it('should set new name of service in serverless.yml and name in package.json', () => { - const defaultServiceYml = - 'service:\n name: service-name\n\nprovider:\n name: aws\n'; - const newServiceYml = - 'service:\n name: new-service-name\n\nprovider:\n name: aws\n'; + const defaultServiceYml = 'service:\n name: service-name\n\nprovider:\n name: aws\n'; + const newServiceYml = 'service:\n name: new-service-name\n\nprovider:\n name: aws\n'; const defaultServiceName = 'service-name'; const newServiceName = 'new-service-name'; diff --git a/lib/utils/segment.js b/lib/utils/segment.js index 89e51270e..1fea1a5e2 100644 --- a/lib/utils/segment.js +++ b/lib/utils/segment.js @@ -14,7 +14,7 @@ function request(url, payload) { timeout: 1000, body: JSON.stringify(payload), }) - .then((response) => response.json()) + .then(response => response.json()) .then(() => BbPromise.resolve()) .catch(() => BbPromise.resolve()); } diff --git a/lib/utils/userStats.js b/lib/utils/userStats.js index 7700a9b75..9d6f1876b 100644 --- a/lib/utils/userStats.js +++ b/lib/utils/userStats.js @@ -25,14 +25,14 @@ function request(url, payload) { timeout: 1000, body: JSON.stringify(payload), }) - .then((response) => { - if (response.status === 404) { - return BbPromise.reject('404 api not found'); - } - return response.json(); - }) - .then((res) => BbPromise.resolve(res)) - .catch((e) => BbPromise.resolve(e)); + .then(response => { + if (response.status === 404) { + return BbPromise.reject('404 api not found'); + } + return response.json(); + }) + .then(res => BbPromise.resolve(res)) + .catch(e => BbPromise.resolve(e)); } function track(eventName, payload) { diff --git a/lib/utils/userStatsValidation.js b/lib/utils/userStatsValidation.js index 16693b5bc..735814109 100644 --- a/lib/utils/userStatsValidation.js +++ b/lib/utils/userStatsValidation.js @@ -16,7 +16,8 @@ function containsSeparators(eventName) { if (underscores !== 1) { console.log(chalk.red('Tracking Error:')); console.log( - chalk.red(`Event name must have single underscore. "${eventName}" contains ${underscores}`)); + chalk.red(`Event name must have single underscore. "${eventName}" contains ${underscores}`) + ); return false; } if (colons !== 1) { diff --git a/lib/utils/yamlAstParser.js b/lib/utils/yamlAstParser.js index 17f719a07..c47b38cd6 100644 --- a/lib/utils/yamlAstParser.js +++ b/lib/utils/yamlAstParser.js @@ -8,11 +8,9 @@ const os = require('os'); const chalk = require('chalk'); const log = require('./log/serverlessLog'); -const findKeyChain = (astContent) => { +const findKeyChain = astContent => { let content = astContent; - const chain = [ - content.key.value, - ]; + const chain = [content.key.value]; while (content.parent) { content = content.parent; if (content.key) { @@ -25,7 +23,7 @@ const findKeyChain = (astContent) => { const parseAST = (ymlAstContent, astObject) => { let newAstObject = astObject || {}; if (ymlAstContent.mappings && _.isArray(ymlAstContent.mappings)) { - _.forEach(ymlAstContent.mappings, (v) => { + _.forEach(ymlAstContent.mappings, v => { if (!v.value) { const errorMessage = [ 'Serverless: ', @@ -58,7 +56,7 @@ const parseAST = (ymlAstContent, astObject) => { const constructPlainObject = (ymlAstContent, branchObject) => { const newbranchObject = branchObject || {}; if (ymlAstContent.mappings && _.isArray(ymlAstContent.mappings)) { - _.forEach(ymlAstContent.mappings, (v) => { + _.forEach(ymlAstContent.mappings, v => { if (!v.value) { // no need to log twice, parseAST will log errors return; @@ -70,7 +68,7 @@ const constructPlainObject = (ymlAstContent, branchObject) => { newbranchObject[v.key.value] = constructPlainObject(v.value, {}); } else if (v.key.kind === 0 && v.value.kind === 3) { const plainArray = []; - _.forEach(v.value.items, (c) => { + _.forEach(v.value.items, c => { plainArray.push(c.value); }); newbranchObject[v.key.value] = plainArray; @@ -82,91 +80,98 @@ const constructPlainObject = (ymlAstContent, branchObject) => { }; const addNewArrayItem = (ymlFile, pathInYml, newValue) => -fs.readFileAsync(ymlFile, 'utf8').then(yamlContent => { - const rawAstObject = yaml.load(yamlContent); - const astObject = parseAST(rawAstObject); - const plainObject = constructPlainObject(rawAstObject); - const pathInYmlArray = pathInYml.split('.'); - - let currentNode = plainObject; - for (let i = 0; i < pathInYmlArray.length - 1; i++) { - const propertyName = pathInYmlArray[i]; - const property = currentNode[propertyName]; - if (_.isUndefined(property) || _.isObject(property)) { - currentNode[propertyName] = property || {}; - currentNode = currentNode[propertyName]; - } else { - throw new Error(`${property} can only be undefined or an object!`); - } - } - - const arrayPropertyName = _.last(pathInYmlArray); - let arrayProperty = currentNode[arrayPropertyName]; - if (_.isUndefined(arrayProperty) || _.isArray(arrayProperty)) { - arrayProperty = arrayProperty || []; - } else { - throw new Error(`${arrayProperty} can only be undefined or an array!`); - } - currentNode[arrayPropertyName] = _.union(arrayProperty, [newValue]); - - const branchToReplaceName = _.head(pathInYmlArray); - const newObject = {}; - newObject[branchToReplaceName] = plainObject[branchToReplaceName]; - const newText = yaml.dump(newObject); - if (astObject[branchToReplaceName]) { - const beginning = yamlContent - .substring(0, astObject[branchToReplaceName].parent.key.startPosition); - const end = yamlContent - .substring(astObject[branchToReplaceName].endPosition, yamlContent.length); - return fs.writeFileAsync(ymlFile, `${beginning}${newText}${end}`); - } - return fs.writeFileAsync(ymlFile, `${yamlContent}${os.EOL}${newText}`); -}); - -const removeExistingArrayItem = (ymlFile, pathInYml, removeValue) => -fs.readFileAsync(ymlFile, 'utf8').then(yamlContent => { - const rawAstObject = yaml.load(yamlContent); - const astObject = parseAST(rawAstObject); - - if (astObject[pathInYml] && astObject[pathInYml].items) { + fs.readFileAsync(ymlFile, 'utf8').then(yamlContent => { + const rawAstObject = yaml.load(yamlContent); + const astObject = parseAST(rawAstObject); const plainObject = constructPlainObject(rawAstObject); const pathInYmlArray = pathInYml.split('.'); let currentNode = plainObject; - const pathInObjectTree = []; for (let i = 0; i < pathInYmlArray.length - 1; i++) { - pathInObjectTree.push(currentNode); - currentNode = currentNode[pathInYmlArray[i]]; - } - const arrayPropertyName = _.last(pathInYmlArray); - const arrayProperty = currentNode[arrayPropertyName]; - _.pull(arrayProperty, removeValue); - - if (_.isEmpty(arrayProperty)) { - _.unset(currentNode, arrayPropertyName); - pathInObjectTree.push(currentNode); - for (let i = pathInObjectTree.length - 1; i > 0; i--) { - if (_.keys(pathInObjectTree[i]).length > 0) { - break; - } - _.unset(pathInObjectTree[i - 1], pathInYmlArray[i - 1]); + const propertyName = pathInYmlArray[i]; + const property = currentNode[propertyName]; + if (_.isUndefined(property) || _.isObject(property)) { + currentNode[propertyName] = property || {}; + currentNode = currentNode[propertyName]; + } else { + throw new Error(`${property} can only be undefined or an object!`); } } - const headObjectPath = _.head(pathInYmlArray); - let newText = ''; - - if (plainObject[headObjectPath]) { - const newObject = {}; - newObject[headObjectPath] = plainObject[headObjectPath]; - newText = yaml.dump(newObject); + const arrayPropertyName = _.last(pathInYmlArray); + let arrayProperty = currentNode[arrayPropertyName]; + if (_.isUndefined(arrayProperty) || _.isArray(arrayProperty)) { + arrayProperty = arrayProperty || []; + } else { + throw new Error(`${arrayProperty} can only be undefined or an array!`); } - const beginning = yamlContent.substring(0, astObject[headObjectPath].parent.key.startPosition); - const end = yamlContent.substring(astObject[pathInYml].endPosition, yamlContent.length); - return fs.writeFileAsync(ymlFile, `${beginning}${newText}${end}`); - } - return BbPromise.resolve(); -}); + currentNode[arrayPropertyName] = _.union(arrayProperty, [newValue]); + + const branchToReplaceName = _.head(pathInYmlArray); + const newObject = {}; + newObject[branchToReplaceName] = plainObject[branchToReplaceName]; + const newText = yaml.dump(newObject); + if (astObject[branchToReplaceName]) { + const beginning = yamlContent.substring( + 0, + astObject[branchToReplaceName].parent.key.startPosition + ); + const end = yamlContent.substring( + astObject[branchToReplaceName].endPosition, + yamlContent.length + ); + return fs.writeFileAsync(ymlFile, `${beginning}${newText}${end}`); + } + return fs.writeFileAsync(ymlFile, `${yamlContent}${os.EOL}${newText}`); + }); + +const removeExistingArrayItem = (ymlFile, pathInYml, removeValue) => + fs.readFileAsync(ymlFile, 'utf8').then(yamlContent => { + const rawAstObject = yaml.load(yamlContent); + const astObject = parseAST(rawAstObject); + + if (astObject[pathInYml] && astObject[pathInYml].items) { + const plainObject = constructPlainObject(rawAstObject); + const pathInYmlArray = pathInYml.split('.'); + + let currentNode = plainObject; + const pathInObjectTree = []; + for (let i = 0; i < pathInYmlArray.length - 1; i++) { + pathInObjectTree.push(currentNode); + currentNode = currentNode[pathInYmlArray[i]]; + } + const arrayPropertyName = _.last(pathInYmlArray); + const arrayProperty = currentNode[arrayPropertyName]; + _.pull(arrayProperty, removeValue); + + if (_.isEmpty(arrayProperty)) { + _.unset(currentNode, arrayPropertyName); + pathInObjectTree.push(currentNode); + for (let i = pathInObjectTree.length - 1; i > 0; i--) { + if (_.keys(pathInObjectTree[i]).length > 0) { + break; + } + _.unset(pathInObjectTree[i - 1], pathInYmlArray[i - 1]); + } + } + + const headObjectPath = _.head(pathInYmlArray); + let newText = ''; + + if (plainObject[headObjectPath]) { + const newObject = {}; + newObject[headObjectPath] = plainObject[headObjectPath]; + newText = yaml.dump(newObject); + } + const beginning = yamlContent.substring( + 0, + astObject[headObjectPath].parent.key.startPosition + ); + const end = yamlContent.substring(astObject[pathInYml].endPosition, yamlContent.length); + return fs.writeFileAsync(ymlFile, `${beginning}${newText}${end}`); + } + return BbPromise.resolve(); + }); module.exports = { addNewArrayItem, diff --git a/lib/utils/yamlAstParser.test.js b/lib/utils/yamlAstParser.test.js index 84e083390..510b2ad53 100644 --- a/lib/utils/yamlAstParser.test.js +++ b/lib/utils/yamlAstParser.test.js @@ -23,11 +23,12 @@ describe('#yamlAstParser', () => { const addNewArrayItemAndVerifyResult = (yamlContent, pathInYaml, newItem, expectedResult) => { const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); writeFileSync(yamlFilePath, yamlContent); - return expect(yamlAstParser.addNewArrayItem(yamlFilePath, pathInYaml, newItem)) - .to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.be.deep.equal(expectedResult); - }); + return expect( + yamlAstParser.addNewArrayItem(yamlFilePath, pathInYaml, newItem) + ).to.be.fulfilled.then(() => { + const yaml = readFileSync(yamlFilePath, 'utf8'); + expect(yaml).to.be.deep.equal(expectedResult); + }); }; it('should add a top level object and item into the yaml file', () => { @@ -53,8 +54,12 @@ describe('#yamlAstParser', () => { }, }, }); - return addNewArrayItemAndVerifyResult(yamlContent, - 'toplevel.second.third', 'foo', expectedResult); + return addNewArrayItemAndVerifyResult( + yamlContent, + 'toplevel.second.third', + 'foo', + expectedResult + ); }); it('should add an item under the existing multiple level object which you specify', () => { @@ -72,16 +77,19 @@ describe('#yamlAstParser', () => { }, }, }; - return addNewArrayItemAndVerifyResult(yamlContent, - 'toplevel.second.third', 'bar', expectedResult); + return addNewArrayItemAndVerifyResult( + yamlContent, + 'toplevel.second.third', + 'bar', + expectedResult + ); }); it('should add an item under partially existing multiple level object', () => { const yamlContent = { toplevel: { first: 'foo', - second: { - }, + second: {}, }, }; const expectedResult = { @@ -92,8 +100,12 @@ describe('#yamlAstParser', () => { }, }, }; - return addNewArrayItemAndVerifyResult(yamlContent, - 'toplevel.second.third', 'bar', expectedResult); + return addNewArrayItemAndVerifyResult( + yamlContent, + 'toplevel.second.third', + 'bar', + expectedResult + ); }); it('should add an item in the middle branch', () => { @@ -112,16 +124,14 @@ describe('#yamlAstParser', () => { }, bottomlevel: 'bar', }; - return addNewArrayItemAndVerifyResult(yamlContent, - 'toplevel.second', 'bar', expectedResult); + return addNewArrayItemAndVerifyResult(yamlContent, 'toplevel.second', 'bar', expectedResult); }); it('should add an item with multiple top level entries', () => { const yamlContent = { toplevel: { first: 'foo', - second: { - }, + second: {}, }, nexttoplevel: { first: 'bar', @@ -138,8 +148,12 @@ describe('#yamlAstParser', () => { first: 'bar', }, }; - return addNewArrayItemAndVerifyResult(yamlContent, - 'toplevel.second.third', 'bar', expectedResult); + return addNewArrayItemAndVerifyResult( + yamlContent, + 'toplevel.second.third', + 'bar', + expectedResult + ); }); it('should do nothing when adding the existing item', () => { @@ -156,16 +170,21 @@ describe('#yamlAstParser', () => { }); describe('#removeExistingArrayItem()', () => { - const removeExistingArrayItemAndVerifyResult = - (yamlContent, pathInYaml, removeItem, expectedResult) => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); - writeFileSync(yamlFilePath, yamlContent); - return expect(yamlAstParser.removeExistingArrayItem(yamlFilePath, pathInYaml, removeItem)) - .to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.be.deep.equal(expectedResult); - }); - }; + const removeExistingArrayItemAndVerifyResult = ( + yamlContent, + pathInYaml, + removeItem, + expectedResult + ) => { + const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); + writeFileSync(yamlFilePath, yamlContent); + return expect( + yamlAstParser.removeExistingArrayItem(yamlFilePath, pathInYaml, removeItem) + ).to.be.fulfilled.then(() => { + const yaml = readFileSync(yamlFilePath, 'utf8'); + expect(yaml).to.be.deep.equal(expectedResult); + }); + }; it('should remove the existing top level object and item from the yaml file', () => { const yamlContent = { @@ -173,8 +192,7 @@ describe('#yamlAstParser', () => { toplevel: ['foo'], }; const expectedResult = { service: 'test-service' }; - return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel', - 'foo', expectedResult); + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel', 'foo', expectedResult); }); it('should remove the existing item under the object which you specify', () => { @@ -186,8 +204,7 @@ describe('#yamlAstParser', () => { service: 'test-service', toplevel: ['foo'], }; - return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel', - 'bar', expectedResult); + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel', 'bar', expectedResult); }); it('should remove the multiple level object and item from the yaml file', () => { @@ -200,8 +217,12 @@ describe('#yamlAstParser', () => { }, }; const expectedResult = { service: 'test-service' }; - return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel.second.third', - 'foo', expectedResult); + return removeExistingArrayItemAndVerifyResult( + yamlContent, + 'toplevel.second.third', + 'foo', + expectedResult + ); }); it('should remove the existing item under the multiple level object which you specify', () => { @@ -221,8 +242,12 @@ describe('#yamlAstParser', () => { }, }, }; - return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel.second.third', - 'bar', expectedResult); + return removeExistingArrayItemAndVerifyResult( + yamlContent, + 'toplevel.second.third', + 'bar', + expectedResult + ); }); it('should remove multilevel object from the middle branch', () => { @@ -239,8 +264,12 @@ describe('#yamlAstParser', () => { service: 'test-service', end: 'end', }; - return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel.second.third', - 'foo', expectedResult); + return removeExistingArrayItemAndVerifyResult( + yamlContent, + 'toplevel.second.third', + 'foo', + expectedResult + ); }); it('should remove item from multilevel object from the middle branch', () => { @@ -262,8 +291,12 @@ describe('#yamlAstParser', () => { }, end: 'end', }; - return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel.second.third', - 'foo', expectedResult); + return removeExistingArrayItemAndVerifyResult( + yamlContent, + 'toplevel.second.third', + 'foo', + expectedResult + ); }); it('should do nothing when you can not find the object which you specify', () => { @@ -272,21 +305,22 @@ describe('#yamlAstParser', () => { toplevel: ['foo', 'bar'], }; - return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel', - 'foo2', yamlContent); + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel', 'foo2', yamlContent); }); it('should remove when with inline declaration of the array', () => { - const yamlContent = - 'toplevel:\n' + - ' second: ["foo2", "bar"]'; + const yamlContent = 'toplevel:\n' + ' second: ["foo2", "bar"]'; const expectedResult = { toplevel: { second: ['foo2'], }, }; - return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel.second', - 'bar', expectedResult); + return removeExistingArrayItemAndVerifyResult( + yamlContent, + 'toplevel.second', + 'bar', + expectedResult + ); }); }); }); diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 823bed854..9a73cac3a 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -10,11 +10,12 @@ const execSync = require('child_process').execSync; try { const serverless = new Serverless(); - (() => serverless.init() - .then(() => serverless.utils.logStat(serverless, 'install')) - .then(() => setupAutocomplete()) - .catch(() => Promise.resolve()) - )(); + (() => + serverless + .init() + .then(() => serverless.utils.logStat(serverless, 'install')) + .then(() => setupAutocomplete()) + .catch(() => Promise.resolve()))(); } catch (error) { // fail silently } diff --git a/scripts/preuninstall.js b/scripts/preuninstall.js index cd04648c1..3a75dcc73 100644 --- a/scripts/preuninstall.js +++ b/scripts/preuninstall.js @@ -5,10 +5,11 @@ const Serverless = require('../lib/Serverless'); try { const serverless = new Serverless(); - (() => serverless.init() - .then(() => serverless.utils.logStat(serverless, 'uninstall')) - .catch(() => Promise.resolve()) - )(); + (() => + serverless + .init() + .then(() => serverless.utils.logStat(serverless, 'uninstall')) + .catch(() => Promise.resolve()))(); } catch (error) { // fail silently } diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js index 9b891dbe3..9938cda39 100644 --- a/tests/.eslintrc.js +++ b/tests/.eslintrc.js @@ -1,9 +1,9 @@ module.exports = { - "parserOptions": { - "ecmaVersion": 2017, + parserOptions: { + ecmaVersion: 2017, }, - "rules": { + rules: { // console.info allowed to report on long going tasks or valuable debug information - "no-console": ["error", { allow: ["info"] }] - } + 'no-console': ['error', { allow: ['info'] }], + }, }; diff --git a/tests/integration-all/api-gateway/service/core.js b/tests/integration-all/api-gateway/service/core.js index aa19d4084..24b040dab 100644 --- a/tests/integration-all/api-gateway/service/core.js +++ b/tests/integration-all/api-gateway/service/core.js @@ -24,13 +24,13 @@ async function cors(event) { } async function customAuthorizers(event) { - return ({ + return { statusCode: 200, body: JSON.stringify({ message: 'Hello from API Gateway! - (customAuthorizers)', event, }), - }); + }; } async function apiKeys(event) { @@ -44,13 +44,19 @@ async function apiKeys(event) { } async function timeout(event) { - return new Promise(resolve => setTimeout(() => resolve({ - statusCode: 200, - body: JSON.stringify({ - message: 'Should not happen (timeout expected)', - event, - }), - }), 2000)); + return new Promise(resolve => + setTimeout( + () => + resolve({ + statusCode: 200, + body: JSON.stringify({ + message: 'Should not happen (timeout expected)', + event, + }), + }), + 2000 + ) + ); } module.exports = { diff --git a/tests/integration-all/api-gateway/tests.js b/tests/integration-all/api-gateway/tests.js index 59db5777e..29041cc30 100644 --- a/tests/integration-all/api-gateway/tests.js +++ b/tests/integration-all/api-gateway/tests.js @@ -43,14 +43,16 @@ describe('AWS - API Gateway Integration Test', () => { // create an external REST API const externalRestApiName = `${stage}-${serviceName}-ext-api`; return createRestApi(externalRestApiName) - .then((restApiMeta) => { + .then(restApiMeta => { restApiId = restApiMeta.id; return getResources(restApiId); }) - .then((resources) => { + .then(resources => { restApiRootResourceId = resources[0].id; - console.info('Created external rest API ' + - `(id: ${restApiId}, root resource id: ${restApiRootResourceId})`); + console.info( + 'Created external rest API ' + + `(id: ${restApiId}, root resource id: ${restApiRootResourceId})` + ); }); }); @@ -70,10 +72,12 @@ describe('AWS - API Gateway Integration Test', () => { }); beforeEach(() => { - return CF.describeStacks({ StackName: stackName }).promise() - .then((result) => _.find(result.Stacks[0].Outputs, - { OutputKey: 'ServiceEndpoint' }).OutputValue) - .then((endpointOutput) => { + return CF.describeStacks({ StackName: stackName }) + .promise() + .then( + result => _.find(result.Stacks[0].Outputs, { OutputKey: 'ServiceEndpoint' }).OutputValue + ) + .then(endpointOutput => { endpoint = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; endpoint = `${endpoint}`; }); @@ -87,7 +91,7 @@ describe('AWS - API Gateway Integration Test', () => { return fetch(testEndpoint, { method: 'GET' }) .then(response => response.json()) - .then((json) => expect(json.message).to.equal(expectedMessage)); + .then(json => expect(json.message).to.equal(expectedMessage)); }); it('should expose an accessible POST HTTP endpoint', () => { @@ -95,7 +99,7 @@ describe('AWS - API Gateway Integration Test', () => { return fetch(testEndpoint, { method: 'POST' }) .then(response => response.json()) - .then((json) => expect(json.message).to.equal(expectedMessage)); + .then(json => expect(json.message).to.equal(expectedMessage)); }); it('should expose an accessible PUT HTTP endpoint', () => { @@ -103,7 +107,7 @@ describe('AWS - API Gateway Integration Test', () => { return fetch(testEndpoint, { method: 'PUT' }) .then(response => response.json()) - .then((json) => expect(json.message).to.equal(expectedMessage)); + .then(json => expect(json.message).to.equal(expectedMessage)); }); it('should expose an accessible DELETE HTTP endpoint', () => { @@ -111,7 +115,7 @@ describe('AWS - API Gateway Integration Test', () => { return fetch(testEndpoint, { method: 'DELETE' }) .then(response => response.json()) - .then((json) => expect(json.message).to.equal(expectedMessage)); + .then(json => expect(json.message).to.equal(expectedMessage)); }); }); @@ -119,44 +123,42 @@ describe('AWS - API Gateway Integration Test', () => { it('should setup simple CORS support via cors: true config', () => { const testEndpoint = `${endpoint}/simple-cors`; - return fetch(testEndpoint, { method: 'OPTIONS' }) - .then((response) => { - const headers = response.headers; - const allowHeaders = [ - 'Content-Type', - 'X-Amz-Date', - 'Authorization', - 'X-Api-Key', - 'X-Amz-Security-Token', - 'X-Amz-User-Agent', - ].join(','); - expect(headers.get('access-control-allow-headers')).to.equal(allowHeaders); - expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); - expect(headers.get('access-control-allow-credentials')).to.equal('false'); - // TODO: for some reason this test fails for now... - // expect(headers.get('access-control-allow-origin')).to.equal('*'); - }); + return fetch(testEndpoint, { method: 'OPTIONS' }).then(response => { + const headers = response.headers; + const allowHeaders = [ + 'Content-Type', + 'X-Amz-Date', + 'Authorization', + 'X-Api-Key', + 'X-Amz-Security-Token', + 'X-Amz-User-Agent', + ].join(','); + expect(headers.get('access-control-allow-headers')).to.equal(allowHeaders); + expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); + expect(headers.get('access-control-allow-credentials')).to.equal('false'); + // TODO: for some reason this test fails for now... + // expect(headers.get('access-control-allow-origin')).to.equal('*'); + }); }); it('should setup CORS support with complex object config', () => { const testEndpoint = `${endpoint}/complex-cors`; - return fetch(testEndpoint, { method: 'OPTIONS' }) - .then((response) => { - const headers = response.headers; - const allowHeaders = [ - 'Content-Type', - 'X-Amz-Date', - 'Authorization', - 'X-Api-Key', - 'X-Amz-Security-Token', - 'X-Amz-User-Agent', - ].join(','); - expect(headers.get('access-control-allow-headers')).to.equal(allowHeaders); - expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); - expect(headers.get('access-control-allow-credentials')).to.equal('true'); - expect(headers.get('access-control-allow-origin')).to.equal('*'); - }); + return fetch(testEndpoint, { method: 'OPTIONS' }).then(response => { + const headers = response.headers; + const allowHeaders = [ + 'Content-Type', + 'X-Amz-Date', + 'Authorization', + 'X-Api-Key', + 'X-Amz-Security-Token', + 'X-Amz-User-Agent', + ].join(','); + expect(headers.get('access-control-allow-headers')).to.equal(allowHeaders); + expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); + expect(headers.get('access-control-allow-credentials')).to.equal('true'); + expect(headers.get('access-control-allow-origin')).to.equal('*'); + }); }); }); @@ -168,23 +170,23 @@ describe('AWS - API Gateway Integration Test', () => { }); it('should reject requests without authorization', () => { - return fetch(testEndpoint) - .then((response) => { - expect(response.status).to.equal(401); - }); + return fetch(testEndpoint).then(response => { + expect(response.status).to.equal(401); + }); }); it('should reject requests with wrong authorization', () => { - return fetch(testEndpoint, { headers: { Authorization: 'Bearer ShouldNotBeAuthorized' } }) - .then((response) => { - expect(response.status).to.equal(401); - }); + return fetch(testEndpoint, { + headers: { Authorization: 'Bearer ShouldNotBeAuthorized' }, + }).then(response => { + expect(response.status).to.equal(401); + }); }); it('should authorize requests with correct authorization', () => { return fetch(testEndpoint, { headers: { Authorization: 'Bearer ShouldBeAuthorized' } }) .then(response => response.json()) - .then((json) => { + .then(json => { expect(json.message).to.equal('Hello from API Gateway! - (customAuthorizers)'); expect(json.event.requestContext.authorizer.principalId).to.equal('SomeRandomId'); expect(json.event.headers.Authorization).to.equal('Bearer ShouldBeAuthorized'); @@ -200,16 +202,15 @@ describe('AWS - API Gateway Integration Test', () => { }); it('should reject a request with an invalid API Key', () => { - return fetch(testEndpoint) - .then((response) => { - expect(response.status).to.equal(403); - }); + return fetch(testEndpoint).then(response => { + expect(response.status).to.equal(403); + }); }); it('should succeed if correct API key is given', () => { return fetch(testEndpoint, { headers: { 'X-API-Key': apiKey } }) .then(response => response.json()) - .then((json) => { + .then(json => { expect(json.message).to.equal('Hello from API Gateway! - (apiKeys)'); }); }); @@ -241,7 +242,7 @@ describe('AWS - API Gateway Integration Test', () => { return fetch(testEndpoint, { method: 'GET' }) .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway! - (minimal)')); + .then(json => expect(json.message).to.equal('Hello from API Gateway! - (minimal)')); }); }); @@ -276,13 +277,14 @@ describe('AWS - API Gateway Integration Test', () => { return fetch(testEndpoint, { method: 'POST' }) .then(response => response.json()) - .then((json) => expect(json.message).to.equal('Hello from API Gateway! - (minimal)')); + .then(json => expect(json.message).to.equal('Hello from API Gateway! - (minimal)')); }); }); describe('Integration Lambda Timeout', () => { - it('should result with 504 status code', - () => fetch(`${endpoint}/integration-lambda-timeout`) - .then(response => expect(response.status).to.equal(504))); + it('should result with 504 status code', () => + fetch(`${endpoint}/integration-lambda-timeout`).then(response => + expect(response.status).to.equal(504) + )); }); }); diff --git a/tests/integration-basic/tests.js b/tests/integration-basic/tests.js index 5bfb551d7..a9b8f6e7e 100644 --- a/tests/integration-basic/tests.js +++ b/tests/integration-basic/tests.js @@ -44,7 +44,8 @@ describe('Service Lifecyle Integration Test', () => { it('should deploy service to aws', () => { execSync(`${serverlessExec} deploy`); - return CF.describeStacks({ StackName }).promise() + return CF.describeStacks({ StackName }) + .promise() .then(d => expect(d.Stacks[0].StackStatus).to.be.equal('UPDATE_COMPLETE')); }); @@ -57,8 +58,7 @@ describe('Service Lifecyle Integration Test', () => { }); it('should deploy updated service to aws', () => { - const newHandler = - ` + const newHandler = ` 'use strict'; module.exports.hello = (event, context, cb) => cb(null, @@ -99,7 +99,8 @@ describe('Service Lifecyle Integration Test', () => { it('should remove service from aws', () => { execSync(`${serverlessExec} remove`); - return CF.describeStacks({ StackName }).promise() + return CF.describeStacks({ StackName }) + .promise() .then(d => expect(d.Stacks[0].StackStatus).to.be.equal('DELETE_COMPLETE')) .catch(error => { if (error.message.indexOf('does not exist') > -1) return BbPromise.resolve(); diff --git a/tests/integration-package/handler.js b/tests/integration-package/handler.js index 4e3448672..773780bbd 100644 --- a/tests/integration-package/handler.js +++ b/tests/integration-package/handler.js @@ -1,12 +1,16 @@ 'use strict'; -module.exports.hello = function (event) { +module.exports.hello = function(event) { return { statusCode: 200, - body: JSON.stringify({ - message: 'Go Serverless v1.0! Your function executed successfully!', - input: event, - }, null, 2), + body: JSON.stringify( + { + message: 'Go Serverless v1.0! Your function executed successfully!', + input: event, + }, + null, + 2 + ), }; // Use this code if you don't use the http event with the LAMBDA-PROXY integration diff --git a/tests/integration-package/packaging.tests.js b/tests/integration-package/packaging.tests.js index 6203525dd..414ee84df 100644 --- a/tests/integration-package/packaging.tests.js +++ b/tests/integration-package/packaging.tests.js @@ -18,10 +18,9 @@ describe('Integration test - Packaging', () => { fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync(`${serverlessExec} package`, { cwd }); - return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) - .then(zipfiles => { - expect(zipfiles).toEqual(['handler.js']); - }); + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { + expect(zipfiles).toEqual(['handler.js']); + }); }); it('packages the default aws template with an npm dep correctly in the zip', () => { @@ -30,30 +29,30 @@ describe('Integration test - Packaging', () => { execSync('npm init --yes', { cwd }); execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); - return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) - .then(zipfiles => { - const nodeModules = new Set( - zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); - const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); - expect(nodeModules).toEqual(new Set(['lodash'])); - expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); - }); + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1]) + ); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set(['lodash'])); + expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); + }); }); - it('doesn\'t package a dev dependency in the zip', () => { + it("doesn't package a dev dependency in the zip", () => { fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync('npm init --yes', { cwd }); execSync('npm i --save-dev lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); - return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) - .then(zipfiles => { - const nodeModules = new Set( - zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); - const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); - expect(nodeModules).toEqual(new Set([])); - expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); - }); + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1]) + ); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set([])); + expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); + }); }); it('ignores package json files per ignore directive in the zip', () => { @@ -63,25 +62,27 @@ describe('Integration test - Packaging', () => { execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); - return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) - .then(zipfiles => { - const nodeModules = new Set( - zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1])); - const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); - expect(nodeModules).toEqual(new Set(['lodash'])); - expect(nonNodeModulesFiles).toEqual(['handler.js']); - }); + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1]) + ); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set(['lodash'])); + expect(nonNodeModulesFiles).toEqual(['handler.js']); + }); }); it('package artifact directive works', () => { fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); fse.copySync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')); - execSync('echo \'package: {artifact: artifact.zip}\' >> serverless.yml', { cwd }); + execSync("echo 'package: {artifact: artifact.zip}' >> serverless.yml", { cwd }); execSync(`${serverlessExec} package`, { cwd }); - const cfnTemplate = JSON.parse(fs.readFileSync(path.join( - cwd, '.serverless/cloudformation-template-update-stack.json'))); - expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key) - .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/artifact.zip/); + const cfnTemplate = JSON.parse( + fs.readFileSync(path.join(cwd, '.serverless/cloudformation-template-update-stack.json')) + ); + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key).toMatch( + /serverless\/aws-nodejs\/dev\/[^]*\/artifact.zip/ + ); delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ Type: 'AWS::Lambda::Function', @@ -95,18 +96,12 @@ describe('Integration test - Packaging', () => { Handler: 'handler.hello', MemorySize: 1024, Role: { - 'Fn::GetAtt': [ - 'IamRoleLambdaExecution', - 'Arn', - ], + 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'], }, Runtime: 'nodejs10.x', Timeout: 6, }, - DependsOn: [ - 'HelloLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['HelloLogGroup', 'IamRoleLambdaExecution'], }); }); @@ -114,10 +109,12 @@ describe('Integration test - Packaging', () => { fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); execSync(`${serverlessExec} package`, { cwd }); - const cfnTemplate = JSON.parse(fs.readFileSync(path.join( - cwd, '.serverless/cloudformation-template-update-stack.json'))); - expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key) - .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/aws-nodejs.zip/); + const cfnTemplate = JSON.parse( + fs.readFileSync(path.join(cwd, '.serverless/cloudformation-template-update-stack.json')) + ); + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key).toMatch( + /serverless\/aws-nodejs\/dev\/[^]*\/aws-nodejs.zip/ + ); delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ Type: 'AWS::Lambda::Function', @@ -131,23 +128,16 @@ describe('Integration test - Packaging', () => { Handler: 'handler.hello', MemorySize: 1024, Role: { - 'Fn::GetAtt': [ - 'IamRoleLambdaExecution', - 'Arn', - ], + 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'], }, Runtime: 'nodejs10.x', Timeout: 6, }, - DependsOn: [ - 'HelloLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['HelloLogGroup', 'IamRoleLambdaExecution'], + }); + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { + expect(zipfiles).toEqual(['handler.js']); }); - return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')) - .then(zipfiles => { - expect(zipfiles).toEqual(['handler.js']); - }); }); it('handles package individually with include/excludes correctly', () => { @@ -155,12 +145,15 @@ describe('Integration test - Packaging', () => { fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler2.js')); execSync(`${serverlessExec} package`, { cwd }); - const cfnTemplate = JSON.parse(fs.readFileSync(path.join( - cwd, '.serverless/cloudformation-template-update-stack.json'))); - expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key) - .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/hello.zip/); - expect(cfnTemplate.Resources.Hello2LambdaFunction.Properties.Code.S3Key) - .toMatch(/serverless\/aws-nodejs\/dev\/[^]*\/hello2.zip/); + const cfnTemplate = JSON.parse( + fs.readFileSync(path.join(cwd, '.serverless/cloudformation-template-update-stack.json')) + ); + expect(cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key).toMatch( + /serverless\/aws-nodejs\/dev\/[^]*\/hello.zip/ + ); + expect(cfnTemplate.Resources.Hello2LambdaFunction.Properties.Code.S3Key).toMatch( + /serverless\/aws-nodejs\/dev\/[^]*\/hello2.zip/ + ); delete cfnTemplate.Resources.HelloLambdaFunction.Properties.Code.S3Key; expect(cfnTemplate.Resources.HelloLambdaFunction).toEqual({ Type: 'AWS::Lambda::Function', @@ -174,18 +167,12 @@ describe('Integration test - Packaging', () => { Handler: 'handler.hello', MemorySize: 1024, Role: { - 'Fn::GetAtt': [ - 'IamRoleLambdaExecution', - 'Arn', - ], + 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'], }, Runtime: 'nodejs10.x', Timeout: 6, }, - DependsOn: [ - 'HelloLogGroup', - 'IamRoleLambdaExecution', - ], + DependsOn: ['HelloLogGroup', 'IamRoleLambdaExecution'], }); return listZipFiles(path.join(cwd, '.serverless/hello.zip')) .then(zipfiles => expect(zipfiles).toEqual(['handler.js'])) diff --git a/tests/integration-package/serverless.yml b/tests/integration-package/serverless.yml index 700a3a5bd..35c720037 100644 --- a/tests/integration-package/serverless.yml +++ b/tests/integration-package/serverless.yml @@ -4,7 +4,6 @@ provider: name: aws runtime: nodejs10.x - functions: hello: handler: handler.hello diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index e9ecf9445..cc4e6ecc0 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -24,7 +24,7 @@ Runner.immediately = process.nextTick; // we observe with native promises, and so they do not interfere with an async leaks detector const BbPromise = require('bluebird'); /* eslint-disable no-underscore-dangle */ -BbPromise.prototype._ensurePossibleRejectionHandled = function () { +BbPromise.prototype._ensurePossibleRejectionHandled = function() { if ((this._bitField & 524288) !== 0) return; this._setRejectionIsUnhandled(); process.nextTick(() => this._notifyUnhandledRejection()); diff --git a/tests/utils/api-gateway/index.js b/tests/utils/api-gateway/index.js index 212f9ef32..07b07b902 100644 --- a/tests/utils/api-gateway/index.js +++ b/tests/utils/api-gateway/index.js @@ -31,8 +31,9 @@ function getResources(restApiId) { restApiId, }; - return APIG.getResources(params).promise() - .then((data) => data.items); + return APIG.getResources(params) + .promise() + .then(data => data.items); } function findRestApis(name) { @@ -44,14 +45,16 @@ function findRestApis(name) { function recursiveFind(found, position) { if (position) params.position = position; - return APIG.getRestApis(params).promise().then(result => { - const matches = result.items.filter(restApi => restApi.name.match(name)); - if (matches.length) { - _.merge(found, matches); - } - if (result.position) return recursiveFind(found, result.position); - return found; - }); + return APIG.getRestApis(params) + .promise() + .then(result => { + const matches = result.items.filter(restApi => restApi.name.match(name)); + if (matches.length) { + _.merge(found, matches); + } + if (result.position) return recursiveFind(found, result.position); + return found; + }); } return recursiveFind([]); diff --git a/tests/utils/aws-cleanup.js b/tests/utils/aws-cleanup.js index 85167e65f..98f100315 100644 --- a/tests/utils/aws-cleanup.js +++ b/tests/utils/aws-cleanup.js @@ -12,7 +12,7 @@ async function findDeploymentBuckets(stacks) { const buckets = []; for (const stack of stacks) { const stackResources = await listStackResources(stack.StackId); - const bucket = stackResources.filter((resource) => { + const bucket = stackResources.filter(resource => { return resource.LogicalResourceId === 'ServerlessDeploymentBucket'; }); buckets.push(...bucket); @@ -40,8 +40,8 @@ async function cleanup() { const apis = await findRestApis(testServiceIdentifier); let bucketsToRemove = []; - const stacksToRemove = stacks.filter((stack) => +new Date(stack.CreationTime) < yesterday); - const apisToRemove = apis.filter((api) => +new Date(api.createdDate) < yesterday); + const stacksToRemove = stacks.filter(stack => +new Date(stack.CreationTime) < yesterday); + const apisToRemove = apis.filter(api => +new Date(api.createdDate) < yesterday); if (stacksToRemove) { bucketsToRemove = await findDeploymentBuckets(stacksToRemove); } @@ -52,8 +52,7 @@ async function cleanup() { if (bucketsToRemove.length) { logger.log('Removing Buckets...'); - const promises = bucketsToRemove - .map(bucket => deleteBucket(bucket.PhysicalResourceId)); + const promises = bucketsToRemove.map(bucket => deleteBucket(bucket.PhysicalResourceId)); try { await Promise.all(promises); } catch (error) { @@ -63,8 +62,7 @@ async function cleanup() { if (stacksToRemove.length) { logger.log('Removing Stacks...'); - const promises = stacksToRemove - .map(stack => deleteStack(stack.StackName)); + const promises = stacksToRemove.map(stack => deleteStack(stack.StackName)); try { await Promise.all(promises); } catch (error) { @@ -83,7 +81,7 @@ async function cleanup() { } } -cleanup().catch((error) => { +cleanup().catch(error => { // eslint-disable-next-line no-console console.error(error); process.exit(1); diff --git a/tests/utils/cloudformation/index.js b/tests/utils/cloudformation/index.js index 4d651acef..8955e56cb 100644 --- a/tests/utils/cloudformation/index.js +++ b/tests/utils/cloudformation/index.js @@ -13,14 +13,16 @@ function findStacks(name, status) { function recursiveFind(found, token) { if (token) params.NextToken = token; - return CF.listStacks(params).promise().then(result => { - const matches = result.StackSummaries.filter(stack => stack.StackName.match(name)); - if (matches.length) { - found.push(...matches); - } - if (result.NextToken) return recursiveFind(found, result.NextToken); - return found; - }); + return CF.listStacks(params) + .promise() + .then(result => { + const matches = result.StackSummaries.filter(stack => stack.StackName.match(name)); + if (matches.length) { + found.push(...matches); + } + if (result.NextToken) return recursiveFind(found, result.NextToken); + return found; + }); } return recursiveFind([]); @@ -45,11 +47,13 @@ function listStackResources(stack) { function recursiveFind(resources, token) { if (token) params.NextToken = token; - return CF.listStackResources(params).promise().then(result => { - resources.push(...result.StackResourceSummaries); - if (result.NextToken) return recursiveFind(resources, result.NextToken); - return resources; - }); + return CF.listStackResources(params) + .promise() + .then(result => { + resources.push(...result.StackResourceSummaries); + if (result.NextToken) return recursiveFind(resources, result.NextToken); + return resources; + }); } return recursiveFind([]); diff --git a/tests/utils/cognito/index.js b/tests/utils/cognito/index.js index ab942ac2d..2c6dca017 100644 --- a/tests/utils/cognito/index.js +++ b/tests/utils/cognito/index.js @@ -10,9 +10,11 @@ function getCognitoUserPoolId(userPoolName) { MaxResults: 50, }; - return cisp.listUserPools(params).promise() - .then((data) => data.UserPools.find((userPool) => - RegExp(userPoolName, 'g').test(userPool.Name)).Id + return cisp + .listUserPools(params) + .promise() + .then( + data => data.UserPools.find(userPool => RegExp(userPoolName, 'g').test(userPool.Name)).Id ); } diff --git a/tests/utils/fs/index.js b/tests/utils/fs/index.js index e1d1b42b9..53220f4b8 100644 --- a/tests/utils/fs/index.js +++ b/tests/utils/fs/index.js @@ -8,8 +8,11 @@ const crypto = require('crypto'); const YAML = require('js-yaml'); const JSZip = require('jszip'); -const tmpDirCommonPath = path.join(os.tmpdir(), 'tmpdirs-serverless', - crypto.randomBytes(2).toString('hex')); +const tmpDirCommonPath = path.join( + os.tmpdir(), + 'tmpdirs-serverless', + crypto.randomBytes(2).toString('hex') +); function getTmpDirPath() { return path.join(tmpDirCommonPath, crypto.randomBytes(8).toString('hex')); @@ -48,8 +51,7 @@ function writeYamlFile(filePath, content) { } function listZipFiles(filename) { - return new JSZip().loadAsync(fs.readFileSync(filename)) - .then(zip => Object.keys(zip.files)); + return new JSZip().loadAsync(fs.readFileSync(filename)).then(zip => Object.keys(zip.files)); } module.exports = { diff --git a/tests/utils/iot/index.js b/tests/utils/iot/index.js index 0cadbff95..8ab68f841 100644 --- a/tests/utils/iot/index.js +++ b/tests/utils/iot/index.js @@ -6,7 +6,8 @@ const { region, persistentRequest } = require('../misc'); function publishIotData(topic, message) { const Iot = new AWS.Iot({ region }); - return Iot.describeEndpoint().promise() + return Iot.describeEndpoint() + .promise() .then(data => { const IotData = new AWS.IotData({ region, endpoint: data.endpointAddress }); diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 971784870..3d85384fa 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -47,12 +47,15 @@ function replaceEnv(values) { return originals; } -function createTestService(tmpDir, options = { - // Either templateName or templateDir have to be provided - templateName: null, // Generic template to use (e.g. 'aws-nodejs') - templateDir: null, // Path to custom pre-prepared service template - serverlessConfigHook: null, // Eventual hook that allows to customize serverless config -}) { +function createTestService( + tmpDir, + options = { + // Either templateName or templateDir have to be provided + templateName: null, // Generic template to use (e.g. 'aws-nodejs') + templateDir: null, // Path to custom pre-prepared service template + serverlessConfigHook: null, // Eventual hook that allows to customize serverless config + } +) { const serviceName = getServiceName(); fse.mkdirsSync(tmpDir); @@ -96,13 +99,17 @@ function persistentRequest(...args) { const funcArgs = args.slice(1); const MAX_TRIES = 5; return new BbPromise((resolve, reject) => { - const doCall = (numTry) => { + const doCall = numTry => { return func.apply(this, funcArgs).then(resolve, e => { - if (numTry < MAX_TRIES && - ((e.providerError && e.providerError.retryable) || e.statusCode === 429)) { + if ( + numTry < MAX_TRIES && + ((e.providerError && e.providerError.retryable) || e.statusCode === 429) + ) { logger.log( - [`Recoverable error occurred (${e.message}), sleeping for 5 seconds.`, - `Try ${numTry + 1} of ${MAX_TRIES}`].join(' ') + [ + `Recoverable error occurred (${e.message}), sleeping for 5 seconds.`, + `Try ${numTry + 1} of ${MAX_TRIES}`, + ].join(' ') ); setTimeout(doCall, 5000, numTry + 1); } else { diff --git a/tests/utils/plugins/index.js b/tests/utils/plugins/index.js index 6f422d1eb..32ca791f4 100644 --- a/tests/utils/plugins/index.js +++ b/tests/utils/plugins/index.js @@ -9,19 +9,19 @@ class ServerlessPlugin { this.options = options; this.serverless = serverless; - Object.assign( - this, - testSubject - ); + Object.assign(this, testSubject); } } function installPlugin(installDir, PluginClass) { const pluginPkg = { name: path.basename(installDir), version: '0.0.0' }; - const className = (new PluginClass()).constructor.name; + const className = new PluginClass().constructor.name; fse.outputFileSync(path.join(installDir, 'package.json'), JSON.stringify(pluginPkg), 'utf8'); - fse.outputFileSync(path.join(installDir, 'index.js'), - `"use strict";\n${PluginClass.toString()}\nmodule.exports = ${className}`, 'utf8'); + fse.outputFileSync( + path.join(installDir, 'index.js'), + `"use strict";\n${PluginClass.toString()}\nmodule.exports = ${className}`, + 'utf8' + ); } module.exports = { diff --git a/tests/utils/s3/index.js b/tests/utils/s3/index.js index 1e48930ab..26d07e1e4 100644 --- a/tests/utils/s3/index.js +++ b/tests/utils/s3/index.js @@ -12,7 +12,8 @@ function createAndRemoveInBucket(bucketName) { Body: 'hello world', }; - return S3.putObject(params).promise() + return S3.putObject(params) + .promise() .then(() => { delete params.Body; return S3.deleteObject(params); @@ -43,8 +44,7 @@ function emptyBucket(bucket) { function deleteBucket(bucket) { const S3 = new AWS.S3({ region }); - return emptyBucket(bucket).then(() => - S3.deleteBucket({ Bucket: bucket }).promise()); + return emptyBucket(bucket).then(() => S3.deleteBucket({ Bucket: bucket }).promise()); } module.exports = { diff --git a/tests/utils/sns/index.js b/tests/utils/sns/index.js index 012293b76..a899982bb 100644 --- a/tests/utils/sns/index.js +++ b/tests/utils/sns/index.js @@ -16,10 +16,11 @@ function createSnsTopic(topicName) { function removeSnsTopic(topicName) { const SNS = new AWS.SNS({ region }); - return SNS.listTopics().promise() + return SNS.listTopics() + .promise() .then(data => { - const topicArn = data.Topics.find(topic => RegExp(topicName, 'g') - .test(topic.TopicArn)).TopicArn; + const topicArn = data.Topics.find(topic => RegExp(topicName, 'g').test(topic.TopicArn)) + .TopicArn; const params = { TopicArn: topicArn, @@ -32,10 +33,11 @@ function removeSnsTopic(topicName) { function publishSnsMessage(topicName, message) { const SNS = new AWS.SNS({ region }); - return SNS.listTopics().promise() + return SNS.listTopics() + .promise() .then(data => { - const topicArn = data.Topics.find(topic => RegExp(topicName, 'g') - .test(topic.TopicArn)).TopicArn; + const topicArn = data.Topics.find(topic => RegExp(topicName, 'g').test(topic.TopicArn)) + .TopicArn; const params = { Message: message, From e6ae8983de198e004ac5c166ccc8ee44315cfa12 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 21 Jun 2019 10:43:50 +0200 Subject: [PATCH 414/504] Fix lint issues after prettification --- lib/classes/Error.js | 3 +- lib/classes/Service.js | 2 +- lib/classes/Variables.js | 2 +- .../aws/package/compile/functions/index.js | 2 +- .../package/compile/functions/index.test.js | 73 +++++++++---------- lib/utils/yamlAstParser.test.js | 2 +- 6 files changed, 40 insertions(+), 44 deletions(-) diff --git a/lib/classes/Error.js b/lib/classes/Error.js index 4beec093a..a31cefe65 100644 --- a/lib/classes/Error.js +++ b/lib/classes/Error.js @@ -85,8 +85,7 @@ module.exports.logError = e => { process.exit(1); } // report error to sentry. - errorReporter.captureException(e, (sendErr, eventId) => { - // eslint-disable-line + errorReporter.captureException(e, () => { // process.exit(1) for CI systems to correctly fail process.exit(1); }); diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 4ba46d0ce..0dbf9c2a8 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -174,7 +174,7 @@ class Service { _.forEach(this.functions, (functionObj, functionName) => { if (!_.isArray(functionObj.events)) { throw new ServerlessError( - `Events for "${functionName}" must be an array,` + ` not an ${typeof functionObj.events}` + `Events for "${functionName}" must be an array, not an ${typeof functionObj.events}` ); } }); diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index 30b685f9a..64c377a9a 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -488,7 +488,7 @@ class Variables { // A sentinel to rid rejected Promises, so any of resolved value can be used as fallback. const FAIL_TOKEN = {}; const variableValues = variableStrings.map(variableString => - this.getValueFromSource(variableString, propertyString).catch(unused => FAIL_TOKEN) + this.getValueFromSource(variableString, propertyString).catch(() => FAIL_TOKEN) ); // eslint-disable-line no-unused-vars const validValue = value => diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index b7d09106d..5ab76c41d 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -334,7 +334,6 @@ class AwsCompileFunctions { let invalidEnvVar = null; _.forEach(_.keys(newFunction.Properties.Environment.Variables), key => { - // eslint-disable-line consistent-return // taken from the bash man pages if (!key.match(/^[A-Za-z_][a-zA-Z0-9_]*$/)) { invalidEnvVar = `Invalid characters in environment variable ${key}`; @@ -349,6 +348,7 @@ class AwsCompileFunctions { return false; } } + return true; }); if (invalidEnvVar) { diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index 62e50d462..237962404 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -1921,46 +1921,43 @@ describe('AwsCompileFunctions', () => { }); }); - it( - 'should consider the providers runtime and memorySize ' + 'when creating a function resource', - () => { - const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; - const s3FileName = awsCompileFunctions.serverless.service.package.artifact - .split(path.sep) - .pop(); - awsCompileFunctions.serverless.service.provider.runtime = 'python2.7'; - awsCompileFunctions.serverless.service.provider.memorySize = 128; - awsCompileFunctions.serverless.service.functions = { - func: { - handler: 'func.function.handler', - name: 'new-service-dev-func', + it('should consider the providers runtime and memorySize when creating a function resource', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep) + .pop(); + awsCompileFunctions.serverless.service.provider.runtime = 'python2.7'; + awsCompileFunctions.serverless.service.provider.memorySize = 128; + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, }, - }; - const compiledFunction = { - Type: 'AWS::Lambda::Function', - DependsOn: ['FuncLogGroup', 'IamRoleLambdaExecution'], - Properties: { - Code: { - S3Key: `${s3Folder}/${s3FileName}`, - S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, - }, - FunctionName: 'new-service-dev-func', - Handler: 'func.function.handler', - MemorySize: 128, - Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, - Runtime: 'python2.7', - Timeout: 6, - }, - }; + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 128, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'python2.7', + Timeout: 6, + }, + }; - return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { - expect( - awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources - .FuncLambdaFunction - ).to.deep.equal(compiledFunction); - }); - } - ); + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled.then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources + .FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); it('should use a custom bucket if specified', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; diff --git a/lib/utils/yamlAstParser.test.js b/lib/utils/yamlAstParser.test.js index 510b2ad53..641c859c8 100644 --- a/lib/utils/yamlAstParser.test.js +++ b/lib/utils/yamlAstParser.test.js @@ -309,7 +309,7 @@ describe('#yamlAstParser', () => { }); it('should remove when with inline declaration of the array', () => { - const yamlContent = 'toplevel:\n' + ' second: ["foo2", "bar"]'; + const yamlContent = 'toplevel:\n second: ["foo2", "bar"]'; const expectedResult = { toplevel: { second: ['foo2'], From f84d9479f65979b8775b6660faa1480071ed274e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 26 Jun 2019 13:14:57 +0200 Subject: [PATCH 415/504] Improve PR template --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1e97020d5..4d9bf3c49 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -34,7 +34,7 @@ Examples: ## Todos: -_Note: Run `npm run test-ci` to run all validation checks on proposed changes_ +_**Note: Run `npm run test-ci` to run all validation checks on proposed changes**_ - [ ] Write tests and confirm existing functionality is not broken. **Validate via `npm test`** @@ -50,5 +50,5 @@ _Note: Run `npm run test-ci` to run all validation checks on proposed changes_ - [ ] Enable "Allow edits from maintainers" for this PR - [ ] Update the messages below -**_Is this ready for review?:_** NO +**_Is this ready for review?:_** NO **_Is it a breaking change?:_** NO From cdd3bec5da85e2d5fbbfc018b47ba738c5515a96 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 26 Jun 2019 14:31:45 +0200 Subject: [PATCH 416/504] Remove jsbeautify configuration --- .jsbeautifyrc | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 .jsbeautifyrc diff --git a/.jsbeautifyrc b/.jsbeautifyrc deleted file mode 100644 index 059689735..000000000 --- a/.jsbeautifyrc +++ /dev/null @@ -1,48 +0,0 @@ -{ - "html": { - "allowed_file_extensions": ["htm", "html", "xhtml", "shtml", "xml", "svg"], - "brace_style": "collapse", - "end_with_newline": false, - "indent_char": " ", - "indent_handlebars": false, - "indent_inner_html": false, - "indent_scripts": "keep", - "indent_size": 2, - "max_preserve_newlines": 4, - "preserve_newlines": true, - "unformatted": ["a", "span", "img", "code", "pre", "sub", "sup", "em", "strong", "b", "i", "u", "strike", "big", "small", "pre", "h1", "h2", "h3", "h4", "h5", "h6"], - "wrap_line_length": 0 - }, - "css": { - "allowed_file_extensions": ["css", "scss", "sass", "less"], - "end_with_newline": true, - "indent_char": " ", - "indent_size": 2, - "max_preserve_newlines": 4, - "newline_between_rules": true, - "selector_separator": " ", - "selector_separator_newline": true - }, - "js": { - "allowed_file_extensions": ["jshintrc", "jsbeautifyrc"], - "brace_style": "collapse", - "break_chained_methods": false, - "e4x": false, - "end_with_newline": false, - "indent_char": " ", - "indent_level": 0, - "indent_size": 2, - "indent_with_tabs": false, - "jslint_happy": false, - "keep_array_indentation": false, - "keep_function_indentation": false, - "max_preserve_newlines": 0, - "preserve_newlines": true, - "space_after_anon_function": false, - "space_before_conditional": true, - "space_in_empty_paren": false, - "space_in_paren": false, - "unescape_strings": false, - "wrap_line_length": 0 - } -} From 6b00ef5bcc6bc843a33f481ee5b2a3e2ca26084c Mon Sep 17 00:00:00 2001 From: Christoph Gysin Date: Wed, 26 Jun 2019 15:49:00 +0300 Subject: [PATCH 417/504] websockets: fix passing log group ARN The log group ARN contains a trailing ":*" that seems to cause deployment to fail. Instead, generate the ARN with the trailing wildcard. fixes #6304 --- lib/plugins/aws/package/compile/events/websockets/lib/stage.js | 2 +- .../aws/package/compile/events/websockets/lib/stage.test.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/stage.js b/lib/plugins/aws/package/compile/events/websockets/lib/stage.js index 71c3498e6..af5b81e1e 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/stage.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/stage.js @@ -34,7 +34,7 @@ module.exports = { Object.assign(stageResource.Properties, { AccessLogSettings: { DestinationArn: { - 'Fn::GetAtt': [logGroupLogicalId, 'Arn'], + 'Fn::Sub': `arn:aws:logs:\${AWS::Region}:\${AWS::AccountId}:log-group:\${${logGroupLogicalId}}`, }, Format: [ '$context.identity.sourceIp', diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js index ab931abd3..5bc91404f 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/stage.test.js @@ -81,7 +81,8 @@ describe('#compileStage()', () => { Description: 'Serverless Websockets', AccessLogSettings: { DestinationArn: { - 'Fn::GetAtt': [logGroupLogicalId, 'Arn'], + 'Fn::Sub': + 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${WebsocketsLogGroup}', }, Format: [ '$context.identity.sourceIp', From abd5a5d94382926ba4678892b2197f89f914058c Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Thu, 27 Jun 2019 11:34:06 +0200 Subject: [PATCH 418/504] Fix linting issues --- scripts/postinstall.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 7ec766e32..ed322e724 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -1,15 +1,15 @@ 'use strict'; const path = require('path'); -const chalk = require('chalk') +const chalk = require('chalk'); /* eslint-disable no-console */ const Serverless = require('../lib/Serverless'); const execSync = require('child_process').execSync; -const truthyStr = (val) => val && !['0', 'false', 'f', 'n', 'no'].includes(val.toLowerCase()) -const { CI, ADBLOCK, SILENT } = process.env +const truthyStr = val => val && !['0', 'false', 'f', 'n', 'no'].includes(val.toLowerCase()); +const { CI, ADBLOCK, SILENT } = process.env; if (!truthyStr(CI) && !truthyStr(ADBLOCK) && !truthyStr(SILENT)) { console.log( chalk.yellow(`\ @@ -20,7 +20,7 @@ if (!truthyStr(CI) && !truthyStr(ADBLOCK) && !truthyStr(SILENT)) { | | +------------------------------------------------------------------+ `) - ) + ); } try { From 8d629552dd98f5b02b44758769b2b5ee5393d1a8 Mon Sep 17 00:00:00 2001 From: Alex DeBrie Date: Tue, 25 Jun 2019 15:16:05 -0500 Subject: [PATCH 419/504] Update docs --- docs/providers/aws/events/apigateway.md | 16 ++++++++ .../events/apiGateway/lib/hack/updateStage.js | 29 +++++++------ .../apiGateway/lib/hack/updateStage.test.js | 41 +++++++++++++++++++ 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 2edeaa6ce..66cc90e2b 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -1488,3 +1488,19 @@ provider: ``` The log streams will be generated in a dedicated log group which follows the naming schema `/aws/api-gateway/{service}-{stage}`. + +By default, API Gateway access logs will use the following format: + +``` +'requestId: $context.requestId, ip: $context.identity.sourceIp, caller: $context.identity.caller, user: $context.identity.user, requestTime: $context.requestTime, httpMethod: $context.httpMethod, resourcePath: $context.resourcePath, status: $context.status, protocol: $context.protocol, responseLength: $context.responseLength' +``` + +You can specify your own [format for API Gateway Access Logs](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html#apigateway-cloudwatch-log-formats) by including your preferred string as a value to `restApi`: + +```yml +# serverless.yml +provider: + name: aws + logs: + restApi: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp" }' +``` \ No newline at end of file diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js index 429aa4096..5e8b2f1ce 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js @@ -4,6 +4,18 @@ const _ = require('lodash'); const BbPromise = require('bluebird'); const isRestApiId = RegExp.prototype.test.bind(/^[a-z0-9]{3,}$/); +const defaultApiGatewayLogFormat = [ + 'requestId: $context.requestId', + 'ip: $context.identity.sourceIp', + 'caller: $context.identity.caller', + 'user: $context.identity.user', + 'requestTime: $context.requestTime', + 'httpMethod: $context.httpMethod', + 'resourcePath: $context.resourcePath', + 'status: $context.status', + 'protocol: $context.protocol', + 'responseLength: $context.responseLength', +].join(', '); // NOTE --> Keep this file in sync with ../stage.js @@ -174,6 +186,10 @@ function handleLogs() { const region = this.options.region; const logGroupName = `/aws/api-gateway/${service}-${stage}`; + const logFormat = provider.logs.restApi === true + ? defaultApiGatewayLogFormat + : provider.logs.restApi; + const destinationArn = { op: 'replace', path: '/accessLogSettings/destinationArn', @@ -182,18 +198,7 @@ function handleLogs() { const format = { op: 'replace', path: '/accessLogSettings/format', - value: [ - 'requestId: $context.requestId', - 'ip: $context.identity.sourceIp', - 'caller: $context.identity.caller', - 'user: $context.identity.user', - 'requestTime: $context.requestTime', - 'httpMethod: $context.httpMethod', - 'resourcePath: $context.resourcePath', - 'status: $context.status', - 'protocol: $context.protocol', - 'responseLength: $context.responseLength', - ].join(', '), + value: logFormat, }; dataTrace = { op: 'replace', path: '/*/*/logging/dataTrace', value: 'true' }; logLevel = { op: 'replace', path: '/*/*/logging/loglevel', value: 'INFO' }; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js index 7c94155f4..32f5db757 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js @@ -335,4 +335,45 @@ describe('#updateStage()', () => { }); } ); + + it('should update the stage with a custom APIGW log format if given', () => { + context.state.service.provider.logs = { + restApi: 'requestId: $context.requestId', + }; + + return updateStage.call(context).then(() => { + const patchOperations = [ + { op: 'replace', path: '/tracingEnabled', value: 'false' }, + { op: 'replace', path: '/accessLogSettings/destinationArn', value: 'arn:aws:logs:us-east-1:123456:log-group:/aws/api-gateway/my-service-dev' }, + { op: 'replace', path: '/accessLogSettings/format', value: 'requestId: $context.requestId' }, + { op: 'replace', path: '/*/*/logging/dataTrace', value: 'true' }, + { op: 'replace', path: '/*/*/logging/loglevel', value: 'INFO' }, + ]; + + expect(providerGetAccountIdStub).to.be.calledOnce; + expect(providerRequestStub.args).to.have.length(4); + expect(providerRequestStub.args[0][0]).to.equal('APIGateway'); + expect(providerRequestStub.args[0][1]).to.equal('getRestApis'); + expect(providerRequestStub.args[0][2]).to.deep.equal({ + limit: 500, + position: undefined, + }); + expect(providerRequestStub.args[1][0]).to.equal('APIGateway'); + expect(providerRequestStub.args[1][1]).to.equal('getStage'); + expect(providerRequestStub.args[1][2]).to.deep.equal({ restApiId: 'someRestApiId', stageName: 'dev' }); + expect(providerRequestStub.args[2][0]).to.equal('APIGateway'); + expect(providerRequestStub.args[2][1]).to.equal('updateStage'); + expect(providerRequestStub.args[2][2]).to.deep.equal({ + restApiId: 'someRestApiId', + stageName: 'dev', + patchOperations, + }); + expect(providerRequestStub.args[3][0]).to.equal('APIGateway'); + expect(providerRequestStub.args[3][1]).to.equal('untagResource'); + expect(providerRequestStub.args[3][2]).to.deep.equal({ + resourceArn: 'arn:aws:apigateway:us-east-1::/restapis/someRestApiId/stages/dev', + tagKeys: ['old'], + }); + }); + }); }); From a2b60c3429e5d9fdcaafa69f00ebc4456fcb0986 Mon Sep 17 00:00:00 2001 From: Alex DeBrie Date: Thu, 27 Jun 2019 10:27:15 -0500 Subject: [PATCH 420/504] Add a logFormat property --- docs/providers/aws/events/apigateway.md | 5 +++-- .../compile/events/apiGateway/lib/hack/updateStage.js | 7 ++++--- .../compile/events/apiGateway/lib/hack/updateStage.test.js | 4 +++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 66cc90e2b..20394ae62 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -1495,12 +1495,13 @@ By default, API Gateway access logs will use the following format: 'requestId: $context.requestId, ip: $context.identity.sourceIp, caller: $context.identity.caller, user: $context.identity.user, requestTime: $context.requestTime, httpMethod: $context.httpMethod, resourcePath: $context.resourcePath, status: $context.status, protocol: $context.protocol, responseLength: $context.responseLength' ``` -You can specify your own [format for API Gateway Access Logs](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html#apigateway-cloudwatch-log-formats) by including your preferred string as a value to `restApi`: +You can specify your own [format for API Gateway Access Logs](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html#apigateway-cloudwatch-log-formats) by including your preferred string in the `logFormat` property: ```yml # serverless.yml provider: name: aws logs: - restApi: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp" }' + restApi: + logFormat: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp" }' ``` \ No newline at end of file diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js index 5e8b2f1ce..afd9a2672 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js @@ -186,9 +186,10 @@ function handleLogs() { const region = this.options.region; const logGroupName = `/aws/api-gateway/${service}-${stage}`; - const logFormat = provider.logs.restApi === true - ? defaultApiGatewayLogFormat - : provider.logs.restApi; + let logFormat = defaultApiGatewayLogFormat; + if (provider.logs.restApi.logFormat) { + logFormat = provider.logs.restApi.logFormat; + } const destinationArn = { op: 'replace', diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js index 32f5db757..0d3d801f1 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js @@ -338,7 +338,9 @@ describe('#updateStage()', () => { it('should update the stage with a custom APIGW log format if given', () => { context.state.service.provider.logs = { - restApi: 'requestId: $context.requestId', + restApi: { + logFormat: 'requestId: $context.requestId', + }, }; return updateStage.call(context).then(() => { From 9329e3f874fd5214a0a0a00b89b1c30888e5f988 Mon Sep 17 00:00:00 2001 From: Alex DeBrie Date: Thu, 27 Jun 2019 10:36:54 -0500 Subject: [PATCH 421/504] Update lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js Co-Authored-By: Daniel Schep --- .../package/compile/events/apiGateway/lib/hack/updateStage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js index afd9a2672..f59571227 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js @@ -187,7 +187,7 @@ function handleLogs() { const logGroupName = `/aws/api-gateway/${service}-${stage}`; let logFormat = defaultApiGatewayLogFormat; - if (provider.logs.restApi.logFormat) { + if (typeof provider.logs.restApi === 'object' && provider.logs.restApi.logFormat) { logFormat = provider.logs.restApi.logFormat; } From 99644975da95a9950347dd2a94635574d3d94836 Mon Sep 17 00:00:00 2001 From: Alex DeBrie Date: Thu, 27 Jun 2019 10:39:24 -0500 Subject: [PATCH 422/504] logFormat --> format --- docs/providers/aws/events/apigateway.md | 4 ++-- .../package/compile/events/apiGateway/lib/hack/updateStage.js | 4 ++-- .../compile/events/apiGateway/lib/hack/updateStage.test.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 20394ae62..3945bac7d 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -1495,7 +1495,7 @@ By default, API Gateway access logs will use the following format: 'requestId: $context.requestId, ip: $context.identity.sourceIp, caller: $context.identity.caller, user: $context.identity.user, requestTime: $context.requestTime, httpMethod: $context.httpMethod, resourcePath: $context.resourcePath, status: $context.status, protocol: $context.protocol, responseLength: $context.responseLength' ``` -You can specify your own [format for API Gateway Access Logs](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html#apigateway-cloudwatch-log-formats) by including your preferred string in the `logFormat` property: +You can specify your own [format for API Gateway Access Logs](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html#apigateway-cloudwatch-log-formats) by including your preferred string in the `format` property: ```yml # serverless.yml @@ -1503,5 +1503,5 @@ provider: name: aws logs: restApi: - logFormat: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp" }' + format: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp" }' ``` \ No newline at end of file diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js index f59571227..81f5a2a43 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js @@ -187,8 +187,8 @@ function handleLogs() { const logGroupName = `/aws/api-gateway/${service}-${stage}`; let logFormat = defaultApiGatewayLogFormat; - if (typeof provider.logs.restApi === 'object' && provider.logs.restApi.logFormat) { - logFormat = provider.logs.restApi.logFormat; + if (typeof provider.logs.restApi === 'object' && provider.logs.restApi.format) { + logFormat = provider.logs.restApi.format; } const destinationArn = { diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js index 0d3d801f1..007c64035 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js @@ -339,7 +339,7 @@ describe('#updateStage()', () => { it('should update the stage with a custom APIGW log format if given', () => { context.state.service.provider.logs = { restApi: { - logFormat: 'requestId: $context.requestId', + format: 'requestId: $context.requestId', }, }; From d2d178c22629f584c17cb9cef9761d87499f8dcb Mon Sep 17 00:00:00 2001 From: Alex DeBrie Date: Thu, 27 Jun 2019 11:07:38 -0500 Subject: [PATCH 423/504] Cleaner check --- .../package/compile/events/apiGateway/lib/hack/updateStage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js index 81f5a2a43..fdd4cef66 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js @@ -187,8 +187,8 @@ function handleLogs() { const logGroupName = `/aws/api-gateway/${service}-${stage}`; let logFormat = defaultApiGatewayLogFormat; - if (typeof provider.logs.restApi === 'object' && provider.logs.restApi.format) { - logFormat = provider.logs.restApi.format; + if (typeof logs === 'object' && logs.format) { + logFormat = logs.format; } const destinationArn = { From 86fb74c7fe5d915430b5c0d86db774c0725e9ae7 Mon Sep 17 00:00:00 2001 From: Alex DeBrie Date: Thu, 27 Jun 2019 11:58:04 -0500 Subject: [PATCH 424/504] New Prettier formatting --- docs/providers/aws/events/apigateway.md | 2 +- .../apiGateway/lib/hack/updateStage.test.js | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 3945bac7d..a7cf95d01 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -1504,4 +1504,4 @@ provider: logs: restApi: format: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp" }' -``` \ No newline at end of file +``` diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js index 007c64035..1c6e79297 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.test.js @@ -346,8 +346,16 @@ describe('#updateStage()', () => { return updateStage.call(context).then(() => { const patchOperations = [ { op: 'replace', path: '/tracingEnabled', value: 'false' }, - { op: 'replace', path: '/accessLogSettings/destinationArn', value: 'arn:aws:logs:us-east-1:123456:log-group:/aws/api-gateway/my-service-dev' }, - { op: 'replace', path: '/accessLogSettings/format', value: 'requestId: $context.requestId' }, + { + op: 'replace', + path: '/accessLogSettings/destinationArn', + value: 'arn:aws:logs:us-east-1:123456:log-group:/aws/api-gateway/my-service-dev', + }, + { + op: 'replace', + path: '/accessLogSettings/format', + value: 'requestId: $context.requestId', + }, { op: 'replace', path: '/*/*/logging/dataTrace', value: 'true' }, { op: 'replace', path: '/*/*/logging/loglevel', value: 'INFO' }, ]; @@ -362,7 +370,10 @@ describe('#updateStage()', () => { }); expect(providerRequestStub.args[1][0]).to.equal('APIGateway'); expect(providerRequestStub.args[1][1]).to.equal('getStage'); - expect(providerRequestStub.args[1][2]).to.deep.equal({ restApiId: 'someRestApiId', stageName: 'dev' }); + expect(providerRequestStub.args[1][2]).to.deep.equal({ + restApiId: 'someRestApiId', + stageName: 'dev', + }); expect(providerRequestStub.args[2][0]).to.equal('APIGateway'); expect(providerRequestStub.args[2][1]).to.equal('updateStage'); expect(providerRequestStub.args[2][2]).to.deep.equal({ From 2bded42fd6d360f2c2ad1af600d183626f51e0e7 Mon Sep 17 00:00:00 2001 From: Alex DeBrie Date: Thu, 27 Jun 2019 12:33:49 -0500 Subject: [PATCH 425/504] Remove unneeded check --- .../package/compile/events/apiGateway/lib/hack/updateStage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js index fdd4cef66..8f5d2834a 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/hack/updateStage.js @@ -187,7 +187,7 @@ function handleLogs() { const logGroupName = `/aws/api-gateway/${service}-${stage}`; let logFormat = defaultApiGatewayLogFormat; - if (typeof logs === 'object' && logs.format) { + if (logs.format) { logFormat = logs.format; } From 3fd6e61e5a9290c90c62cb86b95dad2d938902da Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 09:35:48 +0200 Subject: [PATCH 426/504] Remove obsolete setup task --- tests/integration-package/packaging.tests.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration-package/packaging.tests.js b/tests/integration-package/packaging.tests.js index 414ee84df..2fc8c69c2 100644 --- a/tests/integration-package/packaging.tests.js +++ b/tests/integration-package/packaging.tests.js @@ -11,7 +11,6 @@ describe('Integration test - Packaging', () => { let cwd; beforeEach(() => { cwd = getTmpDirPath(); - fse.mkdirsSync(cwd); }); it('packages the default aws template correctly in the zip', () => { From 3bd0231417b0a51ab3ab6e59b0d32731ab09f1ff Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 09:50:55 +0200 Subject: [PATCH 427/504] Split packaging integration tests into two groups --- ...aging.tests.js => cloudformation.tests.js} | 67 +-------------- .../integration-package/lambda-files.tests.js | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+), 66 deletions(-) rename tests/integration-package/{packaging.tests.js => cloudformation.tests.js} (54%) create mode 100644 tests/integration-package/lambda-files.tests.js diff --git a/tests/integration-package/packaging.tests.js b/tests/integration-package/cloudformation.tests.js similarity index 54% rename from tests/integration-package/packaging.tests.js rename to tests/integration-package/cloudformation.tests.js index 2fc8c69c2..a6d8ee351 100644 --- a/tests/integration-package/packaging.tests.js +++ b/tests/integration-package/cloudformation.tests.js @@ -5,7 +5,7 @@ const path = require('path'); const fse = require('fs-extra'); const { execSync } = require('child_process'); const { serverlessExec } = require('../utils/misc'); -const { getTmpDirPath, listZipFiles } = require('../utils/fs'); +const { getTmpDirPath } = require('../utils/fs'); describe('Integration test - Packaging', () => { let cwd; @@ -13,64 +13,6 @@ describe('Integration test - Packaging', () => { cwd = getTmpDirPath(); }); - it('packages the default aws template correctly in the zip', () => { - fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); - execSync(`${serverlessExec} package`, { cwd }); - return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { - expect(zipfiles).toEqual(['handler.js']); - }); - }); - - it('packages the default aws template with an npm dep correctly in the zip', () => { - fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); - execSync('npm init --yes', { cwd }); - execSync('npm i lodash', { cwd }); - execSync(`${serverlessExec} package`, { cwd }); - return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { - const nodeModules = new Set( - zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1]) - ); - const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); - expect(nodeModules).toEqual(new Set(['lodash'])); - expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); - }); - }); - - it("doesn't package a dev dependency in the zip", () => { - fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); - execSync('npm init --yes', { cwd }); - execSync('npm i --save-dev lodash', { cwd }); - execSync(`${serverlessExec} package`, { cwd }); - return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { - const nodeModules = new Set( - zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1]) - ); - const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); - expect(nodeModules).toEqual(new Set([])); - expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); - }); - }); - - it('ignores package json files per ignore directive in the zip', () => { - fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); - execSync('npm init --yes', { cwd }); - execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); - execSync('npm i lodash', { cwd }); - execSync(`${serverlessExec} package`, { cwd }); - return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { - const nodeModules = new Set( - zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1]) - ); - const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); - expect(nodeModules).toEqual(new Set(['lodash'])); - expect(nonNodeModulesFiles).toEqual(['handler.js']); - }); - }); - it('package artifact directive works', () => { fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); fse.copySync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')); @@ -134,9 +76,6 @@ describe('Integration test - Packaging', () => { }, DependsOn: ['HelloLogGroup', 'IamRoleLambdaExecution'], }); - return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { - expect(zipfiles).toEqual(['handler.js']); - }); }); it('handles package individually with include/excludes correctly', () => { @@ -173,9 +112,5 @@ describe('Integration test - Packaging', () => { }, DependsOn: ['HelloLogGroup', 'IamRoleLambdaExecution'], }); - return listZipFiles(path.join(cwd, '.serverless/hello.zip')) - .then(zipfiles => expect(zipfiles).toEqual(['handler.js'])) - .then(() => listZipFiles(path.join(cwd, '.serverless/hello2.zip'))) - .then(zipfiles => expect(zipfiles).toEqual(['handler2.js'])); }); }); diff --git a/tests/integration-package/lambda-files.tests.js b/tests/integration-package/lambda-files.tests.js new file mode 100644 index 000000000..df0fe8213 --- /dev/null +++ b/tests/integration-package/lambda-files.tests.js @@ -0,0 +1,83 @@ +'use strict'; + +const path = require('path'); +const fse = require('fs-extra'); +const { execSync } = require('child_process'); +const { serverlessExec } = require('../utils/misc'); +const { getTmpDirPath, listZipFiles } = require('../utils/fs'); + +describe('Integration test - Packaging', () => { + let cwd; + beforeEach(() => { + cwd = getTmpDirPath(); + }); + + it('packages the default aws template correctly in the zip', () => { + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + execSync(`${serverlessExec} package`, { cwd }); + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { + expect(zipfiles).toEqual(['handler.js']); + }); + }); + + it('packages the default aws template with an npm dep correctly in the zip', () => { + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + execSync('npm init --yes', { cwd }); + execSync('npm i lodash', { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1]) + ); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set(['lodash'])); + expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); + }); + }); + + it("doesn't package a dev dependency in the zip", () => { + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + execSync('npm init --yes', { cwd }); + execSync('npm i --save-dev lodash', { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1]) + ); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set([])); + expect(nonNodeModulesFiles).toEqual(['handler.js', 'package-lock.json', 'package.json']); + }); + }); + + it('ignores package json files per ignore directive in the zip', () => { + fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + execSync('npm init --yes', { cwd }); + execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); + execSync('npm i lodash', { cwd }); + execSync(`${serverlessExec} package`, { cwd }); + return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { + const nodeModules = new Set( + zipfiles.filter(f => f.startsWith('node_modules')).map(f => f.split(path.sep)[1]) + ); + const nonNodeModulesFiles = zipfiles.filter(f => !f.startsWith('node_modules')); + expect(nodeModules).toEqual(new Set(['lodash'])); + expect(nonNodeModulesFiles).toEqual(['handler.js']); + }); + }); + + it('handles package individually with include/excludes correctly', () => { + fse.copySync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler2.js')); + execSync(`${serverlessExec} package`, { cwd }); + return listZipFiles(path.join(cwd, '.serverless/hello.zip')) + .then(zipfiles => expect(zipfiles).toEqual(['handler.js'])) + .then(() => listZipFiles(path.join(cwd, '.serverless/hello2.zip'))) + .then(zipfiles => expect(zipfiles).toEqual(['handler2.js'])); + }); +}); From cab2ffabcb4ca8b50aedd2d681e53aa2394f171a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 10:02:30 +0200 Subject: [PATCH 428/504] Ensure to expose output of failed commands --- tests/integration-package/cloudformation.tests.js | 2 +- tests/integration-package/lambda-files.tests.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration-package/cloudformation.tests.js b/tests/integration-package/cloudformation.tests.js index a6d8ee351..017c36eda 100644 --- a/tests/integration-package/cloudformation.tests.js +++ b/tests/integration-package/cloudformation.tests.js @@ -3,7 +3,7 @@ const fs = require('fs'); const path = require('path'); const fse = require('fs-extra'); -const { execSync } = require('child_process'); +const { execSync } = require('../utils/child-process'); const { serverlessExec } = require('../utils/misc'); const { getTmpDirPath } = require('../utils/fs'); diff --git a/tests/integration-package/lambda-files.tests.js b/tests/integration-package/lambda-files.tests.js index df0fe8213..dac5619b9 100644 --- a/tests/integration-package/lambda-files.tests.js +++ b/tests/integration-package/lambda-files.tests.js @@ -2,7 +2,7 @@ const path = require('path'); const fse = require('fs-extra'); -const { execSync } = require('child_process'); +const { execSync } = require('../utils/child-process'); const { serverlessExec } = require('../utils/misc'); const { getTmpDirPath, listZipFiles } = require('../utils/fs'); From 11f6a118c88b7f644d3840f02e006ff6047e88da Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 10:07:31 +0200 Subject: [PATCH 429/504] Improve setup of integration package tests --- .../cloudformation.tests.js | 17 +++++++------- .../{ => fixtures/artifact}/artifact.zip | Bin .../{ => fixtures/artifact}/handler.js | 0 .../fixtures/artifact/serverless.yml | 12 ++++++++++ .../fixtures/individually/handler.js | 18 +++++++++++++++ .../fixtures/individually/handler2.js | 18 +++++++++++++++ .../individually/serverless.yml} | 0 .../fixtures/regular/handler.js | 18 +++++++++++++++ .../{ => fixtures/regular}/serverless.yml | 0 .../integration-package/lambda-files.tests.js | 21 +++++++++--------- 10 files changed, 85 insertions(+), 19 deletions(-) rename tests/integration-package/{ => fixtures/artifact}/artifact.zip (100%) rename tests/integration-package/{ => fixtures/artifact}/handler.js (100%) create mode 100644 tests/integration-package/fixtures/artifact/serverless.yml create mode 100644 tests/integration-package/fixtures/individually/handler.js create mode 100644 tests/integration-package/fixtures/individually/handler2.js rename tests/integration-package/{individually.yml => fixtures/individually/serverless.yml} (100%) create mode 100644 tests/integration-package/fixtures/regular/handler.js rename tests/integration-package/{ => fixtures/regular}/serverless.yml (100%) diff --git a/tests/integration-package/cloudformation.tests.js b/tests/integration-package/cloudformation.tests.js index 017c36eda..c4cabbeb1 100644 --- a/tests/integration-package/cloudformation.tests.js +++ b/tests/integration-package/cloudformation.tests.js @@ -7,6 +7,12 @@ const { execSync } = require('../utils/child-process'); const { serverlessExec } = require('../utils/misc'); const { getTmpDirPath } = require('../utils/fs'); +const fixturePaths = { + regular: path.join(__dirname, 'fixtures/regular'), + individually: path.join(__dirname, 'fixtures/individually'), + artifact: path.join(__dirname, 'fixtures/artifact'), +}; + describe('Integration test - Packaging', () => { let cwd; beforeEach(() => { @@ -14,9 +20,7 @@ describe('Integration test - Packaging', () => { }); it('package artifact directive works', () => { - fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'artifact.zip'), path.join(cwd, 'artifact.zip')); - execSync("echo 'package: {artifact: artifact.zip}' >> serverless.yml", { cwd }); + fse.copySync(fixturePaths.artifact, cwd); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse( fs.readFileSync(path.join(cwd, '.serverless/cloudformation-template-update-stack.json')) @@ -47,8 +51,7 @@ describe('Integration test - Packaging', () => { }); it('creates the correct default function resource in cfn template', () => { - fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(fixturePaths.regular, cwd); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse( fs.readFileSync(path.join(cwd, '.serverless/cloudformation-template-update-stack.json')) @@ -79,9 +82,7 @@ describe('Integration test - Packaging', () => { }); it('handles package individually with include/excludes correctly', () => { - fse.copySync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler2.js')); + fse.copySync(fixturePaths.individually, cwd); execSync(`${serverlessExec} package`, { cwd }); const cfnTemplate = JSON.parse( fs.readFileSync(path.join(cwd, '.serverless/cloudformation-template-update-stack.json')) diff --git a/tests/integration-package/artifact.zip b/tests/integration-package/fixtures/artifact/artifact.zip similarity index 100% rename from tests/integration-package/artifact.zip rename to tests/integration-package/fixtures/artifact/artifact.zip diff --git a/tests/integration-package/handler.js b/tests/integration-package/fixtures/artifact/handler.js similarity index 100% rename from tests/integration-package/handler.js rename to tests/integration-package/fixtures/artifact/handler.js diff --git a/tests/integration-package/fixtures/artifact/serverless.yml b/tests/integration-package/fixtures/artifact/serverless.yml new file mode 100644 index 000000000..e5668f229 --- /dev/null +++ b/tests/integration-package/fixtures/artifact/serverless.yml @@ -0,0 +1,12 @@ +service: aws-nodejs + +provider: + name: aws + runtime: nodejs10.x + +functions: + hello: + handler: handler.hello + +package: + artifact: artifact.zip diff --git a/tests/integration-package/fixtures/individually/handler.js b/tests/integration-package/fixtures/individually/handler.js new file mode 100644 index 000000000..773780bbd --- /dev/null +++ b/tests/integration-package/fixtures/individually/handler.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports.hello = function(event) { + return { + statusCode: 200, + body: JSON.stringify( + { + message: 'Go Serverless v1.0! Your function executed successfully!', + input: event, + }, + null, + 2 + ), + }; + + // Use this code if you don't use the http event with the LAMBDA-PROXY integration + // return { message: 'Go Serverless v1.0! Your function executed successfully!', event }; +}; diff --git a/tests/integration-package/fixtures/individually/handler2.js b/tests/integration-package/fixtures/individually/handler2.js new file mode 100644 index 000000000..773780bbd --- /dev/null +++ b/tests/integration-package/fixtures/individually/handler2.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports.hello = function(event) { + return { + statusCode: 200, + body: JSON.stringify( + { + message: 'Go Serverless v1.0! Your function executed successfully!', + input: event, + }, + null, + 2 + ), + }; + + // Use this code if you don't use the http event with the LAMBDA-PROXY integration + // return { message: 'Go Serverless v1.0! Your function executed successfully!', event }; +}; diff --git a/tests/integration-package/individually.yml b/tests/integration-package/fixtures/individually/serverless.yml similarity index 100% rename from tests/integration-package/individually.yml rename to tests/integration-package/fixtures/individually/serverless.yml diff --git a/tests/integration-package/fixtures/regular/handler.js b/tests/integration-package/fixtures/regular/handler.js new file mode 100644 index 000000000..773780bbd --- /dev/null +++ b/tests/integration-package/fixtures/regular/handler.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports.hello = function(event) { + return { + statusCode: 200, + body: JSON.stringify( + { + message: 'Go Serverless v1.0! Your function executed successfully!', + input: event, + }, + null, + 2 + ), + }; + + // Use this code if you don't use the http event with the LAMBDA-PROXY integration + // return { message: 'Go Serverless v1.0! Your function executed successfully!', event }; +}; diff --git a/tests/integration-package/serverless.yml b/tests/integration-package/fixtures/regular/serverless.yml similarity index 100% rename from tests/integration-package/serverless.yml rename to tests/integration-package/fixtures/regular/serverless.yml diff --git a/tests/integration-package/lambda-files.tests.js b/tests/integration-package/lambda-files.tests.js index dac5619b9..99ed6f113 100644 --- a/tests/integration-package/lambda-files.tests.js +++ b/tests/integration-package/lambda-files.tests.js @@ -6,6 +6,11 @@ const { execSync } = require('../utils/child-process'); const { serverlessExec } = require('../utils/misc'); const { getTmpDirPath, listZipFiles } = require('../utils/fs'); +const fixturePaths = { + regular: path.join(__dirname, 'fixtures/regular'), + individually: path.join(__dirname, 'fixtures/individually'), +}; + describe('Integration test - Packaging', () => { let cwd; beforeEach(() => { @@ -13,8 +18,7 @@ describe('Integration test - Packaging', () => { }); it('packages the default aws template correctly in the zip', () => { - fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(fixturePaths.regular, cwd); execSync(`${serverlessExec} package`, { cwd }); return listZipFiles(path.join(cwd, '.serverless/aws-nodejs.zip')).then(zipfiles => { expect(zipfiles).toEqual(['handler.js']); @@ -22,8 +26,7 @@ describe('Integration test - Packaging', () => { }); it('packages the default aws template with an npm dep correctly in the zip', () => { - fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(fixturePaths.regular, cwd); execSync('npm init --yes', { cwd }); execSync('npm i lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -38,8 +41,7 @@ describe('Integration test - Packaging', () => { }); it("doesn't package a dev dependency in the zip", () => { - fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(fixturePaths.regular, cwd); execSync('npm init --yes', { cwd }); execSync('npm i --save-dev lodash', { cwd }); execSync(`${serverlessExec} package`, { cwd }); @@ -54,8 +56,7 @@ describe('Integration test - Packaging', () => { }); it('ignores package json files per ignore directive in the zip', () => { - fse.copySync(path.join(__dirname, 'serverless.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); + fse.copySync(fixturePaths.regular, cwd); execSync('npm init --yes', { cwd }); execSync('echo \'package: {exclude: ["package*.json"]}\' >> serverless.yml', { cwd }); execSync('npm i lodash', { cwd }); @@ -71,9 +72,7 @@ describe('Integration test - Packaging', () => { }); it('handles package individually with include/excludes correctly', () => { - fse.copySync(path.join(__dirname, 'individually.yml'), path.join(cwd, 'serverless.yml')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler.js')); - fse.copySync(path.join(__dirname, 'handler.js'), path.join(cwd, 'handler2.js')); + fse.copySync(fixturePaths.individually, cwd); execSync(`${serverlessExec} package`, { cwd }); return listZipFiles(path.join(cwd, '.serverless/hello.zip')) .then(zipfiles => expect(zipfiles).toEqual(['handler.js'])) From 44c36a295b5c6bcc076da50d224c3f2d071c1b44 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 10:18:49 +0200 Subject: [PATCH 430/504] Test self.provider.region resolution --- tests/integration-package/cloudformation.tests.js | 11 +++++++++++ .../fixtures/regular/serverless.yml | 3 +++ 2 files changed, 14 insertions(+) diff --git a/tests/integration-package/cloudformation.tests.js b/tests/integration-package/cloudformation.tests.js index c4cabbeb1..2f3d946e0 100644 --- a/tests/integration-package/cloudformation.tests.js +++ b/tests/integration-package/cloudformation.tests.js @@ -114,4 +114,15 @@ describe('Integration test - Packaging', () => { DependsOn: ['HelloLogGroup', 'IamRoleLambdaExecution'], }); }); + + it('resolves self.provider.region', () => { + fse.copySync(fixturePaths.regular, cwd); + execSync(`${serverlessExec} package`, { cwd }); + const cfnTemplate = JSON.parse( + fs.readFileSync(path.join(cwd, '.serverless/cloudformation-template-update-stack.json')) + ); + expect(cfnTemplate.Resources.CustomDashnameLambdaFunction.Properties.FunctionName).toEqual( + 'aws-nodejs-us-east-1-custom-name' + ); + }); }); diff --git a/tests/integration-package/fixtures/regular/serverless.yml b/tests/integration-package/fixtures/regular/serverless.yml index 35c720037..b9cffde35 100644 --- a/tests/integration-package/fixtures/regular/serverless.yml +++ b/tests/integration-package/fixtures/regular/serverless.yml @@ -7,3 +7,6 @@ provider: functions: hello: handler: handler.hello + custom-name: + name: ${self:service}-${self:provider.region}-custom-name + handler: handler.hello From 87b88cdbf931b413e0ec81755ff3d90a2afe3e9e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 10:19:22 +0200 Subject: [PATCH 431/504] Fix service.provider.region resolution --- lib/plugins/aws/provider/awsProvider.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index e87d32fc3..fecc37960 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -128,6 +128,9 @@ class AwsProvider { this.serverless = serverless; this.sdk = AWS; this.serverless.setProvider(constants.providerName, this); + if (this.serverless.service.provider.name === 'aws') { + this.serverless.service.provider.region = this.getRegion(); + } this.requestCache = {}; this.requestQueue = new PromiseQueue(2, Infinity); // Store credentials in this variable to avoid creating them several times (messes up MFA). From f4d42d1cb605061afb8ff74c6250aa48f0764ccd Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 10:19:22 +0200 Subject: [PATCH 432/504] Fix service.provider.region resolution --- lib/plugins/aws/provider/awsProvider.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index f58ed2a62..03e964eb0 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -124,6 +124,9 @@ class AwsProvider { this.serverless = serverless; this.sdk = AWS; this.serverless.setProvider(constants.providerName, this); + if (this.serverless.service.provider.name === 'aws') { + this.serverless.service.provider.region = this.getRegion(); + } this.requestCache = {}; this.requestQueue = new PromiseQueue(2, Infinity); // Store credentials in this variable to avoid creating them several times (messes up MFA). From d2ed60bc88d38a817dd8432308cc21743f043ce2 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Fri, 28 Jun 2019 13:30:51 +0200 Subject: [PATCH 433/504] Releasing v1.46.1 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab82f978c..f57d3e689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.46.1 (2019-06-28) + +- [Fix service.provider.region resolution](https://github.com/serverless/serverless/pull/6317) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.46.0...v1.46.1) + # 1.46.0 (2019-06-26) - [Fix formatting issue with Markdown link](https://github.com/serverless/serverless/pull/6228) diff --git a/package.json b/package.json index e1ae79e3a..cc6674916 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.46.0", + "version": "1.46.1", "engines": { "node": ">=6.0" }, From d72a00316cd8f0c69a0af83354c11e8612ca1b92 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 21 Jun 2019 13:58:04 +0200 Subject: [PATCH 434/504] Clear obsolete ESLint instruction --- bin/serverless.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/serverless.js b/bin/serverless.js index 8c0dc90a9..f7f1f1658 100755 --- a/bin/serverless.js +++ b/bin/serverless.js @@ -33,7 +33,7 @@ const invocationId = uuid.v4(); } // requiring here so that if anything went wrong, // during require, it will be caught. - const Serverless = require('../lib/Serverless'); // eslint-disable-line global-require + const Serverless = require('../lib/Serverless'); const serverless = new Serverless({ interactive: typeof process.env.CI === 'undefined', From 25fe373a08d1d6cc4d71eae50d33e909e17ea415 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 21 Jun 2019 13:58:41 +0200 Subject: [PATCH 435/504] Clear no longer used option --- bin/serverless.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bin/serverless.js b/bin/serverless.js index f7f1f1658..251417470 100755 --- a/bin/serverless.js +++ b/bin/serverless.js @@ -35,9 +35,7 @@ const invocationId = uuid.v4(); // during require, it will be caught. const Serverless = require('../lib/Serverless'); - const serverless = new Serverless({ - interactive: typeof process.env.CI === 'undefined', - }); + const serverless = new Serverless(); serverless.invocationId = invocationId; From a607d83ba864df4e4edc61d5f85bc0e054a8ddb9 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 21 Jun 2019 16:24:39 +0200 Subject: [PATCH 436/504] Cleanup CLI flow setup --- bin/serverless.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/bin/serverless.js b/bin/serverless.js index 251417470..31273f1e9 100755 --- a/bin/serverless.js +++ b/bin/serverless.js @@ -42,7 +42,6 @@ const invocationId = uuid.v4(); return serverless .init() .then(() => serverless.run()) - .then(() => process.exit(0)) .catch(err => { // If Enterprise Plugin, capture error let enterpriseErrorHandler = null; @@ -63,7 +62,10 @@ const invocationId = uuid.v4(); }); }); }) - .catch(e => { - process.exitCode = 1; - logError(e); - }))(); + .then( + () => process.exit(0), + e => { + process.exitCode = 1; + logError(e); + } + ))(); From 2f8d186bb1db4ad49c81178ae4e224369ac990c7 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 21 Jun 2019 17:50:33 +0200 Subject: [PATCH 437/504] Seclude getConfigFilePath util --- lib/utils/getServerlessConfigFile.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/utils/getServerlessConfigFile.js b/lib/utils/getServerlessConfigFile.js index a6fa2c259..23ddbb8c7 100644 --- a/lib/utils/getServerlessConfigFile.js +++ b/lib/utils/getServerlessConfigFile.js @@ -6,11 +6,9 @@ const path = require('path'); const fileExists = require('./fs/fileExists'); const readFile = require('./fs/readFile'); -const getServerlessConfigFilePath = serverless => { - const servicePath = serverless.config.servicePath || process.cwd(); - - if (serverless.processedInput.options.config) { - const customPath = path.join(servicePath, serverless.processedInput.options.config); +const getConfigFilePath = (servicePath, options = {}) => { + if (options.config) { + const customPath = path.join(servicePath, options.config); return fileExists(customPath).then(exists => (exists ? customPath : null)); } @@ -39,6 +37,13 @@ const getServerlessConfigFilePath = serverless => { }); }; +const getServerlessConfigFilePath = serverless => { + return getConfigFilePath( + serverless.config.servicePath || process.cwd(), + serverless.processedInput.options + ); +}; + const handleJsConfigFile = jsConfigFile => BbPromise.try(() => { // use require to load serverless.js @@ -71,4 +76,4 @@ const getServerlessConfigFile = _.memoize( serverless => `${serverless.processedInput.options.config} - ${serverless.config.servicePath}` ); -module.exports = { getServerlessConfigFile, getServerlessConfigFilePath }; +module.exports = { getConfigFilePath, getServerlessConfigFile, getServerlessConfigFilePath }; From d7a6534e0810349c62f431a3699d03d24231cffd Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 24 Jun 2019 15:01:21 +0200 Subject: [PATCH 438/504] Seclude installTemplate logic --- lib/plugins/create/create.js | 72 ++++++++++++++----------------- lib/utils/installTemplate.js | 30 +++++++++++++ lib/utils/installTemplate.test.js | 26 +++++++++++ 3 files changed, 88 insertions(+), 40 deletions(-) create mode 100644 lib/utils/installTemplate.js create mode 100644 lib/utils/installTemplate.test.js diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js index 0da256897..771f1299c 100644 --- a/lib/plugins/create/create.js +++ b/lib/plugins/create/create.js @@ -7,6 +7,7 @@ const _ = require('lodash'); const untildify = require('untildify'); const ServerlessError = require('../../classes/Error').ServerlessError; +const installTemplate = require('../../utils/installTemplate'); const userStats = require('../../utils/userStats'); const download = require('../../utils/downloadTemplateFromRepo'); const renameService = require('../../utils/renameService').renameService; @@ -112,7 +113,7 @@ class Create { this.serverless.cli.log('Generating boilerplate...'); if ('template' in this.options) { - this.createFromTemplate(); + return this.createFromTemplate(); } else if ('template-url' in this.options) { return download .downloadTemplateFromRepo( @@ -225,49 +226,40 @@ class Create { this.serverless.config.update({ servicePath: process.cwd() }); } - // copy template files recursively to cwd - // while keeping template file tree - try { - this.serverless.utils.copyDirContentsSync(templateSrcDir, process.cwd()); + return installTemplate(this.options.template, process.cwd()).then( + () => { + // rename the service if the user has provided a path via options and is creating a service + if ((boilerplatePath || serviceName) && notPlugin) { + const newServiceName = serviceName || boilerplatePath.split(path.sep).pop(); - // NPM renames .gitignore to .npmignore on publish so we have to rename it. - if (fse.existsSync(path.join(process.cwd(), 'gitignore'))) { - fse.renameSync( - path.join(process.cwd(), 'gitignore'), - path.join(process.cwd(), '.gitignore') + renameService(newServiceName, this.serverless.config.servicePath); + } + + userStats.track('service_created', { + template: this.options.template, + serviceName, + }); + + this.serverless.cli.asciiGreeting(); + this.serverless.cli.log( + `Successfully generated boilerplate for template: "${this.options.template}"` ); + + if (!(boilerplatePath || serviceName) && notPlugin) { + this.serverless.cli.log( + 'NOTE: Please update the "service" property in serverless.yml with your service name' + ); + } + }, + () => { + const errorMessage = [ + 'Error unable to create a service in this directory. ', + 'Please check that you have the required permissions to write to the directory', + ].join(''); + + throw new this.serverless.classes.Error(errorMessage); } - } catch (err) { - const errorMessage = [ - 'Error unable to create a service in this directory. ', - 'Please check that you have the required permissions to write to the directory', - ].join(''); - - throw new this.serverless.classes.Error(errorMessage); - } - - // rename the service if the user has provided a path via options and is creating a service - if ((boilerplatePath || serviceName) && notPlugin) { - const newServiceName = serviceName || boilerplatePath.split(path.sep).pop(); - - renameService(newServiceName, this.serverless.config.servicePath); - } - - userStats.track('service_created', { - template: this.options.template, - serviceName, - }); - - this.serverless.cli.asciiGreeting(); - this.serverless.cli.log( - `Successfully generated boilerplate for template: "${this.options.template}"` ); - - if (!(boilerplatePath || serviceName) && notPlugin) { - this.serverless.cli.log( - 'NOTE: Please update the "service" property in serverless.yml with your service name' - ); - } } } diff --git a/lib/utils/installTemplate.js b/lib/utils/installTemplate.js new file mode 100644 index 000000000..c0976b34a --- /dev/null +++ b/lib/utils/installTemplate.js @@ -0,0 +1,30 @@ +'use strict'; + +const { join } = require('path'); +const BbPromise = require('bluebird'); +const { copy, exists, rename } = require('fs-extra'); + +const serverlessPath = join(__dirname, '../../'); + +module.exports = (templateName, destPath) => + new BbPromise((resolve, reject) => { + const templateSrcDir = join(serverlessPath, 'lib/plugins/create/templates', templateName); + + copy(templateSrcDir, destPath, copyError => { + if (copyError) { + reject(copyError); + return; + } + const gitignorePath = join(destPath, 'gitignore'); + exists(gitignorePath, hasGitignore => { + if (!hasGitignore) { + resolve(); + return; + } + rename(gitignorePath, join(destPath, '.gitignore'), renameError => { + if (renameError) reject(renameError); + else resolve(); + }); + }); + }); + }); diff --git a/lib/utils/installTemplate.test.js b/lib/utils/installTemplate.test.js new file mode 100644 index 000000000..bbce3e181 --- /dev/null +++ b/lib/utils/installTemplate.test.js @@ -0,0 +1,26 @@ +'use strict'; + +const path = require('path'); +const chai = require('chai'); +const { existsSync } = require('fs-extra'); +const installTemplate = require('./installTemplate'); +const { getTmpDirPath } = require('../../tests/utils/fs'); + +chai.use(require('chai-as-promised')); + +const expect = chai.expect; + +describe('#installTemplate()', () => { + let tmpDirPath; + + beforeEach(() => { + tmpDirPath = getTmpDirPath(); + return installTemplate('aws-nodejs', tmpDirPath); + }); + + it('should install template', () => + expect(existsSync(path.join(tmpDirPath, 'serverless.yml'))).to.be.true); + + it('should handle .gitignore', () => + expect(existsSync(path.join(tmpDirPath, '.gitignore'))).to.be.true); +}); From 1b7ca0535dee4a00ed74e06324d18bf9c68ad170 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 24 Jun 2019 15:23:52 +0200 Subject: [PATCH 439/504] Initial version of interactive project creator --- bin/serverless.js | 12 ++++++ lib/utils/interactiveCreate.js | 68 ++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 81 insertions(+) create mode 100644 lib/utils/interactiveCreate.js diff --git a/bin/serverless.js b/bin/serverless.js index 31273f1e9..62dcbce86 100755 --- a/bin/serverless.js +++ b/bin/serverless.js @@ -24,6 +24,13 @@ process.noDeprecation = true; const invocationId = uuid.v4(); +const isInteractiveCreate = serverless => { + if (process.argv[2]) return false; // Arguments after 'serverless' + if (!process.stdin.isTTY) return false; // No interactivity with user is possible + if (serverless.config.servicePath) return false; // Invoked in existing project + return true; +}; + // boot up error reporting via sentry before anything (() => initializeErrorReporter(invocationId) @@ -39,6 +46,11 @@ const invocationId = uuid.v4(); serverless.invocationId = invocationId; + if (isInteractiveCreate(serverless)) { + const interactiveCreate = require('../lib/utils/interactiveCreate'); + return interactiveCreate(process.cwd()); + } + return serverless .init() .then(() => serverless.run()) diff --git a/lib/utils/interactiveCreate.js b/lib/utils/interactiveCreate.js new file mode 100644 index 000000000..684487698 --- /dev/null +++ b/lib/utils/interactiveCreate.js @@ -0,0 +1,68 @@ +'use strict'; + +const { join } = require('path'); +const chalk = require('chalk'); +const inquirer = require('inquirer'); +const installTemplate = require('./installTemplate'); +const { getConfigFilePath } = require('./getServerlessConfigFile'); + +const isValidServiceName = RegExp.prototype.test.bind(/^[a-zA-Z][a-zA-Z0-9-]{0,100}$/); + +module.exports = path => { + const projectTypeChoice = () => + inquirer + .prompt({ + message: 'What do you want to make?', + type: 'list', + name: 'projectType', + choices: [ + { name: 'AWS Node.js', value: 'aws-nodejs' }, + { name: 'AWS Python', value: 'aws-python' }, + { name: 'Other', value: 'other' }, + ], + }) + .then(({ projectType }) => { + if (projectType === 'other') { + process.stdout.write( + '\nRun “serverless create --help” to view available templates and create a new project ' + + 'from one of those templates.\n\n' + ); + return null; + } + return projectType; + }); + + const projectNameInput = () => + inquirer + .prompt({ + message: 'What do you want to call this project?', + type: 'input', + name: 'projectName', + validate: input => { + if (!isValidServiceName(input)) { + return ( + 'Project name is not valid.\n' + + ' - It should only contain alphanumeric and hyphens.\n' + + ' - It should start with an alphabetic character.\n' + + " - Shouldn't exceed 128 characters" + ); + } + return getConfigFilePath(join(path, input)).then(configFilePath => { + return configFilePath ? `Serverless project already found at ${input} directory` : true; + }); + }, + }) + .then(({ projectName }) => projectName); + + return projectTypeChoice().then(projectType => { + if (!projectType) return null; + return projectNameInput().then(projectName => { + const projectDir = join(process.cwd(), projectName); + return installTemplate(projectType, projectDir).then(() => { + process.stdout.write( + `\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n\n` + ); + }); + }); + }); +}; diff --git a/package.json b/package.json index 0dd870d93..faab77191 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,7 @@ "globby": "^6.1.0", "graceful-fs": "^4.1.15", "https-proxy-agent": "^2.2.1", + "inquirer": "^6.4.1", "is-docker": "^1.1.0", "js-yaml": "^3.13.1", "json-cycle": "^1.3.0", From 35cdcf5cc8aa32b7d0835501c524d7908e1f2096 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 25 Jun 2019 14:13:36 +0200 Subject: [PATCH 440/504] AWS credentials utils --- lib/plugins/aws/utils/credentials.js | 86 +++++++++++++++++++++ lib/plugins/aws/utils/credentials.test.js | 91 +++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 lib/plugins/aws/utils/credentials.js create mode 100644 lib/plugins/aws/utils/credentials.test.js diff --git a/lib/plugins/aws/utils/credentials.js b/lib/plugins/aws/utils/credentials.js new file mode 100644 index 000000000..68664c1a4 --- /dev/null +++ b/lib/plugins/aws/utils/credentials.js @@ -0,0 +1,86 @@ +'use strict'; + +const { join } = require('path'); +const { readFile, writeFile } = require('fs'); +const os = require('os'); +const BbPromise = require('bluebird'); + +const homedir = os.homedir(); +const credentialsFilePath = homedir ? join(homedir, '.aws/credentials') : null; + +const profileNameRe = /^\[([^\]]+)]\s*$/; +const settingRe = /^([a-zA-Z0-9_]+)\s*=\s*([^\s]+)\s*$/; +const settingMap = new Map([ + ['aws_access_key_id', 'accessKeyId'], + ['aws_secret_access_key', 'secretAccessKey'], +]); +const parseFileProfiles = content => { + const profiles = new Map(); + let currentProfile; + for (const line of content.split(/[\n\r]+/)) { + const profileNameMatches = line.match(profileNameRe); + if (profileNameMatches) { + currentProfile = {}; + profiles.set(profileNameMatches[1], currentProfile); + continue; + } + if (!currentProfile) continue; + const settingMatches = line.match(settingRe); + if (!settingMatches) continue; + let [, settingAwsName] = settingMatches; + settingAwsName = settingAwsName.toLowerCase(); + const settingName = settingMap.get(settingAwsName); + if (settingName) currentProfile[settingName] = settingMatches[2]; + } + for (const [profileName, profileData] of profiles) { + if (!profileData.accessKeyId || !profileData.secretAccessKey) profiles.delete(profileName); + } + return profiles; +}; + +module.exports = { + resolveFileProfiles() { + return new BbPromise((resolve, reject) => { + if (!credentialsFilePath) { + resolve(new Map()); + return; + } + readFile(credentialsFilePath, { encoding: 'utf8' }, (error, content) => { + if (error) { + if (error.code === 'ENOENT') { + resolve(new Map()); + return; + } + reject(error); + return; + } + resolve(parseFileProfiles(content)); + }); + }); + }, + + resolveEnvCredentials() { + if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) return null; + return { + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }; + }, + + saveFileProfiles(profiles) { + return new BbPromise((resolve, reject) => { + if (!credentialsFilePath) throw new Error('Could not resolve path to user credentials file'); + const content = `${Array.from(profiles) + .map( + ([name, data]) => + `[${name}]\naws_access_key_id=${data.accessKeyId}\n` + + `aws_secret_access_key=${data.secretAccessKey}\n` + ) + .join('\n')}`; + writeFile(credentialsFilePath, content, error => { + if (error) reject(error); + else resolve(); + }); + }); + }, +}; diff --git a/lib/plugins/aws/utils/credentials.test.js b/lib/plugins/aws/utils/credentials.test.js new file mode 100644 index 000000000..eaa659cb5 --- /dev/null +++ b/lib/plugins/aws/utils/credentials.test.js @@ -0,0 +1,91 @@ +'use strict'; + +const expect = require('chai').expect; +const BbPromise = require('bluebird'); +const os = require('os'); +const path = require('path'); +const { outputFile } = require('fs-extra'); +const overrideEnv = require('process-utils/override-env'); +const credentials = require('./credentials'); + +describe('#credentials', () => { + const credentialsFilePath = path.join(os.homedir(), '.aws', 'credentials'); + + it('should resolve file profiles', () => { + const profiles = new Map([ + [ + 'my-profile1', + { + accessKeyId: 'my-old-profile-key1', + secretAccessKey: 'my-old-profile-secret1', + }, + ], + [ + 'my-profile2', + { + accessKeyId: 'my-old-profile-key2', + secretAccessKey: 'my-old-profile-secret2', + }, + ], + ]); + + const [[profile1Name, profile1], [profile2Name, profile2]] = Array.from(profiles); + const credentialsFileContent = `${[ + `[${[profile1Name]}]`, + `aws_access_key_id = ${profile1.accessKeyId}`, + `aws_secret_access_key = ${profile1.secretAccessKey}`, + '', + `[${[profile2Name]}]`, + `aws_access_key_id = ${profile2.accessKeyId}`, + `aws_secret_access_key = ${profile2.secretAccessKey}`, + ].join('\n')}\n`; + + return new BbPromise((resolve, reject) => { + outputFile(credentialsFilePath, credentialsFileContent, error => { + if (error) reject(error); + else resolve(); + }); + }).then(() => + credentials + .resolveFileProfiles() + .then(resolvedProfiles => expect(resolvedProfiles).to.deep.equal(profiles)) + ); + }); + + it('should resolve env credentials', () => + overrideEnv(() => { + process.env.AWS_ACCESS_KEY_ID = 'foo'; + process.env.AWS_SECRET_ACCESS_KEY = 'bar'; + expect(credentials.resolveEnvCredentials()).to.deep.equal({ + accessKeyId: 'foo', + secretAccessKey: 'bar', + }); + })); + + it('should save file profiles', () => { + const profiles = new Map([ + [ + 'my-profileA', + { + accessKeyId: 'my-old-profile-key1', + secretAccessKey: 'my-old-profile-secret1', + }, + ], + [ + 'my-profileB', + { + accessKeyId: 'my-old-profile-key2', + secretAccessKey: 'my-old-profile-secret2', + }, + ], + ]); + + return credentials + .saveFileProfiles(profiles) + .then(() => + credentials + .resolveFileProfiles() + .then(resolvedProfiles => expect(resolvedProfiles).to.deep.equal(profiles)) + ); + }); +}); From acb397138ce64173632b4bed98bc4b3e4786943c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 25 Jun 2019 14:45:12 +0200 Subject: [PATCH 441/504] Interactive setup of AWS credentials --- lib/utils/interactiveCreate.js | 118 ++++++++++++++++++++++++++++++--- 1 file changed, 109 insertions(+), 9 deletions(-) diff --git a/lib/utils/interactiveCreate.js b/lib/utils/interactiveCreate.js index 684487698..5db9c8f3c 100644 --- a/lib/utils/interactiveCreate.js +++ b/lib/utils/interactiveCreate.js @@ -5,8 +5,12 @@ const chalk = require('chalk'); const inquirer = require('inquirer'); const installTemplate = require('./installTemplate'); const { getConfigFilePath } = require('./getServerlessConfigFile'); +const awsCredentials = require('../plugins/aws/utils/credentials'); const isValidServiceName = RegExp.prototype.test.bind(/^[a-zA-Z][a-zA-Z0-9-]{0,100}$/); +const isValidAwsProfileName = isValidServiceName; +const isValidAwsAccessKeyId = RegExp.prototype.test.bind(/^[A-Z0-9]{10,}$/); +const isValidAwsSecretAccessKey = RegExp.prototype.test.bind(/^[a-zA-Z0-9/]{10,}$/); module.exports = path => { const projectTypeChoice = () => @@ -39,6 +43,7 @@ module.exports = path => { type: 'input', name: 'projectName', validate: input => { + input = input.trim(); if (!isValidServiceName(input)) { return ( 'Project name is not valid.\n' + @@ -52,17 +57,112 @@ module.exports = path => { }); }, }) - .then(({ projectName }) => projectName); + .then(({ projectName }) => projectName.trim()); + + const awsCredentialsConfirm = () => + inquirer + .prompt({ + message: 'Do you want to set them up now?', + type: 'confirm', + name: 'awsSetupNeeded', + }) + .then(({ awsSetupNeeded }) => awsSetupNeeded); + + const awsProfileNameInput = () => + inquirer + .prompt({ + message: 'AWS Profile name', + type: 'input', + name: 'profileName', + validate: input => { + if (isValidAwsProfileName(input.trim())) return true; + return ( + 'AWS profile name is not valid.\n' + + ' - It should only contain alphanumeric and hyphens.\n' + + ' - It should start with an alphabetic character.\n' + + " - Shouldn't exceed 128 characters" + ); + }, + }) + .then(({ profileName }) => profileName.trim()); + + const awsAccessKeyIdInput = () => + inquirer + .prompt({ + message: 'AWS Access Key Id', + type: 'input', + name: 'accessKeyId', + validate: input => { + if (isValidAwsAccessKeyId(input.trim())) return true; + return ( + 'AWS Access Key Id seems not valid.\n' + + ' Expected something like AKIAIOSFODNN7EXAMPLE' + ); + }, + }) + .then(({ accessKeyId }) => accessKeyId.trim()); + + const awsSecretAccessKeyInput = () => + inquirer + .prompt({ + message: 'AWS Secret Access Key', + type: 'input', + name: 'secretAccessKey', + validate: input => { + if (isValidAwsSecretAccessKey(input.trim())) return true; + return ( + 'AWS Secret Access Key seems not valid.\n' + + ' Expected something like wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' + ); + }, + }) + .then(({ secretAccessKey }) => secretAccessKey.trim()); return projectTypeChoice().then(projectType => { if (!projectType) return null; - return projectNameInput().then(projectName => { - const projectDir = join(process.cwd(), projectName); - return installTemplate(projectType, projectDir).then(() => { - process.stdout.write( - `\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n\n` - ); - }); - }); + return projectNameInput() + .then(projectName => { + const projectDir = join(process.cwd(), projectName); + return installTemplate(projectType, projectDir).then(() => { + process.stdout.write( + `\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n\n` + ); + }); + }) + .then(() => + Promise.all([ + awsCredentials.resolveFileProfiles(), + awsCredentials.resolveEnvCredentials(), + ]).then(([fileProfiles, envCredentials]) => { + if (fileProfiles.size || envCredentials) return null; + process.stdout.write( + 'No AWS credentials were found on your computer, ' + + 'you need these to host your application.\n' + ); + return awsCredentialsConfirm().then(result => { + if (!result) return null; + process.stdout.write( + '\nGo here to learn how to create your AWS credentials:\n' + + 'https://slss.io/aws-account and enter them here:\n\n' + ); + return awsProfileNameInput().then(profileName => + awsAccessKeyIdInput().then(accessKeyId => + awsSecretAccessKeyInput().then(secretAccessKey => + awsCredentials + .saveFileProfiles(new Map([[profileName, { accessKeyId, secretAccessKey }]])) + .then(() => + process.stdout.write( + `\n${chalk.green( + 'AWS credentials saved on your machine at ~/.aws/credentials. ' + + 'Go there to change them at any time.' + )}\n\n` + ) + ) + ) + ) + ); + }); + }) + ); }); }; From 9646c6e3ba86d2a19f91534d64c04f16dd25bcd8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 25 Jun 2019 14:53:45 +0200 Subject: [PATCH 442/504] Make interactive create optional --- lib/utils/interactiveCreate.js | 98 +++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/lib/utils/interactiveCreate.js b/lib/utils/interactiveCreate.js index 5db9c8f3c..0ea083cf2 100644 --- a/lib/utils/interactiveCreate.js +++ b/lib/utils/interactiveCreate.js @@ -13,6 +13,15 @@ const isValidAwsAccessKeyId = RegExp.prototype.test.bind(/^[A-Z0-9]{10,}$/); const isValidAwsSecretAccessKey = RegExp.prototype.test.bind(/^[a-zA-Z0-9/]{10,}$/); module.exports = path => { + const createConfirm = () => + inquirer + .prompt({ + message: 'No Serverless project detected. Do you want to create a new one?', + type: 'confirm', + name: 'createWanted', + }) + .then(({ createWanted }) => createWanted); + const projectTypeChoice = () => inquirer .prompt({ @@ -118,51 +127,56 @@ module.exports = path => { }) .then(({ secretAccessKey }) => secretAccessKey.trim()); - return projectTypeChoice().then(projectType => { - if (!projectType) return null; - return projectNameInput() - .then(projectName => { - const projectDir = join(process.cwd(), projectName); - return installTemplate(projectType, projectDir).then(() => { - process.stdout.write( - `\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n\n` - ); - }); - }) - .then(() => - Promise.all([ - awsCredentials.resolveFileProfiles(), - awsCredentials.resolveEnvCredentials(), - ]).then(([fileProfiles, envCredentials]) => { - if (fileProfiles.size || envCredentials) return null; - process.stdout.write( - 'No AWS credentials were found on your computer, ' + - 'you need these to host your application.\n' - ); - return awsCredentialsConfirm().then(result => { - if (!result) return null; + process.stdout.write('\n'); + return createConfirm().then(createWanted => { + if (!createWanted) return null; + + return projectTypeChoice().then(projectType => { + if (!projectType) return null; + return projectNameInput() + .then(projectName => { + const projectDir = join(process.cwd(), projectName); + return installTemplate(projectType, projectDir).then(() => { process.stdout.write( - '\nGo here to learn how to create your AWS credentials:\n' + - 'https://slss.io/aws-account and enter them here:\n\n' - ); - return awsProfileNameInput().then(profileName => - awsAccessKeyIdInput().then(accessKeyId => - awsSecretAccessKeyInput().then(secretAccessKey => - awsCredentials - .saveFileProfiles(new Map([[profileName, { accessKeyId, secretAccessKey }]])) - .then(() => - process.stdout.write( - `\n${chalk.green( - 'AWS credentials saved on your machine at ~/.aws/credentials. ' + - 'Go there to change them at any time.' - )}\n\n` - ) - ) - ) - ) + `\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n\n` ); }); }) - ); + .then(() => + Promise.all([ + awsCredentials.resolveFileProfiles(), + awsCredentials.resolveEnvCredentials(), + ]).then(([fileProfiles, envCredentials]) => { + if (fileProfiles.size || envCredentials) return null; + process.stdout.write( + 'No AWS credentials were found on your computer, ' + + 'you need these to host your application.\n' + ); + return awsCredentialsConfirm().then(result => { + if (!result) return null; + process.stdout.write( + '\nGo here to learn how to create your AWS credentials:\n' + + 'https://slss.io/aws-account and enter them here:\n\n' + ); + return awsProfileNameInput().then(profileName => + awsAccessKeyIdInput().then(accessKeyId => + awsSecretAccessKeyInput().then(secretAccessKey => + awsCredentials + .saveFileProfiles(new Map([[profileName, { accessKeyId, secretAccessKey }]])) + .then(() => + process.stdout.write( + `\n${chalk.green( + 'AWS credentials saved on your machine at ~/.aws/credentials. ' + + 'Go there to change them at any time.' + )}\n\n` + ) + ) + ) + ) + ); + }); + }) + ); + }); }); }; From af3ac05342e58c312ed327e5db723ef70b09e657 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 25 Jun 2019 14:55:22 +0200 Subject: [PATCH 443/504] Improve formatting --- lib/utils/interactiveCreate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/interactiveCreate.js b/lib/utils/interactiveCreate.js index 0ea083cf2..f9ea06a62 100644 --- a/lib/utils/interactiveCreate.js +++ b/lib/utils/interactiveCreate.js @@ -150,7 +150,7 @@ module.exports = path => { if (fileProfiles.size || envCredentials) return null; process.stdout.write( 'No AWS credentials were found on your computer, ' + - 'you need these to host your application.\n' + 'you need these to host your application.\n\n' ); return awsCredentialsConfirm().then(result => { if (!result) return null; From 6df0f6872f6360371d4be05d7abc68efb7e4eae3 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 25 Jun 2019 14:56:19 +0200 Subject: [PATCH 444/504] Reorganize logic --- lib/utils/interactiveCreate.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/utils/interactiveCreate.js b/lib/utils/interactiveCreate.js index f9ea06a62..48f21b8b6 100644 --- a/lib/utils/interactiveCreate.js +++ b/lib/utils/interactiveCreate.js @@ -34,16 +34,7 @@ module.exports = path => { { name: 'Other', value: 'other' }, ], }) - .then(({ projectType }) => { - if (projectType === 'other') { - process.stdout.write( - '\nRun “serverless create --help” to view available templates and create a new project ' + - 'from one of those templates.\n\n' - ); - return null; - } - return projectType; - }); + .then(({ projectType }) => projectType); const projectNameInput = () => inquirer @@ -132,7 +123,13 @@ module.exports = path => { if (!createWanted) return null; return projectTypeChoice().then(projectType => { - if (!projectType) return null; + if (projectType === 'other') { + process.stdout.write( + '\nRun “serverless create --help” to view available templates and create a new project ' + + 'from one of those templates.\n\n' + ); + return null; + } return projectNameInput() .then(projectName => { const projectDir = join(process.cwd(), projectName); From 041e657273a3fb5c9aaa631e76b81ae126d43d36 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 27 Jun 2019 14:33:03 +0200 Subject: [PATCH 445/504] Cleanup region and stage option resolution --- lib/classes/Service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 0dbf9c2a8..86ff9115a 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -36,8 +36,8 @@ class Service { load(rawOptions) { const that = this; const options = rawOptions || {}; - options.stage = options.stage || options.s; - options.region = options.region || options.r; + if (!options.stage && options.s) options.stage = options.s; + if (!options.region && options.r) options.region = options.r; const servicePath = this.serverless.config.servicePath; // skip if the service path is not found From 23b5bcfdeae40da47fc754c3be4b3e7523d1cf53 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 13:32:04 +0200 Subject: [PATCH 446/504] Reconfigure interactive CLI into core plugin --- bin/serverless.js | 12 -- lib/plugins/Plugins.json | 1 + lib/plugins/interactiveCli/index.js | 38 ++++ .../interactiveCli/initializeService.js | 97 ++++++++++ lib/plugins/interactiveCli/setupAws.js | 99 ++++++++++ lib/plugins/interactiveCli/utils.js | 14 ++ lib/utils/interactiveCreate.js | 179 ------------------ 7 files changed, 249 insertions(+), 191 deletions(-) create mode 100644 lib/plugins/interactiveCli/index.js create mode 100644 lib/plugins/interactiveCli/initializeService.js create mode 100644 lib/plugins/interactiveCli/setupAws.js create mode 100644 lib/plugins/interactiveCli/utils.js delete mode 100644 lib/utils/interactiveCreate.js diff --git a/bin/serverless.js b/bin/serverless.js index 62dcbce86..31273f1e9 100755 --- a/bin/serverless.js +++ b/bin/serverless.js @@ -24,13 +24,6 @@ process.noDeprecation = true; const invocationId = uuid.v4(); -const isInteractiveCreate = serverless => { - if (process.argv[2]) return false; // Arguments after 'serverless' - if (!process.stdin.isTTY) return false; // No interactivity with user is possible - if (serverless.config.servicePath) return false; // Invoked in existing project - return true; -}; - // boot up error reporting via sentry before anything (() => initializeErrorReporter(invocationId) @@ -46,11 +39,6 @@ const isInteractiveCreate = serverless => { serverless.invocationId = invocationId; - if (isInteractiveCreate(serverless)) { - const interactiveCreate = require('../lib/utils/interactiveCreate'); - return interactiveCreate(process.cwd()); - } - return serverless .init() .then(() => serverless.run()) diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json index 253d0a750..bbd2edb9e 100644 --- a/lib/plugins/Plugins.json +++ b/lib/plugins/Plugins.json @@ -1,5 +1,6 @@ { "plugins": [ + "./interactiveCli", "./config/config.js", "./create/create.js", "./install/install.js", diff --git a/lib/plugins/interactiveCli/index.js b/lib/plugins/interactiveCli/index.js new file mode 100644 index 000000000..33968b3a4 --- /dev/null +++ b/lib/plugins/interactiveCli/index.js @@ -0,0 +1,38 @@ +'use strict'; + +const _ = require('lodash'); +const initializeService = require('./initializeService'); +const setupAws = require('./setupAws'); + +module.exports = class InteractiveCli { + constructor(serverless) { + if (!process.stdin.isTTY) return; + + const { processedInput } = serverless; + if (!_.isEmpty(processedInput.commands)) return; + if (!_.isEmpty(processedInput.options)) return; + + // Enforce interactive CLI + processedInput.commands.push('interactiveCli'); + this.commands = { + interactiveCli: { + lifecycleEvents: ['initializeService', 'setupAws'], + }, + }; + + this.hooks = { + 'interactiveCli:initializeService': () => { + if (!initializeService.check(serverless)) return null; + process.stdout.write('\n'); + return initializeService.run(serverless); + }, + 'interactiveCli:setupAws': () => { + return setupAws.check(serverless).then(isApplicable => { + if (!isApplicable) return null; + process.stdout.write('\n'); + return setupAws.run(serverless); + }); + }, + }; + } +}; diff --git a/lib/plugins/interactiveCli/initializeService.js b/lib/plugins/interactiveCli/initializeService.js new file mode 100644 index 000000000..41eff2a1a --- /dev/null +++ b/lib/plugins/interactiveCli/initializeService.js @@ -0,0 +1,97 @@ +'use strict'; + +const { join } = require('path'); +const chalk = require('chalk'); +const inquirer = require('inquirer'); +const installTemplate = require('../../utils/installTemplate'); +const { + getConfigFilePath, + getServerlessConfigFile, +} = require('../../utils/getServerlessConfigFile'); +const { confirm } = require('./utils'); + +const isValidServiceName = RegExp.prototype.test.bind(/^[a-zA-Z][a-zA-Z0-9-]{0,100}$/); + +const projectTypeChoice = () => + inquirer + .prompt({ + message: 'What do you want to make?', + type: 'list', + name: 'projectType', + choices: [ + { name: 'AWS Node.js', value: 'aws-nodejs' }, + { name: 'AWS Python', value: 'aws-python' }, + { name: 'Other', value: 'other' }, + ], + }) + .then(({ projectType }) => projectType); + +const projectNameInput = workingDir => + inquirer + .prompt({ + message: 'What do you want to call this project?', + type: 'input', + name: 'projectName', + validate: input => { + input = input.trim(); + if (!isValidServiceName(input)) { + return ( + 'Project name is not valid.\n' + + ' - It should only contain alphanumeric and hyphens.\n' + + ' - It should start with an alphabetic character.\n' + + " - Shouldn't exceed 128 characters" + ); + } + return getConfigFilePath(join(workingDir, input)).then(configFilePath => { + return configFilePath ? `Serverless project already found at ${input} directory` : true; + }); + }, + }) + .then(({ projectName }) => projectName.trim()); + +module.exports = { + check(serverless) { + return !serverless.config.servicePath; + }, + run(serverless) { + const workingDir = process.cwd(); + return confirm('No Serverless project detected. Do you want to create a new one?').then( + isConfirmed => { + if (!isConfirmed) return null; + return projectTypeChoice().then(projectType => { + if (projectType === 'other') { + process.stdout.write( + '\nRun “serverless create --help” to view available templates and create a new project ' + + 'from one of those templates.\n' + ); + return null; + } + return projectNameInput(workingDir).then(projectName => { + const projectDir = join(workingDir, projectName); + return installTemplate(projectType, projectDir) + .then(() => { + process.stdout.write( + `\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n` + ); + + process.chdir(projectDir); + serverless.config.servicePath = projectDir; + getServerlessConfigFile.cache.delete(serverless); + getServerlessConfigFile(serverless); + }) + .then(serverlessConfigFile => { + serverless.pluginManager.serverlessConfigFile = serverlessConfigFile; + return serverless.service.load(); + }) + .then(() => serverless.variables.populateService()) + .then(() => { + serverless.service.mergeArrays(); + serverless.service.setFunctionNames(); + serverless.service.validate(); + }); + }); + }); + } + ); + }, +}; diff --git a/lib/plugins/interactiveCli/setupAws.js b/lib/plugins/interactiveCli/setupAws.js new file mode 100644 index 000000000..ba35afe61 --- /dev/null +++ b/lib/plugins/interactiveCli/setupAws.js @@ -0,0 +1,99 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const chalk = require('chalk'); +const inquirer = require('inquirer'); +const awsCredentials = require('../aws/utils/credentials'); +const { confirm } = require('./utils'); + +const isValidAwsProfileName = RegExp.prototype.test.bind(/^[a-zA-Z][a-zA-Z0-9-]{0,100}$/); +const isValidAwsAccessKeyId = RegExp.prototype.test.bind(/^[A-Z0-9]{10,}$/); +const isValidAwsSecretAccessKey = RegExp.prototype.test.bind(/^[a-zA-Z0-9/]{10,}$/); + +const awsProfileNameInput = () => + inquirer + .prompt({ + message: 'AWS Profile name', + type: 'input', + name: 'profileName', + validate: input => { + if (isValidAwsProfileName(input.trim())) return true; + return ( + 'AWS profile name is not valid.\n' + + ' - It should only contain alphanumeric and hyphens.\n' + + ' - It should start with an alphabetic character.\n' + + " - Shouldn't exceed 128 characters" + ); + }, + }) + .then(({ profileName }) => profileName.trim()); + +const awsAccessKeyIdInput = () => + inquirer + .prompt({ + message: 'AWS Access Key Id', + type: 'input', + name: 'accessKeyId', + validate: input => { + if (isValidAwsAccessKeyId(input.trim())) return true; + return 'AWS Access Key Id seems not valid.\n Expected something like AKIAIOSFODNN7EXAMPLE'; + }, + }) + .then(({ accessKeyId }) => accessKeyId.trim()); + +const awsSecretAccessKeyInput = () => + inquirer + .prompt({ + message: 'AWS Secret Access Key', + type: 'input', + name: 'secretAccessKey', + validate: input => { + if (isValidAwsSecretAccessKey(input.trim())) return true; + return ( + 'AWS Secret Access Key seems not valid.\n' + + ' Expected something like wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' + ); + }, + }) + .then(({ secretAccessKey }) => secretAccessKey.trim()); + +module.exports = { + check(serverless) { + return BbPromise.try(() => { + if (serverless.service.provider.name !== 'aws') return null; + return BbPromise.all([ + awsCredentials.resolveFileProfiles(), + awsCredentials.resolveEnvCredentials(), + ]).then(([fileProfiles, envCredentials]) => !fileProfiles.size && !envCredentials); + }); + }, + run() { + process.stdout.write( + 'No AWS credentials were found on your computer, ' + + 'you need these to host your application.\n\n' + ); + return confirm('Do you want to set them up now?').then(isConfirmed => { + if (!isConfirmed) return null; + process.stdout.write( + '\nGo here to learn how to create your AWS credentials:\n' + + 'https://slss.io/aws-account and enter them here:\n\n' + ); + return awsProfileNameInput().then(profileName => + awsAccessKeyIdInput().then(accessKeyId => + awsSecretAccessKeyInput().then(secretAccessKey => + awsCredentials + .saveFileProfiles(new Map([[profileName, { accessKeyId, secretAccessKey }]])) + .then(() => + process.stdout.write( + `\n${chalk.green( + 'AWS credentials saved on your machine at ~/.aws/credentials. ' + + 'Go there to change them at any time.' + )}\n` + ) + ) + ) + ) + ); + }); + }, +}; diff --git a/lib/plugins/interactiveCli/utils.js b/lib/plugins/interactiveCli/utils.js new file mode 100644 index 000000000..f60b80bd5 --- /dev/null +++ b/lib/plugins/interactiveCli/utils.js @@ -0,0 +1,14 @@ +'use strict'; + +const inquirer = require('inquirer'); + +module.exports = { + confirm: message => + inquirer + .prompt({ + message, + type: 'confirm', + name: 'isConfirmed', + }) + .then(({ isConfirmed }) => isConfirmed), +}; diff --git a/lib/utils/interactiveCreate.js b/lib/utils/interactiveCreate.js deleted file mode 100644 index 48f21b8b6..000000000 --- a/lib/utils/interactiveCreate.js +++ /dev/null @@ -1,179 +0,0 @@ -'use strict'; - -const { join } = require('path'); -const chalk = require('chalk'); -const inquirer = require('inquirer'); -const installTemplate = require('./installTemplate'); -const { getConfigFilePath } = require('./getServerlessConfigFile'); -const awsCredentials = require('../plugins/aws/utils/credentials'); - -const isValidServiceName = RegExp.prototype.test.bind(/^[a-zA-Z][a-zA-Z0-9-]{0,100}$/); -const isValidAwsProfileName = isValidServiceName; -const isValidAwsAccessKeyId = RegExp.prototype.test.bind(/^[A-Z0-9]{10,}$/); -const isValidAwsSecretAccessKey = RegExp.prototype.test.bind(/^[a-zA-Z0-9/]{10,}$/); - -module.exports = path => { - const createConfirm = () => - inquirer - .prompt({ - message: 'No Serverless project detected. Do you want to create a new one?', - type: 'confirm', - name: 'createWanted', - }) - .then(({ createWanted }) => createWanted); - - const projectTypeChoice = () => - inquirer - .prompt({ - message: 'What do you want to make?', - type: 'list', - name: 'projectType', - choices: [ - { name: 'AWS Node.js', value: 'aws-nodejs' }, - { name: 'AWS Python', value: 'aws-python' }, - { name: 'Other', value: 'other' }, - ], - }) - .then(({ projectType }) => projectType); - - const projectNameInput = () => - inquirer - .prompt({ - message: 'What do you want to call this project?', - type: 'input', - name: 'projectName', - validate: input => { - input = input.trim(); - if (!isValidServiceName(input)) { - return ( - 'Project name is not valid.\n' + - ' - It should only contain alphanumeric and hyphens.\n' + - ' - It should start with an alphabetic character.\n' + - " - Shouldn't exceed 128 characters" - ); - } - return getConfigFilePath(join(path, input)).then(configFilePath => { - return configFilePath ? `Serverless project already found at ${input} directory` : true; - }); - }, - }) - .then(({ projectName }) => projectName.trim()); - - const awsCredentialsConfirm = () => - inquirer - .prompt({ - message: 'Do you want to set them up now?', - type: 'confirm', - name: 'awsSetupNeeded', - }) - .then(({ awsSetupNeeded }) => awsSetupNeeded); - - const awsProfileNameInput = () => - inquirer - .prompt({ - message: 'AWS Profile name', - type: 'input', - name: 'profileName', - validate: input => { - if (isValidAwsProfileName(input.trim())) return true; - return ( - 'AWS profile name is not valid.\n' + - ' - It should only contain alphanumeric and hyphens.\n' + - ' - It should start with an alphabetic character.\n' + - " - Shouldn't exceed 128 characters" - ); - }, - }) - .then(({ profileName }) => profileName.trim()); - - const awsAccessKeyIdInput = () => - inquirer - .prompt({ - message: 'AWS Access Key Id', - type: 'input', - name: 'accessKeyId', - validate: input => { - if (isValidAwsAccessKeyId(input.trim())) return true; - return ( - 'AWS Access Key Id seems not valid.\n' + - ' Expected something like AKIAIOSFODNN7EXAMPLE' - ); - }, - }) - .then(({ accessKeyId }) => accessKeyId.trim()); - - const awsSecretAccessKeyInput = () => - inquirer - .prompt({ - message: 'AWS Secret Access Key', - type: 'input', - name: 'secretAccessKey', - validate: input => { - if (isValidAwsSecretAccessKey(input.trim())) return true; - return ( - 'AWS Secret Access Key seems not valid.\n' + - ' Expected something like wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' - ); - }, - }) - .then(({ secretAccessKey }) => secretAccessKey.trim()); - - process.stdout.write('\n'); - return createConfirm().then(createWanted => { - if (!createWanted) return null; - - return projectTypeChoice().then(projectType => { - if (projectType === 'other') { - process.stdout.write( - '\nRun “serverless create --help” to view available templates and create a new project ' + - 'from one of those templates.\n\n' - ); - return null; - } - return projectNameInput() - .then(projectName => { - const projectDir = join(process.cwd(), projectName); - return installTemplate(projectType, projectDir).then(() => { - process.stdout.write( - `\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n\n` - ); - }); - }) - .then(() => - Promise.all([ - awsCredentials.resolveFileProfiles(), - awsCredentials.resolveEnvCredentials(), - ]).then(([fileProfiles, envCredentials]) => { - if (fileProfiles.size || envCredentials) return null; - process.stdout.write( - 'No AWS credentials were found on your computer, ' + - 'you need these to host your application.\n\n' - ); - return awsCredentialsConfirm().then(result => { - if (!result) return null; - process.stdout.write( - '\nGo here to learn how to create your AWS credentials:\n' + - 'https://slss.io/aws-account and enter them here:\n\n' - ); - return awsProfileNameInput().then(profileName => - awsAccessKeyIdInput().then(accessKeyId => - awsSecretAccessKeyInput().then(secretAccessKey => - awsCredentials - .saveFileProfiles(new Map([[profileName, { accessKeyId, secretAccessKey }]])) - .then(() => - process.stdout.write( - `\n${chalk.green( - 'AWS credentials saved on your machine at ~/.aws/credentials. ' + - 'Go there to change them at any time.' - )}\n\n` - ) - ) - ) - ) - ); - }); - }) - ); - }); - }); -}; From 223a7d845e1cd5c7930962d2ad07b5eb71fd59b2 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 14:51:06 +0200 Subject: [PATCH 447/504] Whitespace --- lib/classes/Error.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/classes/Error.js b/lib/classes/Error.js index a31cefe65..de8f5eb21 100644 --- a/lib/classes/Error.js +++ b/lib/classes/Error.js @@ -1,4 +1,5 @@ 'use strict'; + const chalk = require('chalk'); const version = require('./../../package.json').version; // raven implementation examples https://www.npmjs.com/browse/depended/raven From 1776a478f93f13a3240445f3c3328b4936f879cd Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 26 Jun 2019 11:00:22 +0200 Subject: [PATCH 448/504] Ensure deploy is triggered --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8cb78599e..02d055f07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,5 +83,7 @@ jobs: deploy: provider: npm email: services@serverless.com + on: + tags: true api_key: secure: EgoetjrRlGfvGnmVp8A0btr1CzB0hl7owVIpbfk4zXJzhGEbHoVu0CG0IdmyLN+JlaZa7EDJTjkDCd6g3fVAh9TT7ZCeaq8YwbZDrql7mAJj7xYQAyM4eSkc95BRzcFJBx7Mxr6H90IDLxKr6ZtB+HEdiHN+59XbepKYYJeb1jHfnKn5xzOqk4BdnZo6pKfudfeO+7/BwJJ0FwlFA40bY2HS/Lp+NG/2IedNR7k3m/5W83/XH5qlWP8jhBKlxrAzks27aNo+42xHkRCVyPViJKq0mfz1hl2bfswChWHgaCuajp+0amNL39pgIX9eXxFc3bNX9Iftox5t31elEhsw06vvuAaVkKEd+VEMaDySbQ9M+irKZeREg+NFYZLnc2WiEE3Sexo6hm9eM2q2KEZ7bleN9B0CQAut1XXLRQEts80rzss4Z2Q7AZb9cOYBQlj9Wf1X0Y74UqvnDn83a4Y38a+lhx7J2q691ZeM1UFSCdO0QfeJRkB55bSyHqUqrLAqUN7eNsKGdBH0kvYIGFREgGgReEpBRAuNqWuJ/5qexp63Kbf+09raG5IvfxSIM5fJ5KE5VxSduBdRnSH0GNKfjuq296/Rg4fmm/bygZ3Yk5L6Wd41SUU8uHzlZFBwtcvxAKDTQe6s+5JU24ilqxOx6J4Ut34X3dIbLLAmoB1ogdM= From 88d33178232cfc1ca063a938a24ed45df7d68501 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Fri, 28 Jun 2019 15:07:40 +0200 Subject: [PATCH 449/504] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f57d3e689..8c59b8ae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 1.46.1 (2019-06-28) - [Fix service.provider.region resolution](https://github.com/serverless/serverless/pull/6317) +- [Ensure deploy is triggered in CI](https://github.com/serverless/serverless/pull/6306) ## Meta - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.46.0...v1.46.1) From e11cc2c51ba0135e973d7febc9f6f8bde82ade2a Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 15:19:53 +0200 Subject: [PATCH 450/504] Ensure to expose non-errors in informative way --- lib/classes/Error.js | 3 ++- lib/classes/Error.test.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/classes/Error.js b/lib/classes/Error.js index de8f5eb21..c5f2e3ca1 100644 --- a/lib/classes/Error.js +++ b/lib/classes/Error.js @@ -1,6 +1,7 @@ 'use strict'; const chalk = require('chalk'); +const { inspect } = require('util'); const version = require('./../../package.json').version; // raven implementation examples https://www.npmjs.com/browse/depended/raven const errorReporter = require('../utils/sentry').raven; @@ -91,7 +92,7 @@ module.exports.logError = e => { process.exit(1); }); } catch (errorHandlingError) { - throw new Error(e); + throw new Error(inspect(e)); } }; diff --git a/lib/classes/Error.test.js b/lib/classes/Error.test.js index a8ea828c0..43b101394 100644 --- a/lib/classes/Error.test.js +++ b/lib/classes/Error.test.js @@ -141,7 +141,7 @@ describe('Error', () => { thrownError = e; } - expect(thrownError.message).to.equal('INVALID INPUT'); + expect(thrownError.message).to.equal("'INVALID INPUT'"); }); }); From 49afdb5751fa457d6356d45cd7f31bcdcdf88950 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Fri, 28 Jun 2019 15:24:02 +0200 Subject: [PATCH 451/504] Fix Node.js version detection Original detection just picked v8 and v9 --- tests/mocha-reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocha-reporter.js b/tests/mocha-reporter.js index cc4e6ecc0..689814889 100644 --- a/tests/mocha-reporter.js +++ b/tests/mocha-reporter.js @@ -115,7 +115,7 @@ module.exports = class ServerlessSpec extends Spec { // Safe to ignore } - if (process.version[1] < 8) return; // Async leaks detector is not reliable in Node.js v6 + if (process.version.match(/\d+/)[0] < 8) return; // Async leaks detector is not reliable in Node.js v6 // Async leaks detection setTimeout(() => { From 7ae67b11c5a1f165849f0d9a2ad26538b1937b1d Mon Sep 17 00:00:00 2001 From: kizmo04 Date: Sat, 29 Jun 2019 02:42:27 +0900 Subject: [PATCH 452/504] Update credentials.md --- docs/providers/aws/guide/credentials.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/guide/credentials.md b/docs/providers/aws/guide/credentials.md index b1308b372..ca5f27b19 100644 --- a/docs/providers/aws/guide/credentials.md +++ b/docs/providers/aws/guide/credentials.md @@ -28,7 +28,7 @@ Here's how to set up the Serverless Framework with your Amazon Web Services acco If you're new to Amazon Web Services, make sure you put in a credit card. -All AWS users get access to the Free Tier for [AWS Lambda](https://aws.amazon.com/lambda/pricing/). AWS Lambda is part of the non-expiring [AWS Free Tier](https://aws.amazon.com/free/#AWS_FREE_TIER). For additional pricing information for AWS Lambda and Gateway [here](https://aws.amazon.com/lambda/pricing/). If you're using additional AWS services, they could incur additional costs. Please review pricing for you services on AWS [here](https://aws.amazon.com/pricing/). +All AWS users get access to the Free Tier for [AWS Lambda](https://aws.amazon.com/lambda/pricing/). AWS Lambda is part of the non-expiring [AWS Free Tier](https://aws.amazon.com/free/#AWS_FREE_TIER). For additional pricing information for AWS Lambda and AWS API Gateway [here](https://aws.amazon.com/lambda/pricing/). If you're using additional AWS services, they could incur additional costs. Please review pricing for you services on AWS [here](https://aws.amazon.com/pricing/). If you don't have a credit card set up, you may not be able to deploy your resources and you may run into this error: From c66d2b128ca2fec68533a5a5f07f1d1e4a9e74ce Mon Sep 17 00:00:00 2001 From: Edward Goubely Date: Fri, 28 Jun 2019 19:09:29 +0200 Subject: [PATCH 453/504] Make ALB event target group names unique --- lib/plugins/aws/lib/naming.js | 12 ++++++++++++ lib/plugins/aws/lib/naming.test.js | 18 ++++++++++++++++++ .../compile/events/alb/lib/targetGroups.js | 8 +++++++- .../events/alb/lib/targetGroups.test.js | 16 ++++++++++++++-- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index d77336cd9..3ef221a48 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -1,6 +1,7 @@ 'use strict'; const _ = require('lodash'); +const crypto = require('crypto'); /** * A centralized naming object that provides naming standards for the AWS provider plugin. The @@ -381,6 +382,17 @@ module.exports = { getAlbTargetGroupLogicalId(functionName) { return `${this.getNormalizedFunctionName(functionName)}AlbTargetGroup`; }, + getAlbTargetGroupNameTagValue(functionName) { + return `${ + this.provider.serverless.service.service + }-${functionName}-${this.provider.getStage()}`; + }, + getAlbTargetGroupName(functionName) { + return crypto + .createHash('md5') + .update(this.getAlbTargetGroupNameTagValue(functionName)) + .digest('hex'); + }, getAlbListenerRuleLogicalId(functionName, idx) { return `${this.getNormalizedFunctionName(functionName)}AlbListenerRule${idx}`; }, diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 58b9e6d01..87507e43e 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -703,4 +703,22 @@ describe('#naming()', () => { ); }); }); + + describe('#getAlbTargetGroupName()', () => { + it('should return a unique identifier based on the service name, function name and stage', () => { + serverless.service.service = 'myService'; + expect(sdk.naming.getAlbTargetGroupName('functionName')).to.equal( + '1ea0b85512f1943fbae9f40a0451d718' + ); + }); + }); + + describe('#getAlbTargetGroupNameTagValue()', () => { + it('should return the composition of service name, function name and stage', () => { + serverless.service.service = 'myService'; + expect(sdk.naming.getAlbTargetGroupNameTagValue('functionName')).to.equal( + `${serverless.service.service}-functionName-${sdk.naming.provider.getStage()}` + ); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js index ea6ca2207..9d9e81763 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.js @@ -23,7 +23,13 @@ module.exports = { }, }, ], - Name: `${functionName}-${this.provider.getStage()}`, + Name: this.provider.naming.getAlbTargetGroupName(functionName), + Tags: [ + { + Key: 'Name', + Value: this.provider.naming.getAlbTargetGroupNameTagValue(functionName), + }, + ], }, DependsOn: [lambdaPermissionLogicalId], }, diff --git a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js index 3280c1757..0f97c75ce 100644 --- a/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js +++ b/lib/plugins/aws/package/compile/events/alb/lib/targetGroups.test.js @@ -54,7 +54,7 @@ describe('#compileTargetGroups()', () => { expect(resources.FirstAlbTargetGroup).to.deep.equal({ Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', Properties: { - Name: 'first-dev', + Name: 'd370cffdc0b94175c8239183d0b344a4', TargetType: 'lambda', Targets: [ { @@ -63,13 +63,19 @@ describe('#compileTargetGroups()', () => { }, }, ], + Tags: [ + { + Key: 'Name', + Value: 'some-service-first-dev', + }, + ], }, DependsOn: ['FirstLambdaPermissionAlb'], }); expect(resources.SecondAlbTargetGroup).to.deep.equal({ Type: 'AWS::ElasticLoadBalancingV2::TargetGroup', Properties: { - Name: 'second-dev', + Name: '33af5fa54e565b8aca63f904de895aab', TargetType: 'lambda', Targets: [ { @@ -78,6 +84,12 @@ describe('#compileTargetGroups()', () => { }, }, ], + Tags: [ + { + Key: 'Name', + Value: 'some-service-second-dev', + }, + ], }, DependsOn: ['SecondLambdaPermissionAlb'], }); From 8102caa606e5914b6866a3e20e7c268eba613583 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Sat, 29 Jun 2019 16:05:11 +0200 Subject: [PATCH 454/504] Add null as a consultant [null](https://null.tc/) is the company maintaining [Bref](https://bref.sh/). Consulting helps us fund work on the open source project. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8068f35ae..b27fdbf29 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ These consultants use the Serverless Framework and can help you build your serve - [Seraro](http://www.seraro.com/) - Who also runs Atlanta Serverless Meetup (https://www.meetup.com/Atlanta-CABI-Camp-Cloud-AI-Blockchain-IOT) and Delhi Serverless Meetup (https://www.meetup.com/Delhi-NCR-Serverless-Architecture-Meetup/) - [superluminar](https://superluminar.io) - runs serverlessdays Hamburg and Serverless Meetup Hamburg - [Onica](https://www.onica.com/aws-cloud-native-developers/) - AWS Premier Consulting Partner for Cloud Native Development and host of [eleven regional Meetup groups](https://www.onica.com/events/). +- [null](https://null.tc/) - maintains [Bref](https://bref.sh/) to create serverless PHP applications --- From 434ae9c52f5f89e648c7dbff62a1ee5f25cc7e28 Mon Sep 17 00:00:00 2001 From: nahum zsilva Date: Sat, 29 Jun 2019 14:17:20 -0700 Subject: [PATCH 455/504] updating webpack file The current config will not work in the newest webpack version --- .../aws-nodejs-ecma-script/webpack.config.js | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js b/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js index da20b575a..0076bda39 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js @@ -1,23 +1,29 @@ -const path = require('path'); +const path = require("path"); // eslint-disable-next-line import/no-unresolved -const slsw = require('serverless-webpack'); +const slsw = require("serverless-webpack"); module.exports = { entry: slsw.lib.entries, - target: 'node', + output: { + libraryTarget: "commonjs", + filename: "[name].js", + path: path.join(__dirname, ".webpack"), + }, + mode: "development", + target: "node", module: { - loaders: [ + rules: [ { - test: /\.js$/, - loaders: ['babel-loader'], + test: /\.js$/, // include .js files + enforce: "pre", // preload the jshint loader + exclude: /node_modules/, // exclude any and all files in the node_modules folder include: __dirname, - exclude: /node_modules/, + use: [ + { + loader: "babel-loader", + }, + ], }, ], }, - output: { - libraryTarget: 'commonjs', - path: path.join(__dirname, '.webpack'), - filename: '[name].js', - }, }; From d4f3dd570bcea9d630d3a2091c90cd059918209b Mon Sep 17 00:00:00 2001 From: Richard Owen <1186519+richardowen@users.noreply.github.com> Date: Mon, 1 Jul 2019 11:47:30 +0100 Subject: [PATCH 456/504] Typo fix in AWS ALB event documentation --- docs/providers/aws/events/alb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/providers/aws/events/alb.md b/docs/providers/aws/events/alb.md index b357c24fe..1d7fc6c94 100644 --- a/docs/providers/aws/events/alb.md +++ b/docs/providers/aws/events/alb.md @@ -16,7 +16,7 @@ layout: Doc [Application Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) can be used to re-route requests when certain traffic patterns are met. While traffic can be routed to services such as EC2 it [can also be routed to Lambda functions](https://aws.amazon.com/de/blogs/networking-and-content-delivery/lambda-functions-as-targets-for-application-load-balancers/) which can in turn be used process incoming requests. -The Serverless Framework makes it possible to setup the connection between Application Load Balancers and Lamdba functions with the help of the `alb` event. +The Serverless Framework makes it possible to setup the connection between Application Load Balancers and Lambda functions with the help of the `alb` event. ## Event definition From 02192415becae5212cf255f87c934fa158c69438 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 1 Jul 2019 13:05:49 +0200 Subject: [PATCH 457/504] Improve serverless mock --- lib/classes/PluginManager.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 958744f87..8c3ae8d15 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -391,6 +391,7 @@ describe('PluginManager', () => { ({ restoreEnv } = overrideEnv({ whitelist: ['APPDATA', 'PATH'] })); serverless = new Serverless(); serverless.cli = new CLI(); + serverless.processedInput = { commands: [], options: {} }; pluginManager = new PluginManager(serverless); pluginManager.serverless.config.servicePath = 'foo'; }); From 18c4c3a5f538c6d35c636609843e75cd93a7adf6 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 1 Jul 2019 16:04:07 +0200 Subject: [PATCH 458/504] Rely on already mocked homedir --- .../awsConfigCredentials.test.js | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index 28459b68a..da6d095a8 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -9,25 +9,21 @@ const os = require('os'); const path = require('path'); const AwsConfigCredentials = require('./awsConfigCredentials'); const Serverless = require('../../../Serverless'); -const { getTmpDirPath } = require('../../../../tests/utils/fs'); describe('AwsConfigCredentials', () => { let awsConfigCredentials; - let credentialsFileContent; - let credentialsFilePath; let serverless; - let tmpDirPath; + const homeDirPath = os.homedir(); + const awsDirectoryPath = path.join(homeDirPath, '.aws'); + const credentialsFilePath = path.join(awsDirectoryPath, 'credentials'); + const credentialsFileContent = [ + '[my-profile]', + 'aws_access_key_id = my-old-profile-key', + 'aws_secret_access_key = my-old-profile-secret', + ].join('\n'); beforeEach(() => { - tmpDirPath = getTmpDirPath(); - credentialsFilePath = path.join(tmpDirPath, '.aws', 'credentials'); - credentialsFileContent = '[my-profile]\n'; - credentialsFileContent += 'aws_access_key_id = my-old-profile-key\n'; - credentialsFileContent += 'aws_secret_access_key = my-old-profile-secret\n'; - - // stub homedir handler to return the tmpDirPath - sandbox.stub(os, 'homedir').returns(tmpDirPath); - + fse.removeSync(credentialsFilePath); serverless = new Serverless(); return serverless.init().then(() => { const options = { @@ -39,10 +35,6 @@ describe('AwsConfigCredentials', () => { }); }); - afterEach(() => { - sandbox.restore(); - }); - describe('#constructor()', () => { it('should have the command "config"', () => { expect(awsConfigCredentials.commands.config).to.not.equal(undefined); @@ -88,13 +80,16 @@ describe('AwsConfigCredentials', () => { }); it('should throw an error if the home directory was not found', () => { - os.homedir.returns(null); - expect(() => new AwsConfigCredentials(serverless, {})).to.throw(Error); + sandbox.stub(os, 'homedir').returns(null); + try { + expect(() => new AwsConfigCredentials(serverless, {})).to.throw(Error); + } finally { + sandbox.restore(); + } }); it('should create the .aws/credentials file if not yet present', () => { // remove the .aws directory which was created in the before hook of the test - const awsDirectoryPath = path.join(tmpDirPath, '.aws'); fse.removeSync(awsDirectoryPath); awsConfigCredentials = new AwsConfigCredentials(serverless, {}); @@ -164,10 +159,13 @@ describe('AwsConfigCredentials', () => { awsConfigCredentials.options.secret = 'my-new-profile-secret'; awsConfigCredentials.options.overwrite = true; - credentialsFileContent += '[my-other-profile]\n'; - credentialsFileContent += 'aws_secret_access_key = my-other-profile-secret\n'; + const newCredentialsFileContent = [ + credentialsFileContent, + '[my-other-profile]', + 'aws_secret_access_key = my-other-profile-secret', + ].join('\n'); - fse.outputFileSync(credentialsFilePath, credentialsFileContent); + fse.outputFileSync(credentialsFilePath, newCredentialsFileContent); return awsConfigCredentials.configureCredentials().then(() => { const UpdatedCredentialsFileContent = fs.readFileSync(credentialsFilePath).toString(); @@ -181,15 +179,18 @@ describe('AwsConfigCredentials', () => { }); it('should add the missing credentials to the updated profile', () => { - credentialsFileContent = '[my-profile]\n'; - credentialsFileContent += 'aws_secret_access_key = my-profile-secret\n'; + const newCredentialsFileContent = [ + credentialsFileContent, + '[my-profile]', + 'aws_secret_access_key = my-profile-secret', + ].join('\n'); awsConfigCredentials.options.profile = 'my-profile'; awsConfigCredentials.options.key = 'my-new-profile-key'; awsConfigCredentials.options.secret = 'my-new-profile-secret'; awsConfigCredentials.options.overwrite = true; - fse.outputFileSync(credentialsFilePath, credentialsFileContent); + fse.outputFileSync(credentialsFilePath, newCredentialsFileContent); return awsConfigCredentials.configureCredentials().then(() => { const UpdatedCredentialsFileContent = fs.readFileSync(credentialsFilePath).toString(); From 4ad356c2b4715bb7092a5b69007fc065349bc694 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 1 Jul 2019 17:38:21 +0200 Subject: [PATCH 459/504] Ensure user only mode for credentials --- lib/plugins/aws/utils/credentials.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/plugins/aws/utils/credentials.js b/lib/plugins/aws/utils/credentials.js index 68664c1a4..13909e46b 100644 --- a/lib/plugins/aws/utils/credentials.js +++ b/lib/plugins/aws/utils/credentials.js @@ -1,7 +1,7 @@ 'use strict'; const { join } = require('path'); -const { readFile, writeFile } = require('fs'); +const { constants, readFile, writeFile } = require('fs'); const os = require('os'); const BbPromise = require('bluebird'); @@ -77,10 +77,15 @@ module.exports = { `aws_secret_access_key=${data.secretAccessKey}\n` ) .join('\n')}`; - writeFile(credentialsFilePath, content, error => { - if (error) reject(error); - else resolve(); - }); + writeFile( + credentialsFilePath, + content, + { mode: constants.S_IRUSR | constants.S_IWUSR }, + error => { + if (error) reject(error); + else resolve(); + } + ); }); }, }; From 0ad85fe09d80286fc4e72f05e899f91186be092c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 1 Jul 2019 17:46:40 +0200 Subject: [PATCH 460/504] Improve async function configuration --- .../configCredentials/awsConfigCredentials.js | 74 ++++++++++--------- .../awsConfigCredentials.test.js | 9 ++- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.js index 2f9b27076..3679d63e3 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.js @@ -57,51 +57,53 @@ class AwsConfigCredentials { fse.ensureFileSync(this.credentialsFilePath); this.hooks = { - 'config:credentials:config': () => BbPromise.bind(this).then(this.configureCredentials), + 'config:credentials:config': () => this.configureCredentials(), }; } configureCredentials() { - // sanitize - this.options.provider = this.options.provider.toLowerCase(); - this.options.profile = this.options.profile ? this.options.profile : 'default'; + return BbPromise.try(() => { + // sanitize + this.options.provider = this.options.provider.toLowerCase(); + this.options.profile = this.options.profile ? this.options.profile : 'default'; - // resolve if provider option is not 'aws' - if (this.options.provider !== 'aws') { - return BbPromise.resolve(); - } + // resolve if provider option is not 'aws' + if (this.options.provider !== 'aws') return null; - // validate - if (!this.options.key || !this.options.secret) { - throw new this.serverless.classes.Error('Please include --key and --secret options for AWS.'); - } - - this.serverless.cli.log('Setting up AWS...'); - - this.credentials = this.getCredentials(); - - // Get the profile start line and end line numbers inside the credentials array - const profileBoundaries = this.getProfileBoundaries(); - - // Check if the profile exists - const isNewProfile = profileBoundaries.start === -1; - if (isNewProfile) { - this.addProfile(); - } else { - // Only update the profile if the overwrite flag was set - if (!this.options.overwrite) { - const message = [ - `Failed! ~/.aws/credentials already has a "${this.options.profile}" profile.`, - ' Use the overwrite flag ("-o" or "--overwrite") to force the update', - ].join(''); - this.serverless.cli.log(message); - return BbPromise.resolve(); + // validate + if (!this.options.key || !this.options.secret) { + throw new this.serverless.classes.Error( + 'Please include --key and --secret options for AWS.' + ); } - this.updateProfile(profileBoundaries); - } + this.serverless.cli.log('Setting up AWS...'); - return this.saveCredentialsFile(); + this.credentials = this.getCredentials(); + + // Get the profile start line and end line numbers inside the credentials array + const profileBoundaries = this.getProfileBoundaries(); + + // Check if the profile exists + const isNewProfile = profileBoundaries.start === -1; + if (isNewProfile) { + this.addProfile(); + } else { + // Only update the profile if the overwrite flag was set + if (!this.options.overwrite) { + const message = [ + `Failed! ~/.aws/credentials already has a "${this.options.profile}" profile.`, + ' Use the overwrite flag ("-o" or "--overwrite") to force the update', + ].join(''); + this.serverless.cli.log(message); + return null; + } + + this.updateProfile(profileBoundaries); + } + + return this.saveCredentialsFile(); + }); } getCredentials() { diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index da6d095a8..0628e3c01 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -121,8 +121,13 @@ describe('AwsConfigCredentials', () => { it('should throw an error if the "key" and "secret" options are not given', () => { awsConfigCredentials.options.key = false; awsConfigCredentials.options.secret = false; - - expect(() => awsConfigCredentials.configureCredentials()).to.throw(Error); + return awsConfigCredentials.configureCredentials().then( + () => { + throw new Error('Unexpected'); + }, + error => + expect(error.message).to.include('Please include --key and --secret options for AWS') + ); }); it('should not update the profile if the overwrite flag is not set', () => { From 42354bbfbdff345537cd156fd99e32767826e575 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 1 Jul 2019 19:21:23 +0200 Subject: [PATCH 461/504] Create `~/.aws` directory if it doesn't exists --- lib/plugins/aws/utils/credentials.js | 57 +++++++++++++++++++--------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/lib/plugins/aws/utils/credentials.js b/lib/plugins/aws/utils/credentials.js index 13909e46b..2321e42a6 100644 --- a/lib/plugins/aws/utils/credentials.js +++ b/lib/plugins/aws/utils/credentials.js @@ -1,12 +1,13 @@ 'use strict'; const { join } = require('path'); -const { constants, readFile, writeFile } = require('fs'); +const { constants, readFile, writeFile, mkdir } = require('fs'); const os = require('os'); const BbPromise = require('bluebird'); const homedir = os.homedir(); -const credentialsFilePath = homedir ? join(homedir, '.aws/credentials') : null; +const awsConfigDirPath = join(homedir, '.aws'); +const credentialsFilePath = homedir ? join(awsConfigDirPath, 'credentials') : null; const profileNameRe = /^\[([^\]]+)]\s*$/; const settingRe = /^([a-zA-Z0-9_]+)\s*=\s*([^\s]+)\s*$/; @@ -38,6 +39,33 @@ const parseFileProfiles = content => { return profiles; }; +const writeCredentialsContent = content => + new BbPromise((resolve, reject) => + writeFile( + credentialsFilePath, + content, + { mode: constants.S_IRUSR | constants.S_IWUSR }, + writeFileError => { + if (writeFileError) { + if (writeFileError.code === 'ENOENT') { + mkdir( + awsConfigDirPath, + { mode: constants.S_IRUSR | constants.S_IWUSR | constants.S_IXUSR }, + mkdirError => { + if (mkdirError) reject(mkdirError); + else resolve(writeCredentialsContent(content)); + } + ); + } else { + reject(writeFileError); + } + } else { + resolve(); + } + } + ) + ); + module.exports = { resolveFileProfiles() { return new BbPromise((resolve, reject) => { @@ -68,23 +96,18 @@ module.exports = { }, saveFileProfiles(profiles) { - return new BbPromise((resolve, reject) => { + return new BbPromise(resolve => { if (!credentialsFilePath) throw new Error('Could not resolve path to user credentials file'); - const content = `${Array.from(profiles) - .map( - ([name, data]) => - `[${name}]\naws_access_key_id=${data.accessKeyId}\n` + - `aws_secret_access_key=${data.secretAccessKey}\n` + resolve( + writeCredentialsContent( + `${Array.from(profiles) + .map( + ([name, data]) => + `[${name}]\naws_access_key_id=${data.accessKeyId}\n` + + `aws_secret_access_key=${data.secretAccessKey}\n` + ) + .join('\n')}` ) - .join('\n')}`; - writeFile( - credentialsFilePath, - content, - { mode: constants.S_IRUSR | constants.S_IWUSR }, - error => { - if (error) reject(error); - else resolve(); - } ); }); }, From 3e17bb34ffe9a15dbd3a16862c2d373a5fe1fb93 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 1 Jul 2019 19:39:48 +0200 Subject: [PATCH 462/504] Support aws_session_token --- lib/plugins/aws/utils/credentials.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/plugins/aws/utils/credentials.js b/lib/plugins/aws/utils/credentials.js index 2321e42a6..63cf6c45a 100644 --- a/lib/plugins/aws/utils/credentials.js +++ b/lib/plugins/aws/utils/credentials.js @@ -14,6 +14,7 @@ const settingRe = /^([a-zA-Z0-9_]+)\s*=\s*([^\s]+)\s*$/; const settingMap = new Map([ ['aws_access_key_id', 'accessKeyId'], ['aws_secret_access_key', 'secretAccessKey'], + ['aws_session_token', 'sessionToken'], ]); const parseFileProfiles = content => { const profiles = new Map(); @@ -34,7 +35,9 @@ const parseFileProfiles = content => { if (settingName) currentProfile[settingName] = settingMatches[2]; } for (const [profileName, profileData] of profiles) { - if (!profileData.accessKeyId || !profileData.secretAccessKey) profiles.delete(profileName); + if (!profileData.sessionToken && (!profileData.accessKeyId || !profileData.secretAccessKey)) { + profiles.delete(profileName); + } } return profiles; }; @@ -101,11 +104,17 @@ module.exports = { resolve( writeCredentialsContent( `${Array.from(profiles) - .map( - ([name, data]) => - `[${name}]\naws_access_key_id=${data.accessKeyId}\n` + - `aws_secret_access_key=${data.secretAccessKey}\n` - ) + .map(([name, data]) => { + const lineTokens = [`[${name}]`]; + if (data.sessionToken) lineTokens.push(`aws_session_token=${data.sessionToken}`); + else { + lineTokens.push( + `aws_access_key_id=${data.accessKeyId}`, + `aws_secret_access_key=${data.secretAccessKey}` + ); + } + return `${lineTokens.join('\n')}\n`; + }) .join('\n')}` ) ); From f12cdc9a4c4ddf7d081f7df4f890733b3ae175fd Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 1 Jul 2019 19:43:59 +0200 Subject: [PATCH 463/504] Reconfigure to use credentials util Fixes #4506 --- .../configCredentials/awsConfigCredentials.js | 121 +++--------------- .../awsConfigCredentials.test.js | 85 ++---------- 2 files changed, 28 insertions(+), 178 deletions(-) diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.js index 3679d63e3..fbaf18425 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.js @@ -1,12 +1,8 @@ 'use strict'; const BbPromise = require('bluebird'); -const constants = require('constants'); -const path = require('path'); -const fs = require('fs'); -const fse = require('fs-extra'); const os = require('os'); -const _ = require('lodash'); +const credentials = require('../utils/credentials'); class AwsConfigCredentials { constructor(serverless, options) { @@ -52,10 +48,6 @@ class AwsConfigCredentials { ); } - this.credentialsFilePath = path.join(os.homedir(), '.aws', 'credentials'); - // Create the credentials file alongside the .aws directory if it's not yet present - fse.ensureFileSync(this.credentialsFilePath); - this.hooks = { 'config:credentials:config': () => this.configureCredentials(), }; @@ -79,103 +71,26 @@ class AwsConfigCredentials { this.serverless.cli.log('Setting up AWS...'); - this.credentials = this.getCredentials(); - - // Get the profile start line and end line numbers inside the credentials array - const profileBoundaries = this.getProfileBoundaries(); - - // Check if the profile exists - const isNewProfile = profileBoundaries.start === -1; - if (isNewProfile) { - this.addProfile(); - } else { - // Only update the profile if the overwrite flag was set - if (!this.options.overwrite) { - const message = [ - `Failed! ~/.aws/credentials already has a "${this.options.profile}" profile.`, - ' Use the overwrite flag ("-o" or "--overwrite") to force the update', - ].join(''); - this.serverless.cli.log(message); - return null; + return credentials.resolveFileProfiles().then(profiles => { + if (profiles.has(this.options.profile)) { + // Only update the profile if the overwrite flag was set + if (!this.options.overwrite) { + const message = [ + `Failed! ~/.aws/credentials already has a "${this.options.profile}" profile.`, + ' Use the overwrite flag ("-o" or "--overwrite") to force the update', + ].join(''); + this.serverless.cli.log(message); + return null; + } } + profiles.set(this.options.profile, { + accessKeyId: this.options.key, + secretAccessKey: this.options.secret, + }); - this.updateProfile(profileBoundaries); - } - - return this.saveCredentialsFile(); - }); - } - - getCredentials() { - // Get the credentials file lines - const credentialsFileContent = this.serverless.utils.readFileSync(this.credentialsFilePath); - return credentialsFileContent ? credentialsFileContent.split('\n') : []; - } - - addProfile() { - this.credentials.push( - `[${this.options.profile}]`, - `aws_access_key_id = ${this.options.key}`, - `aws_secret_access_key = ${this.options.secret}` - ); - } - - updateProfile(profileBoundries) { - let currentLine = profileBoundries.start; - let endLine = profileBoundries.end; - - // Remove existing 'aws_access_key_id' and 'aws_secret_access_key' properties - while (currentLine < endLine) { - const line = this.credentials[currentLine]; - if (line.indexOf('aws_access_key_id') > -1 || line.indexOf('aws_secret_access_key') > -1) { - this.credentials.splice(currentLine, 1); - endLine--; - } else { - currentLine++; - } - } - - // Add the key and the secret to the beginning of the section - const keyLine = `aws_access_key_id = ${this.options.key}`; - const secretLine = `aws_secret_access_key = ${this.options.secret}`; - - this.credentials.splice(profileBoundries.start + 1, 0, secretLine); - this.credentials.splice(profileBoundries.start + 1, 0, keyLine); - } - - saveCredentialsFile() { - // Generate the file content and add a line break at the end - const updatedCredsFileContent = `${this.credentials.join('\n')}\n`; - - this.serverless.cli.log('Saving your AWS profile in "~/.aws/credentials"...'); - - return this.serverless.utils - .writeFile(this.credentialsFilePath, updatedCredsFileContent) - .then(() => { - // set file permissions to only readable/writable by owner (equivalent to 'chmod 600') - // NOTE: `chmod` doesn't behave as intended on Windows, so skip if we're on Windows. - if (os.platform() !== 'win32') { - fs.chmodSync( - this.credentialsFilePath, - (fs.constants || constants).S_IRUSR | (fs.constants || constants).S_IWUSR - ); - } - - this.serverless.cli.log( - `Success! Your AWS access keys were stored under the "${this.options.profile}" profile.` - ); + return credentials.saveFileProfiles(profiles); }); - } - - getProfileBoundaries() { - // Get the line number of the aws profile definition, defaults to -1 - const start = this.credentials.indexOf(`[${this.options.profile}]`); - - const nextProfile = _.findIndex(this.credentials, line => /\[[^\]]+\]/.test(line), start + 1); - // Get the line number of the next aws profile definition, defaults to the file lines number - const end = nextProfile + 1 || this.credentials.length; - - return { start, end }; + }); } } diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index 0628e3c01..cf37b937f 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -87,15 +87,6 @@ describe('AwsConfigCredentials', () => { sandbox.restore(); } }); - - it('should create the .aws/credentials file if not yet present', () => { - // remove the .aws directory which was created in the before hook of the test - fse.removeSync(awsDirectoryPath); - - awsConfigCredentials = new AwsConfigCredentials(serverless, {}); - const isCredentialsFilePresent = fs.existsSync(path.join(awsDirectoryPath, 'credentials')); - expect(isCredentialsFilePresent).to.equal(true); - }); }); describe('#configureCredentials()', () => { @@ -153,8 +144,8 @@ describe('AwsConfigCredentials', () => { const lineByLineContent = UpdatedCredentialsFileContent.split('\n'); expect(lineByLineContent[0]).to.equal('[my-profile]'); - expect(lineByLineContent[1]).to.equal('aws_access_key_id = my-new-profile-key'); - expect(lineByLineContent[2]).to.equal('aws_secret_access_key = my-new-profile-secret'); + expect(lineByLineContent[1]).to.equal('aws_access_key_id=my-new-profile-key'); + expect(lineByLineContent[2]).to.equal('aws_secret_access_key=my-new-profile-secret'); }); }); @@ -167,6 +158,7 @@ describe('AwsConfigCredentials', () => { const newCredentialsFileContent = [ credentialsFileContent, '[my-other-profile]', + 'aws_access_key_id = my-other-profile-key', 'aws_secret_access_key = my-other-profile-secret', ].join('\n'); @@ -177,9 +169,9 @@ describe('AwsConfigCredentials', () => { const lineByLineContent = UpdatedCredentialsFileContent.split('\n'); expect(lineByLineContent[0]).to.equal('[my-profile]'); - expect(lineByLineContent[1]).to.equal('aws_access_key_id = my-new-profile-key'); - expect(lineByLineContent[2]).to.equal('aws_secret_access_key = my-new-profile-secret'); - expect(lineByLineContent[4]).to.equal('aws_secret_access_key = my-other-profile-secret'); + expect(lineByLineContent[1]).to.equal('aws_access_key_id=my-new-profile-key'); + expect(lineByLineContent[2]).to.equal('aws_secret_access_key=my-new-profile-secret'); + expect(lineByLineContent[6]).to.equal('aws_secret_access_key=my-other-profile-secret'); }); }); @@ -202,8 +194,8 @@ describe('AwsConfigCredentials', () => { const lineByLineContent = UpdatedCredentialsFileContent.split('\n'); expect(lineByLineContent[0]).to.equal('[my-profile]'); - expect(lineByLineContent[1]).to.equal('aws_access_key_id = my-new-profile-key'); - expect(lineByLineContent[2]).to.equal('aws_secret_access_key = my-new-profile-secret'); + expect(lineByLineContent[1]).to.equal('aws_access_key_id=my-new-profile-key'); + expect(lineByLineContent[2]).to.equal('aws_secret_access_key=my-new-profile-secret'); }); }); @@ -217,8 +209,8 @@ describe('AwsConfigCredentials', () => { const lineByLineContent = UpdatedCredentialsFileContent.split('\n'); expect(lineByLineContent[0]).to.equal('[my-profile]'); - expect(lineByLineContent[1]).to.equal('aws_access_key_id = my-profile-key'); - expect(lineByLineContent[2]).to.equal('aws_secret_access_key = my-profile-secret'); + expect(lineByLineContent[1]).to.equal('aws_access_key_id=my-profile-key'); + expect(lineByLineContent[2]).to.equal('aws_secret_access_key=my-profile-secret'); }); }); @@ -236,61 +228,4 @@ describe('AwsConfigCredentials', () => { })); } }); - - describe('#getCredentials()', () => { - it('should load credentials file and return the credentials lines', () => { - fse.outputFileSync(credentialsFilePath, credentialsFileContent); - const credentials = awsConfigCredentials.getCredentials(); - - expect(credentials[0]).to.equal('[my-profile]'); - expect(credentials[1]).to.equal('aws_access_key_id = my-old-profile-key'); - }); - - it('should return an empty array if the file is empty', () => { - const credentials = awsConfigCredentials.getCredentials(); - - expect(credentials.length).to.equal(0); - }); - }); - - describe('#getProfileBoundaries()', () => { - it('should return the start and end numbers of the profile', () => { - awsConfigCredentials.options.profile = 'my-profile'; - awsConfigCredentials.credentials = [ - '[my-profile]', - 'aws_access_key_id = my-other-profile-key', - ]; - - const profileBoundaries = awsConfigCredentials.getProfileBoundaries(); - - expect(profileBoundaries.start).to.equal(0); - expect(profileBoundaries.end).to.equal(2); - }); - - it('should set the start property to -1 if the profile was not found', () => { - awsConfigCredentials.options.profile = 'my-not-yet-saved-profile'; - awsConfigCredentials.credentials = [ - '[my-profile]', - 'aws_access_key_id = my-other-profile-key', - ]; - - const profileBoundaries = awsConfigCredentials.getProfileBoundaries(); - - expect(profileBoundaries.start).to.equal(-1); - }); - - it('should set the end to the credentials length if no other profile was found', () => { - awsConfigCredentials.options.profile = 'my-profile'; - awsConfigCredentials.credentials = [ - '[my-profile]', - 'aws_access_key_id = my-other-profile-key', - '# a comment', - 'aws_secret_access_key = my-profile-secret', - ]; - - const profileBoundaries = awsConfigCredentials.getProfileBoundaries(); - - expect(profileBoundaries.end).to.equal(4); - }); - }); }); From f04d02a68acbee0a4907f2b0143dd1676b64f800 Mon Sep 17 00:00:00 2001 From: Xin Date: Tue, 2 Jul 2019 16:43:10 +1000 Subject: [PATCH 464/504] add invoke local option --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b27fdbf29..4c646a68f 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Use this to quickly upload and overwrite your AWS Lambda code on AWS, allowing y serverless deploy function -f hello ``` -6. **Invoke the Function:** +6. **Invoke the Function on AWS:** Invokes an AWS Lambda Function on AWS and returns logs. @@ -82,6 +82,14 @@ Invokes an AWS Lambda Function on AWS and returns logs. serverless invoke -f hello -l ``` +7. **Invoke the Function on your machine:** + +Invokes an AWS Lambda Function on your local machine and returns logs. + +```bash +serverless invoke local -f hello -l +``` + 7. **Fetch the Function Logs:** Open up a separate tab in your console and stream all logs for a specific Function using this command. From 2475922eaa250ea0dc62d21359823c28c7fc3c44 Mon Sep 17 00:00:00 2001 From: Xin Date: Tue, 2 Jul 2019 16:43:30 +1000 Subject: [PATCH 465/504] update numbers --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c646a68f..962f8694a 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Invokes an AWS Lambda Function on your local machine and returns logs. serverless invoke local -f hello -l ``` -7. **Fetch the Function Logs:** +8. **Fetch the Function Logs:** Open up a separate tab in your console and stream all logs for a specific Function using this command. @@ -98,7 +98,7 @@ Open up a separate tab in your console and stream all logs for a specific Functi serverless logs -f hello -t ``` -8. **Remove the Service:** +9. **Remove the Service:** Removes all Functions, Events and Resources from your AWS account. From 4a2b112aa82509d64a9d9dff0b9a3174981d7527 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 2 Jul 2019 10:25:32 +0200 Subject: [PATCH 466/504] Rename installTemplate into createFromTemplate --- lib/plugins/create/create.js | 4 ++-- lib/plugins/interactiveCli/initializeService.js | 4 ++-- lib/utils/{installTemplate.js => createFromTemplate.js} | 0 .../{installTemplate.test.js => createFromTemplate.test.js} | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) rename lib/utils/{installTemplate.js => createFromTemplate.js} (100%) rename lib/utils/{installTemplate.test.js => createFromTemplate.test.js} (80%) diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js index 771f1299c..9ebee0a93 100644 --- a/lib/plugins/create/create.js +++ b/lib/plugins/create/create.js @@ -7,7 +7,7 @@ const _ = require('lodash'); const untildify = require('untildify'); const ServerlessError = require('../../classes/Error').ServerlessError; -const installTemplate = require('../../utils/installTemplate'); +const createFromTemplate = require('../../utils/createFromTemplate'); const userStats = require('../../utils/userStats'); const download = require('../../utils/downloadTemplateFromRepo'); const renameService = require('../../utils/renameService').renameService; @@ -226,7 +226,7 @@ class Create { this.serverless.config.update({ servicePath: process.cwd() }); } - return installTemplate(this.options.template, process.cwd()).then( + return createFromTemplate(this.options.template, process.cwd()).then( () => { // rename the service if the user has provided a path via options and is creating a service if ((boilerplatePath || serviceName) && notPlugin) { diff --git a/lib/plugins/interactiveCli/initializeService.js b/lib/plugins/interactiveCli/initializeService.js index 41eff2a1a..256a60e49 100644 --- a/lib/plugins/interactiveCli/initializeService.js +++ b/lib/plugins/interactiveCli/initializeService.js @@ -3,7 +3,7 @@ const { join } = require('path'); const chalk = require('chalk'); const inquirer = require('inquirer'); -const installTemplate = require('../../utils/installTemplate'); +const createFromTemplate = require('../../utils/createFromTemplate'); const { getConfigFilePath, getServerlessConfigFile, @@ -68,7 +68,7 @@ module.exports = { } return projectNameInput(workingDir).then(projectName => { const projectDir = join(workingDir, projectName); - return installTemplate(projectType, projectDir) + return createFromTemplate(projectType, projectDir) .then(() => { process.stdout.write( `\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n` diff --git a/lib/utils/installTemplate.js b/lib/utils/createFromTemplate.js similarity index 100% rename from lib/utils/installTemplate.js rename to lib/utils/createFromTemplate.js diff --git a/lib/utils/installTemplate.test.js b/lib/utils/createFromTemplate.test.js similarity index 80% rename from lib/utils/installTemplate.test.js rename to lib/utils/createFromTemplate.test.js index bbce3e181..d7b5e03c0 100644 --- a/lib/utils/installTemplate.test.js +++ b/lib/utils/createFromTemplate.test.js @@ -3,14 +3,14 @@ const path = require('path'); const chai = require('chai'); const { existsSync } = require('fs-extra'); -const installTemplate = require('./installTemplate'); +const installTemplate = require('./createFromTemplate'); const { getTmpDirPath } = require('../../tests/utils/fs'); chai.use(require('chai-as-promised')); const expect = chai.expect; -describe('#installTemplate()', () => { +describe('#createFromTemplate()', () => { let tmpDirPath; beforeEach(() => { @@ -18,7 +18,7 @@ describe('#installTemplate()', () => { return installTemplate('aws-nodejs', tmpDirPath); }); - it('should install template', () => + it('should create from template', () => expect(existsSync(path.join(tmpDirPath, 'serverless.yml'))).to.be.true); it('should handle .gitignore', () => From 9ca7b4c2daa70024a9695981d9593418a547b2a6 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 2 Jul 2019 10:45:34 +0200 Subject: [PATCH 467/504] Rely on more natural array content check --- lib/plugins/interactiveCli/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/interactiveCli/index.js b/lib/plugins/interactiveCli/index.js index 33968b3a4..1d13e68ff 100644 --- a/lib/plugins/interactiveCli/index.js +++ b/lib/plugins/interactiveCli/index.js @@ -9,7 +9,7 @@ module.exports = class InteractiveCli { if (!process.stdin.isTTY) return; const { processedInput } = serverless; - if (!_.isEmpty(processedInput.commands)) return; + if (processedInput.commands.length) return; if (!_.isEmpty(processedInput.options)) return; // Enforce interactive CLI From cea1a969117f6acae16cbb0c1fc14bd42f3274b8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 2 Jul 2019 10:51:13 +0200 Subject: [PATCH 468/504] Rely on recommended usage --- lib/plugins/aws/configCredentials/awsConfigCredentials.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index cf37b937f..c752a9f82 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -2,7 +2,7 @@ const expect = require('chai').expect; const sandbox = require('sinon'); -const constants = require('constants'); +const constants = require('fs').constants; const fs = require('fs'); const fse = require('fs-extra'); const os = require('os'); From 1f96c48cb1fd8eda959015b267d70dff1065e831 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 2 Jul 2019 11:01:06 +0200 Subject: [PATCH 469/504] Configure sanity test --- lib/plugins/interactiveCli/index.test.js | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 lib/plugins/interactiveCli/index.test.js diff --git a/lib/plugins/interactiveCli/index.test.js b/lib/plugins/interactiveCli/index.test.js new file mode 100644 index 000000000..52905c395 --- /dev/null +++ b/lib/plugins/interactiveCli/index.test.js @@ -0,0 +1,28 @@ +'use strict'; + +const chai = require('chai'); +const InteractiveCli = require('./'); +const Serverless = require('../../Serverless'); + +const expect = chai.expect; +chai.use(require('chai-as-promised')); +chai.use(require('sinon-chai')); + +describe('interactiveCli', () => { + let interactiveCli; + let serverless; + + beforeEach(() => { + serverless = new Serverless(); + serverless.processedInput = { commands: [], options: {} }; + const backupIsTTY = process.stdin.isTTY; + process.stdin.isTTY = true; + try { + interactiveCli = new InteractiveCli(serverless); + } finally { + process.stdin.isTTY = backupIsTTY; + } + }); + + it('should have commands', () => expect(interactiveCli.commands).to.be.not.empty); +}); From 7ac85e4d120f7e1e076ee55ce58c292e8ee67942 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 2 Jul 2019 11:43:22 +0200 Subject: [PATCH 470/504] Simplify mode access setting --- lib/plugins/aws/utils/credentials.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/plugins/aws/utils/credentials.js b/lib/plugins/aws/utils/credentials.js index 63cf6c45a..feb4e9f21 100644 --- a/lib/plugins/aws/utils/credentials.js +++ b/lib/plugins/aws/utils/credentials.js @@ -51,14 +51,10 @@ const writeCredentialsContent = content => writeFileError => { if (writeFileError) { if (writeFileError.code === 'ENOENT') { - mkdir( - awsConfigDirPath, - { mode: constants.S_IRUSR | constants.S_IWUSR | constants.S_IXUSR }, - mkdirError => { - if (mkdirError) reject(mkdirError); - else resolve(writeCredentialsContent(content)); - } - ); + mkdir(awsConfigDirPath, { mode: constants.S_IRWXU }, mkdirError => { + if (mkdirError) reject(mkdirError); + else resolve(writeCredentialsContent(content)); + }); } else { reject(writeFileError); } From f6b05ab801b7a22fd2d78f8326450133edcd9871 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Tue, 2 Jul 2019 11:44:35 +0200 Subject: [PATCH 471/504] Do not tweak file access on Windows --- lib/plugins/aws/utils/credentials.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/plugins/aws/utils/credentials.js b/lib/plugins/aws/utils/credentials.js index feb4e9f21..5cf1953ea 100644 --- a/lib/plugins/aws/utils/credentials.js +++ b/lib/plugins/aws/utils/credentials.js @@ -9,6 +9,7 @@ const homedir = os.homedir(); const awsConfigDirPath = join(homedir, '.aws'); const credentialsFilePath = homedir ? join(awsConfigDirPath, 'credentials') : null; +const isWindows = process.platform === 'win32'; const profileNameRe = /^\[([^\]]+)]\s*$/; const settingRe = /^([a-zA-Z0-9_]+)\s*=\s*([^\s]+)\s*$/; const settingMap = new Map([ @@ -47,11 +48,11 @@ const writeCredentialsContent = content => writeFile( credentialsFilePath, content, - { mode: constants.S_IRUSR | constants.S_IWUSR }, + !isWindows ? { mode: constants.S_IRUSR | constants.S_IWUSR } : null, writeFileError => { if (writeFileError) { if (writeFileError.code === 'ENOENT') { - mkdir(awsConfigDirPath, { mode: constants.S_IRWXU }, mkdirError => { + mkdir(awsConfigDirPath, !isWindows ? { mode: constants.S_IRWXU } : null, mkdirError => { if (mkdirError) reject(mkdirError); else resolve(writeCredentialsContent(content)); }); From b46a9fe8ec5f421531ceeb2e68d04560940641e0 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 2 Jul 2019 12:29:00 +0200 Subject: [PATCH 472/504] Fix formatting via Prettier --- .../aws-nodejs-ecma-script/webpack.config.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js b/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js index 0076bda39..62072d11d 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/webpack.config.js @@ -1,26 +1,26 @@ -const path = require("path"); +const path = require('path'); // eslint-disable-next-line import/no-unresolved -const slsw = require("serverless-webpack"); +const slsw = require('serverless-webpack'); module.exports = { entry: slsw.lib.entries, output: { - libraryTarget: "commonjs", - filename: "[name].js", - path: path.join(__dirname, ".webpack"), + libraryTarget: 'commonjs', + filename: '[name].js', + path: path.join(__dirname, '.webpack'), }, - mode: "development", - target: "node", + mode: 'development', + target: 'node', module: { rules: [ { test: /\.js$/, // include .js files - enforce: "pre", // preload the jshint loader + enforce: 'pre', // preload the jshint loader exclude: /node_modules/, // exclude any and all files in the node_modules folder include: __dirname, use: [ { - loader: "babel-loader", + loader: 'babel-loader', }, ], }, From 00b40d149c434ce48582b18839ec351111aadbfc Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Tue, 2 Jul 2019 12:34:59 +0200 Subject: [PATCH 473/504] Update packages --- .../create/templates/aws-nodejs-ecma-script/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/package.json b/lib/plugins/create/templates/aws-nodejs-ecma-script/package.json index 79f22191f..cd6b738a0 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/package.json +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/package.json @@ -11,8 +11,8 @@ "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.23.0", "babel-preset-env": "^1.6.0", - "serverless-webpack": "^3.1.1", - "webpack": "^3.3.0" + "serverless-webpack": "^5.3.1", + "webpack": "^4.35.2" }, "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", "license": "MIT" From dd57fae87cbfb0e38ac0c19a954fff7b2258ad4c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 3 Jul 2019 10:56:48 +0200 Subject: [PATCH 474/504] Improve comment --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 049a33aca..e0e721271 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: node_js git: - # Do not take whole history, but ensure to not break things: + # Minimize git history, but ensure to not break things: # - Merging multiple PR's around same time may introduce a case where it's not # the last merge commit that is to be tested depth: 10 From a02d261903e159951091409b27181ec5a3430040 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 3 Jul 2019 10:58:54 +0200 Subject: [PATCH 475/504] Unify env configuration --- .travis.yml | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index e0e721271..98d0b5c21 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,10 @@ branches: - master # Do not build PR branches - /^v\d+\.\d+\.\d+$/ # Ensure to build release tags -env: SLS_IGNORE_WARNING=* # Default env +env: + global: + - SLS_IGNORE_WARNING=* + - FORCE_COLOR=1 # Ensure colored output for processes combined with '&&' stages: - name: Test @@ -29,9 +32,6 @@ jobs: - name: 'Prettier check updated, Lint updated, Unit Tests - Linux - Node.js v12' if: type = pull_request node_js: 12 - env: - - SLS_IGNORE_WARNING=* - - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' # Combine with '&&' to not continue on fail script: npm run prettier-check-updated && npm run lint-updated && npm test @@ -39,18 +39,12 @@ jobs: - name: 'Lint, Unit Tests - Linux - Node.js v12' if: type != pull_request node_js: 12 - env: - - SLS_IGNORE_WARNING=* - - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' # Combine with '&&' to not continue on fail script: npm run lint && npm test - name: 'Unit Tests - Windows - Node.js v12' os: windows node_js: 12 - env: - - SLS_IGNORE_WARNING=* - - FORCE_COLOR=1 # For some reason on Windows colors support is not detected before_script: # Ensure Python 2 in Windows enviroment (Ruby is already preinstalled) - | @@ -62,9 +56,6 @@ jobs: - name: 'Isolated Unit Tests, Package Integration Tests - Linux - Node.js v10' node_js: 10 - env: - - SLS_IGNORE_WARNING=* - - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' # Combine with '&&' to not continue on fail script: npm run test-isolated && npm run integration-test-run-package @@ -79,8 +70,6 @@ jobs: name: 'Integration Tests - Linux - Node.js v12' node_js: 12 env: - - SLS_IGNORE_WARNING=* - - FORCE_COLOR=1 # TTY is lost as processes are combined '&&' # AWS_ACCESS_KEY_ID - secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY= # AWS_SECRET_ACCESS_KEY From d7944b582dd7b2295a152295b237f9c53e503a72 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 3 Jul 2019 10:59:07 +0200 Subject: [PATCH 476/504] Whitespace --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 98d0b5c21..7e10df6e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,6 +63,7 @@ jobs: node_js: 8 script: npm run coverage after_success: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage + - name: 'Unit Tests - Linux - Node.js v6' node_js: 6 From bafb4859dfa5b4d7d79621a6ada4216666c03510 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 3 Jul 2019 12:32:47 +0200 Subject: [PATCH 477/504] Ensure to cache resolved dependencies --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7e10df6e0..8afd4837c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,12 @@ git: # the last merge commit that is to be tested depth: 10 +cache: + # Not relying on 'npm' shortcut - per Travis docs it's the only 'node_modules' that it'll be cached + directories: + - $HOME/.npm + - node_modules + branches: only: - master # Do not build PR branches From 5b106418bd7ee3dc51eeccfa0bb1b1108d5355a4 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 3 Jul 2019 18:36:01 +0200 Subject: [PATCH 478/504] Ensure to install dependencies at latest versions --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8afd4837c..9db25b4c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,13 @@ cache: - $HOME/.npm - node_modules +# Ensure to install dependencies at their latest versions +install: + # Note: with `npm update` there seems no way to update all project dependency groups in one run + - npm update --no-save # Updates just dependencies + # Note: npm documents --dev option for dev dependencies update, but it's only --save-dev that works + - npm update --save-dev --no-save # Updates just devDependencies + branches: only: - master # Do not build PR branches From a5b00dbb06afa01b9ef0e73c097c71740777296c Mon Sep 17 00:00:00 2001 From: Johann Bich Date: Thu, 4 Jul 2019 17:05:22 +0900 Subject: [PATCH 479/504] allow create from templates hosted on github entreprise --- lib/utils/downloadTemplateFromRepo.js | 22 ++++++++++++----- lib/utils/downloadTemplateFromRepo.test.js | 28 +++++++++++++++++++++- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/lib/utils/downloadTemplateFromRepo.js b/lib/utils/downloadTemplateFromRepo.js index 49eaa7fe1..a4e7b8dbc 100644 --- a/lib/utils/downloadTemplateFromRepo.js +++ b/lib/utils/downloadTemplateFromRepo.js @@ -7,6 +7,7 @@ const download = require('download'); const BbPromise = require('bluebird'); const fse = require('fs-extra'); const qs = require('querystring'); +const fetch = require('node-fetch'); const renameService = require('./renameService').renameService; const ServerlessError = require('../classes/Error').ServerlessError; const copyDirContentsSync = require('./fs/copyDirContentsSync'); @@ -46,10 +47,12 @@ function validateUrl(url, hostname, service, owner, repo) { } /** + * isGhe = is github entreprise url? * @param {Object} url + * @param {Boolean} isGhe * @returns {Object} */ -function parseGitHubURL(url) { +function parseGitHubURL(url, isGhe) { const pathLength = 4; const parts = url.pathname.split('/'); const isSubdirectory = parts.length > pathLength; @@ -57,12 +60,14 @@ function parseGitHubURL(url) { const repo = parts[2]; const branch = isSubdirectory ? parts[pathLength] : 'master'; - // validate if given url is a valid GitHub url - validateUrl(url, 'github.com', 'GitHub', owner, repo); + if (!isGhe) { + // validate if given url is a valid GitHub url + validateUrl(url, 'github.com', 'GitHub', owner, repo); + } const downloadUrl = `https://${ url.auth ? `${url.auth}@` : '' - }github.com/${owner}/${repo}/archive/${branch}.zip`; + }${isGhe ? url.hostname : 'github.com'}/${owner}/${repo}/archive/${branch}.zip`; return { owner, @@ -152,7 +157,7 @@ function parseRepoURL(inputUrl) { switch (url.hostname) { case 'github.com': { - return parseGitHubURL(url); + return parseGitHubURL(url, false); } case 'bitbucket.org': { return parseBitbucketURL(url); @@ -161,8 +166,13 @@ function parseRepoURL(inputUrl) { return parseGitlabURL(url); } default: { + // test for github entreprise url + // check if the hostname contains 'github' + if (url.hostname.indexOf('github') !== -1) { + return parseGitHubURL(url, true); + } const msg = - 'The URL you passed is not one of the valid providers: "GitHub", "Bitbucket", or "GitLab".'; + 'The URL you passed is not one of the valid providers: "GitHub", "GitHub Entreprise", "Bitbucket", or "GitLab".'; throw new ServerlessError(msg); } } diff --git a/lib/utils/downloadTemplateFromRepo.test.js b/lib/utils/downloadTemplateFromRepo.test.js index b035d375a..e1a19a26a 100644 --- a/lib/utils/downloadTemplateFromRepo.test.js +++ b/lib/utils/downloadTemplateFromRepo.test.js @@ -51,7 +51,7 @@ describe('downloadTemplateFromRepo', () => { }); it('should throw an error if the passed URL is not a valid GitHub URL', () => { - expect(() => downloadTemplateFromRepo('http://no-github-url.com/foo/bar')).to.throw(Error); + expect(() => downloadTemplateFromRepo('http://no-git-url.com/foo/bar')).to.throw(Error); }); it('should throw an error if a directory with the same service name is already present', () => { @@ -170,6 +170,32 @@ describe('downloadTemplateFromRepo', () => { }); }); + it('should parse a valid GitHub Entreprise URL', () => { + const output = parseRepoURL('https://github.mydomain.com/serverless/serverless'); + + expect(output).to.deep.eq({ + owner: 'serverless', + repo: 'serverless', + branch: 'master', + downloadUrl: 'https://github.mydomain.com/serverless/serverless/archive/master.zip', + isSubdirectory: false, + pathToDirectory: '', + }); + }); + + it('should parse a valid GitHub URL with subdirectory', () => { + const output = parseRepoURL('https://github.mydomain.com/serverless/serverless/tree/master/assets'); + + expect(output).to.deep.eq({ + owner: 'serverless', + repo: 'serverless', + branch: 'master', + downloadUrl: 'https://github.mydomain.com/serverless/serverless/archive/master.zip', + isSubdirectory: true, + pathToDirectory: 'assets', + }); + }); + it('should parse a valid BitBucket URL ', () => { const output = parseRepoURL('https://bitbucket.org/atlassian/localstack'); From 49068f95f7eb41247c3268393d44b79997b4237c Mon Sep 17 00:00:00 2001 From: Johann Bich Date: Thu, 4 Jul 2019 17:05:40 +0900 Subject: [PATCH 480/504] remove useless dependency --- lib/utils/downloadTemplateFromRepo.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/utils/downloadTemplateFromRepo.js b/lib/utils/downloadTemplateFromRepo.js index a4e7b8dbc..4bd6d3953 100644 --- a/lib/utils/downloadTemplateFromRepo.js +++ b/lib/utils/downloadTemplateFromRepo.js @@ -7,7 +7,6 @@ const download = require('download'); const BbPromise = require('bluebird'); const fse = require('fs-extra'); const qs = require('querystring'); -const fetch = require('node-fetch'); const renameService = require('./renameService').renameService; const ServerlessError = require('../classes/Error').ServerlessError; const copyDirContentsSync = require('./fs/copyDirContentsSync'); From c18cf13d9f82d45003634b85bc7ebf4833b97b25 Mon Sep 17 00:00:00 2001 From: Johann Bich Date: Thu, 4 Jul 2019 17:05:46 +0900 Subject: [PATCH 481/504] fix authentication issue --- lib/utils/downloadTemplateFromRepo.js | 15 +++++++++----- lib/utils/downloadTemplateFromRepo.test.js | 24 +++++++++++++++++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lib/utils/downloadTemplateFromRepo.js b/lib/utils/downloadTemplateFromRepo.js index 4bd6d3953..6788050d0 100644 --- a/lib/utils/downloadTemplateFromRepo.js +++ b/lib/utils/downloadTemplateFromRepo.js @@ -65,8 +65,8 @@ function parseGitHubURL(url, isGhe) { } const downloadUrl = `https://${ - url.auth ? `${url.auth}@` : '' - }${isGhe ? url.hostname : 'github.com'}/${owner}/${repo}/archive/${branch}.zip`; + isGhe ? url.hostname : 'github.com' + }/${owner}/${repo}/archive/${branch}.zip`; return { owner, @@ -75,6 +75,7 @@ function parseGitHubURL(url, isGhe) { downloadUrl, isSubdirectory, pathToDirectory: getPathDirectory(pathLength + 1, parts), + auth: url.auth ? url.auth : '', }; } @@ -104,6 +105,7 @@ function parseBitbucketURL(url) { downloadUrl, isSubdirectory, pathToDirectory: getPathDirectory(pathLength + 1, parts), + auth: '', }; } @@ -132,6 +134,7 @@ function parseGitlabURL(url) { downloadUrl, isSubdirectory, pathToDirectory: getPathDirectory(pathLength + 1, parts), + auth: '', }; } @@ -211,13 +214,15 @@ function downloadTemplateFromRepo(inputUrl, templateName, downloadPath) { log(`Downloading and installing "${serviceName}"...`); - // download service - return download(repoInformation.downloadUrl, downloadServicePath, { + const downloadOptions = { timeout: 30000, extract: true, strip: 1, mode: '755', - }) + auth: repoInformation.auth || '', + }; + // download service + return download(repoInformation.downloadUrl, downloadServicePath, downloadOptions) .then(() => { // if it's a directory inside of git if (repoInformation.isSubdirectory) { diff --git a/lib/utils/downloadTemplateFromRepo.test.js b/lib/utils/downloadTemplateFromRepo.test.js index e1a19a26a..c1a2adf90 100644 --- a/lib/utils/downloadTemplateFromRepo.test.js +++ b/lib/utils/downloadTemplateFromRepo.test.js @@ -154,6 +154,7 @@ describe('downloadTemplateFromRepo', () => { downloadUrl: 'https://github.com/serverless/serverless/archive/master.zip', isSubdirectory: false, pathToDirectory: '', + auth: '', }); }); @@ -167,6 +168,7 @@ describe('downloadTemplateFromRepo', () => { downloadUrl: 'https://github.com/serverless/serverless/archive/master.zip', isSubdirectory: true, pathToDirectory: 'assets', + auth: '', }); }); @@ -180,10 +182,11 @@ describe('downloadTemplateFromRepo', () => { downloadUrl: 'https://github.mydomain.com/serverless/serverless/archive/master.zip', isSubdirectory: false, pathToDirectory: '', + auth: '', }); }); - it('should parse a valid GitHub URL with subdirectory', () => { + it('should parse a valid GitHub Entreprise with subdirectory', () => { const output = parseRepoURL('https://github.mydomain.com/serverless/serverless/tree/master/assets'); expect(output).to.deep.eq({ @@ -193,6 +196,21 @@ describe('downloadTemplateFromRepo', () => { downloadUrl: 'https://github.mydomain.com/serverless/serverless/archive/master.zip', isSubdirectory: true, pathToDirectory: 'assets', + auth: '', + }); + }); + + it('should parse a valid GitHub Entreprise URL with authentication', () => { + const output = parseRepoURL('https://username:password@github.com/serverless/serverless/'); + + expect(output).to.deep.eq({ + owner: 'serverless', + repo: 'serverless', + branch: 'master', + downloadUrl: 'https://github.com/serverless/serverless/archive/master.zip', + isSubdirectory: false, + pathToDirectory: '', + auth: 'username:password', }); }); @@ -206,6 +224,7 @@ describe('downloadTemplateFromRepo', () => { downloadUrl: 'https://bitbucket.org/atlassian/localstack/get/master.zip', isSubdirectory: false, pathToDirectory: '', + auth: '', }); }); @@ -221,6 +240,7 @@ describe('downloadTemplateFromRepo', () => { downloadUrl: 'https://bitbucket.org/atlassian/localstack/get/mvn.zip', isSubdirectory: true, pathToDirectory: `localstack${path.sep}dashboard`, + auth: '', }); }); @@ -235,6 +255,7 @@ describe('downloadTemplateFromRepo', () => { 'https://gitlab.com/serverless/serverless/-/archive/master/serverless-master.zip', isSubdirectory: false, pathToDirectory: '', + auth: '', }); }); @@ -248,6 +269,7 @@ describe('downloadTemplateFromRepo', () => { downloadUrl: 'https://gitlab.com/serverless/serverless/-/archive/dev/serverless-dev.zip', isSubdirectory: true, pathToDirectory: 'subdir', + auth: '', }); }); }); From 4d792fa22e4636bfa3a5b5dc77b02abb992cc9e8 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 4 Jul 2019 13:52:02 +0200 Subject: [PATCH 482/504] Reconfigure Travis with force abort on failure patch --- .travis.yml | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9db25b4c3..57070cfbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,6 +36,11 @@ stages: - name: Deploy if: tag =~ ^v\d+\.\d+\.\d+$ +before_script: + # Fail build right after first script fail, unfortunately Travis doesn't do that: https://github.com/travis-ci/travis-ci/issues/1066 + # More info on below: https://www.davidpashley.com/articles/writing-robust-shell-scripts/#idm5413512 + - set -e + jobs: include: # In most cases it's best to configure one job per Platform & Node.js version combination @@ -45,20 +50,23 @@ jobs: - name: 'Prettier check updated, Lint updated, Unit Tests - Linux - Node.js v12' if: type = pull_request node_js: 12 - # Combine with '&&' to not continue on fail - script: npm run prettier-check-updated && npm run lint-updated && npm test + script: + - npm run prettier-check-updated + - npm run lint-updated + - npm test # master branch and version tags - name: 'Lint, Unit Tests - Linux - Node.js v12' if: type != pull_request node_js: 12 - # Combine with '&&' to not continue on fail - script: npm run lint && npm test + script: + - npm run lint + - npm test - name: 'Unit Tests - Windows - Node.js v12' os: windows node_js: 12 - before_script: + before_install: # Ensure Python 2 in Windows enviroment (Ruby is already preinstalled) - | if [ $TRAVIS_OS_NAME = windows ] @@ -69,13 +77,16 @@ jobs: - name: 'Isolated Unit Tests, Package Integration Tests - Linux - Node.js v10' node_js: 10 - # Combine with '&&' to not continue on fail - script: npm run test-isolated && npm run integration-test-run-package + script: + - npm run test-isolated + - npm run integration-test-run-package - name: 'Unit Tests, Coverage - Linux - Node.js v8' node_js: 8 script: npm run coverage - after_success: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage + after_success: + - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js + - rm -rf ./coverage - name: 'Unit Tests - Linux - Node.js v6' node_js: 6 @@ -89,7 +100,8 @@ jobs: # AWS_SECRET_ACCESS_KEY - secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI= script: - - npm run integration-test-run-basic && npm run integration-test-run-all + - npm run integration-test-run-basic || { npm run integration-test-cleanup; exit 1; } + - npm run integration-test-run-all || { npm run integration-test-cleanup; exit 1; } - npm run integration-test-cleanup - stage: Deploy From 9cb34c4444f7a389661ae2623c5d2c5abb7b966e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 4 Jul 2019 13:53:12 +0200 Subject: [PATCH 483/504] Improve line comment --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 57070cfbf..731f0c7de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ branches: env: global: - SLS_IGNORE_WARNING=* - - FORCE_COLOR=1 # Ensure colored output for processes combined with '&&' + - FORCE_COLOR=1 # Ensure colored output (color support is not detected in some cases) stages: - name: Test From c93f8252f9bcaa6c3a6aa4d9fe5a206fc42bf603 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 4 Jul 2019 14:12:57 +0200 Subject: [PATCH 484/504] Ensure to fail builds on failed deploy --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.travis.yml b/.travis.yml index 731f0c7de..93ae18c4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,18 @@ before_script: # More info on below: https://www.davidpashley.com/articles/writing-robust-shell-scripts/#idm5413512 - set -e +# Ensure to fail build if deploy fails, Travis doesn't ensure that: https://github.com/travis-ci/travis-ci/issues/921 +before_deploy: + # Remove eventual old npm logs + - rm -rf ~/.npm/_logs +after_deploy: + - | + # npm creates log only on failure + if [ -d ~/.npm/_logs ]; then + # Undocumented way to force build to fail + travis_terminate 1 + fi + jobs: include: # In most cases it's best to configure one job per Platform & Node.js version combination From 4d3afe2677fdb93b9763abf055b3771682bfc8f4 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 4 Jul 2019 14:15:40 +0200 Subject: [PATCH 485/504] Improve comments --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 93ae18c4e..26433d8a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,8 +37,8 @@ stages: if: tag =~ ^v\d+\.\d+\.\d+$ before_script: - # Fail build right after first script fail, unfortunately Travis doesn't do that: https://github.com/travis-ci/travis-ci/issues/1066 - # More info on below: https://www.davidpashley.com/articles/writing-robust-shell-scripts/#idm5413512 + # Fail build right after first script fails. Travis doesn't ensure that: https://github.com/travis-ci/travis-ci/issues/1066 + # More info on below line: https://www.davidpashley.com/articles/writing-robust-shell-scripts/#idm5413512 - set -e # Ensure to fail build if deploy fails, Travis doesn't ensure that: https://github.com/travis-ci/travis-ci/issues/921 @@ -49,7 +49,7 @@ after_deploy: - | # npm creates log only on failure if [ -d ~/.npm/_logs ]; then - # Undocumented way to force build to fail + # Undocumented way to force Travis build to fail travis_terminate 1 fi From dc2b25353857c87e1619896c3e101479c1096ba3 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Thu, 4 Jul 2019 14:20:22 +0200 Subject: [PATCH 486/504] Improve comment --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 26433d8a0..e05e5c044 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ git: depth: 10 cache: - # Not relying on 'npm' shortcut - per Travis docs it's the only 'node_modules' that it'll be cached + # Not relying on 'npm' shortcut, as per Travis docs it's the only 'node_modules' that it'll cache directories: - $HOME/.npm - node_modules From 57c1cc36e85f5acaa4b38dbee725921fb96d387f Mon Sep 17 00:00:00 2001 From: Johann Bich Date: Fri, 5 Jul 2019 13:59:16 +0900 Subject: [PATCH 487/504] remove useless boolean / replaced switch case with ifs --- lib/utils/downloadTemplateFromRepo.js | 35 ++++++++-------------- lib/utils/downloadTemplateFromRepo.test.js | 6 ++-- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/lib/utils/downloadTemplateFromRepo.js b/lib/utils/downloadTemplateFromRepo.js index 6788050d0..f3b2902ee 100644 --- a/lib/utils/downloadTemplateFromRepo.js +++ b/lib/utils/downloadTemplateFromRepo.js @@ -46,18 +46,17 @@ function validateUrl(url, hostname, service, owner, repo) { } /** - * isGhe = is github entreprise url? * @param {Object} url - * @param {Boolean} isGhe * @returns {Object} */ -function parseGitHubURL(url, isGhe) { +function parseGitHubURL(url) { const pathLength = 4; const parts = url.pathname.split('/'); const isSubdirectory = parts.length > pathLength; const owner = parts[1]; const repo = parts[2]; const branch = isSubdirectory ? parts[pathLength] : 'master'; + const isGhe = url.hostname !== 'github.com'; if (!isGhe) { // validate if given url is a valid GitHub url @@ -157,27 +156,17 @@ function parseRepoURL(inputUrl) { throw new ServerlessError('The URL you passed is not a valid URL'); } - switch (url.hostname) { - case 'github.com': { - return parseGitHubURL(url, false); - } - case 'bitbucket.org': { - return parseBitbucketURL(url); - } - case 'gitlab.com': { - return parseGitlabURL(url); - } - default: { - // test for github entreprise url - // check if the hostname contains 'github' - if (url.hostname.indexOf('github') !== -1) { - return parseGitHubURL(url, true); - } - const msg = - 'The URL you passed is not one of the valid providers: "GitHub", "GitHub Entreprise", "Bitbucket", or "GitLab".'; - throw new ServerlessError(msg); - } + if (url.hostname === 'github.com' || url.hostname.indexOf('github.') !== -1) { + return parseGitHubURL(url); + } else if (url.hostname === 'bitbucket.org') { + return parseBitbucketURL(url); + } else if (url.hostname === 'gitlab.com') { + return parseGitlabURL(url); } + + const msg = + 'The URL you passed is not one of the valid providers: "GitHub", "GitHub Entreprise", "Bitbucket", or "GitLab".'; + throw new ServerlessError(msg); } /** diff --git a/lib/utils/downloadTemplateFromRepo.test.js b/lib/utils/downloadTemplateFromRepo.test.js index c1a2adf90..98e8fd2da 100644 --- a/lib/utils/downloadTemplateFromRepo.test.js +++ b/lib/utils/downloadTemplateFromRepo.test.js @@ -187,7 +187,9 @@ describe('downloadTemplateFromRepo', () => { }); it('should parse a valid GitHub Entreprise with subdirectory', () => { - const output = parseRepoURL('https://github.mydomain.com/serverless/serverless/tree/master/assets'); + const output = parseRepoURL( + 'https://github.mydomain.com/serverless/serverless/tree/master/assets' + ); expect(output).to.deep.eq({ owner: 'serverless', @@ -209,8 +211,8 @@ describe('downloadTemplateFromRepo', () => { branch: 'master', downloadUrl: 'https://github.com/serverless/serverless/archive/master.zip', isSubdirectory: false, - pathToDirectory: '', auth: 'username:password', + pathToDirectory: '', }); }); From 92bd3fd814b409467b0335d5c0eecb52bd86ad83 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Fri, 5 Jul 2019 14:58:13 -0400 Subject: [PATCH 488/504] SFE plugin & sdk version info in both `--version` and error output --- lib/classes/CLI.js | 4 +++- lib/classes/Error.js | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/classes/CLI.js b/lib/classes/CLI.js index 53c53f699..7d96cc1d9 100644 --- a/lib/classes/CLI.js +++ b/lib/classes/CLI.js @@ -1,6 +1,8 @@ 'use strict'; const version = require('../../package.json').version; +const enterpriseVersion = require('@serverless/enterprise-plugin/package.json').version; +const sdkVersion = require('@serverless/platform-sdk/package.json').version; const minimist = require('minimist'); const _ = require('lodash'); const os = require('os'); @@ -309,7 +311,7 @@ class CLI { } getVersionNumber() { - this.consoleLog(version); + this.consoleLog(`${version} (Enterprise Plugin: ${enterpriseVersion}, Platform SDK: ${sdkVersion})`); } asciiGreeting() { diff --git a/lib/classes/Error.js b/lib/classes/Error.js index cd66e850c..1e4fcce28 100644 --- a/lib/classes/Error.js +++ b/lib/classes/Error.js @@ -1,6 +1,8 @@ 'use strict'; const chalk = require('chalk'); const version = require('./../../package.json').version; +const sfeVersion = require('@serverless/enterprise-plugin/package.json').version; +const sdkVersion = require('@serverless/platform-sdk/package.json').version; // raven implementation examples https://www.npmjs.com/browse/depended/raven const errorReporter = require('../utils/sentry').raven; @@ -73,9 +75,11 @@ module.exports.logError = (e) => { consoleLog(' '); consoleLog(chalk.yellow(' Your Environment Information ---------------------------')); - consoleLog(chalk.yellow(` OS: ${platform}`)); - consoleLog(chalk.yellow(` Node Version: ${nodeVersion}`)); - consoleLog(chalk.yellow(` Serverless Version: ${slsVersion}`)); + consoleLog(chalk.yellow(` Operating System: ${platform}`)); + consoleLog(chalk.yellow(` Node Version: ${nodeVersion}`)); + consoleLog(chalk.yellow(` Serverless Version: ${slsVersion}`)); + consoleLog(chalk.yellow(` Enterprise Plugin Version: ${sfeVersion}`)); + consoleLog(chalk.yellow(` Platform SDK Version: ${sdkVersion}`)); consoleLog(' '); // Exit early for users who have opted out of tracking From 3364b58e9881120cf107d349231c08146020256d Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 8 Jul 2019 11:26:48 +0200 Subject: [PATCH 489/504] Minor code improvements --- lib/utils/downloadTemplateFromRepo.js | 11 ++++++----- lib/utils/downloadTemplateFromRepo.test.js | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/utils/downloadTemplateFromRepo.js b/lib/utils/downloadTemplateFromRepo.js index f3b2902ee..1bbd0424b 100644 --- a/lib/utils/downloadTemplateFromRepo.js +++ b/lib/utils/downloadTemplateFromRepo.js @@ -56,15 +56,15 @@ function parseGitHubURL(url) { const owner = parts[1]; const repo = parts[2]; const branch = isSubdirectory ? parts[pathLength] : 'master'; - const isGhe = url.hostname !== 'github.com'; + const isGitHubEnterprise = url.hostname !== 'github.com'; - if (!isGhe) { + if (!isGitHubEnterprise) { // validate if given url is a valid GitHub url validateUrl(url, 'github.com', 'GitHub', owner, repo); } const downloadUrl = `https://${ - isGhe ? url.hostname : 'github.com' + isGitHubEnterprise ? url.hostname : 'github.com' }/${owner}/${repo}/archive/${branch}.zip`; return { @@ -74,7 +74,7 @@ function parseGitHubURL(url) { downloadUrl, isSubdirectory, pathToDirectory: getPathDirectory(pathLength + 1, parts), - auth: url.auth ? url.auth : '', + auth: url.auth || '', }; } @@ -181,6 +181,7 @@ function downloadTemplateFromRepo(inputUrl, templateName, downloadPath) { let serviceName; let dirName; let downloadServicePath; + const { auth } = repoInformation; if (repoInformation.isSubdirectory) { const folderName = repoInformation.pathToDirectory.split('/').splice(-1)[0]; @@ -208,7 +209,7 @@ function downloadTemplateFromRepo(inputUrl, templateName, downloadPath) { extract: true, strip: 1, mode: '755', - auth: repoInformation.auth || '', + auth, }; // download service return download(repoInformation.downloadUrl, downloadServicePath, downloadOptions) diff --git a/lib/utils/downloadTemplateFromRepo.test.js b/lib/utils/downloadTemplateFromRepo.test.js index 98e8fd2da..eee3f4c1a 100644 --- a/lib/utils/downloadTemplateFromRepo.test.js +++ b/lib/utils/downloadTemplateFromRepo.test.js @@ -51,7 +51,7 @@ describe('downloadTemplateFromRepo', () => { }); it('should throw an error if the passed URL is not a valid GitHub URL', () => { - expect(() => downloadTemplateFromRepo('http://no-git-url.com/foo/bar')).to.throw(Error); + expect(() => downloadTemplateFromRepo('http://no-git-hub-url.com/foo/bar')).to.throw(Error); }); it('should throw an error if a directory with the same service name is already present', () => { From c234227a168ccf202fc4536c2ccd2559dd183b77 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 8 Jul 2019 11:32:46 +0200 Subject: [PATCH 490/504] Update documentation --- docs/providers/aws/cli-reference/install.md | 7 +++++++ docs/providers/azure/cli-reference/install.md | 7 +++++++ docs/providers/google/cli-reference/install.md | 7 +++++++ docs/providers/openwhisk/cli-reference/install.md | 7 +++++++ 4 files changed, 28 insertions(+) diff --git a/docs/providers/aws/cli-reference/install.md b/docs/providers/aws/cli-reference/install.md index 2220121e1..167dab1ab 100644 --- a/docs/providers/aws/cli-reference/install.md +++ b/docs/providers/aws/cli-reference/install.md @@ -29,6 +29,13 @@ serverless install --url https://github.com/some/service - `install:install` +## Supported Code Hosting Platforms + +- GitHub +- GitHub Enterprise +- GitLab +- BitBucket + ## Examples ### Installing a service from a GitHub URL diff --git a/docs/providers/azure/cli-reference/install.md b/docs/providers/azure/cli-reference/install.md index 805b24b2d..a3c39473f 100644 --- a/docs/providers/azure/cli-reference/install.md +++ b/docs/providers/azure/cli-reference/install.md @@ -29,6 +29,13 @@ serverless install --url https://github.com/some/service - `install:install` +## Supported Code Hosting Platforms + +- GitHub +- GitHub Enterprise +- GitLab +- BitBucket + ## Examples ### Installing a service from a GitHub URL diff --git a/docs/providers/google/cli-reference/install.md b/docs/providers/google/cli-reference/install.md index 4a2cfda3c..0406ffa75 100644 --- a/docs/providers/google/cli-reference/install.md +++ b/docs/providers/google/cli-reference/install.md @@ -25,6 +25,13 @@ serverless install --url https://github.com/some/service - `--url` or `-u` The services GitHub URL. **Required**. - `--name` or `-n` Name for the service. +## Supported Code Hosting Platforms + +- GitHub +- GitHub Enterprise +- GitLab +- BitBucket + ## Examples ### Installing a service from a GitHub URL diff --git a/docs/providers/openwhisk/cli-reference/install.md b/docs/providers/openwhisk/cli-reference/install.md index a43a74fe9..88a2ff607 100644 --- a/docs/providers/openwhisk/cli-reference/install.md +++ b/docs/providers/openwhisk/cli-reference/install.md @@ -29,6 +29,13 @@ serverless install --url https://github.com/some/service - `install:install` +## Supported Code Hosting Platforms + +- GitHub +- GitHub Enterprise +- GitLab +- BitBucket + ## Examples ### Installing a service from a GitHub URL From c86c7d51c4a0a5374e5ea7659f26f717b5615149 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Mon, 8 Jul 2019 16:05:59 +0200 Subject: [PATCH 491/504] Run Prettier --- CHANGELOG.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c59b8ae6..48dffddc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [Ensure deploy is triggered in CI](https://github.com/serverless/serverless/pull/6306) ## Meta + - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.46.0...v1.46.1) # 1.46.0 (2019-06-26) @@ -31,6 +32,7 @@ - [Feature/support external websocket api](https://github.com/serverless/serverless/pull/6272) ## Meta + - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.45.1...v1.46.0) # 1.45.1 (2019-06-12) @@ -39,8 +41,8 @@ - [Fix Travis CI deploy config](https://github.com/serverless/serverless/pull/6234) ## Meta -- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.45.0...v1.45.1) +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.45.0...v1.45.1) # 1.45.0 (2019-06-12) @@ -64,16 +66,16 @@ - [Update Scala version to 2.13.0 for aws-scala-sbt template](https://github.com/serverless/serverless/pull/6222) ## Meta -- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.1...v1.45.0) +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.1...v1.45.0) # 1.44.1 (2019-05-28) - [Fix enterprise plugin lookup in global yarn installs](https://github.com/serverless/serverless/pull/6183) ## Meta -- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.0...v1.44.1) +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.44.0...v1.44.1) # 1.44.0 (2019-05-28) @@ -88,8 +90,8 @@ - [Upgrade mocha, switch from istanbul to nyc, improve tests configuration](https://github.com/serverless/serverless/pull/6169) ## Meta -- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.43.0...v1.44.0) +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.43.0...v1.44.0) # 1.43.0 (2019-05-20) @@ -100,6 +102,7 @@ - [Fix tests setup issues](https://github.com/serverless/serverless/pull/6147) ## Meta + - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.42.3...v1.43.0) # 1.42.3 (2019-05-14) @@ -115,7 +118,8 @@ - [Improve handling of custom API Gateway options](https://github.com/serverless/serverless/pull/6129) ## Meta - - [Comparison since last release](https://github.com/serverless/serverless/compare/v1.42.2...v1.42.3) + +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.42.2...v1.42.3) # 1.42.2 (2019-05-10) From 153e19ebda6563ed9114c80a58324f78fd11de24 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 8 Jul 2019 16:35:42 +0200 Subject: [PATCH 492/504] Use destructuring --- lib/plugins/aws/configCredentials/awsConfigCredentials.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js index c752a9f82..6caf002f3 100644 --- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js +++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js @@ -2,7 +2,7 @@ const expect = require('chai').expect; const sandbox = require('sinon'); -const constants = require('fs').constants; +const { constants } = require('fs'); const fs = require('fs'); const fse = require('fs-extra'); const os = require('os'); From 4cccb85c8766994c0b553141aafeee4185efac5d Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 8 Jul 2019 16:47:09 +0200 Subject: [PATCH 493/504] Fix AWS setup link --- lib/plugins/interactiveCli/setupAws.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/interactiveCli/setupAws.js b/lib/plugins/interactiveCli/setupAws.js index ba35afe61..05f19e1ee 100644 --- a/lib/plugins/interactiveCli/setupAws.js +++ b/lib/plugins/interactiveCli/setupAws.js @@ -76,7 +76,7 @@ module.exports = { if (!isConfirmed) return null; process.stdout.write( '\nGo here to learn how to create your AWS credentials:\n' + - 'https://slss.io/aws-account and enter them here:\n\n' + 'https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration-creds and enter them here:\n\n' ); return awsProfileNameInput().then(profileName => awsAccessKeyIdInput().then(accessKeyId => From 614abc6e9d9d514feea4d509c1cb13ed87601f85 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 8 Jul 2019 12:35:40 -0400 Subject: [PATCH 494/504] huh...travis? From 96f76a2478e8f3d31715f28b1b63ac0cb74f7540 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 8 Jul 2019 22:19:12 +0200 Subject: [PATCH 495/504] Style inquirer --- .../interactiveCli/initializeService.js | 2 +- lib/plugins/interactiveCli/inquirer.js | 36 +++++++++++++++++++ lib/plugins/interactiveCli/setupAws.js | 2 +- lib/plugins/interactiveCli/utils.js | 2 +- package.json | 1 + 5 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 lib/plugins/interactiveCli/inquirer.js diff --git a/lib/plugins/interactiveCli/initializeService.js b/lib/plugins/interactiveCli/initializeService.js index 256a60e49..808bf43d2 100644 --- a/lib/plugins/interactiveCli/initializeService.js +++ b/lib/plugins/interactiveCli/initializeService.js @@ -2,7 +2,7 @@ const { join } = require('path'); const chalk = require('chalk'); -const inquirer = require('inquirer'); +const inquirer = require('./inquirer'); const createFromTemplate = require('../../utils/createFromTemplate'); const { getConfigFilePath, diff --git a/lib/plugins/interactiveCli/inquirer.js b/lib/plugins/interactiveCli/inquirer.js new file mode 100644 index 000000000..5508a4a89 --- /dev/null +++ b/lib/plugins/interactiveCli/inquirer.js @@ -0,0 +1,36 @@ +// Customize inquirer style + +'use strict'; + +const { dirname } = require('path'); +const requireUncached = require('ncjsm/require-uncached'); +const resolve = require('ncjsm/resolve/sync'); +const chalk = require('chalk'); + +const inquirersChalkPath = resolve(dirname(require.resolve('inquirer')), 'chalk'); + +module.exports = requireUncached(inquirersChalkPath, () => { + // Ensure distinct chalk instance for inquirer and hack it with altered styles + Object.defineProperties(require(inquirersChalkPath), { + cyan: { + get() { + return chalk.bold; + }, + }, + bold: { + get() { + return chalk.bold.yellow; + }, + }, + }); + + // 'Serverless:' prefix + const BasePrompt = require('inquirer/lib/prompts/base'); + const originalGetQuestion = BasePrompt.prototype.getQuestion; + BasePrompt.prototype.getQuestion = function() { + this.opt.prefix = 'Serverless:'; + return originalGetQuestion.call(this); + }; + + return require('inquirer'); +}); diff --git a/lib/plugins/interactiveCli/setupAws.js b/lib/plugins/interactiveCli/setupAws.js index 05f19e1ee..5b3d5f390 100644 --- a/lib/plugins/interactiveCli/setupAws.js +++ b/lib/plugins/interactiveCli/setupAws.js @@ -2,7 +2,7 @@ const BbPromise = require('bluebird'); const chalk = require('chalk'); -const inquirer = require('inquirer'); +const inquirer = require('./inquirer'); const awsCredentials = require('../aws/utils/credentials'); const { confirm } = require('./utils'); diff --git a/lib/plugins/interactiveCli/utils.js b/lib/plugins/interactiveCli/utils.js index f60b80bd5..0f2f53f55 100644 --- a/lib/plugins/interactiveCli/utils.js +++ b/lib/plugins/interactiveCli/utils.js @@ -1,6 +1,6 @@ 'use strict'; -const inquirer = require('inquirer'); +const inquirer = require('./inquirer'); module.exports = { confirm: message => diff --git a/package.json b/package.json index faab77191..3eb4ff632 100644 --- a/package.json +++ b/package.json @@ -141,6 +141,7 @@ "mkdirp": "^0.5.1", "moment": "^2.24.0", "nanomatch": "^1.2.13", + "ncjsm": "^2.3.0", "node-fetch": "^1.7.3", "object-hash": "^1.3.1", "promise-queue": "^2.2.5", From 3fdd9b2866d0e8ed299afe3002c5a95d1bf8f94e Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 8 Jul 2019 22:19:31 +0200 Subject: [PATCH 496/504] Improve question (remove redundand wording --- .../interactiveCli/initializeService.js | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/lib/plugins/interactiveCli/initializeService.js b/lib/plugins/interactiveCli/initializeService.js index 808bf43d2..dd74c87d1 100644 --- a/lib/plugins/interactiveCli/initializeService.js +++ b/lib/plugins/interactiveCli/initializeService.js @@ -55,43 +55,41 @@ module.exports = { }, run(serverless) { const workingDir = process.cwd(); - return confirm('No Serverless project detected. Do you want to create a new one?').then( - isConfirmed => { - if (!isConfirmed) return null; - return projectTypeChoice().then(projectType => { - if (projectType === 'other') { - process.stdout.write( - '\nRun “serverless create --help” to view available templates and create a new project ' + - 'from one of those templates.\n' - ); - return null; - } - return projectNameInput(workingDir).then(projectName => { - const projectDir = join(workingDir, projectName); - return createFromTemplate(projectType, projectDir) - .then(() => { - process.stdout.write( - `\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n` - ); + return confirm('No project detected. Do you want to create a new one?').then(isConfirmed => { + if (!isConfirmed) return null; + return projectTypeChoice().then(projectType => { + if (projectType === 'other') { + process.stdout.write( + '\nRun “serverless create --help” to view available templates and create a new project ' + + 'from one of those templates.\n' + ); + return null; + } + return projectNameInput(workingDir).then(projectName => { + const projectDir = join(workingDir, projectName); + return createFromTemplate(projectType, projectDir) + .then(() => { + process.stdout.write( + `\n${chalk.green(`Project successfully created in '${projectName}' folder.`)}\n` + ); - process.chdir(projectDir); - serverless.config.servicePath = projectDir; - getServerlessConfigFile.cache.delete(serverless); - getServerlessConfigFile(serverless); - }) - .then(serverlessConfigFile => { - serverless.pluginManager.serverlessConfigFile = serverlessConfigFile; - return serverless.service.load(); - }) - .then(() => serverless.variables.populateService()) - .then(() => { - serverless.service.mergeArrays(); - serverless.service.setFunctionNames(); - serverless.service.validate(); - }); - }); + process.chdir(projectDir); + serverless.config.servicePath = projectDir; + getServerlessConfigFile.cache.delete(serverless); + getServerlessConfigFile(serverless); + }) + .then(serverlessConfigFile => { + serverless.pluginManager.serverlessConfigFile = serverlessConfigFile; + return serverless.service.load(); + }) + .then(() => serverless.variables.populateService()) + .then(() => { + serverless.service.mergeArrays(); + serverless.service.setFunctionNames(); + serverless.service.validate(); + }); }); - } - ); + }); + }); }, }; From dafbf820564acef11f310cd38397ced19f893038 Mon Sep 17 00:00:00 2001 From: Daniel Schep Date: Mon, 8 Jul 2019 16:20:07 -0400 Subject: [PATCH 497/504] prettier & dep bump --- lib/classes/CLI.js | 6 ++++-- lib/classes/Error.js | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/classes/CLI.js b/lib/classes/CLI.js index b976a4ea2..4c1f9ea74 100644 --- a/lib/classes/CLI.js +++ b/lib/classes/CLI.js @@ -2,7 +2,7 @@ const version = require('../../package.json').version; const enterpriseVersion = require('@serverless/enterprise-plugin/package.json').version; -const sdkVersion = require('@serverless/platform-sdk/package.json').version; +const { sdkVersion } = require('@serverless/enterprise-plugin'); const minimist = require('minimist'); const _ = require('lodash'); const os = require('os'); @@ -323,7 +323,9 @@ class CLI { } getVersionNumber() { - this.consoleLog(`${version} (Enterprise Plugin: ${enterpriseVersion}, Platform SDK: ${sdkVersion})`); + this.consoleLog( + `${version} (Enterprise Plugin: ${enterpriseVersion}, Platform SDK: ${sdkVersion})` + ); } asciiGreeting() { diff --git a/lib/classes/Error.js b/lib/classes/Error.js index b34478f7c..ca618ddfe 100644 --- a/lib/classes/Error.js +++ b/lib/classes/Error.js @@ -4,7 +4,7 @@ const chalk = require('chalk'); const { inspect } = require('util'); const version = require('./../../package.json').version; const sfeVersion = require('@serverless/enterprise-plugin/package.json').version; -const sdkVersion = require('@serverless/platform-sdk/package.json').version; +const { sdkVersion } = require('@serverless/enterprise-plugin'); // raven implementation examples https://www.npmjs.com/browse/depended/raven const errorReporter = require('../utils/sentry').raven; diff --git a/package.json b/package.json index 0dd870d93..33df5b7ec 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "strip-ansi": "^5.2.0" }, "dependencies": { - "@serverless/enterprise-plugin": "^1.0.7", + "@serverless/enterprise-plugin": "^1.2.0", "archiver": "^1.3.0", "async": "^1.5.2", "aws-sdk": "^2.482.0", From f1526d05529876d90b7066ea8e065f6766685e97 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 8 Jul 2019 22:25:09 +0200 Subject: [PATCH 498/504] Improve style --- lib/plugins/interactiveCli/setupAws.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/plugins/interactiveCli/setupAws.js b/lib/plugins/interactiveCli/setupAws.js index 5b3d5f390..c40333273 100644 --- a/lib/plugins/interactiveCli/setupAws.js +++ b/lib/plugins/interactiveCli/setupAws.js @@ -13,7 +13,7 @@ const isValidAwsSecretAccessKey = RegExp.prototype.test.bind(/^[a-zA-Z0-9/]{10,} const awsProfileNameInput = () => inquirer .prompt({ - message: 'AWS Profile name', + message: 'AWS Profile name:', type: 'input', name: 'profileName', validate: input => { @@ -31,7 +31,7 @@ const awsProfileNameInput = () => const awsAccessKeyIdInput = () => inquirer .prompt({ - message: 'AWS Access Key Id', + message: 'AWS Access Key Id:', type: 'input', name: 'accessKeyId', validate: input => { @@ -44,7 +44,7 @@ const awsAccessKeyIdInput = () => const awsSecretAccessKeyInput = () => inquirer .prompt({ - message: 'AWS Secret Access Key', + message: 'AWS Secret Access Key:', type: 'input', name: 'secretAccessKey', validate: input => { @@ -75,8 +75,9 @@ module.exports = { return confirm('Do you want to set them up now?').then(isConfirmed => { if (!isConfirmed) return null; process.stdout.write( - '\nGo here to learn how to create your AWS credentials:\n' + - 'https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration-creds and enter them here:\n\n' + `\nGo here to learn how to create your AWS credentials:\n${chalk.bold( + 'https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration-creds' + )}\nThen enter them here:\n\n` ); return awsProfileNameInput().then(profileName => awsAccessKeyIdInput().then(accessKeyId => @@ -86,8 +87,9 @@ module.exports = { .then(() => process.stdout.write( `\n${chalk.green( - 'AWS credentials saved on your machine at ~/.aws/credentials. ' + - 'Go there to change them at any time.' + `AWS credentials saved on your machine at ${chalk.bold( + '~/.aws/credentials' + )}. Go there to change them at any time.` )}\n` ) ) From 858dc22690ab8ab5b1c9c7533fb74e37b29b924c Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 8 Jul 2019 22:41:24 +0200 Subject: [PATCH 499/504] Expose customized inquirer for other plugins --- lib/plugins/interactiveCli/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/plugins/interactiveCli/index.js b/lib/plugins/interactiveCli/index.js index 1d13e68ff..8e807ede7 100644 --- a/lib/plugins/interactiveCli/index.js +++ b/lib/plugins/interactiveCli/index.js @@ -1,6 +1,7 @@ 'use strict'; const _ = require('lodash'); +const inquirer = require('./inquirer'); const initializeService = require('./initializeService'); const setupAws = require('./setupAws'); @@ -8,6 +9,8 @@ module.exports = class InteractiveCli { constructor(serverless) { if (!process.stdin.isTTY) return; + // Expose customized inquirer for other plugins + serverless.interactiveCli = { inquirer }; const { processedInput } = serverless; if (processedInput.commands.length) return; if (!_.isEmpty(processedInput.options)) return; From d09b754874581bf025436cef5e45eccad7364f81 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Fri, 21 Jun 2019 15:04:28 +0200 Subject: [PATCH 500/504] Add support for existing S3 buckets --- docs/providers/aws/events/s3.md | 20 +++ lib/plugins/aws/customResources/index.js | 110 ++++++++++++++++ lib/plugins/aws/customResources/index.test.js | 117 +++++++++++++++++ .../aws/customResources/resources/README.md | 3 + .../customResources/resources/s3/handler.js | 73 +++++++++++ .../resources/s3/lib/bucket.js | 101 +++++++++++++++ .../resources/s3/lib/permissions.js | 42 ++++++ .../aws/customResources/resources/utils.js | 93 +++++++++++++ .../customResources/resources/utils.test.js | 31 +++++ lib/plugins/aws/deploy/lib/uploadArtifacts.js | 18 ++- .../aws/deploy/lib/uploadArtifacts.test.js | 52 +++++++- lib/plugins/aws/lib/naming.js | 20 +++ lib/plugins/aws/lib/naming.test.js | 40 ++++++ .../aws/package/compile/events/s3/index.js | 99 +++++++++++++- .../package/compile/events/s3/index.test.js | 122 ++++++++++++++++-- lib/utils/fs/createZipFile.js | 39 ++++++ lib/utils/fs/createZipFile.test.js | 27 ++++ tests/integration-all/s3/service/core.js | 44 +++++++ .../integration-all/s3/service/serverless.yml | 31 +++++ tests/integration-all/s3/service/utils.js | 22 ++++ tests/integration-all/s3/tests.js | 106 +++++++++++++++ tests/utils/misc/index.js | 15 +++ tests/utils/s3/index.js | 19 ++- 23 files changed, 1225 insertions(+), 19 deletions(-) create mode 100644 lib/plugins/aws/customResources/index.js create mode 100644 lib/plugins/aws/customResources/index.test.js create mode 100644 lib/plugins/aws/customResources/resources/README.md create mode 100644 lib/plugins/aws/customResources/resources/s3/handler.js create mode 100644 lib/plugins/aws/customResources/resources/s3/lib/bucket.js create mode 100644 lib/plugins/aws/customResources/resources/s3/lib/permissions.js create mode 100644 lib/plugins/aws/customResources/resources/utils.js create mode 100644 lib/plugins/aws/customResources/resources/utils.test.js create mode 100644 lib/utils/fs/createZipFile.js create mode 100644 lib/utils/fs/createZipFile.test.js create mode 100644 tests/integration-all/s3/service/core.js create mode 100644 tests/integration-all/s3/service/serverless.yml create mode 100644 tests/integration-all/s3/service/utils.js create mode 100644 tests/integration-all/s3/tests.js diff --git a/docs/providers/aws/events/s3.md b/docs/providers/aws/events/s3.md index d61638a80..76495866d 100644 --- a/docs/providers/aws/events/s3.md +++ b/docs/providers/aws/events/s3.md @@ -114,3 +114,23 @@ resources: Ref: AWS::AccountId SourceArn: 'arn:aws:s3:::my-custom-bucket-name' ``` + +## Using existing buckets + +Sometimes you might want to attach Lambda functions to existing S3 buckets. In that case you just need to set the `existing` event configuration property to `true`. All the other config parameters can also be used on existing buckets: + +**NOTE:** Using the `existing` config will add an additional Lambda function and IAM Role to your stack. The Lambda function backs-up the Custom S3 Resource which is used to support existing S3 buckets. + +```yaml +functions: + users: + handler: users.handler + events: + - s3: + bucket: legacy-photos + event: s3:ObjectCreated:* + rules: + - prefix: uploads/ + - suffix: .jpg + existing: true +``` diff --git a/lib/plugins/aws/customResources/index.js b/lib/plugins/aws/customResources/index.js new file mode 100644 index 000000000..5adccee57 --- /dev/null +++ b/lib/plugins/aws/customResources/index.js @@ -0,0 +1,110 @@ +'use strict'; + +const path = require('path'); +const createZipFile = require('../../../utils/fs/createZipFile'); + +function addCustomResourceToService(resourceName, iamRoleStatements) { + let FunctionName; + let Handler; + let customResourceFunctionLogicalId; + + const Statement = iamRoleStatements; + const customResourcesRoleLogicalId = this.provider.naming.getCustomResourcesRoleLogicalId(); + const srcDirPath = path.join(__dirname, 'resources'); + const destDirPath = path.join( + this.serverless.config.servicePath, + '.serverless', + this.provider.naming.getCustomResourcesArtifactDirectoryName() + ); + const zipFilePath = `${destDirPath}.zip`; + this.serverless.utils.writeFileDir(zipFilePath); + + if (resourceName === 's3') { + FunctionName = `${this.serverless.service.service}-${ + this.options.stage + }-${this.provider.naming.getCustomResourceS3HandlerFunctionName()}`; + Handler = 's3/handler.handler'; + customResourceFunctionLogicalId = this.provider.naming.getCustomResourceS3HandlerFunctionLogicalId(); + } + + return createZipFile(srcDirPath, zipFilePath).then(outputFilePath => { + let S3Bucket = { + Ref: this.provider.naming.getDeploymentBucketLogicalId(), + }; + if (this.serverless.service.package.deploymentBucket) { + S3Bucket = this.serverless.service.package.deploymentBucket; + } + const s3Folder = this.serverless.service.package.artifactDirectoryName; + const s3FileName = outputFilePath.split(path.sep).pop(); + const S3Key = `${s3Folder}/${s3FileName}`; + + const customResourceRole = { + [customResourcesRoleLogicalId]: { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { + Service: ['lambda.amazonaws.com'], + }, + Action: ['sts:AssumeRole'], + }, + ], + }, + Policies: [ + { + PolicyName: { + 'Fn::Join': [ + '-', + [ + this.provider.getStage(), + this.provider.serverless.service.service, + 'custom-resources-lambda', + ], + ], + }, + PolicyDocument: { + Version: '2012-10-17', + Statement, + }, + }, + ], + }, + }, + }; + + const customResourceFunction = { + [customResourceFunctionLogicalId]: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket, + S3Key, + }, + FunctionName, + Handler, + MemorySize: 1024, + Role: { + 'Fn::GetAtt': [customResourcesRoleLogicalId, 'Arn'], + }, + Runtime: 'nodejs10.x', + Timeout: 6, + }, + DependsOn: [customResourcesRoleLogicalId], + }, + }; + + Object.assign( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + customResourceFunction, + customResourceRole + ); + }); +} + +module.exports = { + addCustomResourceToService, +}; diff --git a/lib/plugins/aws/customResources/index.test.js b/lib/plugins/aws/customResources/index.test.js new file mode 100644 index 000000000..e3732939f --- /dev/null +++ b/lib/plugins/aws/customResources/index.test.js @@ -0,0 +1,117 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const chai = require('chai'); +const AwsProvider = require('../provider/awsProvider'); +const Serverless = require('../../../Serverless'); +const { createTmpDir } = require('../../../../tests/utils/fs'); +const { addCustomResourceToService } = require('./index.js'); + +const expect = chai.expect; +chai.use(require('chai-as-promised')); + +describe('#addCustomResourceToService()', () => { + let tmpDirPath; + let serverless; + let provider; + let context; + const iamRoleStatements = [ + { + Effect: 'Allow', + Resource: 'arn:aws:lambda:*:*:function:custom-resource-func', + Action: ['lambda:AddPermission', 'lambda:RemovePermission'], + }, + ]; + + beforeEach(() => { + const options = { + stage: 'dev', + region: 'us-east-1', + }; + tmpDirPath = createTmpDir(); + serverless = new Serverless(); + provider = new AwsProvider(serverless, options); + serverless.setProvider('aws', provider); + serverless.service.service = 'some-service'; + serverless.service.provider.compiledCloudFormationTemplate = { + Resources: {}, + }; + serverless.config.servicePath = tmpDirPath; + serverless.service.package.artifactDirectoryName = 'artifact-dir-name'; + context = { + serverless, + provider, + options, + }; + }); + + describe('when using the custom S3 resouce', () => { + it('should add the custom resource to the service', () => { + return expect( + addCustomResourceToService.call(context, 's3', iamRoleStatements) + ).to.be.fulfilled.then(() => { + const { Resources } = serverless.service.provider.compiledCloudFormationTemplate; + const customResourcesZipFilePath = path.join( + tmpDirPath, + '.serverless', + 'custom-resources.zip' + ); + + expect(fs.existsSync(customResourcesZipFilePath)).to.equal(true); + expect(Resources).to.deep.equal({ + CustomDashresourceDashexistingDashs3LambdaFunction: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + S3Key: 'artifact-dir-name/custom-resources.zip', + }, + FunctionName: 'some-service-dev-custom-resource-existing-s3', + Handler: 's3/handler.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleCustomResourcesLambdaExecution', 'Arn'] }, + Runtime: 'nodejs10.x', + Timeout: 6, + }, + DependsOn: ['IamRoleCustomResourcesLambdaExecution'], + }, + IamRoleCustomResourcesLambdaExecution: { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: ['sts:AssumeRole'], + Effect: 'Allow', + Principal: { + Service: ['lambda.amazonaws.com'], + }, + }, + ], + Version: '2012-10-17', + }, + Policies: [ + { + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Resource: 'arn:aws:lambda:*:*:function:custom-resource-func', + Action: ['lambda:AddPermission', 'lambda:RemovePermission'], + }, + ], + Version: '2012-10-17', + }, + PolicyName: { + 'Fn::Join': ['-', ['dev', 'some-service', 'custom-resources-lambda']], + }, + }, + ], + }, + }, + }); + }); + }); + }); +}); diff --git a/lib/plugins/aws/customResources/resources/README.md b/lib/plugins/aws/customResources/resources/README.md new file mode 100644 index 000000000..bde6a0fc3 --- /dev/null +++ b/lib/plugins/aws/customResources/resources/README.md @@ -0,0 +1,3 @@ +# Serverless Custom CloudFormation Resources + +This directory contains the Lambda functions for the Serverless Custom CloudFormation Resources. diff --git a/lib/plugins/aws/customResources/resources/s3/handler.js b/lib/plugins/aws/customResources/resources/s3/handler.js new file mode 100644 index 000000000..a54318145 --- /dev/null +++ b/lib/plugins/aws/customResources/resources/s3/handler.js @@ -0,0 +1,73 @@ +'use strict'; + +const { addPermission, removePermission } = require('./lib/permissions'); +const { updateConfiguration, removeConfiguration } = require('./lib/bucket'); +const { getEnvironment, getLambdaArn, handlerWrapper } = require('../utils'); + +function handler(event, context) { + if (event.RequestType === 'Create') { + return create(event, context); + } else if (event.RequestType === 'Update') { + return update(event, context); + } else if (event.RequestType === 'Delete') { + return remove(event, context); + } + throw new Error(`Unhandled RequestType ${event.RequestType}`); +} + +function create(event, context) { + const { Region, AccountId } = getEnvironment(context); + const { FunctionName, BucketName, BucketConfig } = event.ResourceProperties; + + const lambdaArn = getLambdaArn(Region, AccountId, FunctionName); + + return addPermission({ + functionName: FunctionName, + bucketName: BucketName, + region: Region, + }).then(() => + updateConfiguration({ + lambdaArn, + region: Region, + functionName: FunctionName, + bucketName: BucketName, + bucketConfig: BucketConfig, + }) + ); +} + +function update(event, context) { + const { Region, AccountId } = getEnvironment(context); + const { FunctionName, BucketName, BucketConfig } = event.ResourceProperties; + + const lambdaArn = getLambdaArn(Region, AccountId, FunctionName); + + return updateConfiguration({ + lambdaArn, + region: Region, + functionName: FunctionName, + bucketName: BucketName, + bucketConfig: BucketConfig, + }); +} + +function remove(event, context) { + const { Region } = getEnvironment(context); + const { FunctionName, BucketName } = event.ResourceProperties; + + return removePermission({ + functionName: FunctionName, + bucketName: BucketName, + region: Region, + }).then(() => + removeConfiguration({ + region: Region, + functionName: FunctionName, + bucketName: BucketName, + }) + ); +} + +module.exports = { + handler: handlerWrapper(handler, 'CustomResouceExistingS3'), +}; diff --git a/lib/plugins/aws/customResources/resources/s3/lib/bucket.js b/lib/plugins/aws/customResources/resources/s3/lib/bucket.js new file mode 100644 index 000000000..0b07588bf --- /dev/null +++ b/lib/plugins/aws/customResources/resources/s3/lib/bucket.js @@ -0,0 +1,101 @@ +'use strict'; + +const crypto = require('crypto'); +const AWS = require('aws-sdk'); + +function generateId(functionName, bucketConfig) { + const md5 = crypto + .createHash('md5') + .update(JSON.stringify(bucketConfig)) + .digest('hex'); + return `${functionName}-${md5}`; +} + +function createFilter(config) { + const rules = config.Rules; + if (rules && rules.length) { + const FilterRules = rules.map(rule => { + const key = Object.keys(rule)[0]; + const Name = key.toLowerCase(); + const Value = rule[key].toLowerCase(); + return { + Name, + Value, + }; + }); + return { + Key: { + FilterRules, + }, + }; + } + return undefined; +} + +function getConfiguration(config) { + const { bucketName, region } = config; + const s3 = new AWS.S3({ region }); + const Bucket = bucketName; + const payload = { + Bucket, + }; + return s3 + .getBucketNotificationConfiguration(payload) + .promise() + .then(data => data); +} + +function updateConfiguration(config) { + const { lambdaArn, functionName, bucketName, bucketConfig, region } = config; + const s3 = new AWS.S3({ region }); + const Bucket = bucketName; + + return getConfiguration(config).then(NotificationConfiguration => { + // remove configurations for this specific function + NotificationConfiguration.LambdaFunctionConfigurations = NotificationConfiguration.LambdaFunctionConfigurations.filter( + conf => !conf.Id.startsWith(functionName) + ); + + // add the event to the existing NotificationConfiguration + const Events = [bucketConfig.Event]; + const LambdaFunctionArn = lambdaArn; + const Id = generateId(functionName, bucketConfig); + const Filter = createFilter(bucketConfig); + NotificationConfiguration.LambdaFunctionConfigurations.push({ + Events, + LambdaFunctionArn, + Filter, + Id, + }); + + const payload = { + Bucket, + NotificationConfiguration, + }; + return s3.putBucketNotificationConfiguration(payload).promise(); + }); +} + +function removeConfiguration(config) { + const { functionName, bucketName, region } = config; + const s3 = new AWS.S3({ region }); + const Bucket = bucketName; + + return getConfiguration(config).then(NotificationConfiguration => { + // remove configurations for this specific function + NotificationConfiguration.LambdaFunctionConfigurations = NotificationConfiguration.LambdaFunctionConfigurations.filter( + conf => !conf.Id.startsWith(functionName) + ); + + const payload = { + Bucket, + NotificationConfiguration, + }; + return s3.putBucketNotificationConfiguration(payload).promise(); + }); +} + +module.exports = { + updateConfiguration, + removeConfiguration, +}; diff --git a/lib/plugins/aws/customResources/resources/s3/lib/permissions.js b/lib/plugins/aws/customResources/resources/s3/lib/permissions.js new file mode 100644 index 000000000..d26a215d0 --- /dev/null +++ b/lib/plugins/aws/customResources/resources/s3/lib/permissions.js @@ -0,0 +1,42 @@ +'use strict'; + +const AWS = require('aws-sdk'); + +function getStatementId(functionName, bucketName) { + const normalizedBucketName = bucketName.replace(/[.:*]/g, ''); + const id = `${functionName}-${normalizedBucketName}`; + if (id.length < 100) { + return id; + } + return id.substring(0, 100); +} + +function addPermission(config) { + const { functionName, bucketName, region } = config; + const lambda = new AWS.Lambda({ region }); + const partition = region && /^cn-/.test(region) ? 'aws-cn' : 'aws'; + const payload = { + Action: 'lambda:InvokeFunction', + FunctionName: functionName, + Principal: 's3.amazonaws.com', + StatementId: getStatementId(functionName, bucketName), + SourceArn: `arn:${partition}:s3:::${bucketName}`, + }; + return lambda.addPermission(payload).promise(); +} + +function removePermission(config) { + const { functionName, bucketName, region } = config; + const lambda = new AWS.Lambda({ region }); + const payload = { + FunctionName: functionName, + StatementId: getStatementId(functionName, bucketName), + }; + return lambda.removePermission(payload).promise(); +} + +module.exports = { + getStatementId, + addPermission, + removePermission, +}; diff --git a/lib/plugins/aws/customResources/resources/utils.js b/lib/plugins/aws/customResources/resources/utils.js new file mode 100644 index 000000000..fe497b0b1 --- /dev/null +++ b/lib/plugins/aws/customResources/resources/utils.js @@ -0,0 +1,93 @@ +'use strict'; + +const https = require('https'); +const url = require('url'); + +const logger = console; + +function response(event, context, status, data = {}, err) { + const reason = err ? err.message : ''; + const { StackId, RequestId, LogicalResourceId, PhysicalResourceId, ResponseURL } = event; + + const body = JSON.stringify({ + StackId, + RequestId, + LogicalResourceId, + PhysicalResourceId, + Status: status, + Reason: reason && `${reason} See details in CloudWatch Log: ${context.logStreamName}`, + Data: data, + }); + + logger.log(body); + + const parsedUrl = url.parse(ResponseURL); + const options = { + hostname: parsedUrl.hostname, + port: 443, + path: parsedUrl.path, + method: 'PUT', + headers: { + 'Content-Type': '', + 'Content-Length': body.length, + }, + }; + + return new Promise((resolve, reject) => { + const request = https.request(options, resp => { + logger.log(`STATUS: ${resp.statusCode}`); + logger.log(`HEADERS: ${JSON.stringify(resp.headers)}`); + return resolve(data); + }); + + request.on('error', error => { + logger.log(`sendResponse Error:\n${error}`); + return reject(error); + }); + + request.on('end', () => { + logger.log('end'); + return resolve(); + }); + + request.write(body); + request.end(); + }); +} + +function getLambdaArn(region, accountId, functionName) { + return `arn:aws:lambda:${region}:${accountId}:function:${functionName}`; +} + +function getEnvironment(context) { + const arn = context.invokedFunctionArn.match( + /^arn:aws.*:lambda:(\w+-\w+-\d+):(\d+):function:(.*)$/ + ); + return { + LambdaArn: arn[0], + Region: arn[1], + AccountId: arn[2], + LambdaName: arn[3], + }; +} + +function handlerWrapper(handler, PhysicalResourceId) { + return (event, context, callback) => { + // extend the `event` object to include the PhysicalResourceId + event = Object.assign({}, event, { PhysicalResourceId }); + return Promise.resolve(handler(event, context, callback)) + .then( + result => response(event, context, 'SUCCESS', result), + error => response(event, context, 'FAILED', {}, error) + ) + .then(result => callback(null, result), callback); + }; +} + +module.exports = { + logger, + response, + getEnvironment, + getLambdaArn, + handlerWrapper, +}; diff --git a/lib/plugins/aws/customResources/resources/utils.test.js b/lib/plugins/aws/customResources/resources/utils.test.js new file mode 100644 index 000000000..3a7409702 --- /dev/null +++ b/lib/plugins/aws/customResources/resources/utils.test.js @@ -0,0 +1,31 @@ +'use strict'; + +const { expect } = require('chai'); +const { getLambdaArn, getEnvironment } = require('./utils'); + +describe('#getLambdaArn()', () => { + it('should return the Lambda arn', () => { + const region = 'us-east-1'; + const accountId = '123456'; + const functionName = 'some-function'; + const arn = getLambdaArn(region, accountId, functionName); + + expect(arn).to.equal('arn:aws:lambda:us-east-1:123456:function:some-function'); + }); +}); + +describe('#getEnvironment()', () => { + it('should return an object with information about the execution environment', () => { + const context = { + invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456:function:some-function', + }; + const env = getEnvironment(context); + + expect(env).to.deep.equal({ + LambdaArn: 'arn:aws:lambda:us-east-1:123456:function:some-function', + Region: 'us-east-1', + AccountId: '123456', + LambdaName: 'some-function', + }); + }); +}); diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.js index 483671639..57484fc3f 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.js @@ -14,7 +14,8 @@ module.exports = { uploadArtifacts() { return BbPromise.bind(this) .then(this.uploadCloudFormationFile) - .then(this.uploadFunctionsAndLayers); + .then(this.uploadFunctionsAndLayers) + .then(this.uploadCustomResources); }, uploadCloudFormationFile() { @@ -128,6 +129,21 @@ module.exports = { { concurrency: NUM_CONCURRENT_UPLOADS } ); }, + + uploadCustomResources() { + const artifactFilePath = path.join( + this.serverless.config.servicePath, + '.serverless', + `${this.provider.naming.getCustomResourcesArtifactDirectoryName()}.zip` + ); + + if (this.serverless.utils.fileExistsSync(artifactFilePath)) { + this.serverless.cli.log('Uploading custom CloudFormation resources...'); + return this.uploadZipFile(artifactFilePath); + } + + return BbPromise.resolve(); + }, }; function setServersideEncryptionOptions(putParams, deploymentBucketOptions) { diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js index 26699dc44..3bfe4e94d 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js @@ -4,6 +4,7 @@ const sinon = require('sinon'); const fs = require('fs'); +const fse = require('fs-extra'); const path = require('path'); const chai = require('chai'); const proxyquire = require('proxyquire'); @@ -11,7 +12,7 @@ const normalizeFiles = require('../../lib/normalizeFiles'); const AwsProvider = require('../../provider/awsProvider'); const AwsDeploy = require('../index'); const Serverless = require('../../../../Serverless'); -const { getTmpDirPath } = require('../../../../../tests/utils/fs'); +const { getTmpDirPath, createTmpDir } = require('../../../../../tests/utils/fs'); chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); @@ -332,4 +333,53 @@ describe('uploadArtifacts', () => { }); }); }); + + describe('#uploadCustomResources()', () => { + let uploadStub; + let serviceDirPath; + + beforeEach(() => { + uploadStub = sinon.stub(awsDeploy.provider, 'request').resolves(); + serviceDirPath = createTmpDir(); + serverless.config.servicePath = serviceDirPath; + }); + + afterEach(() => { + uploadStub.restore(); + }); + + it('should not attempt to upload a custom resources if the artifact does not exist', () => { + return expect(awsDeploy.uploadCustomResources()).to.eventually.be.fulfilled.then(() => { + expect(uploadStub).not.to.be.calledOnce; + }); + }); + + it('should upload the custom resources .zip file to the S3 bucket', () => { + const customResourcesFilePath = path.join( + serviceDirPath, + '.serverless', + 'custom-resources.zip' + ); + fse.ensureFileSync(customResourcesFilePath); + + cryptoStub + .createHash() + .update() + .digest.onCall(0) + .returns('local-hash-zip-file'); + + return expect(awsDeploy.uploadCustomResources()).to.eventually.be.fulfilled.then(() => { + expect(uploadStub).to.have.been.calledOnce; + expect(uploadStub).to.have.been.calledWithExactly('S3', 'upload', { + Bucket: awsDeploy.bucketName, + Key: `${awsDeploy.serverless.service.package.artifactDirectoryName}/custom-resources.zip`, + Body: sinon.match.object.and(sinon.match.has('path', customResourcesFilePath)), + ContentType: 'application/zip', + Metadata: { + filesha256: 'local-hash-zip-file', + }, + }); + }); + }); + }); }); diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index 3ef221a48..fb863e60c 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -451,4 +451,24 @@ module.exports = { getLambdaAlbPermissionLogicalId(functionName) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlb`; }, + + // Custom Resources + getCustomResourcesArtifactDirectoryName() { + return 'custom-resources'; + }, + getCustomResourcesRoleLogicalId() { + return 'IamRoleCustomResourcesLambdaExecution'; + }, + // S3 + getCustomResourceS3HandlerFunctionName() { + return 'custom-resource-existing-s3'; + }, + getCustomResourceS3HandlerFunctionLogicalId() { + return this.getLambdaLogicalId( + `${this.getNormalizedFunctionName(this.getCustomResourceS3HandlerFunctionName())}` + ); + }, + getCustomResourceS3ResourceLogicalId(functionName, idx) { + return `${this.getNormalizedFunctionName(functionName)}CustomS3${idx}`; + }, }; diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 87507e43e..bbc9699e2 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -721,4 +721,44 @@ describe('#naming()', () => { ); }); }); + + describe('#getCustomResourcesArtifactDirectoryName()', () => { + it('should return the custom resources artifact directory name', () => { + expect(sdk.naming.getCustomResourcesArtifactDirectoryName()).to.equal('custom-resources'); + }); + }); + + describe('#getCustomResourcesRoleLogicalId()', () => { + it('should return the custom resources role logical id', () => { + expect(sdk.naming.getCustomResourcesRoleLogicalId()).to.equal( + 'IamRoleCustomResourcesLambdaExecution' + ); + }); + }); + + describe('#getCustomResourceS3HandlerFunctionName()', () => { + it('should return the nane of the S3 custom resouce handler function', () => { + expect(sdk.naming.getCustomResourceS3HandlerFunctionName()).to.equal( + 'custom-resource-existing-s3' + ); + }); + }); + + describe('#getCustomResourceS3HandlerFunctionLogicalId()', () => { + it('should return the logical id of the S3 custom resouce handler function', () => { + expect(sdk.naming.getCustomResourceS3HandlerFunctionLogicalId()).to.equal( + 'CustomDashresourceDashexistingDashs3LambdaFunction' + ); + }); + }); + + describe('#getCustomResourceS3ResourceLogicalId()', () => { + it('should return the logical id of the S3 custom resouce', () => { + const functionName = 'my-function'; + const index = 1; + expect(sdk.naming.getCustomResourceS3ResourceLogicalId(functionName, index)).to.equal( + 'MyDashfunctionCustomS31' + ); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/s3/index.js b/lib/plugins/aws/package/compile/events/s3/index.js index 1fc7281e3..4bcf943f5 100644 --- a/lib/plugins/aws/package/compile/events/s3/index.js +++ b/lib/plugins/aws/package/compile/events/s3/index.js @@ -1,18 +1,25 @@ 'use strict'; const _ = require('lodash'); +const BbPromise = require('bluebird'); +const { addCustomResourceToService } = require('../../../../customResources'); class AwsCompileS3Events { - constructor(serverless) { + constructor(serverless, options) { this.serverless = serverless; + this.options = options; this.provider = this.serverless.getProvider('aws'); this.hooks = { - 'package:compileEvents': this.compileS3Events.bind(this), + 'package:compileEvents': () => { + return BbPromise.bind(this) + .then(this.newS3Buckets) + .then(this.existingS3Buckets); + }, }; } - compileS3Events() { + newS3Buckets() { const bucketsLambdaConfigurations = {}; const s3EnabledFunctions = []; this.serverless.service.getAllFunctions().forEach(functionName => { @@ -21,6 +28,9 @@ class AwsCompileS3Events { if (functionObj.events) { functionObj.events.forEach(event => { if (event.s3) { + // return immediately if it's an existing S3 event since we treat them differently + if (event.s3.existing) return null; + let bucketName; let notificationEvent = 's3:ObjectCreated:*'; let filter = {}; @@ -107,6 +117,8 @@ class AwsCompileS3Events { const s3EnabledFunction = { functionName, bucketName }; s3EnabledFunctions.push(s3EnabledFunction); } + + return null; }); } }); @@ -183,6 +195,87 @@ class AwsCompileS3Events { ); }); } + + existingS3Buckets() { + const { service } = this.serverless; + const { provider } = service; + const { compiledCloudFormationTemplate } = provider; + const iamRoleStatements = []; + + service.getAllFunctions().forEach(functionName => { + let funcUsesExistingS3Bucket = false; + const functionObj = service.getFunction(functionName); + const FunctionName = functionObj.name; + + if (functionObj.events) { + functionObj.events.forEach((event, idx) => { + if (event.s3 && _.isObject(event.s3) && event.s3.existing) { + let rules = null; + idx = idx += 1; + const bucket = event.s3.bucket; + const notificationEvent = event.s3.event || 's3:ObjectCreated:*'; + funcUsesExistingS3Bucket = true; + + rules = _.map(event.s3.rules, rule => { + const key = Object.keys(rule)[0]; + const value = rule[key]; + return { + [_.startCase(key)]: value, + }; + }); + + const eventFunctionLogicalId = this.provider.naming.getLambdaLogicalId(functionName); + const customResourceFunctionLogicalId = this.provider.naming.getCustomResourceS3HandlerFunctionLogicalId(); + const customS3ResourceLogicalId = this.provider.naming.getCustomResourceS3ResourceLogicalId( + functionName, + idx + ); + + const customS3Resource = { + [customS3ResourceLogicalId]: { + Type: 'Custom::S3', + Version: 1.0, + DependsOn: [eventFunctionLogicalId, customResourceFunctionLogicalId], + Properties: { + ServiceToken: { + 'Fn::GetAtt': [customResourceFunctionLogicalId, 'Arn'], + }, + FunctionName, + BucketName: bucket, + BucketConfig: { + Event: notificationEvent, + Rules: rules, + }, + }, + }, + }; + + _.merge(compiledCloudFormationTemplate.Resources, customS3Resource); + + iamRoleStatements.push({ + Effect: 'Allow', + Resource: `arn:aws:s3:::${bucket}`, + Action: ['s3:PutBucketNotification', 's3:GetBucketNotification'], + }); + } + }); + } + + if (funcUsesExistingS3Bucket) { + iamRoleStatements.push({ + Effect: 'Allow', + Resource: `arn:aws:lambda:*:*:function:${FunctionName}`, + Action: ['lambda:AddPermission', 'lambda:RemovePermission'], + }); + } + }); + + if (iamRoleStatements.length) { + return addCustomResourceToService.call(this, 's3', iamRoleStatements); + } + + return null; + } } module.exports = AwsCompileS3Events; diff --git a/lib/plugins/aws/package/compile/events/s3/index.test.js b/lib/plugins/aws/package/compile/events/s3/index.test.js index f81990dd6..496d94cb0 100644 --- a/lib/plugins/aws/package/compile/events/s3/index.test.js +++ b/lib/plugins/aws/package/compile/events/s3/index.test.js @@ -1,9 +1,16 @@ 'use strict'; -const expect = require('chai').expect; +/* eslint-disable no-unused-expressions */ + +const sinon = require('sinon'); +const chai = require('chai'); const AwsProvider = require('../../../../provider/awsProvider'); const AwsCompileS3Events = require('./index'); const Serverless = require('../../../../../../Serverless'); +const customResources = require('../../../../customResources'); + +const { expect } = chai; +chai.use(require('sinon-chai')); describe('AwsCompileS3Events', () => { let serverless; @@ -22,7 +29,7 @@ describe('AwsCompileS3Events', () => { expect(awsCompileS3Events.provider).to.be.instanceof(AwsProvider)); }); - describe('#compileS3Events()', () => { + describe('#newS3Buckets()', () => { it('should throw an error if s3 event type is not a string or an object', () => { awsCompileS3Events.serverless.service.functions = { first: { @@ -34,7 +41,7 @@ describe('AwsCompileS3Events', () => { }, }; - expect(() => awsCompileS3Events.compileS3Events()).to.throw(Error); + expect(() => awsCompileS3Events.newS3Buckets()).to.throw(Error); }); it('should throw an error if the "bucket" property is not given', () => { @@ -50,7 +57,7 @@ describe('AwsCompileS3Events', () => { }, }; - expect(() => awsCompileS3Events.compileS3Events()).to.throw(Error); + expect(() => awsCompileS3Events.newS3Buckets()).to.throw(Error); }); it('should throw an error if the "rules" property is not an array', () => { @@ -68,7 +75,7 @@ describe('AwsCompileS3Events', () => { }, }; - expect(() => awsCompileS3Events.compileS3Events()).to.throw(Error); + expect(() => awsCompileS3Events.newS3Buckets()).to.throw(Error); }); it('should throw an error if the "rules" property is invalid', () => { @@ -86,7 +93,7 @@ describe('AwsCompileS3Events', () => { }, }; - expect(() => awsCompileS3Events.compileS3Events()).to.throw(Error); + expect(() => awsCompileS3Events.newS3Buckets()).to.throw(Error); }); it('should create corresponding resources when S3 events are given', () => { @@ -107,7 +114,7 @@ describe('AwsCompileS3Events', () => { }, }; - awsCompileS3Events.compileS3Events(); + awsCompileS3Events.newS3Buckets(); expect( awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources @@ -152,7 +159,7 @@ describe('AwsCompileS3Events', () => { }, }; - awsCompileS3Events.compileS3Events(); + awsCompileS3Events.newS3Buckets(); expect( awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources @@ -185,7 +192,7 @@ describe('AwsCompileS3Events', () => { }, }; - awsCompileS3Events.compileS3Events(); + awsCompileS3Events.newS3Buckets(); expect( awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources @@ -220,11 +227,106 @@ describe('AwsCompileS3Events', () => { }, }; - awsCompileS3Events.compileS3Events(); + awsCompileS3Events.newS3Buckets(); expect( awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate.Resources ).to.deep.equal({}); }); }); + + describe('#existingS3Buckets()', () => { + let addCustomResourceToServiceStub; + + beforeEach(() => { + addCustomResourceToServiceStub = sinon + .stub(customResources.addCustomResourceToService, 'call') + .resolves(); + }); + + afterEach(() => { + customResources.addCustomResourceToService.call.restore(); + }); + + it('should create the necessary resources for the most minimal configuration', () => { + awsCompileS3Events.serverless.service.functions = { + first: { + name: 'first', + events: [ + { + s3: { + bucket: 'existing-s3-bucket', + existing: true, + }, + }, + ], + }, + }; + + awsCompileS3Events.existingS3Buckets(); + + const { + Resources, + } = awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate; + + expect(addCustomResourceToServiceStub).to.have.been.calledOnce; + expect(addCustomResourceToServiceStub.args[0][1]).to.equal('s3'); + expect(Resources.FirstCustomS31).to.deep.equal({ + Type: 'Custom::S3', + Version: 1, + DependsOn: ['FirstLambdaFunction', 'CustomDashresourceDashexistingDashs3LambdaFunction'], + Properties: { + ServiceToken: { + 'Fn::GetAtt': ['CustomDashresourceDashexistingDashs3LambdaFunction', 'Arn'], + }, + FunctionName: 'first', + BucketName: 'existing-s3-bucket', + BucketConfig: { Event: 's3:ObjectCreated:*', Rules: [] }, + }, + }); + }); + + it('should create the necessary resources for a service using different config parameters', () => { + awsCompileS3Events.serverless.service.functions = { + first: { + name: 'second', + events: [ + { + s3: { + bucket: 'existing-s3-bucket', + event: 's3:ObjectCreated:Put', + rules: [{ prefix: 'uploads' }, { suffix: '.jpg' }], + existing: true, + }, + }, + ], + }, + }; + + awsCompileS3Events.existingS3Buckets(); + + const { + Resources, + } = awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate; + + expect(addCustomResourceToServiceStub).to.have.been.calledOnce; + expect(addCustomResourceToServiceStub.args[0][1]).to.equal('s3'); + expect(Resources.FirstCustomS31).to.deep.equal({ + Type: 'Custom::S3', + Version: 1, + DependsOn: ['FirstLambdaFunction', 'CustomDashresourceDashexistingDashs3LambdaFunction'], + Properties: { + ServiceToken: { + 'Fn::GetAtt': ['CustomDashresourceDashexistingDashs3LambdaFunction', 'Arn'], + }, + FunctionName: 'second', + BucketName: 'existing-s3-bucket', + BucketConfig: { + Event: 's3:ObjectCreated:Put', + Rules: [{ Prefix: 'uploads' }, { Suffix: '.jpg' }], + }, + }, + }); + }); + }); }); diff --git a/lib/utils/fs/createZipFile.js b/lib/utils/fs/createZipFile.js new file mode 100644 index 000000000..5207313ec --- /dev/null +++ b/lib/utils/fs/createZipFile.js @@ -0,0 +1,39 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const archiver = require('archiver'); +const BbPromise = require('bluebird'); +const walkDirSync = require('../fs/walkDirSync'); + +function createZipFile(srcDirPath, outputFilePath) { + const files = walkDirSync(srcDirPath).map(file => ({ + input: file, + output: file.replace(path.join(srcDirPath, path.sep), ''), + })); + + return new BbPromise((resolve, reject) => { + const output = fs.createWriteStream(outputFilePath); + const archive = archiver('zip', { + zlib: { level: 9 }, + }); + + output.on('open', () => { + archive.pipe(output); + + files.forEach(file => { + // TODO: update since this is REALLY slow + if (fs.lstatSync(file.input).isFile()) { + archive.append(fs.createReadStream(file.input), { name: file.output }); + } + }); + + archive.finalize(); + }); + + archive.on('error', err => reject(err)); + output.on('close', () => resolve(outputFilePath)); + }); +} + +module.exports = createZipFile; diff --git a/lib/utils/fs/createZipFile.test.js b/lib/utils/fs/createZipFile.test.js new file mode 100644 index 000000000..1e609c6a9 --- /dev/null +++ b/lib/utils/fs/createZipFile.test.js @@ -0,0 +1,27 @@ +'use strict'; + +const path = require('path'); +const chai = require('chai'); +const createZipFile = require('./createZipFile'); +const { createTmpFile, listZipFiles } = require('../../../tests/utils/fs'); + +// Configure chai +chai.use(require('chai-as-promised')); +chai.use(require('sinon-chai')); +const expect = require('chai').expect; + +describe('#createZipFile()', () => { + it('should create a zip file with the source directory content', () => { + const toZipFilePath = createTmpFile('foo.json'); + const zipFilePath = createTmpFile('package.zip'); + + const srcDirPath = toZipFilePath + .split(path.sep) + .slice(0, -1) + .join(path.sep); + + return createZipFile(srcDirPath, zipFilePath) + .then(listZipFiles) + .then(files => expect(files).to.deep.equal(['foo.json'])); + }); +}); diff --git a/tests/integration-all/s3/service/core.js b/tests/integration-all/s3/service/core.js new file mode 100644 index 000000000..4a9a8f848 --- /dev/null +++ b/tests/integration-all/s3/service/core.js @@ -0,0 +1,44 @@ +'use strict'; + +const { log } = require('./utils'); + +function minimal(event, context, callback) { + const functionName = 'minimal'; + const response = { message: `Hello from S3! - (${functionName})`, event }; + const message = [ + event.Records[0].eventSource, + event.Records[0].eventName, + ' ', + response.message, + ].join(''); + log(functionName, message); + return callback(null, response); +} + +function extended(event, context, callback) { + const functionName = 'extended'; + const response = { message: `Hello from S3! - (${functionName})`, event }; + const message = [ + event.Records[0].eventSource, + event.Records[0].eventName, + ' ', + response.message, + ].join(''); + log(functionName, message); + return callback(null, response); +} + +function existing(event, context, callback) { + const functionName = 'existing'; + const response = { message: `Hello from S3! - (${functionName})`, event }; + const message = [ + event.Records[0].eventSource, + event.Records[0].eventName, + ' ', + response.message, + ].join(''); + log(functionName, message); + return callback(null, response); +} + +module.exports = { minimal, extended, existing }; diff --git a/tests/integration-all/s3/service/serverless.yml b/tests/integration-all/s3/service/serverless.yml new file mode 100644 index 000000000..22e450ac3 --- /dev/null +++ b/tests/integration-all/s3/service/serverless.yml @@ -0,0 +1,31 @@ +service: CHANGE_TO_UNIQUE_PER_RUN + +provider: + name: aws + runtime: nodejs10.x + versionFunctions: false + +functions: + minimal: + handler: core.minimal + events: + - s3: CHANGE_TO_UNIQUE_PER_RUN + extended: + handler: core.extended + events: + - s3: + bucket: CHANGE_TO_UNIQUE_PER_RUN + event: s3:ObjectRemoved:* + rules: + - prefix: photos/ + - suffix: .jpg + existing: + handler: core.existing + events: + - s3: + bucket: CHANGE_TO_UNIQUE_PER_RUN + event: s3:ObjectCreated:* + rules: + - prefix: files/ + - suffix: .txt + existing: true diff --git a/tests/integration-all/s3/service/utils.js b/tests/integration-all/s3/service/utils.js new file mode 100644 index 000000000..f4a99e00a --- /dev/null +++ b/tests/integration-all/s3/service/utils.js @@ -0,0 +1,22 @@ +'use strict'; + +const logger = console; + +function getMarkers(functionName) { + return { + start: `--- START ${functionName} ---`, + end: `--- END ${functionName} ---`, + }; +} + +function log(functionName, message) { + const markers = getMarkers(functionName); + logger.log(markers.start); + logger.log(message); + logger.log(markers.end); +} + +module.exports = { + getMarkers, + log, +}; diff --git a/tests/integration-all/s3/tests.js b/tests/integration-all/s3/tests.js new file mode 100644 index 000000000..8af13d8d3 --- /dev/null +++ b/tests/integration-all/s3/tests.js @@ -0,0 +1,106 @@ +'use strict'; + +const path = require('path'); +const { expect } = require('chai'); + +const { getTmpDirPath } = require('../../utils/fs'); +const { createBucket, createAndRemoveInBucket, deleteBucket } = require('../../utils/s3'); +const { + createTestService, + deployService, + removeService, + waitForFunctionLogs, +} = require('../../utils/misc'); +const { getMarkers } = require('./service/utils'); + +describe('AWS - S3 Integration Test', () => { + let serviceName; + let stackName; + let tmpDirPath; + let bucketMinimalSetup; + let bucketExtendedSetup; + let bucketExistingSetup; + const stage = 'dev'; + + beforeAll(() => { + tmpDirPath = getTmpDirPath(); + console.info(`Temporary path: ${tmpDirPath}`); + const serverlessConfig = createTestService(tmpDirPath, { + templateDir: path.join(__dirname, 'service'), + serverlessConfigHook: + // Ensure unique S3 bucket names for each test (to avoid collision among concurrent CI runs) + config => { + bucketMinimalSetup = `${config.service}-s3-minimal`; + bucketExtendedSetup = `${config.service}-s3-extended`; + bucketExistingSetup = `${config.service}-s3-existing`; + config.functions.minimal.events[0].s3 = bucketMinimalSetup; + config.functions.extended.events[0].s3.bucket = bucketExtendedSetup; + config.functions.existing.events[0].s3.bucket = bucketExistingSetup; + }, + }); + serviceName = serverlessConfig.service; + stackName = `${serviceName}-${stage}`; + // create an external S3 bucket + // NOTE: deployment can only be done once the S3 bucket is created + console.info(`Creating S3 bucket "${bucketExistingSetup}"...`); + return createBucket(bucketExistingSetup).then(() => { + console.info(`Deploying "${stackName}" service...`); + deployService(); + }); + }); + + afterAll(() => { + console.info('Removing service...'); + removeService(); + console.info(`Deleting S3 bucket "${bucketExistingSetup}"...`); + return deleteBucket(bucketExistingSetup); + }); + + describe('Minimal Setup', () => { + it('should invoke function when an object is created', () => { + const functionName = 'minimal'; + const markers = getMarkers(functionName); + const expectedMessage = `Hello from S3! - (${functionName})`; + + return createAndRemoveInBucket(bucketMinimalSetup) + .then(() => waitForFunctionLogs(functionName, markers.start, markers.end)) + .then(logs => { + expect(/aws:s3/g.test(logs)).to.equal(true); + expect(/ObjectCreated:Put/g.test(logs)).to.equal(true); + expect(logs.includes(expectedMessage)).to.equal(true); + }); + }); + }); + + describe('Extended Setup', () => { + it('should invoke function when an object is removed', () => { + const functionName = 'extended'; + const markers = getMarkers(functionName); + const expectedMessage = `Hello from S3! - (${functionName})`; + + return createAndRemoveInBucket(bucketExtendedSetup, { prefix: 'photos/', suffix: '.jpg' }) + .then(() => waitForFunctionLogs(functionName, markers.start, markers.end)) + .then(logs => { + expect(/aws:s3/g.test(logs)).to.equal(true); + expect(/ObjectRemoved:Delete/g.test(logs)).to.equal(true); + expect(logs.includes(expectedMessage)).to.equal(true); + }); + }); + }); + + describe('Existing Setup', () => { + it('should invoke function when an object is created', () => { + const functionName = 'existing'; + const markers = getMarkers(functionName); + const expectedMessage = `Hello from S3! - (${functionName})`; + + return createAndRemoveInBucket(bucketExistingSetup, { prefix: 'files/', suffix: '.txt' }) + .then(() => waitForFunctionLogs(functionName, markers.start, markers.end)) + .then(logs => { + expect(/aws:s3/g.test(logs)).to.equal(true); + expect(/ObjectCreated:Put/g.test(logs)).to.equal(true); + expect(logs.includes(expectedMessage)).to.equal(true); + }); + }); + }); +}); diff --git a/tests/utils/misc/index.js b/tests/utils/misc/index.js index 3d85384fa..0f787ee87 100644 --- a/tests/utils/misc/index.js +++ b/tests/utils/misc/index.js @@ -94,6 +94,20 @@ function getFunctionLogs(functionName) { return logsString; } +function waitForFunctionLogs(functionName, startMarker, endMarker) { + let logs; + return new BbPromise(resolve => { + const interval = setInterval(() => { + logs = getFunctionLogs(functionName); + if (logs && logs.includes(startMarker) && logs.includes(endMarker)) { + clearInterval(interval); + return resolve(logs); + } + return null; + }, 2000); + }); +} + function persistentRequest(...args) { const func = args[0]; const funcArgs = args.slice(1); @@ -159,6 +173,7 @@ module.exports = { replaceEnv, createTestService, getFunctionLogs, + waitForFunctionLogs, persistentRequest, skippedWithNotice, skipWithNotice, diff --git a/tests/utils/s3/index.js b/tests/utils/s3/index.js index 26d07e1e4..4768e50fd 100644 --- a/tests/utils/s3/index.js +++ b/tests/utils/s3/index.js @@ -3,12 +3,22 @@ const AWS = require('aws-sdk'); const { region, persistentRequest } = require('../misc'); -function createAndRemoveInBucket(bucketName) { +function createBucket(bucket) { const S3 = new AWS.S3({ region }); + return S3.createBucket({ Bucket: bucket }).promise(); +} + +function createAndRemoveInBucket(bucket, opts = {}) { + const S3 = new AWS.S3({ region }); + + const prefix = opts.prefix || ''; + const suffix = opts.suffix || ''; + const fileName = opts.fileName || 'object'; + const params = { - Bucket: bucketName, - Key: 'object', + Bucket: bucket, + Key: `${prefix}${fileName}${suffix}`, Body: 'hello world', }; @@ -16,7 +26,7 @@ function createAndRemoveInBucket(bucketName) { .promise() .then(() => { delete params.Body; - return S3.deleteObject(params); + return S3.deleteObject(params).promise(); }); } @@ -48,6 +58,7 @@ function deleteBucket(bucket) { } module.exports = { + createBucket: persistentRequest.bind(this, createBucket), createAndRemoveInBucket: persistentRequest.bind(this, createAndRemoveInBucket), emptyBucket: persistentRequest.bind(this, emptyBucket), deleteBucket: persistentRequest.bind(this, deleteBucket), From c14cece26a1eed49cc2f5c1b42f41a2fe82d01d0 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 10 Jul 2019 09:36:04 +0200 Subject: [PATCH 501/504] Update AWS setup doc link --- lib/plugins/interactiveCli/setupAws.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/interactiveCli/setupAws.js b/lib/plugins/interactiveCli/setupAws.js index c40333273..729d4a67c 100644 --- a/lib/plugins/interactiveCli/setupAws.js +++ b/lib/plugins/interactiveCli/setupAws.js @@ -76,7 +76,7 @@ module.exports = { if (!isConfirmed) return null; process.stdout.write( `\nGo here to learn how to create your AWS credentials:\n${chalk.bold( - 'https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration-creds' + 'https://github.com/serverless/enterprise/blob/master/docs/setup-aws-account.md' )}\nThen enter them here:\n\n` ); return awsProfileNameInput().then(profileName => From c9b149d1c1f382d49b1200f6c299289ddb435734 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 10 Jul 2019 10:22:07 +0200 Subject: [PATCH 502/504] Bump dependencies --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index cada2c599..a75848818 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "useStderr": true }, "devDependencies": { - "@serverless/eslint-config": "^1.0.0", + "@serverless/eslint-config": "^1.0.1", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "child-process-ext": "^2.0.0", @@ -113,10 +113,10 @@ "strip-ansi": "^5.2.0" }, "dependencies": { - "@serverless/enterprise-plugin": "^1.0.7", + "@serverless/enterprise-plugin": "^1.2.0", "archiver": "^1.3.0", "async": "^1.5.2", - "aws-sdk": "^2.482.0", + "aws-sdk": "^2.490.0", "bluebird": "^3.5.5", "cachedir": "^2.2.0", "chalk": "^2.4.2", @@ -127,16 +127,16 @@ "fs-extra": "^0.26.7", "get-stdin": "^5.0.1", "globby": "^6.1.0", - "graceful-fs": "^4.1.15", - "https-proxy-agent": "^2.2.1", + "graceful-fs": "^4.2.0", + "https-proxy-agent": "^2.2.2", "inquirer": "^6.4.1", "is-docker": "^1.1.0", "js-yaml": "^3.13.1", "json-cycle": "^1.3.0", "json-refs": "^2.1.7", - "jszip": "^3.2.1", + "jszip": "^3.2.2", "jwt-decode": "^2.2.0", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "minimist": "^1.2.0", "mkdirp": "^0.5.1", "moment": "^2.24.0", From 8f59ffc6699aa7772505b106ca4aa49f0563c2b7 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 10 Jul 2019 10:25:48 +0200 Subject: [PATCH 503/504] Release v1.47.0 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48dffddc5..4d221d9e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +# 1.47.0 (2019-07-10) + +- [Add Onica as a Consultant](https://github.com/serverless/serverless/pull/6300) +- [Correct typo](https://github.com/serverless/serverless/pull/6301) +- [Adapt new ESLint and Prettier configuration](https://github.com/serverless/serverless/pull/6284) +- [Ensure deploy is triggered in CI](https://github.com/serverless/serverless/pull/6306) +- [Remove jsbeautify configuration](https://github.com/serverless/serverless/pull/6309) +- [Improve PR template](https://github.com/serverless/serverless/pull/6308) +- [Allow users to specify API Gateway Access Log format](https://github.com/serverless/serverless/pull/6299) +- [Fix service.provider.region resolution](https://github.com/serverless/serverless/pull/6317) +- [Add null as a consultant](https://github.com/serverless/serverless/pull/6323) +- [Update very minor typo in credentials.md](https://github.com/serverless/serverless/pull/6321) +- [Expose non-errors in informative way](https://github.com/serverless/serverless/pull/6318) +- [Fix async leaks detection conditional](https://github.com/serverless/serverless/pull/6319) +- [Typo fix in AWS ALB event documentation](https://github.com/serverless/serverless/pull/6325) +- [Websockets: fix passing log group ARN](https://github.com/serverless/serverless/pull/6310) +- [Specify invoke local option in the guide](https://github.com/serverless/serverless/pull/6327) +- [Update Webpack version and usage of aws-nodejs-ecma-script template](https://github.com/serverless/serverless/pull/6324) +- [Make ALB event target group names unique](https://github.com/serverless/serverless/pull/6322) +- [Improve Travis CI conf](https://github.com/serverless/serverless/pull/6330) +- [Support for Github Entreprise in sls create](https://github.com/serverless/serverless/pull/6332) +- [Merge patch 1.46.1 release artifacts back into master](https://github.com/serverless/serverless/pull/6343) +- [Add support for existing S3 buckets](https://github.com/serverless/serverless/pull/6290) +- [PLAT-1202 - Interactive `serverless` create](https://github.com/serverless/serverless/pull/6294) +- [PLAT-1091 - message in `npm i` output about the `serverless` quickstart command](https://github.com/serverless/serverless/pull/6238) + +## Meta + +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.46.1...v1.47.0) + # 1.46.1 (2019-06-28) - [Fix service.provider.region resolution](https://github.com/serverless/serverless/pull/6317) diff --git a/package.json b/package.json index a75848818..6aee4e928 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.46.1", + "version": "1.47.0", "engines": { "node": ">=6.0" }, From 1d4ef3b2b6e1a8a6b2e6220a5389c904bb1f9c20 Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Wed, 10 Jul 2019 11:10:16 +0200 Subject: [PATCH 504/504] Add tests --- lib/classes/CLI.test.js | 29 ++++++++++++++++++++++++++++- lib/classes/Error.test.js | 18 ++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/classes/CLI.test.js b/lib/classes/CLI.test.js index ff927101b..29540d41d 100644 --- a/lib/classes/CLI.test.js +++ b/lib/classes/CLI.test.js @@ -1,6 +1,8 @@ 'use strict'; -const expect = require('chai').expect; +/* eslint-disable no-unused-expressions */ + +const chai = require('chai'); const sinon = require('sinon'); const CLI = require('../../lib/classes/CLI'); const os = require('os'); @@ -11,6 +13,9 @@ const stripAnsi = require('strip-ansi'); const Serverless = require('../../lib/Serverless'); const { getTmpDirPath } = require('../../tests/utils/fs'); +const { expect } = chai; +chai.use(require('sinon-chai')); + describe('CLI', () => { let cli; let serverless; @@ -427,6 +432,28 @@ describe('CLI', () => { }); }); + describe('#getVersionNumber()', () => { + let consoleLogSpy; + + beforeEach(() => { + cli = new CLI(serverless); + consoleLogSpy = sinon.spy(cli, 'consoleLog'); + }); + + afterEach(() => { + cli.consoleLog.restore(); + }); + + it('should log the version numbers', () => { + cli.getVersionNumber(); + + expect(consoleLogSpy).to.have.been.calledOnce; + expect(consoleLogSpy.args[0][0]).to.match(/[0-9]+\.[0-9]+\.[0-9]+ .+/); + expect(consoleLogSpy.args[0][0]).to.include('Enterprise Plugin'); + expect(consoleLogSpy.args[0][0]).to.include('Platform SDK'); + }); + }); + describe('#processInput()', () => { it('should only return the commands when only commands are given', () => { cli = new CLI(serverless, ['deploy', 'functions']); diff --git a/lib/classes/Error.test.js b/lib/classes/Error.test.js index 43b101394..8998ab695 100644 --- a/lib/classes/Error.test.js +++ b/lib/classes/Error.test.js @@ -91,6 +91,24 @@ describe('Error', () => { expect(message).to.have.string('a message'); }); + it('should log environment information', () => { + const error = new ServerlessError('a message', 'a status code'); + logError(error); + + const message = consoleLogSpy.args.join('\n'); + + expect(consoleLogSpy.called).to.equal(true); + + expect(message).to.have.string('Serverless Error'); + expect(message).to.have.string('a message'); + expect(message).to.have.string('Your Environment Information'); + expect(message).to.have.string('Operating System:'); + expect(message).to.have.string('Node Version:'); + expect(message).to.have.string('Serverless Version:'); + expect(message).to.have.string('Enterprise Plugin Version:'); + expect(message).to.have.string('Platform SDK Version:'); + }); + it('should capture the exception and exit the process with 1 if errorReporter is setup', () => { const error = new Error('an unexpected error'); logError(error);