├── .github └── workflows │ ├── ci.yml │ └── codeql-analysis.yml ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── bin ├── auto-cdk └── auto-cdk.ts ├── cli ├── build.ts └── dev.ts ├── docs ├── CLI.md └── examples │ ├── api-with-existing-cdk-app │ ├── api │ │ └── index.ts │ ├── app.ts │ ├── cdk.json │ └── package.json │ ├── api-with-greedy-proxy │ ├── README.md │ ├── api │ │ └── {proxy+} │ │ │ └── index.ts │ └── package.json │ ├── api-with-parametized-path │ ├── api │ │ └── {id} │ │ │ └── index.ts │ └── package.json │ └── api-with-single-handler-all-methods │ ├── .gitignore │ ├── README.md │ ├── api │ ├── index.ts │ ├── posts │ │ └── index.ts │ └── users │ │ └── index.ts │ ├── package.json │ └── sample-template.json ├── lib ├── autocdk.ts ├── build │ ├── index.ts │ └── webpack │ │ ├── compiler.ts │ │ ├── config.ts │ │ └── entrypoints.ts ├── config.ts ├── index.ts ├── resources.ts ├── routes.ts └── utils.ts ├── package.json ├── test ├── autocdk.test.ts ├── build │ └── webpack │ │ └── entries.test.ts ├── mock │ └── api │ │ ├── another │ │ └── test.ts │ │ ├── index.ts │ │ └── {id} │ │ ├── index.ts │ │ └── settings.ts ├── routes.test.ts └── utils.test.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CICD 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Setup Node 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: 12.x 16 | registry-url: 'https://registry.npmjs.org' 17 | - run: npm i -g yarn 18 | - run: yarn 19 | - run: yarn test 20 | - run: yarn lint 21 | - run: yarn build 22 | - run: yarn publish 23 | env: 24 | NODE_AUTH_TOKEN: ${{ secrets.YARN_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | branches: [master, ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 20 * * 3' 11 | 12 | jobs: 13 | CodeQL-Build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | with: 21 | # We must fetch at least the immediate parents so that if this is 22 | # a pull request then we can checkout the head. 23 | fetch-depth: 2 24 | 25 | # If this run was triggered by a pull request event, then checkout 26 | # the head of the pull request instead of the merge commit. 27 | - run: git checkout HEAD^2 28 | if: ${{ github.event_name == 'pull_request' }} 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v1 33 | # Override language selection by uncommenting this and choosing your languages 34 | # with: 35 | # languages: go, javascript, csharp, python, cpp, java 36 | 37 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 38 | # If this step fails, then you should remove it and run the build manually (see below) 39 | - name: Autobuild 40 | uses: github/codeql-action/autobuild@v1 41 | 42 | # ℹ️ Command-line programs to run using the OS shell. 43 | # 📚 https://git.io/JvXDl 44 | 45 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 46 | # and modify them (or add more) to build your code if your project 47 | # uses a compiled language 48 | 49 | #- run: | 50 | # make bootstrap 51 | # make release 52 | 53 | - name: Perform CodeQL Analysis 54 | uses: github/codeql-action/analyze@v1 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .DS_Store 3 | */**/*.js 4 | node_modules 5 | */**/*.d.ts 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing! 4 | 5 | The below document describes how to build and test the project and submit your contributions. 6 | 7 | * [Getting Started](#getting-started) 8 | * [Pull Requests](#pull-requests) 9 | * [Tools](#tools) 10 | * [Troubleshooting](#troubleshooting) 11 | 12 | ## Getting Started 13 | 14 | The basic commands to get the repository cloned and built locally follow: 15 | 16 | ```bash 17 | $ git clone https://github.com/wulfmann/auto-cdk.git 18 | $ cd auto-cdk 19 | $ yarn install 20 | $ yarn build 21 | ``` 22 | 23 | ### Linking 24 | 25 | Since a lot of the functionality depends on the filesystem structure, it's usually easier to [link the project](https://classic.yarnpkg.com/en/docs/cli/link/)locally so that you can iterate while using it in a spearate place. 26 | 27 | You can do this by running: 28 | 29 | ```bash 30 | $ yarn link 31 | ``` 32 | 33 | Now you can create a separate node project somewhere else by running the following in a new directory and following the prompts: 34 | 35 | ```bash 36 | $ yarn init 37 | $ yarn add auto-cdk 38 | $ yarn link auto-cdk 39 | ``` 40 | 41 | Now changes you make to `auto-cdk` will reflect in the new project. It is recommended that you run `auto-cdk` in [watch mode](#watch-mode) while developing. 42 | 43 | To unlink later you can run: 44 | 45 | ```bash 46 | $ yarn unlink auto-cdk 47 | ``` 48 | 49 | ### Watching 50 | 51 | You can enable live-compiling by running: 52 | 53 | ```bash 54 | $ yarn watch 55 | ``` 56 | 57 | ## Pull Requests 58 | 59 | ### Open Issue 60 | 61 | ### Add your Changss 62 | 63 | ### Tests 64 | 65 | ### Commit 66 | 67 | ### Pull Request 68 | 69 | ### Merge 70 | 71 | ## Tools 72 | 73 | This section describes some of the tools this project uses for building, testing and maintaining code quality. 74 | 75 | ### Linting 76 | 77 | This project uses [TSLint](https://palantir.github.io/tslint/) for code linting. 78 | 79 | To check if any files have bad formatting run: 80 | 81 | ```bash 82 | $ yarn lint 83 | ``` 84 | 85 | To auto-fix linting issues (ones that can anyways) run: 86 | 87 | ```bash 88 | $ yarn lint --fix 89 | ``` 90 | 91 | ### Formatting 92 | 93 | This project uses [Prettier](https://prettier.io) for code formatting. 94 | 95 | To check if any files have bad formatting run: 96 | 97 | ```bash 98 | $ yarn format --check 99 | ``` 100 | 101 | To allow the formatter to modify files run: 102 | 103 | ```bash 104 | $ yarn format --write 105 | ``` 106 | 107 | ### Testing 108 | 109 | This project uses [Jest](https://jestjs.io) for testing. 110 | 111 | To run the tests, run the following: 112 | 113 | ```bash 114 | $ yarn test 115 | ``` 116 | 117 | ## Troubleshooting 118 | 119 | Ensure that you can reproduce your issue after doing a complete rebuild: 120 | 121 | ```bash 122 | $ git clean -fqdx . 123 | $ yarn build 124 | ``` 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto CDK 2 | 3 | `auto-cdk` lets you generate an api gateway with lambda integrations based on the filesystem. It makes use of [AWS CDK](https://aws.amazon.com/cdk/) to generate cloudformation stacks, and [Webpack](https://webpack.js.org) for bundling and code-splitting. 4 | 5 | **Caveats** 6 | 7 | Currently this project only aims to build and package node/typescript-based integrations. It is on the roadmap to support more, but will not be available until a later version. 8 | 9 | ## Quickstart 10 | 11 | ```bash 12 | yarn add auto-cdk 13 | ``` 14 | 15 | Create an `api` directory and add a file to it that exports a function named `handler`: 16 | 17 | ```bash 18 | $ mkdir api && touch api/index.ts 19 | ``` 20 | 21 | ```js 22 | // api/index.ts 23 | 24 | exports.handler = (event, ctx) => { 25 | return { 26 | statusCode: 200, 27 | body: 'hello world', 28 | headers: { 29 | 'Content-Type': 'text/html' 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | Run: 36 | 37 | ```bash 38 | $ yarn dev 39 | ``` 40 | 41 | You should now have webpack auto-compiling when your source changes, and a cdk stack that has been generated in `cdk.out`. 42 | 43 | ### Bonus 44 | 45 | If you install [AWS sam-cli](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html), you can run the api on localhost with the following: 46 | 47 | ```bash 48 | $ sam local start-api cdk.out/*.template.json 49 | ``` 50 | 51 | View more examples [here](/docs/examples). 52 | 53 | ## Features 54 | 55 | * Automatic CDK Stack Generation 56 | * Code-Splitting and Bundling with Webpack 57 | * Out of the box typescript support 58 | 59 | ## Contributing 60 | 61 | If you would like to make a contribution or learn more about running this project locally, please review the [Contributing Documentation](/CONTRIBUTING.md). -------------------------------------------------------------------------------- /bin/auto-cdk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./auto-cdk.js') 3 | -------------------------------------------------------------------------------- /bin/auto-cdk.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as arg from 'arg'; 3 | 4 | const defaultCommand = 'dev'; 5 | 6 | export type Command = (argv?: string[]) => void; 7 | 8 | export type CommandMap = { 9 | [command: string]: () => Promise 10 | } 11 | 12 | const commands: CommandMap = { 13 | build: async () => await import('../cli/build').then((i) => i.build), 14 | dev: async () => await import('../cli/dev').then((i) => i.dev) 15 | }; 16 | 17 | const argMap = { 18 | // Types 19 | '--help': Boolean, 20 | '--version': Boolean, 21 | '--verbose': arg.COUNT, 22 | 23 | // Aliases 24 | '-v': '--verbose' 25 | }; 26 | 27 | const args = arg(argMap); 28 | 29 | if (args['--version']) { 30 | console.log(`auto-cdk v`); 31 | process.exit(0); 32 | } 33 | 34 | const foundCommand = Boolean(commands[args._[0]]); 35 | const command = foundCommand ? args._[0] : defaultCommand; 36 | const forwardedArgs = foundCommand ? args._.slice(1) : args._; 37 | 38 | if (args['--help']) { 39 | forwardedArgs.push('--help') 40 | } 41 | 42 | commands[command]().then((exec) => exec(forwardedArgs)); -------------------------------------------------------------------------------- /cli/build.ts: -------------------------------------------------------------------------------- 1 | import { basename } from 'path'; 2 | import { AutoCdk } from '../lib'; 3 | import { builder } from '../lib/build'; 4 | import { Environment } from '../lib/config'; 5 | 6 | export const build = async (args: any): Promise => { 7 | try { 8 | const appName = basename(process.cwd()); 9 | const app = new AutoCdk(appName, Environment.PRODUCTION); 10 | await builder(app.config); 11 | await app.constructResources(); 12 | app.synth(); 13 | } catch (e) { 14 | console.error(e); 15 | return Promise.reject(); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /cli/dev.ts: -------------------------------------------------------------------------------- 1 | import { basename } from 'path'; 2 | import { AutoCdk } from '../lib'; 3 | import { builder } from '../lib/build'; 4 | import { Environment } from '../lib/config'; 5 | 6 | export const dev = async (args: any): Promise => { 7 | try { 8 | const appName = basename(process.cwd()); 9 | const app = new AutoCdk(appName, Environment.DEVELOPMENT); 10 | await builder(app.config); 11 | await app.constructResources(); 12 | app.synth(); 13 | } catch (e) { 14 | console.error(e); 15 | return Promise.reject(); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /docs/CLI.md: -------------------------------------------------------------------------------- 1 | # CLI 2 | 3 | This document serves as a reference for the `auto-cdk` cli. 4 | 5 | ## Usage 6 | 7 | ```text 8 | auto-cdk [subcommand] [options] 9 | ``` 10 | 11 | ## Global Options 12 | 13 | |----|-----------|--------|-------| 14 | |name|description|required|default| 15 | |--version|Displays the version of `auto-cdk`|false|none| 16 | |--debug|Runs `auto-cdk` with debug logging|false|none| 17 | |--help|Displays the help text|false|none| 18 | 19 | ## Subcommands 20 | 21 | ### build 22 | 23 | |----|-----------|--------|-------| 24 | |name|description|required|default| 25 | 26 | ### dev 27 | 28 | |----|-----------|--------|-------| 29 | |name|description|required|default| 30 | 31 | ## Configuration 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/examples/api-with-existing-cdk-app/api/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulfmann/auto-cdk/9bbbaaf1fc535c88bb7cf8551700fbbba8f00c06/docs/examples/api-with-existing-cdk-app/api/index.ts -------------------------------------------------------------------------------- /docs/examples/api-with-existing-cdk-app/app.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk'; 2 | import { AutoCdk } from 'auto-cdk'; 3 | 4 | const app = new cdk.App(); 5 | const stack = new cdk.Stack(); 6 | 7 | new AutoCdk('my-api', { 8 | app, 9 | stack 10 | }).build(): 11 | -------------------------------------------------------------------------------- /docs/examples/api-with-existing-cdk-app/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node app.ts" 3 | } -------------------------------------------------------------------------------- /docs/examples/api-with-existing-cdk-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-with-existing-cdk-app", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "dev": "auto-cdk dev", 7 | "build": "auto-cdk build" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "auto-cdk": "^0.1.1", 12 | "aws-cdk": "" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/examples/api-with-greedy-proxy/README.md: -------------------------------------------------------------------------------- 1 | # API with a Greedy Proxy 2 | 3 | You can add a [greedy resource](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html) by adding a directory named `{proxy+}` to your api structure. You can learn more about proxy resources in the [AWS Documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html). 4 | 5 | -------------------------------------------------------------------------------- /docs/examples/api-with-greedy-proxy/api/{proxy+}/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulfmann/auto-cdk/9bbbaaf1fc535c88bb7cf8551700fbbba8f00c06/docs/examples/api-with-greedy-proxy/api/{proxy+}/index.ts -------------------------------------------------------------------------------- /docs/examples/api-with-greedy-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-with-greedy-proxy", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "dev": "auto-cdk dev", 7 | "build": "auto-cdk build" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "auto-cdk": "^0.1.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/examples/api-with-parametized-path/api/{id}/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulfmann/auto-cdk/9bbbaaf1fc535c88bb7cf8551700fbbba8f00c06/docs/examples/api-with-parametized-path/api/{id}/index.ts -------------------------------------------------------------------------------- /docs/examples/api-with-parametized-path/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-with-parametized-path", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "dev": "auto-cdk dev", 7 | "build": "auto-cdk build" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "auto-cdk": "^0.1.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/examples/api-with-single-handler-all-methods/.gitignore: -------------------------------------------------------------------------------- 1 | cdk.out/ 2 | dist/ -------------------------------------------------------------------------------- /docs/examples/api-with-single-handler-all-methods/README.md: -------------------------------------------------------------------------------- 1 | # Example API with auto-cdk 2 | 3 | This example has a simple api in the `api` directory and a dependency on `auto-cdk`. 4 | 5 | You can start the webpack compiler in watch mode by running: `yarn dev`. 6 | 7 | You can build for production with: `yarn build`. 8 | 9 | If you have [sam-cli](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) you can run `yarn dev & yarn sam` to start both in watch mode and get live reload for the api running on localhost. 10 | -------------------------------------------------------------------------------- /docs/examples/api-with-single-handler-all-methods/api/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulfmann/auto-cdk/9bbbaaf1fc535c88bb7cf8551700fbbba8f00c06/docs/examples/api-with-single-handler-all-methods/api/index.ts -------------------------------------------------------------------------------- /docs/examples/api-with-single-handler-all-methods/api/posts/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulfmann/auto-cdk/9bbbaaf1fc535c88bb7cf8551700fbbba8f00c06/docs/examples/api-with-single-handler-all-methods/api/posts/index.ts -------------------------------------------------------------------------------- /docs/examples/api-with-single-handler-all-methods/api/users/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulfmann/auto-cdk/9bbbaaf1fc535c88bb7cf8551700fbbba8f00c06/docs/examples/api-with-single-handler-all-methods/api/users/index.ts -------------------------------------------------------------------------------- /docs/examples/api-with-single-handler-all-methods/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-with-single-handler-all-methods", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "dev": "auto-cdk dev", 7 | "build": "auto-cdk build", 8 | "sam": "sam local start-api -t cdk.out/api.template.json" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "auto-cdk": "^0.1.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/examples/api-with-single-handler-all-methods/sample-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "Resources": { 3 | "apiC8550315": { 4 | "Type": "AWS::ApiGateway::RestApi", 5 | "Properties": { 6 | "Name": "api" 7 | } 8 | }, 9 | "apiCloudWatchRoleAC81D93E": { 10 | "Type": "AWS::IAM::Role", 11 | "Properties": { 12 | "AssumeRolePolicyDocument": { 13 | "Statement": [ 14 | { 15 | "Action": "sts:AssumeRole", 16 | "Effect": "Allow", 17 | "Principal": { 18 | "Service": "apigateway.amazonaws.com" 19 | } 20 | } 21 | ], 22 | "Version": "2012-10-17" 23 | }, 24 | "ManagedPolicyArns": [ 25 | { 26 | "Fn::Join": [ 27 | "", 28 | [ 29 | "arn:", 30 | { 31 | "Ref": "AWS::Partition" 32 | }, 33 | ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" 34 | ] 35 | ] 36 | } 37 | ] 38 | } 39 | }, 40 | "apiAccount57E28B43": { 41 | "Type": "AWS::ApiGateway::Account", 42 | "Properties": { 43 | "CloudWatchRoleArn": { 44 | "Fn::GetAtt": [ 45 | "apiCloudWatchRoleAC81D93E", 46 | "Arn" 47 | ] 48 | } 49 | }, 50 | "DependsOn": [ 51 | "apiC8550315" 52 | ] 53 | }, 54 | "apiDeployment149F129457e6bb80d0bc1859bf31179be6efb638": { 55 | "Type": "AWS::ApiGateway::Deployment", 56 | "Properties": { 57 | "RestApiId": { 58 | "Ref": "apiC8550315" 59 | }, 60 | "Description": "Automatically created by the RestApi construct" 61 | }, 62 | "DependsOn": [ 63 | "apiANY4728F8A3", 64 | "apipostsANY8A1EF778", 65 | "apiposts2859138C", 66 | "apiA9E0BAD5", 67 | "apiusersANY4A3D9359", 68 | "apiusers90C8981D" 69 | ] 70 | }, 71 | "apiDeploymentStageprod896C8101": { 72 | "Type": "AWS::ApiGateway::Stage", 73 | "Properties": { 74 | "RestApiId": { 75 | "Ref": "apiC8550315" 76 | }, 77 | "DeploymentId": { 78 | "Ref": "apiDeployment149F129457e6bb80d0bc1859bf31179be6efb638" 79 | }, 80 | "StageName": "prod" 81 | } 82 | }, 83 | "apiA9E0BAD5": { 84 | "Type": "AWS::ApiGateway::Resource", 85 | "Properties": { 86 | "ParentId": { 87 | "Fn::GetAtt": [ 88 | "apiC8550315", 89 | "RootResourceId" 90 | ] 91 | }, 92 | "PathPart": "api", 93 | "RestApiId": { 94 | "Ref": "apiC8550315" 95 | } 96 | } 97 | }, 98 | "apiposts2859138C": { 99 | "Type": "AWS::ApiGateway::Resource", 100 | "Properties": { 101 | "ParentId": { 102 | "Ref": "apiA9E0BAD5" 103 | }, 104 | "PathPart": "posts", 105 | "RestApiId": { 106 | "Ref": "apiC8550315" 107 | } 108 | } 109 | }, 110 | "apipostsANYApiPermissionapi4F59AA66ANYapiposts5FF718E9": { 111 | "Type": "AWS::Lambda::Permission", 112 | "Properties": { 113 | "Action": "lambda:InvokeFunction", 114 | "FunctionName": { 115 | "Fn::GetAtt": [ 116 | "apiapipostsANYFunctionFunction80280F8D", 117 | "Arn" 118 | ] 119 | }, 120 | "Principal": "apigateway.amazonaws.com", 121 | "SourceArn": { 122 | "Fn::Join": [ 123 | "", 124 | [ 125 | "arn:", 126 | { 127 | "Ref": "AWS::Partition" 128 | }, 129 | ":execute-api:", 130 | { 131 | "Ref": "AWS::Region" 132 | }, 133 | ":", 134 | { 135 | "Ref": "AWS::AccountId" 136 | }, 137 | ":", 138 | { 139 | "Ref": "apiC8550315" 140 | }, 141 | "/", 142 | { 143 | "Ref": "apiDeploymentStageprod896C8101" 144 | }, 145 | "/*/api/posts" 146 | ] 147 | ] 148 | } 149 | } 150 | }, 151 | "apipostsANYApiPermissionTestapi4F59AA66ANYapipostsEDECFA79": { 152 | "Type": "AWS::Lambda::Permission", 153 | "Properties": { 154 | "Action": "lambda:InvokeFunction", 155 | "FunctionName": { 156 | "Fn::GetAtt": [ 157 | "apiapipostsANYFunctionFunction80280F8D", 158 | "Arn" 159 | ] 160 | }, 161 | "Principal": "apigateway.amazonaws.com", 162 | "SourceArn": { 163 | "Fn::Join": [ 164 | "", 165 | [ 166 | "arn:", 167 | { 168 | "Ref": "AWS::Partition" 169 | }, 170 | ":execute-api:", 171 | { 172 | "Ref": "AWS::Region" 173 | }, 174 | ":", 175 | { 176 | "Ref": "AWS::AccountId" 177 | }, 178 | ":", 179 | { 180 | "Ref": "apiC8550315" 181 | }, 182 | "/test-invoke-stage/*/api/posts" 183 | ] 184 | ] 185 | } 186 | } 187 | }, 188 | "apipostsANY8A1EF778": { 189 | "Type": "AWS::ApiGateway::Method", 190 | "Properties": { 191 | "HttpMethod": "ANY", 192 | "ResourceId": { 193 | "Ref": "apiposts2859138C" 194 | }, 195 | "RestApiId": { 196 | "Ref": "apiC8550315" 197 | }, 198 | "AuthorizationType": "NONE", 199 | "Integration": { 200 | "IntegrationHttpMethod": "POST", 201 | "Type": "AWS_PROXY", 202 | "Uri": { 203 | "Fn::Join": [ 204 | "", 205 | [ 206 | "arn:", 207 | { 208 | "Ref": "AWS::Partition" 209 | }, 210 | ":apigateway:", 211 | { 212 | "Ref": "AWS::Region" 213 | }, 214 | ":lambda:path/2015-03-31/functions/", 215 | { 216 | "Fn::GetAtt": [ 217 | "apiapipostsANYFunctionFunction80280F8D", 218 | "Arn" 219 | ] 220 | }, 221 | "/invocations" 222 | ] 223 | ] 224 | } 225 | } 226 | } 227 | }, 228 | "apiusers90C8981D": { 229 | "Type": "AWS::ApiGateway::Resource", 230 | "Properties": { 231 | "ParentId": { 232 | "Ref": "apiA9E0BAD5" 233 | }, 234 | "PathPart": "users", 235 | "RestApiId": { 236 | "Ref": "apiC8550315" 237 | } 238 | } 239 | }, 240 | "apiusersANYApiPermissionapi4F59AA66ANYapiusersBB57DBD7": { 241 | "Type": "AWS::Lambda::Permission", 242 | "Properties": { 243 | "Action": "lambda:InvokeFunction", 244 | "FunctionName": { 245 | "Fn::GetAtt": [ 246 | "apiapiusersANYFunctionFunction27017680", 247 | "Arn" 248 | ] 249 | }, 250 | "Principal": "apigateway.amazonaws.com", 251 | "SourceArn": { 252 | "Fn::Join": [ 253 | "", 254 | [ 255 | "arn:", 256 | { 257 | "Ref": "AWS::Partition" 258 | }, 259 | ":execute-api:", 260 | { 261 | "Ref": "AWS::Region" 262 | }, 263 | ":", 264 | { 265 | "Ref": "AWS::AccountId" 266 | }, 267 | ":", 268 | { 269 | "Ref": "apiC8550315" 270 | }, 271 | "/", 272 | { 273 | "Ref": "apiDeploymentStageprod896C8101" 274 | }, 275 | "/*/api/users" 276 | ] 277 | ] 278 | } 279 | } 280 | }, 281 | "apiusersANYApiPermissionTestapi4F59AA66ANYapiusers915EE57D": { 282 | "Type": "AWS::Lambda::Permission", 283 | "Properties": { 284 | "Action": "lambda:InvokeFunction", 285 | "FunctionName": { 286 | "Fn::GetAtt": [ 287 | "apiapiusersANYFunctionFunction27017680", 288 | "Arn" 289 | ] 290 | }, 291 | "Principal": "apigateway.amazonaws.com", 292 | "SourceArn": { 293 | "Fn::Join": [ 294 | "", 295 | [ 296 | "arn:", 297 | { 298 | "Ref": "AWS::Partition" 299 | }, 300 | ":execute-api:", 301 | { 302 | "Ref": "AWS::Region" 303 | }, 304 | ":", 305 | { 306 | "Ref": "AWS::AccountId" 307 | }, 308 | ":", 309 | { 310 | "Ref": "apiC8550315" 311 | }, 312 | "/test-invoke-stage/*/api/users" 313 | ] 314 | ] 315 | } 316 | } 317 | }, 318 | "apiusersANY4A3D9359": { 319 | "Type": "AWS::ApiGateway::Method", 320 | "Properties": { 321 | "HttpMethod": "ANY", 322 | "ResourceId": { 323 | "Ref": "apiusers90C8981D" 324 | }, 325 | "RestApiId": { 326 | "Ref": "apiC8550315" 327 | }, 328 | "AuthorizationType": "NONE", 329 | "Integration": { 330 | "IntegrationHttpMethod": "POST", 331 | "Type": "AWS_PROXY", 332 | "Uri": { 333 | "Fn::Join": [ 334 | "", 335 | [ 336 | "arn:", 337 | { 338 | "Ref": "AWS::Partition" 339 | }, 340 | ":apigateway:", 341 | { 342 | "Ref": "AWS::Region" 343 | }, 344 | ":lambda:path/2015-03-31/functions/", 345 | { 346 | "Fn::GetAtt": [ 347 | "apiapiusersANYFunctionFunction27017680", 348 | "Arn" 349 | ] 350 | }, 351 | "/invocations" 352 | ] 353 | ] 354 | } 355 | } 356 | } 357 | }, 358 | "apiANYApiPermissionapi4F59AA66ANYapiFA540F0D": { 359 | "Type": "AWS::Lambda::Permission", 360 | "Properties": { 361 | "Action": "lambda:InvokeFunction", 362 | "FunctionName": { 363 | "Fn::GetAtt": [ 364 | "apiapiANYFunctionFunctionD0B038B8", 365 | "Arn" 366 | ] 367 | }, 368 | "Principal": "apigateway.amazonaws.com", 369 | "SourceArn": { 370 | "Fn::Join": [ 371 | "", 372 | [ 373 | "arn:", 374 | { 375 | "Ref": "AWS::Partition" 376 | }, 377 | ":execute-api:", 378 | { 379 | "Ref": "AWS::Region" 380 | }, 381 | ":", 382 | { 383 | "Ref": "AWS::AccountId" 384 | }, 385 | ":", 386 | { 387 | "Ref": "apiC8550315" 388 | }, 389 | "/", 390 | { 391 | "Ref": "apiDeploymentStageprod896C8101" 392 | }, 393 | "/*/api" 394 | ] 395 | ] 396 | } 397 | } 398 | }, 399 | "apiANYApiPermissionTestapi4F59AA66ANYapi8313EDBB": { 400 | "Type": "AWS::Lambda::Permission", 401 | "Properties": { 402 | "Action": "lambda:InvokeFunction", 403 | "FunctionName": { 404 | "Fn::GetAtt": [ 405 | "apiapiANYFunctionFunctionD0B038B8", 406 | "Arn" 407 | ] 408 | }, 409 | "Principal": "apigateway.amazonaws.com", 410 | "SourceArn": { 411 | "Fn::Join": [ 412 | "", 413 | [ 414 | "arn:", 415 | { 416 | "Ref": "AWS::Partition" 417 | }, 418 | ":execute-api:", 419 | { 420 | "Ref": "AWS::Region" 421 | }, 422 | ":", 423 | { 424 | "Ref": "AWS::AccountId" 425 | }, 426 | ":", 427 | { 428 | "Ref": "apiC8550315" 429 | }, 430 | "/test-invoke-stage/*/api" 431 | ] 432 | ] 433 | } 434 | } 435 | }, 436 | "apiANY4728F8A3": { 437 | "Type": "AWS::ApiGateway::Method", 438 | "Properties": { 439 | "HttpMethod": "ANY", 440 | "ResourceId": { 441 | "Ref": "apiA9E0BAD5" 442 | }, 443 | "RestApiId": { 444 | "Ref": "apiC8550315" 445 | }, 446 | "AuthorizationType": "NONE", 447 | "Integration": { 448 | "IntegrationHttpMethod": "POST", 449 | "Type": "AWS_PROXY", 450 | "Uri": { 451 | "Fn::Join": [ 452 | "", 453 | [ 454 | "arn:", 455 | { 456 | "Ref": "AWS::Partition" 457 | }, 458 | ":apigateway:", 459 | { 460 | "Ref": "AWS::Region" 461 | }, 462 | ":lambda:path/2015-03-31/functions/", 463 | { 464 | "Fn::GetAtt": [ 465 | "apiapiANYFunctionFunctionD0B038B8", 466 | "Arn" 467 | ] 468 | }, 469 | "/invocations" 470 | ] 471 | ] 472 | } 473 | } 474 | } 475 | }, 476 | "apiapipostsANYFunctionFunctionServiceRoleB4A84966": { 477 | "Type": "AWS::IAM::Role", 478 | "Properties": { 479 | "AssumeRolePolicyDocument": { 480 | "Statement": [ 481 | { 482 | "Action": "sts:AssumeRole", 483 | "Effect": "Allow", 484 | "Principal": { 485 | "Service": "lambda.amazonaws.com" 486 | } 487 | } 488 | ], 489 | "Version": "2012-10-17" 490 | }, 491 | "ManagedPolicyArns": [ 492 | { 493 | "Fn::Join": [ 494 | "", 495 | [ 496 | "arn:", 497 | { 498 | "Ref": "AWS::Partition" 499 | }, 500 | ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 501 | ] 502 | ] 503 | } 504 | ] 505 | } 506 | }, 507 | "apiapipostsANYFunctionFunction80280F8D": { 508 | "Type": "AWS::Lambda::Function", 509 | "Properties": { 510 | "Code": { 511 | "S3Bucket": { 512 | "Ref": "AssetParameters37f0f4416bf3508e811a5895e5da4c0d45caaf69176a33c055e5f0a63c3ced7dS3Bucket54C64172" 513 | }, 514 | "S3Key": { 515 | "Fn::Join": [ 516 | "", 517 | [ 518 | { 519 | "Fn::Select": [ 520 | 0, 521 | { 522 | "Fn::Split": [ 523 | "||", 524 | { 525 | "Ref": "AssetParameters37f0f4416bf3508e811a5895e5da4c0d45caaf69176a33c055e5f0a63c3ced7dS3VersionKeyE0C74E6B" 526 | } 527 | ] 528 | } 529 | ] 530 | }, 531 | { 532 | "Fn::Select": [ 533 | 1, 534 | { 535 | "Fn::Split": [ 536 | "||", 537 | { 538 | "Ref": "AssetParameters37f0f4416bf3508e811a5895e5da4c0d45caaf69176a33c055e5f0a63c3ced7dS3VersionKeyE0C74E6B" 539 | } 540 | ] 541 | } 542 | ] 543 | } 544 | ] 545 | ] 546 | } 547 | }, 548 | "Handler": "index.handler", 549 | "Role": { 550 | "Fn::GetAtt": [ 551 | "apiapipostsANYFunctionFunctionServiceRoleB4A84966", 552 | "Arn" 553 | ] 554 | }, 555 | "Runtime": "nodejs12.x" 556 | }, 557 | "DependsOn": [ 558 | "apiapipostsANYFunctionFunctionServiceRoleB4A84966" 559 | ] 560 | }, 561 | "apiapiusersANYFunctionFunctionServiceRoleD705655D": { 562 | "Type": "AWS::IAM::Role", 563 | "Properties": { 564 | "AssumeRolePolicyDocument": { 565 | "Statement": [ 566 | { 567 | "Action": "sts:AssumeRole", 568 | "Effect": "Allow", 569 | "Principal": { 570 | "Service": "lambda.amazonaws.com" 571 | } 572 | } 573 | ], 574 | "Version": "2012-10-17" 575 | }, 576 | "ManagedPolicyArns": [ 577 | { 578 | "Fn::Join": [ 579 | "", 580 | [ 581 | "arn:", 582 | { 583 | "Ref": "AWS::Partition" 584 | }, 585 | ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 586 | ] 587 | ] 588 | } 589 | ] 590 | } 591 | }, 592 | "apiapiusersANYFunctionFunction27017680": { 593 | "Type": "AWS::Lambda::Function", 594 | "Properties": { 595 | "Code": { 596 | "S3Bucket": { 597 | "Ref": "AssetParametersc0642791fd57051c310f540e9c1eef09e0883c4e8bfd3c4ce4b749cd8da0a4b4S3Bucket6D64E14D" 598 | }, 599 | "S3Key": { 600 | "Fn::Join": [ 601 | "", 602 | [ 603 | { 604 | "Fn::Select": [ 605 | 0, 606 | { 607 | "Fn::Split": [ 608 | "||", 609 | { 610 | "Ref": "AssetParametersc0642791fd57051c310f540e9c1eef09e0883c4e8bfd3c4ce4b749cd8da0a4b4S3VersionKey84C79F22" 611 | } 612 | ] 613 | } 614 | ] 615 | }, 616 | { 617 | "Fn::Select": [ 618 | 1, 619 | { 620 | "Fn::Split": [ 621 | "||", 622 | { 623 | "Ref": "AssetParametersc0642791fd57051c310f540e9c1eef09e0883c4e8bfd3c4ce4b749cd8da0a4b4S3VersionKey84C79F22" 624 | } 625 | ] 626 | } 627 | ] 628 | } 629 | ] 630 | ] 631 | } 632 | }, 633 | "Handler": "index.handler", 634 | "Role": { 635 | "Fn::GetAtt": [ 636 | "apiapiusersANYFunctionFunctionServiceRoleD705655D", 637 | "Arn" 638 | ] 639 | }, 640 | "Runtime": "nodejs12.x" 641 | }, 642 | "DependsOn": [ 643 | "apiapiusersANYFunctionFunctionServiceRoleD705655D" 644 | ] 645 | }, 646 | "apiapiANYFunctionFunctionServiceRoleA46995B7": { 647 | "Type": "AWS::IAM::Role", 648 | "Properties": { 649 | "AssumeRolePolicyDocument": { 650 | "Statement": [ 651 | { 652 | "Action": "sts:AssumeRole", 653 | "Effect": "Allow", 654 | "Principal": { 655 | "Service": "lambda.amazonaws.com" 656 | } 657 | } 658 | ], 659 | "Version": "2012-10-17" 660 | }, 661 | "ManagedPolicyArns": [ 662 | { 663 | "Fn::Join": [ 664 | "", 665 | [ 666 | "arn:", 667 | { 668 | "Ref": "AWS::Partition" 669 | }, 670 | ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 671 | ] 672 | ] 673 | } 674 | ] 675 | } 676 | }, 677 | "apiapiANYFunctionFunctionD0B038B8": { 678 | "Type": "AWS::Lambda::Function", 679 | "Properties": { 680 | "Code": { 681 | "S3Bucket": { 682 | "Ref": "AssetParameters2288a22aa274ce0f1db5773622c8a26b66df89c50881a31343112a309bba2d5bS3Bucket5491774E" 683 | }, 684 | "S3Key": { 685 | "Fn::Join": [ 686 | "", 687 | [ 688 | { 689 | "Fn::Select": [ 690 | 0, 691 | { 692 | "Fn::Split": [ 693 | "||", 694 | { 695 | "Ref": "AssetParameters2288a22aa274ce0f1db5773622c8a26b66df89c50881a31343112a309bba2d5bS3VersionKey85267BD1" 696 | } 697 | ] 698 | } 699 | ] 700 | }, 701 | { 702 | "Fn::Select": [ 703 | 1, 704 | { 705 | "Fn::Split": [ 706 | "||", 707 | { 708 | "Ref": "AssetParameters2288a22aa274ce0f1db5773622c8a26b66df89c50881a31343112a309bba2d5bS3VersionKey85267BD1" 709 | } 710 | ] 711 | } 712 | ] 713 | } 714 | ] 715 | ] 716 | } 717 | }, 718 | "Handler": "index.handler", 719 | "Role": { 720 | "Fn::GetAtt": [ 721 | "apiapiANYFunctionFunctionServiceRoleA46995B7", 722 | "Arn" 723 | ] 724 | }, 725 | "Runtime": "nodejs12.x" 726 | }, 727 | "DependsOn": [ 728 | "apiapiANYFunctionFunctionServiceRoleA46995B7" 729 | ] 730 | } 731 | }, 732 | "Outputs": { 733 | "apiEndpoint9349E63C": { 734 | "Value": { 735 | "Fn::Join": [ 736 | "", 737 | [ 738 | "https://", 739 | { 740 | "Ref": "apiC8550315" 741 | }, 742 | ".execute-api.", 743 | { 744 | "Ref": "AWS::Region" 745 | }, 746 | ".", 747 | { 748 | "Ref": "AWS::URLSuffix" 749 | }, 750 | "/", 751 | { 752 | "Ref": "apiDeploymentStageprod896C8101" 753 | }, 754 | "/" 755 | ] 756 | ] 757 | } 758 | } 759 | }, 760 | "Parameters": { 761 | "AssetParameters37f0f4416bf3508e811a5895e5da4c0d45caaf69176a33c055e5f0a63c3ced7dS3Bucket54C64172": { 762 | "Type": "String", 763 | "Description": "S3 bucket for asset \"37f0f4416bf3508e811a5895e5da4c0d45caaf69176a33c055e5f0a63c3ced7d\"" 764 | }, 765 | "AssetParameters37f0f4416bf3508e811a5895e5da4c0d45caaf69176a33c055e5f0a63c3ced7dS3VersionKeyE0C74E6B": { 766 | "Type": "String", 767 | "Description": "S3 key for asset version \"37f0f4416bf3508e811a5895e5da4c0d45caaf69176a33c055e5f0a63c3ced7d\"" 768 | }, 769 | "AssetParameters37f0f4416bf3508e811a5895e5da4c0d45caaf69176a33c055e5f0a63c3ced7dArtifactHash12862E4F": { 770 | "Type": "String", 771 | "Description": "Artifact hash for asset \"37f0f4416bf3508e811a5895e5da4c0d45caaf69176a33c055e5f0a63c3ced7d\"" 772 | }, 773 | "AssetParametersc0642791fd57051c310f540e9c1eef09e0883c4e8bfd3c4ce4b749cd8da0a4b4S3Bucket6D64E14D": { 774 | "Type": "String", 775 | "Description": "S3 bucket for asset \"c0642791fd57051c310f540e9c1eef09e0883c4e8bfd3c4ce4b749cd8da0a4b4\"" 776 | }, 777 | "AssetParametersc0642791fd57051c310f540e9c1eef09e0883c4e8bfd3c4ce4b749cd8da0a4b4S3VersionKey84C79F22": { 778 | "Type": "String", 779 | "Description": "S3 key for asset version \"c0642791fd57051c310f540e9c1eef09e0883c4e8bfd3c4ce4b749cd8da0a4b4\"" 780 | }, 781 | "AssetParametersc0642791fd57051c310f540e9c1eef09e0883c4e8bfd3c4ce4b749cd8da0a4b4ArtifactHashDB848C20": { 782 | "Type": "String", 783 | "Description": "Artifact hash for asset \"c0642791fd57051c310f540e9c1eef09e0883c4e8bfd3c4ce4b749cd8da0a4b4\"" 784 | }, 785 | "AssetParameters2288a22aa274ce0f1db5773622c8a26b66df89c50881a31343112a309bba2d5bS3Bucket5491774E": { 786 | "Type": "String", 787 | "Description": "S3 bucket for asset \"2288a22aa274ce0f1db5773622c8a26b66df89c50881a31343112a309bba2d5b\"" 788 | }, 789 | "AssetParameters2288a22aa274ce0f1db5773622c8a26b66df89c50881a31343112a309bba2d5bS3VersionKey85267BD1": { 790 | "Type": "String", 791 | "Description": "S3 key for asset version \"2288a22aa274ce0f1db5773622c8a26b66df89c50881a31343112a309bba2d5b\"" 792 | }, 793 | "AssetParameters2288a22aa274ce0f1db5773622c8a26b66df89c50881a31343112a309bba2d5bArtifactHash3DA1EC83": { 794 | "Type": "String", 795 | "Description": "Artifact hash for asset \"2288a22aa274ce0f1db5773622c8a26b66df89c50881a31343112a309bba2d5b\"" 796 | } 797 | } 798 | } -------------------------------------------------------------------------------- /lib/autocdk.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as ag from '@aws-cdk/aws-apigateway'; 3 | import * as lambda from '@aws-cdk/aws-lambda'; 4 | import { constructRouteMap, IRouteMap } from './routes'; 5 | import { constructResourceMap, IResourceMap, IResource, ResourceType } from './resources'; 6 | import { Config, Environment, ConfigProps } from './config'; 7 | import { isNotFound } from './utils'; 8 | 9 | const INDEX = 'index'; 10 | 11 | export type ResourceLike = ag.Resource | ag.IResource; 12 | export type MethodLike = ag.Method; 13 | 14 | export interface AutoCdkProps{ 15 | app?: cdk.App; 16 | stack?: cdk.Stack; 17 | api?: ag.RestApi; 18 | config?: ConfigProps; 19 | } 20 | 21 | export interface CreateIntegrationProps { 22 | runtime?: lambda.Runtime; 23 | handler?: string; 24 | } 25 | 26 | export class AutoCdk { 27 | public readonly app: cdk.App; 28 | public readonly stack: cdk.Stack; 29 | public readonly api: ag.RestApi; 30 | public readonly config: Config; 31 | public routeMap: IRouteMap; 32 | private readonly id: string; 33 | 34 | constructor(id: string, env: Environment, props?: AutoCdkProps) { 35 | this.id = id; 36 | this.config = new Config({ env, ...props?.config }); 37 | 38 | if (this.config.debug) { 39 | console.log(`Using: ${this.config.workingDirectory} as working directory`); 40 | } 41 | 42 | this.app = props?.app || new cdk.App({ 43 | outdir: `${this.config.workingDirectory}/cdk.out` 44 | }); 45 | 46 | this.stack = props?.stack || new cdk.Stack(this.app, id); 47 | this.api = props?.api || new ag.RestApi(this.stack, id); 48 | } 49 | 50 | public synth() { 51 | return this.app.synth(); 52 | } 53 | 54 | public async constructRoutes(): Promise { 55 | if (this.config.debug) { 56 | console.log('Constructing Routes') 57 | } 58 | const routeMap = await constructRouteMap(this.config.rootDirectory, this.config); 59 | this.routeMap = routeMap; 60 | return this.routeMap; 61 | } 62 | 63 | public async constructResources(): Promise { 64 | if (this.config.debug) { 65 | console.log('Constructing Resources') 66 | } 67 | const routes = await this.constructRoutes(); 68 | const resourceMap = constructResourceMap(routes, this.config.rootDirectory, this.config); 69 | this.createResourcesFromMap(this.api.root, resourceMap); 70 | } 71 | 72 | private createResourcesFromMap(parent: ResourceLike, resourceMap: IResourceMap) { 73 | if (resourceMap.type === ResourceType.RESOURCE) { 74 | if (resourceMap.children) { 75 | const resource = this.createResource(parent, resourceMap.name); 76 | const children = resourceMap.children; 77 | Object.keys(children).forEach(child => { 78 | this.createResourcesFromMap(resource, children[child]); 79 | }) 80 | } else { 81 | if (this.config.createEmptyResources) { 82 | this.createResource(parent, resourceMap.name); 83 | } 84 | } 85 | } else { 86 | if (resourceMap.name === INDEX) { 87 | this.createMethod(parent, resourceMap); 88 | } else { 89 | const resource = this.createResource(parent, resourceMap.name); 90 | this.createMethod(resource, resourceMap); 91 | } 92 | } 93 | } 94 | 95 | private createResource(parent: ResourceLike, path: string, options?: ag.ResourceOptions) { 96 | if (this.config.debug) { 97 | console.log(`Creating Resource: ${path}`); 98 | } 99 | 100 | return parent.addResource(path, options); 101 | } 102 | 103 | private createMethod(parent: ResourceLike, resource: IResourceMap, options?: ag.MethodOptions) { 104 | const method = 'ANY'; 105 | 106 | if (this.config.debug) { 107 | console.log(`Attaching Method: ${method}, to Resource: ${parent.path}`); 108 | } 109 | 110 | const logicalId = `${this.id}${parent.path}${method}Function`; 111 | const integration = this.createIntegration(resource, logicalId); 112 | return parent.addMethod(method, integration, options); 113 | } 114 | 115 | private createIntegration(resource: IResourceMap, id: string, props?: CreateIntegrationProps) { 116 | const normalizedAssetDir = this.config.assetDir.endsWith('/') ? this.config.assetDir : `${this.config.assetDir}/`; 117 | const assetPath = `${normalizedAssetDir}${resource.assetPath}`; 118 | const lambdaProps = { 119 | runtime: props?.runtime || this.config.defaultRuntime, 120 | handler: props?.handler || this.config.defaultHandler, 121 | code: lambda.Code.fromAsset(assetPath) 122 | }; 123 | 124 | try { 125 | const fn = this.createLambda(`${id}Function`, lambdaProps); 126 | return new ag.LambdaIntegration(fn); 127 | } catch (e) { 128 | if (isNotFound(e)) { 129 | console.error(`Unable to add lambda integration. ${assetPath} was not found. This most likely means webpack did not run before this process.`); 130 | process.exit(); 131 | } else { 132 | throw e; 133 | } 134 | } 135 | } 136 | 137 | private createLambda(id: string, props: lambda.FunctionProps) { 138 | return new lambda.Function(this.stack, id, props); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/build/index.ts: -------------------------------------------------------------------------------- 1 | import { constructRouteMap } from '../routes'; 2 | import { createEntrypoints } from './webpack/entrypoints'; 3 | import { createConfig } from './webpack/config'; 4 | import { compile } from './webpack/compiler'; 5 | import { Config } from '../config'; 6 | 7 | export async function builder(config: Config): Promise { 8 | const routes = await constructRouteMap(config.rootDirectory, config); 9 | const entrypoints = createEntrypoints(routes, config.rootDirectory, config); 10 | const configs = await createConfig(config, entrypoints, config.env); 11 | return await compile(configs, config.env); 12 | } 13 | -------------------------------------------------------------------------------- /lib/build/webpack/compiler.ts: -------------------------------------------------------------------------------- 1 | import * as webpack from 'webpack'; 2 | import { Configuration, Stats } from 'webpack'; 3 | import { Environment } from '../../config'; 4 | 5 | const compilerHandler = (err: Error, stats: Stats): void => { 6 | if (err) { 7 | const reason = err?.toString() 8 | if (reason) { 9 | throw new Error(reason) 10 | } else { 11 | throw new Error(`There was an error when running the webpack compiler: ${err}`); 12 | } 13 | } 14 | 15 | const info = stats.toJson(); 16 | 17 | if (stats.hasErrors()) { 18 | console.error(info.errors); 19 | } 20 | 21 | if (stats.hasWarnings()) { 22 | console.warn(info.warnings); 23 | } 24 | 25 | console.log(stats.toString({ 26 | chunks: false, // Makes the build much quieter 27 | colors: true // Shows colors in the console 28 | })); 29 | } 30 | 31 | export const compile = (config: Configuration, env: Environment): Promise => { 32 | console.log('Beginning Compile') 33 | return new Promise(async (resolve, reject) => { 34 | try { 35 | const compiler = webpack(config); 36 | if (env === Environment.DEVELOPMENT) { 37 | compiler.watch({ ignored: /node_modules/ }, compilerHandler); 38 | await compiler.hooks.afterEmit.tapAsync('developmentCompiler', () => { 39 | resolve() 40 | }); 41 | } else { 42 | compiler.run(compilerHandler); 43 | await compiler.hooks.afterEmit.tapAsync('productionCompiler', () => { 44 | resolve() 45 | }); 46 | } 47 | } catch (e) { 48 | reject(e); 49 | } 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /lib/build/webpack/config.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { Config } from '../../config'; 3 | import { Configuration, ProgressPlugin } from 'webpack'; 4 | import { Entrypoint } from './entrypoints'; 5 | import { CleanWebpackPlugin } from 'clean-webpack-plugin'; 6 | 7 | /** 8 | * Generates the configuration that webpack will use 9 | */ 10 | export const createDevelopmentConfig = async (config: Config, entry: Entrypoint): Promise => { 11 | return { 12 | mode: 'development', 13 | entry, 14 | output: { 15 | path: join(config.workingDirectory, 'dist'), 16 | filename: '[name]/index.js' 17 | }, 18 | watch: true, 19 | plugins: [ 20 | new ProgressPlugin(), 21 | new CleanWebpackPlugin({ 22 | cleanStaleWebpackAssets: false 23 | }), 24 | ] 25 | }; 26 | } 27 | 28 | 29 | /** 30 | * Generates the configuration that webpack will use 31 | */ 32 | export const createProductionConfig = async (config: Config, entry: Entrypoint): Promise => { 33 | return { 34 | mode: 'production', 35 | entry, 36 | output: { 37 | path: join(config.workingDirectory, 'dist'), 38 | filename: '[name]/index.js' 39 | }, 40 | plugins: [ 41 | new ProgressPlugin(), 42 | new CleanWebpackPlugin(), 43 | ] 44 | }; 45 | } 46 | 47 | export const createConfig = async(config: Config, entry: Entrypoint, env: string): Promise => { 48 | if (env === 'development') { 49 | return createDevelopmentConfig(config, entry); 50 | } else if (env === 'production') { 51 | return createProductionConfig(config, entry); 52 | } else { 53 | return Promise.reject(`expected ${env} to be one of development or production. Not sure how to handle this environment`) 54 | } 55 | } -------------------------------------------------------------------------------- /lib/build/webpack/entrypoints.ts: -------------------------------------------------------------------------------- 1 | import { parse } from 'path'; 2 | import { IRouteMap } from '../../routes'; 3 | import { Config } from '../../config'; 4 | 5 | export interface Entrypoint { 6 | [key: string]: string; 7 | } 8 | 9 | /** 10 | * Converts a IRouteMap to a map of webpack entrypoints 11 | */ 12 | export const createEntrypoints = (routeMap: IRouteMap, targetDirectory: string, config: Config): Entrypoint => { 13 | let result: Entrypoint = {}; 14 | if (routeMap.children) { 15 | for (const key in routeMap.children) { 16 | Object.assign(result, createEntrypoints(routeMap.children[key], targetDirectory, config)); 17 | } 18 | } else { 19 | const parsed = parse(routeMap.path); 20 | let adjustedDir = parsed.dir; 21 | if (config.includeRoot) { 22 | adjustedDir = parsed.dir.startsWith(targetDirectory) ? parsed.dir.slice(targetDirectory.length): parsed.dir; 23 | } 24 | const key = `${adjustedDir}/${parsed.name}` 25 | result[key] = routeMap.relativePath; 26 | } 27 | return result 28 | } 29 | -------------------------------------------------------------------------------- /lib/config.ts: -------------------------------------------------------------------------------- 1 | import { Runtime } from "@aws-cdk/aws-lambda"; 2 | 3 | export enum Environment { 4 | PRODUCTION='production', 5 | DEVELOPMENT='development' 6 | } 7 | 8 | export interface ConfigProps { 9 | /** 10 | * Create resources on the api gateway even when the directory is empty 11 | * @default true 12 | */ 13 | createEmptyResources?: boolean; 14 | 15 | /** 16 | * The working directory 17 | * @default process.cwd() 18 | */ 19 | workingDirectory?: string; 20 | 21 | /** 22 | * The directory that contains the root of the api tree 23 | * @default api 24 | */ 25 | rootDirectory?: string; 26 | 27 | /** 28 | * The default lambda runtime 29 | * @default Runtime.NODEJS_12_X 30 | */ 31 | defaultRuntime?: Runtime; 32 | 33 | /** 34 | * The default lambda handler 35 | * @default Runtime.NODEJS_12_X 36 | */ 37 | defaultHandler?: string; 38 | 39 | /** 40 | * The directory where the lambda assets will be packaged 41 | * @default dist 42 | */ 43 | assetDir?: string; 44 | 45 | /** 46 | * Enable debug mode (verbose logging) 47 | * @default false 48 | */ 49 | debug?: boolean; 50 | 51 | /** 52 | * Include the root directory in the api paths 53 | * @default false 54 | */ 55 | includeRoot?: boolean; 56 | 57 | /** 58 | * The environment 59 | * @default development 60 | */ 61 | env?: Environment; 62 | } 63 | 64 | export class Config { 65 | public readonly createEmptyResources: boolean; 66 | public readonly workingDirectory: string; 67 | public readonly rootDirectory: string; 68 | public readonly defaultRuntime: Runtime; 69 | public readonly defaultHandler: string; 70 | public readonly assetDir: string; 71 | public readonly debug: boolean; 72 | public readonly includeRoot: boolean; 73 | public readonly env: Environment; 74 | 75 | constructor(props?: ConfigProps) { 76 | this.createEmptyResources = props?.createEmptyResources || true; 77 | this.workingDirectory = props?.workingDirectory || process.cwd(); 78 | this.rootDirectory = props?.rootDirectory || 'api'; 79 | this.defaultRuntime = props?.defaultRuntime || Runtime.NODEJS_12_X; 80 | this.defaultHandler = props?.defaultHandler || 'index.handler'; 81 | this.assetDir = props?.assetDir || 'dist'; 82 | this.debug = props?.debug || false; 83 | this.includeRoot = props?.includeRoot || false; 84 | this.env = props?.env || Environment.DEVELOPMENT; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export { AutoCdk, AutoCdkProps } from './autocdk'; 2 | export { IRoute, IRouteMap, RouteType } from './routes'; 3 | export { IResource, IResourceMap, ResourceType } from './resources'; 4 | 5 | -------------------------------------------------------------------------------- /lib/resources.ts: -------------------------------------------------------------------------------- 1 | import { parse } from 'path'; 2 | import { IRouteMap, RouteType } from './routes'; 3 | import { Config } from './config'; 4 | 5 | export enum ResourceType { 6 | RESOURCE = 'RESOURCE', 7 | METHOD = 'METHOD' 8 | } 9 | 10 | export interface IResourceMap { 11 | name: string; 12 | path: string; 13 | assetPath: string; 14 | type: ResourceType; 15 | children?: IResource; 16 | } 17 | 18 | export interface IResource { 19 | [key: string]: IResourceMap; 20 | } 21 | 22 | export const constructResourceMap = (route: IRouteMap, targetDirectory: string, config: Config): IResourceMap => { 23 | if (config.debug) { 24 | console.log(`Constructing ResourceMap for: ${route.name}`); 25 | } 26 | 27 | const parsed = parse(route.path); 28 | 29 | let adjustedDir = parsed.dir; 30 | if (config.includeRoot) { 31 | adjustedDir = parsed.dir.startsWith(targetDirectory) ? parsed.dir.slice(targetDirectory.length): parsed.dir; 32 | } 33 | 34 | const assetPath = `${adjustedDir}/${parsed.name}`; 35 | 36 | if (route.type === RouteType.DIRECTORY) { 37 | const item: IResourceMap = { 38 | name: parsed.name, 39 | path: route.path, 40 | assetPath: assetPath, 41 | type: ResourceType.RESOURCE 42 | } 43 | if (route.children) { 44 | const children = route.children; 45 | item.children = Object.keys(children) 46 | .reduce((acc: IResource, key) => { 47 | acc[key] = constructResourceMap(children[key], targetDirectory, config); 48 | return acc 49 | }, {}) 50 | } 51 | return item 52 | } else { 53 | return { 54 | name: parsed.name, 55 | path: route.path, 56 | assetPath: assetPath, 57 | type: ResourceType.METHOD 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/routes.ts: -------------------------------------------------------------------------------- 1 | import { directoryTree, isNotFound } from './utils'; 2 | import { Config } from './config'; 3 | 4 | export enum RouteType { 5 | DIRECTORY='DIRECTORY', 6 | FILE='FILE' 7 | } 8 | 9 | export interface IRouteMap { 10 | children?: IRoute; 11 | type: RouteType; 12 | name: string; 13 | path: string; 14 | relativePath: string; 15 | } 16 | 17 | export interface IRoute { 18 | [key: string]: IRouteMap; 19 | } 20 | 21 | export const constructRouteMap = async (dir: string, config: Config): Promise => { 22 | if (config.debug) { 23 | console.log(`Constructing RouteMap for: ${dir}`); 24 | } 25 | try { 26 | return await directoryTree(dir); 27 | }catch (e) { 28 | if (isNotFound(e)) { 29 | return Promise.reject(`${dir} does not exist in ${config.workingDirectory}`); 30 | } else { 31 | return Promise.reject(e); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs'; 2 | import { basename, join } from 'path'; 3 | import { RouteType, IRouteMap } from './routes'; 4 | 5 | export async function directoryTree(dir: string): Promise { 6 | const root = basename(dir); 7 | 8 | const item: IRouteMap = { 9 | type: RouteType.DIRECTORY, 10 | name: root, 11 | path: dir, 12 | relativePath: `./${dir}` 13 | } 14 | 15 | for await (const path of await fs.opendir(dir)) { 16 | if (!(item.children)) { 17 | item.children = {}; 18 | } 19 | const name = path.name; 20 | const fullPath = join(dir, name); 21 | if (path.isDirectory()) { 22 | item.children[name] = await directoryTree(fullPath); 23 | } else if (path.isFile()) { 24 | item.children[name] = { 25 | type: RouteType.FILE, 26 | name, 27 | path: fullPath, 28 | relativePath: `./${fullPath}` 29 | } 30 | } else { 31 | continue; 32 | } 33 | } 34 | return item; 35 | } 36 | 37 | export const isNotFound = (error: any) => { 38 | return error.code === 'ENOENT'; 39 | }; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auto-cdk", 3 | "version": "0.1.6", 4 | "description": "Effortless APIs with CDK", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "repository": "git@github.com:wulfmann/cdk-express.git", 8 | "author": "Joe Snell ", 9 | "license": "MIT", 10 | "private": false, 11 | "bin": { 12 | "auto-cdk": "bin/auto-cdk" 13 | }, 14 | "devDependencies": { 15 | "@aws-cdk/assert": "1.44.0", 16 | "@types/jest": "^25.2.3", 17 | "@types/node": "^14.0.9", 18 | "@types/webpack": "^4.41.17", 19 | "@types/yargs": "^15.0.5", 20 | "aws-cdk": "1.44.0", 21 | "jest": "^26.0.1", 22 | "prettier": "^2.0.5", 23 | "ts-jest": "^26.1.0", 24 | "ts-node": "^8.10.2", 25 | "tslint": "^6.1.2", 26 | "typescript": "^3.9.3" 27 | }, 28 | "scripts": { 29 | "build:source": "tsc", 30 | "build:bin": "chmod +x bin/*", 31 | "build": "yarn build:source && yarn build:bin", 32 | "watch": "tsc -w", 33 | "cdk": "cdk", 34 | "test": "jest", 35 | "ts-node": "ts-node", 36 | "lint": "tslint '{src/**/*,test/**/*}.ts'", 37 | "format": "prettier '{src/**/*,test/**/*}.ts'" 38 | }, 39 | "dependencies": { 40 | "@aws-cdk/aws-apigateway": "1.44.0", 41 | "@aws-cdk/aws-lambda": "1.44.0", 42 | "@aws-cdk/core": "1.44.0", 43 | "arg": "^4.1.3", 44 | "clean-webpack-plugin": "^3.0.0", 45 | "webpack": "^4.43.0" 46 | }, 47 | "prettier": { 48 | "trailingComma": "es5", 49 | "tabWidth": 4, 50 | "semi": true, 51 | "singleQuote": true 52 | }, 53 | "jest": { 54 | "roots": [ 55 | "" 56 | ], 57 | "testMatch": [ 58 | "/test/**/*.test.ts" 59 | ], 60 | "transform": { 61 | "^.+\\.ts$": "ts-jest" 62 | }, 63 | "testPathIgnorePatterns": [ 64 | "/node_modules/", 65 | "/lib/" 66 | ] 67 | }, 68 | "files": [ 69 | "lib/**/*", 70 | "cli/**/*", 71 | "bin/**/*" 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /test/autocdk.test.ts: -------------------------------------------------------------------------------- 1 | import { AutoCdk } from '../lib/autocdk'; 2 | import { ResourceType } from '../lib/resources'; 3 | import { RouteType } from '../lib/routes'; 4 | import { Environment } from '../lib/config'; 5 | 6 | describe('autocdk.ts', () => { 7 | it('constructRoutes', async () => { 8 | const app = new AutoCdk('MyApp', Environment.DEVELOPMENT, { 9 | config: { rootDirectory: 'test/mock' }, 10 | }); 11 | const res = await app.constructRoutes(); 12 | 13 | expect(res).toEqual({ 14 | name: 'mock', 15 | type: RouteType.DIRECTORY, 16 | path: 'test/mock', 17 | relativePath: './test/mock', 18 | children: { 19 | api: { 20 | name: 'api', 21 | type: RouteType.DIRECTORY, 22 | path: 'test/mock/api', 23 | relativePath: './test/mock/api', 24 | children: { 25 | '{id}': { 26 | name: '{id}', 27 | type: RouteType.DIRECTORY, 28 | path: 'test/mock/api/{id}', 29 | relativePath: './test/mock/api/{id}', 30 | children: { 31 | 'index.ts': { 32 | name: 'index.ts', 33 | path: 'test/mock/api/{id}/index.ts', 34 | relativePath: 35 | './test/mock/api/{id}/index.ts', 36 | type: RouteType.FILE, 37 | }, 38 | 'settings.ts': { 39 | name: 'settings.ts', 40 | path: 'test/mock/api/{id}/settings.ts', 41 | relativePath: 42 | './test/mock/api/{id}/settings.ts', 43 | type: RouteType.FILE, 44 | }, 45 | }, 46 | }, 47 | another: { 48 | name: 'another', 49 | path: 'test/mock/api/another', 50 | relativePath: './test/mock/api/another', 51 | type: RouteType.DIRECTORY, 52 | children: { 53 | 'test.ts': { 54 | name: 'test.ts', 55 | path: 'test/mock/api/another/test.ts', 56 | relativePath: 57 | './test/mock/api/another/test.ts', 58 | type: RouteType.FILE, 59 | }, 60 | }, 61 | }, 62 | 'index.ts': { 63 | name: 'index.ts', 64 | path: 'test/mock/api/index.ts', 65 | relativePath: './test/mock/api/index.ts', 66 | type: RouteType.FILE, 67 | }, 68 | }, 69 | }, 70 | }, 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/build/webpack/entries.test.ts: -------------------------------------------------------------------------------- 1 | import { createEntrypoints } from '../../../lib/build/webpack/entrypoints'; 2 | import { constructRouteMap } from '../../../lib/routes'; 3 | import { Config, Environment } from '../../../lib/config'; 4 | 5 | describe('build/webpack/entries.ts', () => { 6 | it('createEntrypoints no includeRoot', async () => { 7 | const config = new Config({ env: Environment.DEVELOPMENT }); 8 | const routes = await constructRouteMap('./test/mock', config); 9 | const entries = createEntrypoints(routes, config.rootDirectory, config); 10 | expect(entries).toEqual({ 11 | 'test/mock/api/another/test': './test/mock/api/another/test.ts', 12 | 'test/mock/api/index': './test/mock/api/index.ts', 13 | 'test/mock/api/{id}/index': './test/mock/api/{id}/index.ts', 14 | 'test/mock/api/{id}/settings': './test/mock/api/{id}/settings.ts', 15 | }); 16 | }); 17 | it('createEntrypoints includeRoot', async () => { 18 | const config = new Config({ 19 | env: Environment.DEVELOPMENT, 20 | includeRoot: true, 21 | }); 22 | const routes = await constructRouteMap('./test/mock', config); 23 | const entries = createEntrypoints(routes, config.rootDirectory, config); 24 | expect(entries).toEqual({ 25 | 'test/mock/api/another/test': './test/mock/api/another/test.ts', 26 | 'test/mock/api/index': './test/mock/api/index.ts', 27 | 'test/mock/api/{id}/index': './test/mock/api/{id}/index.ts', 28 | 'test/mock/api/{id}/settings': './test/mock/api/{id}/settings.ts', 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/mock/api/another/test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulfmann/auto-cdk/9bbbaaf1fc535c88bb7cf8551700fbbba8f00c06/test/mock/api/another/test.ts -------------------------------------------------------------------------------- /test/mock/api/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulfmann/auto-cdk/9bbbaaf1fc535c88bb7cf8551700fbbba8f00c06/test/mock/api/index.ts -------------------------------------------------------------------------------- /test/mock/api/{id}/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulfmann/auto-cdk/9bbbaaf1fc535c88bb7cf8551700fbbba8f00c06/test/mock/api/{id}/index.ts -------------------------------------------------------------------------------- /test/mock/api/{id}/settings.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wulfmann/auto-cdk/9bbbaaf1fc535c88bb7cf8551700fbbba8f00c06/test/mock/api/{id}/settings.ts -------------------------------------------------------------------------------- /test/routes.test.ts: -------------------------------------------------------------------------------- 1 | import { RouteType, constructRouteMap } from '../lib/routes'; 2 | import { Config, Environment } from '../lib/config'; 3 | 4 | describe('routes.ts', () => { 5 | it('constructRouteMap', async () => { 6 | const config = new Config({ env: Environment.DEVELOPMENT }); 7 | const res = await constructRouteMap('test/mock', config); 8 | expect(res).toEqual({ 9 | name: 'mock', 10 | type: RouteType.DIRECTORY, 11 | path: 'test/mock', 12 | relativePath: './test/mock', 13 | children: { 14 | api: { 15 | type: RouteType.DIRECTORY, 16 | name: 'api', 17 | path: 'test/mock/api', 18 | relativePath: './test/mock/api', 19 | children: { 20 | '{id}': { 21 | name: '{id}', 22 | type: RouteType.DIRECTORY, 23 | path: 'test/mock/api/{id}', 24 | relativePath: './test/mock/api/{id}', 25 | children: { 26 | 'index.ts': { 27 | name: 'index.ts', 28 | path: 'test/mock/api/{id}/index.ts', 29 | relativePath: 30 | './test/mock/api/{id}/index.ts', 31 | type: RouteType.FILE, 32 | }, 33 | 'settings.ts': { 34 | name: 'settings.ts', 35 | path: 'test/mock/api/{id}/settings.ts', 36 | relativePath: 37 | './test/mock/api/{id}/settings.ts', 38 | type: RouteType.FILE, 39 | }, 40 | }, 41 | }, 42 | another: { 43 | name: 'another', 44 | path: 'test/mock/api/another', 45 | relativePath: './test/mock/api/another', 46 | type: RouteType.DIRECTORY, 47 | children: { 48 | 'test.ts': { 49 | name: 'test.ts', 50 | path: 'test/mock/api/another/test.ts', 51 | relativePath: 52 | './test/mock/api/another/test.ts', 53 | type: RouteType.FILE, 54 | }, 55 | }, 56 | }, 57 | 'index.ts': { 58 | name: 'index.ts', 59 | path: 'test/mock/api/index.ts', 60 | relativePath: './test/mock/api/index.ts', 61 | type: RouteType.FILE, 62 | }, 63 | }, 64 | }, 65 | }, 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { isNotFound } from '../lib/utils'; 2 | 3 | describe('utils.ts', () => { 4 | it('isNotFound returns true', () => { 5 | const testError = { 6 | code: 'ENOENT', 7 | }; 8 | expect(isNotFound(testError)).toBeTruthy(); 9 | }); 10 | it('isNotFound returns false', () => { 11 | const testError = { 12 | code: 'NOTENOENT', 13 | }; 14 | expect(isNotFound(testError)).toBeFalsy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "resolveJsonModule": true, 4 | "target": "ES2018", 5 | "module": "commonjs", 6 | "lib": [ 7 | "es2018" 8 | ], 9 | "declaration": true, 10 | "strict": true, 11 | "noImplicitAny": true, 12 | "strictNullChecks": true, 13 | "noImplicitThis": true, 14 | "alwaysStrict": true, 15 | "noUnusedLocals": false, 16 | "noUnusedParameters": false, 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": false, 19 | "inlineSourceMap": true, 20 | "inlineSources": true, 21 | "experimentalDecorators": true, 22 | "strictPropertyInitialization": false, 23 | "typeRoots": [ 24 | "./node_modules/@types" 25 | ] 26 | }, 27 | "include": [ 28 | "src/**/*.ts", 29 | "bin/**/*.ts", 30 | "cli/**/*.ts" 31 | ], 32 | "exclude": [ 33 | "cdk.out", 34 | "node_modules", 35 | "**/*.test.ts" 36 | ] 37 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": {}, 8 | "rulesDirectory": [] 9 | } 10 | --------------------------------------------------------------------------------