├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── src ├── config.js └── normalizeFiles.js └── tests ├── base ├── base.test.js ├── handler.js ├── package-lock.json ├── package.json └── serverless.yml ├── custom-package-path ├── custom-package-path.test.js ├── handler.js ├── package-lock.json ├── package.json └── serverless.yml ├── disabled-for ├── disabled-for.test.js ├── handler.js ├── package-lock.json ├── package.json └── serverless.yml ├── empty-config ├── empty-config.test.js ├── handler.js ├── package-lock.json ├── package.json └── serverless.yml ├── helpers ├── clear-sls-cache.js ├── get-seed-state.js ├── index.js ├── npm-install.js ├── remove-node-modules.js ├── run-incremental-sls-cmds.js ├── run-sls-command.js ├── setup-tests.js └── setup-versions.js ├── lambda-edge ├── handler.js ├── lambda-edge.test.js ├── package-lock.json ├── package.json └── serverless.yml ├── layers ├── layer │ └── layer.txt ├── layers.test.js ├── package-lock.json ├── package.json └── serverless.yml ├── normalize-files └── normalizeFiles.test.js ├── package-individually ├── handler.js ├── package-individually.test.js ├── package-lock.json ├── package.json └── serverless.yml ├── referenced-config ├── config.yml ├── handler.js ├── package-lock.json ├── package.json ├── referenced-config.test.js └── serverless.yml ├── serverless-js ├── handler.js ├── package-lock.json ├── package.json ├── serverless-js.test.js └── serverless.js └── serverless-versions ├── edge.js ├── functions └── prebuilt.zip ├── handler.js ├── layer └── layer.txt ├── package.json ├── serverless-versions.template.js └── serverless.yml /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true, 5 | "node": true, 6 | "jest": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "globals": { 10 | "Atomics": "readonly", 11 | "SharedArrayBuffer": "readonly" 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2018, 15 | "sourceType": "module" 16 | }, 17 | "rules": {} 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | os: [ubuntu-latest, windows-latest] 20 | node-version: [10.x, 12.x, 14.x] 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v2 25 | 26 | - name: Configure AWS credentials 27 | uses: aws-actions/configure-aws-credentials@v1 28 | with: 29 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 30 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 31 | aws-region: us-east-1 32 | 33 | - name: Use Node.js ${{ matrix.node-version }} 34 | uses: actions/setup-node@v1 35 | with: 36 | node-version: ${{ matrix.node-version }} 37 | 38 | - name: Install dependencies 39 | run: npm ci 40 | 41 | - name: Lint 42 | run: npm run lint 43 | 44 | - name: Test 45 | run: npm run test 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # vim 40 | .*.sw* 41 | Session.vim 42 | 43 | # Serverless 44 | .webpack 45 | .serverless 46 | 47 | # env 48 | env.yml 49 | .env 50 | 51 | # Jetbrains IDEs 52 | .idea 53 | 54 | # Custom artifact path 55 | .package 56 | 57 | # Generated tests 58 | tests/serverless-versions-* 59 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .serverless 3 | .github 4 | tests 5 | *.test.js 6 | yarn.lock 7 | yarn-error.log 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to serverless-seed 2 | 3 | ## Releases 4 | 5 | To cut a release, start by merging the PRs that are going into this release. 6 | 7 | 1. Update version 8 | 9 | ```bash 10 | $ npm version patch 11 | ``` 12 | 13 | 2. Publish a release to npm 14 | 15 | ```bash 16 | $ npm publish 17 | ``` 18 | 19 | 3. Push git tag 20 | 21 | ```bash 22 | $ git push origin v0.3.14 23 | ``` 24 | 25 | 4. Publish GitHub release 26 | 27 | In the **Tag version** of the release draft, select the version that was just published to npm. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Anomaly Innovations 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serverless Seed Plugin [![npm](https://img.shields.io/npm/v/serverless-seed.svg)](https://www.npmjs.com/package/serverless-seed) [![Build Status](https://github.com/seed-run/serverless-seed/workflows/CI/badge.svg)](https://github.com/seed-run/serverless-seed/actions) 2 | 3 | A Serverless plugin for optimizing deploys in [Seed](https://seed.run). This plugin allows Seed to enable **Incremental Lambda Deploys** for your Serverless services. 4 | 5 | ## Getting Started 6 | 7 | Install the `serverless-seed` plugin using: 8 | 9 | ```bash 10 | $ npm install --save-dev serverless-seed 11 | ``` 12 | 13 | Then add it to your `serverless.yml`. 14 | 15 | ```yaml 16 | plugins: 17 | - serverless-seed 18 | ``` 19 | 20 | And enable **Incremental Lambda Deploys**. 21 | 22 | ```yaml 23 | custom: 24 | seed: 25 | incremental: 26 | enabled: true 27 | ``` 28 | 29 | That's it! Now deploy to [Seed](https://seed.run) and enjoy Incremental Lambda Deploys! You can [read more about this in detail over on our docs](https://seed.run/docs/incremental-lambda-deploys). 30 | 31 | To disable Incremental Lambda Deploys for certain stages. 32 | 33 | ```yaml 34 | custom: 35 | seed: 36 | incremental: 37 | enabled: true 38 | disabledFor: 39 | - prod 40 | - staging 41 | ``` 42 | 43 | ## Community 44 | 45 | [Follow us on Twitter](https://twitter.com/SEED_run) or [sign up for our newsletter](https://emailoctopus.com/lists/14c85084-324e-11ea-be00-06b4694bee2a/forms/subscribe). 46 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const _ = require("lodash"); 4 | const path = require("path"); 5 | const crypto = require("crypto"); 6 | const fs = require("fs").promises; 7 | const serverlessConfigUtils = require("serverless/lib/utils/getServerlessConfigFile"); 8 | 9 | const config = require("./src/config"); 10 | const normalizeFiles = require("./src/normalizeFiles"); 11 | 12 | function hash(str) { 13 | return crypto.createHash("sha256").update(str).digest("base64"); 14 | } 15 | 16 | function applyDefaultConfig(userConfig, defaultConfig) { 17 | userConfig = userConfig || { seed: {} }; 18 | 19 | return _.merge(defaultConfig, userConfig.seed); 20 | } 21 | 22 | class ServerlessSeedPlugin { 23 | constructor(serverless, options) { 24 | this.options = options; 25 | this.serverless = serverless; 26 | 27 | this.isLocal = process.env.__LOCAL__ === "true"; 28 | 29 | this.provider = this.serverless.getProvider("aws"); 30 | 31 | this.hooks = { 32 | "after:package:finalize": this.afterPackageFinalize.bind(this), 33 | }; 34 | } 35 | 36 | async afterPackageFinalize() { 37 | const stage = this.provider.getStage(); 38 | 39 | const pluginConfig = applyDefaultConfig( 40 | this.serverless.service.custom, 41 | config 42 | ); 43 | const incrementalConfig = pluginConfig.incremental; 44 | 45 | if ( 46 | incrementalConfig.enabled !== true || 47 | incrementalConfig.disabledFor.indexOf(stage) !== -1 48 | ) { 49 | return; 50 | } 51 | 52 | this.serverless.cli.log("Seed: Generating incremental deploy state..."); 53 | 54 | // Write to the state file to start with, so it can be used if there are any 55 | // unhandled exceptions 56 | await this.createStateFile(); 57 | 58 | let state, 59 | s3Key, 60 | region, 61 | layers, 62 | s3Bucket, 63 | functions, 64 | serverlessConfigString, 65 | cloudFormationTemplateString; 66 | 67 | try { 68 | region = this.provider.getRegion(); 69 | 70 | cloudFormationTemplateString = this.getCloudFormationString(); 71 | 72 | serverlessConfigString = await this.getServerlessConfigString(); 73 | 74 | s3Key = this.serverless.service.package.artifactDirectoryName; 75 | 76 | try { 77 | // This does a CloudFormation describeStackResource, hence the await 78 | s3Bucket = await this.provider.getServerlessDeploymentBucketName(); 79 | } catch (e) { 80 | // Absorb the error so we can test locally 81 | if (this.isLocal) { 82 | s3Bucket = null; 83 | } else { 84 | throw e; 85 | } 86 | } 87 | 88 | layers = this.createLayersList(); 89 | 90 | functions = this.createFunctionsList(); 91 | 92 | state = { 93 | status: "success", 94 | data: { 95 | s3Key, 96 | region, 97 | layers, 98 | s3Bucket, 99 | functions, 100 | serverlessConfigString, 101 | cloudFormationTemplateString, 102 | serverlessConfigHash: hash(serverlessConfigString), 103 | cloudFormationTemplateHash: hash(cloudFormationTemplateString), 104 | }, 105 | }; 106 | } catch (e) { 107 | this.serverless.cli.log( 108 | "Seed: There was a problem generating the incremental deploy state" 109 | ); 110 | this.serverless.cli.log(` ${e.message}`); 111 | 112 | state = { 113 | status: "fail", 114 | error: { 115 | name: e.name, 116 | stack: e.stack, 117 | message: e.message, 118 | }, 119 | }; 120 | } 121 | 122 | await this.printToStateFile(state); 123 | } 124 | 125 | getCloudFormationString() { 126 | const cloudFormationTemplate = normalizeFiles.normalizeCloudFormationTemplate( 127 | this.serverless.service.provider.compiledCloudFormationTemplate 128 | ); 129 | return JSON.stringify(cloudFormationTemplate); 130 | } 131 | 132 | async getServerlessConfigString() { 133 | const slsConfig = await serverlessConfigUtils.getServerlessConfigFile( 134 | this.serverless 135 | ); 136 | return JSON.stringify(normalizeFiles.normalizeServerlessConfig(slsConfig)); 137 | } 138 | 139 | async createStateFile() { 140 | await this.printToStateFile({ status: "processing" }); 141 | } 142 | 143 | async printToStateFile(state) { 144 | const packagePath = this.realArtifactPath(); 145 | 146 | try { 147 | await fs.writeFile( 148 | path.join(packagePath, "seed-state.json"), 149 | JSON.stringify(state, null, 2) 150 | ); 151 | } catch (e) { 152 | this.serverless.cli.log( 153 | "Seed: There was a problem creating the incremental deploy state file" 154 | ); 155 | } 156 | } 157 | 158 | realArtifactPath() { 159 | return ( 160 | this.options.package || 161 | this.serverless.service.package.path || 162 | path.join(this.serverless.config.servicePath || ".", ".serverless") 163 | ); 164 | } 165 | 166 | getRealArtifactPath(artifactPath) { 167 | // Case 1: "package.artifact" IS defined 168 | // artifactPath = "user/defined/path/to/lambda.zip" 169 | // Case 2: "package.artifact" NOT defined + NO sls package --package 170 | // artifactPath = "/path/to/.serverless/lambda.zip" 171 | // Case 3: "package.artifact" NOT defined + HAS sls package --package 172 | // artifactPath = "/path/to/.serverless/lambda.zip" (wrong! need to correct) 173 | // Case 4: serverless-bundle is used 174 | // + "package.artifact" NOT defined 175 | // + HAS sls package --package 176 | // artifactPath = ".serverless/lambda.zip" 177 | 178 | // Note that we current don't have a test for Case 4, b/c with "serverless-bundle" 179 | // enabled, running `sls package` directly inside terminal works, but running 180 | // it through "runIncrementalSlsCmds()" inside jest test fails. 181 | 182 | // Handle case 1 183 | artifactPath = artifactPath || this.serverless.service.package.artifact; 184 | if ( 185 | !path.isAbsolute(artifactPath) && 186 | !artifactPath.startsWith(".serverless") 187 | ) { 188 | return artifactPath; 189 | } 190 | 191 | // Handle case 2, 3, 4 192 | const pathParts = artifactPath.split("/"); 193 | const filename = pathParts[pathParts.length - 1]; 194 | return path.join(this.realArtifactPath(), filename); 195 | } 196 | 197 | createLayersList() { 198 | const layers = this.serverless.service.layers; 199 | return Object.keys(layers).map((name) => ({ 200 | name, 201 | artifact: this.getRealArtifactPath(layers[name].package.artifact), 202 | })); 203 | } 204 | 205 | createFunctionsList() { 206 | const functions = this.serverless.service.functions; 207 | return Object.keys(functions).map((name) => ({ 208 | name: functions[name].name, 209 | artifact: this.getRealArtifactPath(functions[name].package.artifact), 210 | })); 211 | } 212 | } 213 | 214 | module.exports = ServerlessSeedPlugin; 215 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-seed", 3 | "version": "0.3.15", 4 | "description": "A Serverless plugin for optimizing deploys in Seed", 5 | "main": "index.js", 6 | "author": { 7 | "name": "Anomaly Innovations", 8 | "url": "https://anoma.ly" 9 | }, 10 | "license": "MIT", 11 | "scripts": { 12 | "lint": "eslint . --fix --ext .js,.ts", 13 | "test": "node tests/helpers/setup-versions.js && jest --no-watchman" 14 | }, 15 | "jest": { 16 | "setupFilesAfterEnv": [ 17 | "/tests/helpers/setup-tests.js" 18 | ] 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/seed-run/serverless-seed.git" 23 | }, 24 | "dependencies": { 25 | "lodash": "^4.17.20", 26 | "serverless": "2.15.0" 27 | }, 28 | "devDependencies": { 29 | "eslint": "^7.14.0", 30 | "fs-extra": "^9.0.1", 31 | "husky": "^4.3.0", 32 | "jest": "^26.6.3", 33 | "lint-staged": "^10.5.2", 34 | "prettier": "^2.2.1" 35 | }, 36 | "husky": { 37 | "hooks": { 38 | "pre-commit": "lint-staged" 39 | } 40 | }, 41 | "lint-staged": { 42 | "*.{js,ts,css,json,md}": [ 43 | "prettier --write" 44 | ], 45 | "*.{js,ts}": [ 46 | "eslint --fix" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | incremental: { 5 | // Turn on incremental deploys 6 | enabled: false, 7 | // Disabled incremental deploys for the following stages 8 | disabledFor: [], 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /src/normalizeFiles.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const normalizeFiles = require("serverless/lib/plugins/aws/lib/normalizeFiles"); 3 | 4 | module.exports = { 5 | normalizeCloudFormationTemplate: function (template) { 6 | // Apply Serverless' normalization 7 | const normalizedTemplate = normalizeFiles.normalizeCloudFormationTemplate( 8 | template 9 | ); 10 | 11 | // Create list of all Lambda version refs 12 | const lambdaVersions = {}; 13 | 14 | Object.entries(normalizedTemplate.Resources).forEach(([key, value]) => { 15 | if (value.Type && value.Type === "AWS::Lambda::Version") { 16 | lambdaVersions[key] = true; 17 | delete normalizedTemplate.Resources[key]; 18 | } 19 | 20 | // Disabling Lambda@Edge test cases 21 | // // Handle Lambda@Edge versions 22 | // if ( 23 | // value.Type && 24 | // value.Type === "AWS::Lambda::Permission" && 25 | // value.Properties && 26 | // value.Properties.Principal && 27 | // value.Properties.Principal === "edgelambda.amazonaws.com" && 28 | // value.Properties.FunctionName && 29 | // value.Properties.FunctionName.Ref 30 | // ) { 31 | // value.Properties.FunctionName.Ref = null; 32 | // } 33 | // 34 | // if ( 35 | // key === "CloudFrontDistribution" && 36 | // value.Properties && 37 | // value.Properties.DistributionConfig && 38 | // value.Properties.DistributionConfig.DefaultCacheBehavior 39 | // ) { 40 | // ( 41 | // value.Properties.DistributionConfig.DefaultCacheBehavior 42 | // .LambdaFunctionAssociations || [] 43 | // ).forEach(function (lambdaAssoc) { 44 | // if ( 45 | // lambdaAssoc.LambdaFunctionARN && 46 | // lambdaAssoc.LambdaFunctionARN.Ref 47 | // ) { 48 | // lambdaAssoc.LambdaFunctionARN.Ref = null; 49 | // } 50 | // }); 51 | // } 52 | }); 53 | 54 | Object.entries(normalizedTemplate.Outputs).forEach(([key, value]) => { 55 | // Remove Lambda versions 56 | if (value.Value && value.Value.Ref && lambdaVersions[value.Value.Ref]) { 57 | delete normalizedTemplate.Outputs[key]; 58 | } 59 | // Remove Layer Keys 60 | if (key.endsWith("LambdaLayerS3Key") || key.endsWith("LambdaLayerHash")) { 61 | value.Value = null; 62 | } 63 | }); 64 | 65 | return normalizedTemplate; 66 | }, 67 | normalizeServerlessConfig: function (config) { 68 | const normalizedConfig = _.cloneDeep(config); 69 | 70 | if ( 71 | normalizedConfig.provider && 72 | normalizedConfig.provider.compiledCloudFormationTemplate 73 | ) { 74 | normalizedConfig.provider.compiledCloudFormationTemplate = null; 75 | } 76 | if ( 77 | normalizedConfig.provider && 78 | normalizedConfig.provider.coreCloudFormationTemplate 79 | ) { 80 | normalizedConfig.provider.coreCloudFormationTemplate = null; 81 | } 82 | 83 | // Handle package individually 84 | if (normalizedConfig.package && normalizedConfig.package.individually) { 85 | normalizedConfig.package.artifactDirectoryName = null; 86 | } 87 | 88 | // Remove versions from functions 89 | if (normalizedConfig.functions) { 90 | Object.entries(normalizedConfig.functions).forEach((entries) => { 91 | const value = entries[1]; 92 | if (value.versionLogicalId) { 93 | value.versionLogicalId = null; 94 | } 95 | }); 96 | } 97 | 98 | return normalizedConfig; 99 | }, 100 | }; 101 | -------------------------------------------------------------------------------- /tests/base/base.test.js: -------------------------------------------------------------------------------- 1 | const { clearSlsCache, runIncrementalSlsCmds } = require("../helpers"); 2 | 3 | beforeEach(async () => { 4 | await clearSlsCache(__dirname); 5 | }); 6 | 7 | afterAll(async () => { 8 | await clearSlsCache(__dirname); 9 | }); 10 | 11 | test("base", async () => { 12 | const [state1, state2] = await runIncrementalSlsCmds(__dirname, "handler.js"); 13 | 14 | expect(state1.data.cloudFormationTemplateHash).toEqual( 15 | state2.data.cloudFormationTemplateHash 16 | ); 17 | expect(state1.data.serverlessConfigHash).toEqual( 18 | state2.data.serverlessConfigHash 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/base/handler.js: -------------------------------------------------------------------------------- 1 | module.exports.hello = () => { 2 | return { 3 | statusCode: 200, 4 | body: "Go Serverless v2.0! Your function executed successfully!", 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/base/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/base/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base", 3 | "private": true, 4 | "description": "tests", 5 | "version": "0.0.1", 6 | "main": "handler.js", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /tests/base/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-seed-test 2 | 3 | plugins: 4 | - '../../index' 5 | 6 | custom: 7 | seed: 8 | incremental: 9 | enabled: true 10 | 11 | provider: 12 | name: aws 13 | runtime: nodejs10.x 14 | stage: dev 15 | region: us-east-1 16 | 17 | functions: 18 | hello: 19 | handler: handler.hello 20 | events: 21 | - http: 22 | path: hello 23 | method: get 24 | -------------------------------------------------------------------------------- /tests/custom-package-path/custom-package-path.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | getSeedState, 3 | successRegex, 4 | clearSlsCache, 5 | runSlsCommand, 6 | } = require("../helpers"); 7 | 8 | beforeEach(async () => { 9 | await clearSlsCache(__dirname, ".package"); 10 | }); 11 | 12 | afterAll(async () => { 13 | await clearSlsCache(__dirname, ".package"); 14 | }); 15 | 16 | test("custom-package-path", async () => { 17 | const result = await runSlsCommand(__dirname, "package --package=.package"); 18 | 19 | expect(result).toMatch(successRegex); 20 | 21 | const state = await getSeedState(__dirname, ".package"); 22 | expect(state).toMatchObject({ status: "success" }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/custom-package-path/handler.js: -------------------------------------------------------------------------------- 1 | module.exports.hello = () => { 2 | return { 3 | statusCode: 200, 4 | body: "Go Serverless v2.0! Your function executed successfully!", 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/custom-package-path/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-package-path", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/custom-package-path/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-package-path", 3 | "private": true, 4 | "description": "tests", 5 | "version": "0.0.1", 6 | "main": "handler.js", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /tests/custom-package-path/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-seed-test 2 | 3 | plugins: 4 | - '../../index' 5 | 6 | custom: 7 | seed: 8 | incremental: 9 | enabled: true 10 | 11 | provider: 12 | name: aws 13 | runtime: nodejs10.x 14 | stage: dev 15 | region: us-east-1 16 | 17 | functions: 18 | hello: 19 | handler: handler.hello 20 | events: 21 | - http: 22 | path: hello 23 | method: get 24 | -------------------------------------------------------------------------------- /tests/disabled-for/disabled-for.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | errorRegex, 3 | successRegex, 4 | clearSlsCache, 5 | runSlsCommand, 6 | } = require("../helpers"); 7 | 8 | beforeEach(async () => { 9 | await clearSlsCache(__dirname); 10 | }); 11 | 12 | afterAll(async () => { 13 | await clearSlsCache(__dirname); 14 | }); 15 | 16 | test("disabled-for", async () => { 17 | const result = await runSlsCommand(__dirname); 18 | 19 | expect(result).not.toMatch(errorRegex); 20 | expect(result).not.toMatch(successRegex); 21 | }); 22 | 23 | test("disabled-for-stage-option", async () => { 24 | const result = await runSlsCommand(__dirname, "package --stage=prod"); 25 | 26 | expect(result).not.toMatch(errorRegex); 27 | expect(result).not.toMatch(successRegex); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/disabled-for/handler.js: -------------------------------------------------------------------------------- 1 | module.exports.hello = () => { 2 | return { 3 | statusCode: 200, 4 | body: "Go Serverless v2.0! Your function executed successfully!", 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/disabled-for/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "disabled-for", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/disabled-for/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "disabled-for", 3 | "private": true, 4 | "description": "tests", 5 | "version": "0.0.1", 6 | "main": "handler.js", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /tests/disabled-for/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-seed-test 2 | 3 | plugins: 4 | - '../../index' 5 | 6 | custom: 7 | seed: 8 | incremental: 9 | enabled: true 10 | disabledFor: 11 | - dev 12 | - prod 13 | 14 | provider: 15 | name: aws 16 | runtime: nodejs10.x 17 | stage: dev 18 | region: us-east-1 19 | 20 | functions: 21 | hello: 22 | handler: handler.hello 23 | events: 24 | - http: 25 | path: hello 26 | method: get 27 | -------------------------------------------------------------------------------- /tests/empty-config/empty-config.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | errorRegex, 3 | successRegex, 4 | clearSlsCache, 5 | runSlsCommand, 6 | } = require("../helpers"); 7 | 8 | beforeEach(async () => { 9 | await clearSlsCache(__dirname); 10 | }); 11 | 12 | afterAll(async () => { 13 | await clearSlsCache(__dirname); 14 | }); 15 | 16 | test("empty-config", async () => { 17 | const result = await runSlsCommand(__dirname); 18 | 19 | expect(result).not.toMatch(errorRegex); 20 | expect(result).not.toMatch(successRegex); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/empty-config/handler.js: -------------------------------------------------------------------------------- 1 | module.exports.hello = () => { 2 | return { 3 | statusCode: 200, 4 | body: "Go Serverless v2.0! Your function executed successfully!", 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/empty-config/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "empty-config", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/empty-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "empty-config", 3 | "private": true, 4 | "description": "tests", 5 | "version": "0.0.1", 6 | "main": "handler.js", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /tests/empty-config/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-seed-test 2 | 3 | plugins: 4 | - '../../index' 5 | 6 | provider: 7 | name: aws 8 | runtime: nodejs10.x 9 | stage: dev 10 | region: us-east-1 11 | 12 | functions: 13 | hello: 14 | handler: handler.hello 15 | events: 16 | - http: 17 | path: hello 18 | method: get 19 | -------------------------------------------------------------------------------- /tests/helpers/clear-sls-cache.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require("util"); 2 | const { exec } = require("child_process"); 3 | 4 | const execPromise = promisify(exec); 5 | const TIMEOUT = 30000; 6 | 7 | async function clearNpmCache(cwd, dir = ".serverless") { 8 | await execPromise(`rm -rf ${dir}/`, { 9 | cwd, 10 | TIMEOUT, 11 | }); 12 | } 13 | 14 | module.exports = clearNpmCache; 15 | -------------------------------------------------------------------------------- /tests/helpers/get-seed-state.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs").promises; 3 | 4 | async function getSeedState(cwd, packagePath = ".serverless") { 5 | let contents = null; 6 | 7 | try { 8 | contents = JSON.parse( 9 | await fs.readFile(path.join(cwd, packagePath, "seed-state.json"), "utf8") 10 | ); 11 | } catch (e) { 12 | console.log(e); 13 | } 14 | 15 | return contents; 16 | } 17 | 18 | module.exports = getSeedState; 19 | -------------------------------------------------------------------------------- /tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | const npmInstall = require("./npm-install"); 2 | const getSeedState = require("./get-seed-state"); 3 | const clearSlsCache = require("./clear-sls-cache"); 4 | const runSlsCommand = require("./run-sls-command"); 5 | const removeNodeModules = require("./remove-node-modules"); 6 | const runIncrementalSlsCmds = require("./run-incremental-sls-cmds"); 7 | 8 | const errorRegex = /(Error|Exception) ---/; 9 | const successRegex = /Seed: Generating incremental deploy state.../; 10 | 11 | module.exports = { 12 | errorRegex, 13 | npmInstall, 14 | getSeedState, 15 | successRegex, 16 | clearSlsCache, 17 | runSlsCommand, 18 | removeNodeModules, 19 | runIncrementalSlsCmds, 20 | }; 21 | -------------------------------------------------------------------------------- /tests/helpers/npm-install.js: -------------------------------------------------------------------------------- 1 | const { exists } = require("fs"); 2 | const { promisify } = require("util"); 3 | const { exec } = require("child_process"); 4 | 5 | const removeNodeModules = require("./remove-node-modules"); 6 | 7 | const execPromise = promisify(exec); 8 | const existsPromise = promisify(exists); 9 | const TIMEOUT = 30000; 10 | 11 | async function npmInstall(cwd) { 12 | const hasPackageJson = await existsPromise(`${cwd}/package.json`); 13 | 14 | if (hasPackageJson) { 15 | await removeNodeModules(cwd); 16 | 17 | await execPromise("npm install", { 18 | cwd, 19 | TIMEOUT, 20 | }); 21 | } 22 | } 23 | 24 | module.exports = npmInstall; 25 | -------------------------------------------------------------------------------- /tests/helpers/remove-node-modules.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require("util"); 2 | const { exec } = require("child_process"); 3 | 4 | const execPromise = promisify(exec); 5 | const TIMEOUT = 30000; 6 | 7 | async function removeNodeModules(cwd) { 8 | await execPromise("rm -rf node_modules/", { 9 | cwd, 10 | TIMEOUT, 11 | }); 12 | } 13 | 14 | module.exports = removeNodeModules; 15 | -------------------------------------------------------------------------------- /tests/helpers/run-incremental-sls-cmds.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs").promises; 3 | 4 | const npmInstall = require("./npm-install"); 5 | const getSeedState = require("./get-seed-state"); 6 | const runSlsCommand = require("./run-sls-command"); 7 | 8 | const contents = []; 9 | 10 | async function changeFiles(files, cwd, change = null) { 11 | files.forEach(async (file, i) => { 12 | file = path.join(cwd, file); 13 | 14 | if (change !== null) { 15 | contents[i] = await fs.readFile(file); 16 | } 17 | await fs.writeFile( 18 | file, 19 | change !== null ? contents[i] + change : contents[i] 20 | ); 21 | }); 22 | } 23 | 24 | async function runIncrementalSlsCmds(cwd, filesToChange, packagePath) { 25 | const cmd = packagePath ? `package --package ${packagePath}` : "package"; 26 | filesToChange = Array.isArray(filesToChange) 27 | ? filesToChange 28 | : [filesToChange]; 29 | 30 | await npmInstall(cwd); 31 | 32 | await changeFiles(filesToChange, cwd, " /**hi**/"); 33 | await runSlsCommand(cwd, cmd, false); 34 | 35 | const state1 = await getSeedState(cwd, packagePath); 36 | 37 | await changeFiles(filesToChange, cwd); 38 | await runSlsCommand(cwd, cmd, false); 39 | 40 | const state2 = await getSeedState(cwd, packagePath); 41 | 42 | return [state1, state2]; 43 | } 44 | 45 | module.exports = runIncrementalSlsCmds; 46 | -------------------------------------------------------------------------------- /tests/helpers/run-sls-command.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require("util"); 2 | const { exec } = require("child_process"); 3 | const npmInstall = require("./npm-install"); 4 | 5 | const execPromise = promisify(exec); 6 | const TIMEOUT = 30000; 7 | const PKG_CMD = "package"; 8 | 9 | async function runSlsCommand(cwd, cmd = PKG_CMD, runInstall = true) { 10 | runInstall && (await npmInstall(cwd)); 11 | 12 | try { 13 | const { stdout } = await execPromise(`__LOCAL__=true serverless ${cmd}`, { 14 | cwd, 15 | TIMEOUT, 16 | }); 17 | 18 | return stdout.toString("utf8"); 19 | } catch (e) { 20 | console.log(e); 21 | return ""; 22 | } 23 | } 24 | 25 | module.exports = runSlsCommand; 26 | -------------------------------------------------------------------------------- /tests/helpers/setup-tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | The default timeout is 5000ms on async tests. 3 | Because we npm install and remove directories, tests can take time to run. 4 | Setting to 3 minutes to support slow machines. 5 | */ 6 | jest.setTimeout(180000); 7 | -------------------------------------------------------------------------------- /tests/helpers/setup-versions.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs-extra"); 3 | 4 | const versions = ["1.55.0", "1.77.1", "2.15.0", "2.64.1"]; 5 | 6 | const testsDir = path.join(process.cwd(), "tests"); 7 | const templateTestPath = path.join(testsDir, "serverless-versions"); 8 | 9 | // Remove node_modules from tempalte test 10 | fs.removeSync(path.join(templateTestPath, "node_modules")); 11 | 12 | // Create new versions 13 | versions.forEach(copyVersion); 14 | 15 | function copyVersion(version) { 16 | console.log(`Generating tests for ${version}...`); 17 | 18 | const newPath = path.join( 19 | testsDir, 20 | processString("serverless-versions-%version-string%", version) 21 | ); 22 | 23 | fs.copySync(templateTestPath, newPath); 24 | 25 | // Remove old lockfile 26 | fs.removeSync(path.join(newPath, "package-lock.json")); 27 | 28 | // Setup files 29 | processFile(path.join(newPath, "package.json"), version); 30 | processFile(path.join(newPath, "serverless.yml"), version); 31 | processFile( 32 | path.join(newPath, "serverless-versions.template.js"), 33 | version, 34 | path.join( 35 | newPath, 36 | processString("serverless-%version-string%.test.js", version) 37 | ) 38 | ); 39 | } 40 | 41 | function processString(str, version) { 42 | const versionString = version.replace(/\./g, ""); 43 | 44 | return str 45 | .replace(/%version%/g, version) 46 | .replace(/%version-string%/g, versionString); 47 | } 48 | 49 | function processFile(templatePath, version, toFile) { 50 | toFile = toFile || templatePath; 51 | 52 | const template = fs.readFileSync(templatePath, { encoding: "utf-8" }); 53 | fs.writeFileSync(toFile, processString(template, version)); 54 | 55 | // If moving the file, remove the template 56 | if (toFile !== templatePath) { 57 | fs.removeSync(templatePath); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/lambda-edge/handler.js: -------------------------------------------------------------------------------- 1 | module.exports.main = (event, context, callback) => { 2 | const response = event.Records[0].cf.response; 3 | const headers = response.headers; 4 | 5 | headers["x-serverless-time"] = [ 6 | { key: "x-serverless-time", value: Date.now().toString() }, 7 | ]; 8 | 9 | return callback(null, response); 10 | }; 11 | -------------------------------------------------------------------------------- /tests/lambda-edge/lambda-edge.test.js: -------------------------------------------------------------------------------- 1 | const { clearSlsCache, runIncrementalSlsCmds } = require("../helpers"); 2 | 3 | beforeEach(async () => { 4 | await clearSlsCache(__dirname); 5 | }); 6 | 7 | afterAll(async () => { 8 | await clearSlsCache(__dirname); 9 | }); 10 | 11 | test("lambda-edge", async () => { 12 | const [state1, state2] = await runIncrementalSlsCmds(__dirname, "handler.js"); 13 | 14 | expect(state1.data.cloudFormationTemplateHash).not.toEqual( 15 | state2.data.cloudFormationTemplateHash 16 | ); 17 | expect(state1.data.serverlessConfigHash).toEqual( 18 | state2.data.serverlessConfigHash 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/lambda-edge/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-edge", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/lambda-edge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-edge", 3 | "private": true, 4 | "description": "tests", 5 | "version": "0.0.1", 6 | "main": "handler.js", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /tests/lambda-edge/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-seed-test 2 | 3 | plugins: 4 | - '../../index' 5 | 6 | custom: 7 | seed: 8 | incremental: 9 | enabled: true 10 | 11 | provider: 12 | name: aws 13 | runtime: nodejs10.x 14 | stage: dev 15 | region: us-east-1 16 | 17 | functions: 18 | cfLambda: 19 | handler: handler.main 20 | events: 21 | - cloudFront: 22 | eventType: viewer-response 23 | origin: "s3://lambda-edge-seed-test.s3.amazonaws.com" 24 | -------------------------------------------------------------------------------- /tests/layers/layer/layer.txt: -------------------------------------------------------------------------------- 1 | Layer Text 2 | -------------------------------------------------------------------------------- /tests/layers/layers.test.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const { clearSlsCache, runIncrementalSlsCmds } = require("../helpers"); 3 | 4 | beforeEach(async () => { 5 | await clearSlsCache(__dirname); 6 | }); 7 | 8 | afterAll(async () => { 9 | await clearSlsCache(__dirname); 10 | }); 11 | 12 | test("layers", async () => { 13 | const [state1, state2] = await runIncrementalSlsCmds( 14 | __dirname, 15 | path.join("layer", "layer.txt") 16 | ); 17 | 18 | expect(state1.data.cloudFormationTemplateHash).toEqual( 19 | state2.data.cloudFormationTemplateHash 20 | ); 21 | expect(state1.data.serverlessConfigHash).toEqual( 22 | state2.data.serverlessConfigHash 23 | ); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/layers/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layers", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/layers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layers", 3 | "private": true, 4 | "description": "tests", 5 | "version": "0.0.1", 6 | "main": "handler.js", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /tests/layers/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-seed-test 2 | 3 | plugins: 4 | - '../../index' 5 | 6 | custom: 7 | seed: 8 | incremental: 9 | enabled: true 10 | 11 | provider: 12 | name: aws 13 | runtime: nodejs10.x 14 | stage: dev 15 | region: us-east-1 16 | 17 | layers: 18 | layer: 19 | path: layer 20 | -------------------------------------------------------------------------------- /tests/normalize-files/normalizeFiles.test.js: -------------------------------------------------------------------------------- 1 | const normalizeFiles = require("../../src/normalizeFiles"); 2 | 3 | test("normalize-files-cf", async () => { 4 | const template = { 5 | Resources: { 6 | GetLambdaVersion1: { 7 | Type: "AWS::Lambda::Version", 8 | DeletionPolicy: "Retain", 9 | Properties: { 10 | FunctionName: { 11 | Ref: "GetLambdaFunction", 12 | }, 13 | CodeSha256: "3DEObMEp7eCuwOdyFDgOS7yqQJ1p33uPjn280kqc7Hg=", 14 | }, 15 | }, 16 | CfLambda1LambdaFunctionInvokePermission: { 17 | Type: "AWS::Lambda::Permission", 18 | Properties: { 19 | Action: "lambda:InvokeFunction", 20 | Principal: "edgelambda.amazonaws.com", 21 | FunctionName: { 22 | Ref: 23 | "CfLambda1LambdaVersiongUHtkolzILhRJq3kn5H9mRT6Y5jTI4DBcz7YRmlc0", 24 | }, 25 | }, 26 | }, 27 | CloudFrontDistribution: { 28 | Properties: { 29 | DistributionConfig: { 30 | DefaultCacheBehavior: { 31 | LambdaFunctionAssociations: [ 32 | { 33 | LambdaFunctionARN: { 34 | Ref: 35 | "CfLambda1LambdaVersiongUHtkolzILhRJq3kn5H9mRT6Y5jTI4DBcz7YRmlc0", 36 | }, 37 | }, 38 | ], 39 | }, 40 | }, 41 | }, 42 | }, 43 | }, 44 | Outputs: { 45 | GetLambdaFunctionQualifiedArn: { 46 | Description: "Current Lambda function version", 47 | Value: { 48 | Ref: "GetLambdaVersion1", 49 | }, 50 | }, 51 | Layer1LambdaLayerHash: { 52 | Description: "Current Lambda layer hash", 53 | Value: "33164e2a83a901b9e62b547ce001aa09c09deede", 54 | }, 55 | Layer1LambdaLayerS3Key: { 56 | Description: "Current Lambda layer S3Key", 57 | Value: 58 | "serverless/serverless-seed-test/dev/1607575759130-2020-12-10T04:49:19.130Z/layer.zip", 59 | }, 60 | }, 61 | }; 62 | const normalizedTemplate = normalizeFiles.normalizeCloudFormationTemplate( 63 | template 64 | ); 65 | 66 | expect(normalizedTemplate.Resources.GetLambdaVersion1).toBeUndefined(); 67 | 68 | // Disabling Lambda@Edge test cases 69 | // expect( 70 | // normalizedTemplate.Resources.CfLambda1LambdaFunctionInvokePermission 71 | // .Properties.FunctionName.Ref 72 | // ).toBeNull(); 73 | // normalizedTemplate.Resources.CloudFrontDistribution.Properties.DistributionConfig.DefaultCacheBehavior.LambdaFunctionAssociations.forEach( 74 | // (lambda) => { 75 | // expect(lambda.LambdaFunctionARN.Ref).toBeNull(); 76 | // } 77 | // ); 78 | 79 | expect( 80 | normalizedTemplate.Outputs.GetLambdaFunctionQualifiedArn 81 | ).toBeUndefined(); 82 | expect(normalizedTemplate.Outputs.Layer1LambdaLayerHash.Value).toBeNull(); 83 | expect(normalizedTemplate.Outputs.Layer1LambdaLayerS3Key.Value).toBeNull(); 84 | }); 85 | 86 | test("normalize-files-sls", async () => { 87 | const config = { 88 | service: { 89 | name: "sample-service", 90 | }, 91 | package: { 92 | individually: true, 93 | artifactDirectoryName: 94 | "serverless/serverless-seed-test/dev/1608056831725-2020-12-15T18:27:11.725Z", 95 | }, 96 | provider: { 97 | name: "aws", 98 | compiledCloudFormationTemplate: { 99 | AWSTemplateFormatVersion: "2010-09-09", 100 | }, 101 | coreCloudFormationTemplate: { 102 | AWSTemplateFormatVersion: "2010-09-09", 103 | }, 104 | }, 105 | functions: { 106 | cfLambda1: { 107 | handler: "handler.main", 108 | versionLogicalId: 109 | "CfLambda1LambdaVersionhy5E0G9fN2CvYUCt23oQzDwN5hPbzIugWoXSzbLo", 110 | }, 111 | }, 112 | }; 113 | const normalizedConfig = normalizeFiles.normalizeServerlessConfig(config); 114 | 115 | expect(normalizedConfig.provider.compiledCloudFormationTemplate).toBeNull(); 116 | expect(normalizedConfig.provider.coreCloudFormationTemplate).toBeNull(); 117 | expect(normalizedConfig.package.artifactDirectoryName).toBeNull(); 118 | expect(normalizedConfig.functions.cfLambda1.versionLogicalId).toBeNull(); 119 | }); 120 | -------------------------------------------------------------------------------- /tests/package-individually/handler.js: -------------------------------------------------------------------------------- 1 | module.exports.hello = () => { 2 | return { 3 | statusCode: 200, 4 | body: "Go Serverless v2.0! Your function executed successfully!", 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/package-individually/package-individually.test.js: -------------------------------------------------------------------------------- 1 | const { clearSlsCache, runIncrementalSlsCmds } = require("../helpers"); 2 | 3 | beforeEach(async () => { 4 | await clearSlsCache(__dirname); 5 | }); 6 | 7 | afterAll(async () => { 8 | await clearSlsCache(__dirname); 9 | }); 10 | 11 | test("package-individually", async () => { 12 | const [state1, state2] = await runIncrementalSlsCmds(__dirname, "handler.js"); 13 | 14 | expect(state1.data.cloudFormationTemplateHash).toEqual( 15 | state2.data.cloudFormationTemplateHash 16 | ); 17 | expect(state1.data.serverlessConfigHash).toEqual( 18 | state2.data.serverlessConfigHash 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/package-individually/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "package-individually", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/package-individually/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "package-individually", 3 | "private": true, 4 | "description": "tests", 5 | "version": "0.0.1", 6 | "main": "handler.js", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /tests/package-individually/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-seed-test 2 | 3 | plugins: 4 | - '../../index' 5 | 6 | package: 7 | individually: true 8 | 9 | custom: 10 | seed: 11 | incremental: 12 | enabled: true 13 | 14 | provider: 15 | name: aws 16 | runtime: nodejs10.x 17 | stage: dev 18 | region: us-east-1 19 | 20 | functions: 21 | hello: 22 | handler: handler.hello 23 | events: 24 | - http: 25 | path: hello 26 | method: get 27 | -------------------------------------------------------------------------------- /tests/referenced-config/config.yml: -------------------------------------------------------------------------------- 1 | custom: 2 | seed: 3 | incremental: 4 | enabled: true 5 | -------------------------------------------------------------------------------- /tests/referenced-config/handler.js: -------------------------------------------------------------------------------- 1 | module.exports.hello = () => { 2 | return { 3 | statusCode: 200, 4 | body: "Go Serverless v2.0! Your function executed successfully!", 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/referenced-config/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "referenced-config", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/referenced-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "referenced-config", 3 | "private": true, 4 | "description": "tests", 5 | "version": "0.0.1", 6 | "main": "handler.js", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /tests/referenced-config/referenced-config.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | successRegex, 3 | getSeedState, 4 | clearSlsCache, 5 | runSlsCommand, 6 | } = require("../helpers"); 7 | 8 | beforeEach(async () => { 9 | await clearSlsCache(__dirname); 10 | }); 11 | 12 | afterAll(async () => { 13 | await clearSlsCache(__dirname); 14 | }); 15 | 16 | test("base", async () => { 17 | const result = await runSlsCommand(__dirname); 18 | 19 | expect(result).toMatch(successRegex); 20 | 21 | const state = await getSeedState(__dirname); 22 | expect(state).toMatchObject({ status: "success" }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/referenced-config/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-seed-test 2 | 3 | plugins: 4 | - '../../index' 5 | 6 | custom: ${file(config.yml):custom} 7 | 8 | provider: 9 | name: aws 10 | runtime: nodejs10.x 11 | stage: dev 12 | region: us-east-1 13 | 14 | functions: 15 | hello: 16 | handler: handler.hello 17 | events: 18 | - http: 19 | path: hello 20 | method: get 21 | -------------------------------------------------------------------------------- /tests/serverless-js/handler.js: -------------------------------------------------------------------------------- 1 | module.exports.hello = () => { 2 | return { 3 | statusCode: 200, 4 | body: "Go Serverless v2.0! Your function executed successfully!", 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/serverless-js/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-js", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /tests/serverless-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-js", 3 | "private": true, 4 | "description": "tests", 5 | "version": "0.0.1", 6 | "main": "handler.js", 7 | "license": "ISC" 8 | } 9 | -------------------------------------------------------------------------------- /tests/serverless-js/serverless-js.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | successRegex, 3 | getSeedState, 4 | clearSlsCache, 5 | runSlsCommand, 6 | } = require("../helpers"); 7 | 8 | beforeEach(async () => { 9 | await clearSlsCache(__dirname); 10 | }); 11 | 12 | afterAll(async () => { 13 | await clearSlsCache(__dirname); 14 | }); 15 | 16 | test("serverless-js", async () => { 17 | const result = await runSlsCommand(__dirname); 18 | 19 | expect(result).toMatch(successRegex); 20 | 21 | const state = await getSeedState(__dirname); 22 | expect(state).toMatchObject({ status: "success" }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/serverless-js/serverless.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | service: "serverless-seed-test", 5 | plugins: ["../../index"], 6 | custom: { 7 | seed: { 8 | incremental: { 9 | enabled: true, 10 | }, 11 | }, 12 | }, 13 | provider: { 14 | name: "aws", 15 | runtime: "nodejs10.x", 16 | stage: "dev", 17 | region: "us-east-1", 18 | }, 19 | functions: { 20 | hello: { 21 | handler: "handler.hello", 22 | events: [ 23 | { 24 | http: { 25 | path: "hello", 26 | method: "get", 27 | }, 28 | }, 29 | ], 30 | }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /tests/serverless-versions/edge.js: -------------------------------------------------------------------------------- 1 | module.exports.main = (event, context, callback) => { 2 | const response = event.Records[0].cf.response; 3 | const headers = response.headers; 4 | 5 | headers["x-serverless-time"] = [ 6 | { key: "x-serverless-time", value: Date.now().toString() }, 7 | ]; 8 | 9 | return callback(null, response); 10 | }; 11 | -------------------------------------------------------------------------------- /tests/serverless-versions/functions/prebuilt.zip: -------------------------------------------------------------------------------- 1 | placeholder 2 | -------------------------------------------------------------------------------- /tests/serverless-versions/handler.js: -------------------------------------------------------------------------------- 1 | module.exports.hello = () => { 2 | return { 3 | statusCode: 200, 4 | body: "Go Serverless v2.0! Your function executed successfully!", 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/serverless-versions/layer/layer.txt: -------------------------------------------------------------------------------- 1 | Layer Text 2 | -------------------------------------------------------------------------------- /tests/serverless-versions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-%version-string%", 3 | "private": true, 4 | "description": "tests", 5 | "version": "0.0.1", 6 | "main": "handler.js", 7 | "license": "ISC", 8 | "devDependencies": { 9 | "serverless": "%version%" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/serverless-versions/serverless-versions.template.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const { clearSlsCache, runIncrementalSlsCmds } = require("../helpers"); 3 | 4 | beforeEach(async () => { 5 | await clearSlsCache(__dirname); 6 | }); 7 | 8 | afterAll(async () => { 9 | await clearSlsCache(__dirname); 10 | }); 11 | 12 | test("serverless-%version-string%", async () => { 13 | const [state1, state2] = await runIncrementalSlsCmds(__dirname, [ 14 | "handler.js", 15 | "edge.js", 16 | path.join("layer", "layer.txt"), 17 | ]); 18 | 19 | expect(state1.data.layers[0]).toEqual({ 20 | name: "layer", 21 | artifact: path.resolve(path.join(__dirname, ".serverless/layer.zip")), 22 | }); 23 | expect(state1.data.functions[0]).toEqual({ 24 | name: "serverless-seed-test-dev-hello", 25 | artifact: path.resolve( 26 | path.join(__dirname, ".serverless/serverless-seed-test.zip") 27 | ), 28 | }); 29 | expect(state1.data.functions[1]).toEqual({ 30 | name: "serverless-seed-test-dev-prebuilt", 31 | artifact: "functions/prebuilt.zip", 32 | }); 33 | expect(state1.data.functions[2]).toEqual({ 34 | name: "serverless-seed-test-dev-cfLambda", 35 | artifact: path.resolve( 36 | path.join(__dirname, ".serverless/serverless-seed-test.zip") 37 | ), 38 | }); 39 | expect(state1.data.cloudFormationTemplateHash).not.toEqual( 40 | state2.data.cloudFormationTemplateHash 41 | ); 42 | expect(state1.data.serverlessConfigHash).toEqual( 43 | state2.data.serverlessConfigHash 44 | ); 45 | }); 46 | 47 | test("serverless-custom-package-path-%version-string%", async () => { 48 | const [state1, state2] = await runIncrementalSlsCmds( 49 | __dirname, 50 | ["handler.js", "edge.js", path.join("layer", "layer.txt")], 51 | "sls-output" 52 | ); 53 | 54 | expect(state1.data.layers[0]).toEqual({ 55 | name: "layer", 56 | artifact: "sls-output/layer.zip", 57 | }); 58 | expect(state1.data.functions[0]).toEqual({ 59 | name: "serverless-seed-test-dev-hello", 60 | artifact: "sls-output/serverless-seed-test.zip", 61 | }); 62 | expect(state1.data.functions[1]).toEqual({ 63 | name: "serverless-seed-test-dev-prebuilt", 64 | artifact: "functions/prebuilt.zip", 65 | }); 66 | expect(state1.data.functions[2]).toEqual({ 67 | name: "serverless-seed-test-dev-cfLambda", 68 | artifact: "sls-output/serverless-seed-test.zip", 69 | }); 70 | expect(state1.data.cloudFormationTemplateHash).not.toEqual( 71 | state2.data.cloudFormationTemplateHash 72 | ); 73 | expect(state1.data.serverlessConfigHash).toEqual( 74 | state2.data.serverlessConfigHash 75 | ); 76 | }); 77 | -------------------------------------------------------------------------------- /tests/serverless-versions/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-seed-test 2 | 3 | frameworkVersion: "%version%" 4 | 5 | plugins: 6 | - '../../index' 7 | 8 | custom: 9 | seed: 10 | incremental: 11 | enabled: true 12 | 13 | provider: 14 | name: aws 15 | runtime: nodejs10.x 16 | stage: dev 17 | region: us-east-1 18 | 19 | layers: 20 | layer: 21 | path: layer 22 | 23 | functions: 24 | hello: 25 | handler: handler.hello 26 | events: 27 | - http: 28 | path: hello 29 | method: get 30 | 31 | prebuilt: 32 | handler: prebuilt.hello 33 | package: 34 | artifact: functions/prebuilt.zip 35 | 36 | cfLambda: 37 | handler: edge.main 38 | events: 39 | - cloudFront: 40 | eventType: viewer-response 41 | origin: "s3://lambda-edge-seed-test.s3.amazonaws.com" 42 | --------------------------------------------------------------------------------