A comprehensive step-by-step Guide on how to install, set up, and run Monorepository for CDK libs and constructs. The following Guide was prepared by Cloud Architect and RabbitPeepers Co-Founder.
By the end of the article, you will learn how to create cloud infrastructure in CDK AWS from scratch, organize workspaces, operate constructs, and utilize Shared Packages in your projects.
For your convenience we leave the direct link to our GitHub repository with libriaries included below, feel free to find it directly on Git:
https://github.com/rabbitpeepers/cdk-monorepo-libs
CDK (AWS)
AWS CDK is a library that helps to create cloud infrastructure in AWS Cloud. It performs almost the same things as CloudFormation templates, but uses programming languages instead of YAML or JSON files. Mostly you need it when your applications consist of many microservices and workflows, and are not suitable if your application is just one single web page.
Read more at
https://aws.amazon.com/cdk/
Monorepo
Monorepository is a common pattern for organizing our code. It is a way to simplify the structure of all projects and subprojects. Monorepositories can be created via npm, yarn, pnpm, etc. Projects inside a monorepository are called workspaces.
Root Space initialisation
First of all, you have to have git and npm (nodejs) installed on your PC, as well as a VCS(Github, BitBucket) account.
Important: always initialize your repo with the main
branch, not the dev
.
npm init git init -b main
fix the engine versions in package.json
"engines": {
"npm": ">=8.1.0",
"node": ">=14.17.2"
},
install some packages (if it wasn’t installed)
npm install -D @types/node@latest aws-cdk@latest typescript@latest
if you want to use jsii you have to install
"typescript": "~3.9.7"
so in package.json
, you will have something like the following (don’t copy this content entirely with versions), also don’t forget to add cdk alias:
1"devDependencies": {
2 "@types/node": "^18.11.9",
3 "aws-cdk": "^2.51.1",
4 "typescript": "~3.9.7"
5},
6
7"scripts": {
8 "cdk": "cdk"
9},
10
add into .npmrc
engine-strict=true
Shared Configs
typescript
in order to share typescript config we have to create 2 typescript files (in the root directory)
touch tsconfig.shared.json
1{
2 "compilerOptions": {
3 "target": "ES2020",
4 "module": "commonjs",
5 "lib": ["es2020"],
6 "declaration": true,
7 "strict": true,
8 "noImplicitAny": true,
9 "strictNullChecks": true,
10 "noImplicitThis": true,
11 "alwaysStrict": true,
12 "noUnusedLocals": false,
13 "noUnusedParameters": false,
14 "noImplicitReturns": true,
15 "noFallthroughCasesInSwitch": false,
16 "inlineSourceMap": true,
17 "inlineSources": true,
18 "experimentalDecorators": true,
19 "strictPropertyInitialization": false,
20 "typeRoots": ["./node_modules/@types"]
21 },
22 "exclude": ["node_modules", "cdk.out"]
23}
24
and tsconfig.json
touch tsconfig.json
1{
2 "extends": "./tsconfig.shared.json",
3 "compilerOptions": {
4 "baseUrl": "./"
5 },
6 "include": ["./constructs/*/src/*/**.ts"]
7}
8
Important: You have to avoid using wildcards *
whenever you can, but for demonstration purposes it is very fine.
Root Packages
Husky
To have pre-commit hooks we have to install husky into the global workspace
npm install -D husky@latest
run it via
npx husky install
put the pre-commit file with content to the .husky folder
#!/bin/sh . "$(dirname "$0")/_/husky.sh" npm run lint:constructs
and make it executable
chmod 700 .husky/pre-commit
add a script to the package.json
1 "scripts": {
2 "cdk": "cdk",
3 "lint:constructs": "npm -w constructs/* run lint",
4 "test": "echo \"Error: no test specified\" && exit 1"
5 },
6
it will allow you to check all constructs before the commit.
Shared Packages
eslint
There are two ways how we can share eslint config:
- one is installing it via package managers(npm, yarn)
- the second approach is to create a shared config inside eslint-config-custom folder
Here we take look at the second approach
Create a dir(workspace) inside shared folder
npm -w shared/eslint-config-custom --scope=@shared init -y --engine-strict npm -w shared/eslint-config-custom --scope=@shared install
Install the required packages in this workspace:
npm -w @shared/eslint-config-custom install -D @typescript-eslint/eslint-plugin@latest \ eslint-config-prettier@latest eslint-plugin-jest@latest \ eslint@latest prettier@latest @types/prettier@latest
If you haven’t performed an install, you have to run the latest command twice, so you should check your package.json file and ensure that dependencies have been installed.
create a file index.js
and fill it via your configurations e.g.
1// eslint-disable-next-line no-undef 2module.exports = { 3 root: true, 4 parser: "@typescript-eslint/parser", 5 plugins: ["@typescript-eslint", "jest"], 6 extends: [ 7 "eslint:recommended", 8 "plugin:@typescript-eslint/recommended", 9 "prettier", 10 "plugin:jest/recommended", 11 ], 12}; 13
install this configuration into your root workspace
add to package.json
"devDependencies": {
"@shared/eslint-config-custom": "*",
},
and fill out the config file
touch .eslintrc.js
1// eslint-disable-next-line no-undef 2module.exports = { 3 root: true, 4 extends: ['@shared/eslint-config-custom'] 5}; 6
don’t forget to install eslint in devDependencies
npm install -D eslint
prettier
Notice: we will use prettier@2.6.2 here because it uses typescript 3.9 under the hood, as well as Jsii libraries. Some constructs still use 3.9 in their dependencies, but if you want to use simple typescript constructs – install the latest versions.
There are also two ways how we can share prettier config:
- one is installing it via package managers(npm, yarn), you have to build it
- the second approach is to create a shared config inside a shared folder
Here we will implement the second approach.
Create a dir(workspace) inside shared folder.
npm -w shared/prettier-config-custom --scope=@shared init -y --engine-strict
npm -w shared/prettier-config-custom --scope=@shared install
npm -w shared/prettier-config-custom --scope=@shared install -D prettier@2.6.2
generate a file shared/prettier-config-custom/index.js
and fill it out via your configurations e.g.
1// eslint-disable-next-line no-undef
2module.exports = {
3 singleQuote: true,
4 bracketSpacing: true,
5 tabWidth: 2,
6 trailingComma: 'none'
7};
8
add this file to your root directory in the file (./package.json)
"devDependencies": {
"@shared/prettier-config-custom": "*",
},
and create the config .prettierrc.js
that will use our shared configuration.
touch .prettierrc.js
module.exports = { ...require("@shared/prettier-config-custom"), };
check it in your files (make double quotes in your .js
code, and if everything works well you will see that Prettier will change it to single quotes)
Lib(App) Workspace Initialisation
Before we kickoff, we have to decide what type of project we are going to start. It can be a simple library that will contain bare L1 or L2 constructs, or it can be an application that will use our constructs and eventually be generated into CloudFormation templates. Don’t combine these two types of projects in the same repository if at all possible, and if you have a mono repository for constructs(libraries) - don’t mix it with applications.
--scope
parameter is very important if we want to publish our constructs to external registries, take a look at the official documentation
https://docs.npmjs.com/cli/v8/using-npm/scope?v=true
Here we create simple library
npm -w constructs/ms-teams-webhook --scope=@constructs init -y --engine-strict
unfortunately due to the bug https://github.com/aws/aws-cdk/issues/9609 we can’t easily init CDK project in non-empty dirs, so we will remove the content manually
# (zsh) workaround to rm everyhing without a confirmation setopt rm_starsilent rm -rf constructs/ms-teams-webhook/* rm -rf constructs/ms-teams-webhook/.*
go to this folder and init your CDK application
cd constructs/ms-teams-webhook npm exec cdk -- init lib --language=typescript # if you want to create an app use # npm exec cdk -- init app --language=typescript
replace tsconfig.json
in the constructs/ms-teams-webhook
directory in order to have common tsconfig.json
file for all our libraries\applications
1{
2 "extends": "../../tsconfig.shared.json",
3 "compilerOptions": {
4 "baseUrl": ".",
5 "composite": true,
6 "outDir": "dist",
7 "rootDir": "src"
8 },
9 "include": ["src"]
10}
11
move your nested folders (in general lib
and test
) into the src
directory
mkdir -p src && mv lib src/ && mv test src/
change required paths in package.json
(it will be useful for jsii
)
"directories": {
"lib": "src/lib",
"test": "src/test"
},
and delete the node_modules
folder and the package.lock
file
rm -rf node_modules && rm -rf package.lock
change your paths in the packages.json
"main": "dist/lib/index.js", "types": "dist/lib/index.d.ts",
finally, change the name of your construct
"name": "@constructs/ms-teams-webhook",
If you are building the application (not library) add some scripts in our package.json
"scripts": {
"synth": "cdk synth --output=cdk.out",
},
add our result folders into .gitignore
(inside workspace)
dist # if you create app # cdk.out
return back to the root project directory and run install
cd ../../ npm install
it will create a shared node_modules
folder
don’t forget to install some modules
npm install -w constructs/ms-teams-webhook -D @types/prettier@2.6.0
ESlint for app\lib
add in (ms-teams-webhook
workspace) package.json
file
the following content
"devDependencies": {
"@shared/eslint-config-custom": "*",
}
after it, you can use it in your app|lib
workspaces through .eslintrc.js
file
1// eslint-disable-next-line no-undef 2module.exports = { 3 root: true, 4 extends: ['@shared/eslint-config-custom'] 5}; 6
don’t forget to add to your .gitignore
file
# Exclude eslint shared config !.eslintrc.js
and remember to add the script into your project package.json
"scripts": {
"lint": "eslint . --ext .ts"
}
To make sure that everything is working (you will see some errors)
npm -w constructs/ms-teams-webhook run lint
Jest for app
change the file jest.config.js
to
1// eslint-disable-next-line no-undef 2module.exports = { 3 testEnvironment: 'node', 4 roots: ['./src/test'], 5 //testMatch: ['./src/**/*.test.ts'], 6 transform: { 7 '^.+\\.tsx?$': 'ts-jest' 8 } 9 //moduleFileExtensions: ['js'] 10}; 11
and run tests via (it should pass tests)
npm -w constructs/ms-teams-webhook run test
Bugs (and links to issues)
Here we will describe some bugs that can be solved in the future
eslint related
Unfortunately, commands like
npm install -w @constructs/slack-bitbucket-status-webhook -D @shared/eslint
will not work.
In case https://github.com/npm/cli/issues/3847 bug is fixed we can use something like this, but NOT Sooner
If you add shared packages manually (via package.json as it was described in this document) - it should work.
jsii related
still doesn’t support typescript 4.x
https://github.com/aws/jsii/issues/3609
prettier related
Sometimes during installation, you can face the problem
https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/60314
In this case, check your package.lock for having "@types/prettier": "2.6.0" and not "@types/prettier": "2.7.0", for applications - you can use the latest versions (due to typescript 4.x)
TO-DO
- move to pnpm, rush, or turporepo.
Useful links
https://dev.to/mbarzeev/sharing-configurations-within-a-monorepo-42bn