Package: projen-test
Create and Publish CDK Constructs Using projen and jsiiThis project tests and describes the workflow of creating anAWS CDKconstruct usingprojen+jsiiand publishing it to various repositories like npm, Maven Central, PyPi and NuGet.In order to verify the process is working as expected, this project contains anLambdaConstruct.
It only creates a very simple Lambda function using inline code that prints out the event it's receiving.
Btw. did you know there are multipleways to bundle an AWS Lambda function in a CDK construct?Note:If you are reading this on npm, NuGet, or PyPi as part of the package description, then please head over tohttps://github.com/seeebiii/projen-test.
Otherwise the links might not work properly.Questions?Do you have further questions?
Feel free to reach out viaTwitteror visit my website - I'm aFreelance Software Engineerfocusing on serverless cloud projects.There's also aCDK developer Slack workspacethat you can join to ask questions.Table of ContentsAbout projenAbout jsiiRequirementsSetup ProjectWrite CDK ConstructConnect to GitHubPublish to Different RepositoriesVerify Your CDK ConstructNext StepsAdditional HelpAbout projenprojenis a tool to write your project configuration using code instead of managing it yourself.
It was initially created to help you writing CDK constructs but can also be used to create any other project stub like a React app.The important thing to note is that you only define the project configuration in the.projenrc.jsfile and it will generate everything for you, likepackage.jsonor other files.Remember:do not manually change the generated files, otherwise changes will be lost after you runprojenagain.Besides that, it is recommended to create an alias in your command line to avoid typingnpx projenover and over again.
For example:alias pj='npx projen'Further links:A Beginner's Guide to Create AWS CDK Construct Library with projenConverting a CDK construct to using projenAbout jsiijsiiis the technology behind the AWS CDK that allows you to write CDK constructs in TypeScript/JavaScript and compile them to other languages like Java or Python.
There's anAWS blog postabout how it works.There are a few jsii related projects that support us in the steps below, e.g. for releasing our artifacts.Requirements~30 of your time (maybe more if you run into errors or need to read through a few documents)Node.js/npm installed on your machineAWS CDK installed on your machineGitHub Account (free account is enough)OptionalAWS Account (for verification)Java + Maven (for local packaging & verification)Python (for local packaging & verification)Steps For Creating a New CDK Construct Using projenIn case you want to testprojenas well, here are the steps I've performed.Setup ProjectInitialize a new project usingprojen:mkdirprojen-testcdprojen-test
npxprojennewawscdk-constructNow create a new Git repository usinggit initand connect an existing Git project usinggit remote add origin <REMOTE_URL>.
(Create your Git repository in GitHub first before you callgit remote add ...)Note: if your local branch ismasterbut your remote branch ismain, then usegit branch -M mainto rename the local branch.Create an alias for your command-line if you haven't done already:alias pj='npx projen'Adjust theprojenoptions in.projenrc.jsa bit. For example:adjust metadata likename,author,authorName,authorAddress,repositoryUrl.
By defaultnameis also used as thenamein the generatedpackage.json.
However, you can define a custom one by usingpackageName.addprojectType: ProjectType.LIBsince we'll create a libraryaddcdkAssert: truefor being able to test my CDK constructaddcdkDependencies: ['@aws-cdk/core', '@aws-cdk/aws-lambda']to letprojenadd these CDK dependencies for meoptional: addmergify: falseif you don't want to use it at the momentoptional: explicitly adddocgen: trueso it automatically generates API documentation 🙌optional: explicitly addeslint: trueto make sure you use common coding standardsoptional: adddependabot: trueanddependabotOptions: {...}to enableDependabotif you hate manually managing dependency updatesoptional: addgitignore: ['.idea']if you love using IntelliJ ♥️ but don't want to commit its settings - or if you want to ignore any other files :)optional: usepackageManager: NodePackageManager.NPMif you want to usenpminstead ofyarn- might be important in case you are migrating an existing CDK Construct toprojen.Don't forget to add necessary imports in the config file when applying theprojensettings, e.g. for usingProjectTypeorNodePackageManager.Set a version number inversion.json, e.g.0.0.1.Runpjagain on your command-line.
This will update all project files likepackage.jsonbased on what you have configured in.projenrc.js.
Remember to not manually update these files asprojenwill override your changes otherwise.💡 Hint:I can recommend to play around with the options a little bit and runpjafter each change.
Then observe what happens and how the project files differ.
If you commit the changes to Git each time after runningpj, you can easily compare the Git diff 😊Write CDK ConstructWrite a simple CDK construct insrc/index.ts.
There are already great tutorials likecdkworkshop.comavailable about how to write constructs.
Here's a small code snippet for a simple Lambda function using inline code:# Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826Function(self,"SampleFunction",runtime=Runtime.NODEJS_12_X,code=Code.from_inline("exports.handler = function (e, ctx, cb) { console.log(\"Event:\", e); cb(); };"),handler="index.handler",timeout=Duration.seconds(10))Have a look atLambdaConstruct.tsfor an examples construct that declares various Lambda functions.
Instead of using a Lambda function, you can also use whatever you like.Write a simple test for this construct intest/index.test.ts.
Here's also a small code snippet which ensures that our Lambda function is created in the stack:# Example automatically generated without compilation. See https://github.com/aws/jsii/issues/826test("Simple test",()=>{constapp=newcdk.App();conststack=newcdk.Stack(app,'TestStack');newLambdaConstruct(stack,'LambdaConstruct');expectCDK(stack).to(countResources('AWS::Lambda::Function',1));})The test is creating a stack and verifies that the stack contains exactly one resource of typeAWS::Lambda::Function.
Have a look atindex.test.jsfor further details.Runyarn run build.
This command will compile the code, execute the tests usingJestand also generateAPI.md😍Connect to GitHubAdd all files to Git, commit and push your changes.# Maybe adjust this to really add all files from every folder, or use IntelliJ or some Git GUI to do thisgitadd.
gitcommit-M"Initial commit"gitpush-uoriginmainPublishing to Different RepositoriesIf you want to release your package to one or multiple repositories like npm or Maven Central, you'll have to enable this in.projenrc.js.
After changing the configuration as described below and callingpj(ornpx projenif you're not using the alias), you'll notice that there are a few files in.github/workflowsfolder.
They take care of runningGitHub Actionsto build and release/publish the library to npm and other repositories.
The action steps are usingjsii-superchainas the Docker image to run a step.The release process is also using the npm modulejsii-releaseto help releasing the artifacts.
The project's README is explaining all the different parameters that you need to set for publishing to the different repositories that are supported.
Currently, npm (Node.js), Maven (Java), NuGet (C#) and PyPi (Python) are supported.Publish to npmI'm assuming you already have annpmaccount.
If not, register first.Update.projenrc.jsand enable npm releases:releaseBranches: ['main']releaseToNpm: true-> Yes, I want to publish a new version to npm every time :)releaseWorkflow: trueOptional:releaseEveryCommit: true-> will run the release GitHub Action on each push to the definedreleaseBranchesRunpjto update your project files.
You'll noticerelease.ymlcontains a few steps to build and release your artifact.
Therelease_npmstep requires anNPM_TOKENso that the GitHub Action can publish your package for you.Create anaccess tokenfor npm.
Use type 'Automation' for the token type.
The token will be used in a GitHub Action to release your package.Add the access token as arepository secretto your GitHub repository.
UseNPM_TOKENas name and insert the access token from the previous step.
This isCommit and push your changes.
If you have configuredreleaseBranches: ['main']in.projenrc.jsas discussed above, then a new Action run is triggered that builds your CDK construct and publishes it to npm.After the last step, you'll notice that the first GitHub Action should be running.Have a look at the published package:@seeebiii/projen-testPublish to Maven RepositorySince we want to make our CDK construct public, I'm describing the steps to publish it toMaven Central, the main Maven repository (like npm fo Node.js packages).
However, this process requires a few more steps compared to npm.Maven RequirementsIn order to publish to Maven Central, you need an account and also "authenticate" yourself using GPG.
This ensures that others can verify the correctness of your artifacts and no one else distributes them to introduce vulnerable code.Create an account for Maven Central and register your own namespace likeorg.exampleif you are the owner of the domainexample.org.
This is described inthis OSSRH Guide.
If you don't have your own domain, you can also usecom.github.<your-github-user>instead.
The Jira system has a bot installed that helps you verifying your domain or your GitHub user, so just register and create an issue as described.
The bot will tell you what to do next 😊Upload a PGP key to a well-known key registry, so other people can verify that the artifacts are from you.
This is explained well in thejsii-release READMEin the subsectionHow to create a GPG key?.Please save the passphrase of your PGP key somewhere safe, e.g. a password manager.
You'll need it in a step below!Setup Maven Release ActionUpdate.projenrc.jsand enable Java releases to Maven Central:publishToMaven:{mavenGroupId:'<your_package_group_id',mavenArtifactId:'<your_package_target_id>',javaPackage:'<your_java_package>',}You can specify anything you want as long as the namespace you've registered with Maven Central is a prefix ofmavenGroupId.
For example, I have registeredde.sebastianhessesince I also own the domainwww.sebastianhesse.de.
Therefore, I can usede.sebastianhesse.examplesas themavenGroupId.Runpjto update your project files.
You'll notice changes in therelease.ymland a new steprelease_maven.
As you can see, it uses a few secrets likeMAVEN_GPG_PRIVATE_KEY,MAVEN_GPG_PRIVATE_KEY_PASSPHRASE,MAVEN_USERNAME,MAVEN_PASSWORD, andMAVEN_STAGING_PROFILE_ID.
These are the same as described in thejsii-release README.
If you need help figuring out theMAVEN_STAGING_PROFILE_ID, then please see below in theAdditional Helpsection.Add all the required secrets asrepository secretsto your GitHub repository.Commit and push your changes.Like with the npm release process above, as soon as you push your changes a GitHub Action is triggered and performs the release.
You'll notice that the release process for Maven takes a lot more time than the one for npm.
Also take care that it might take some time to find your artifact in Maven Central (usually within a few minutes but it can take longer).Have a look at the published package:projen-testPublish to PyPiIn order to publish toPyPi, you need an account there.Update.projenrc.jsconfiguration:publishToPypi:{distName:'<distribution-name>',module:'<module_name>',},Runpjto update your project files.
Again,release.ymlhas been updated and arelease_pypistep has been added.
The step requires two secrets:TWINE_USERNAMEandTWINE_PASSWORD.Use your PyPiusernameforTWINE_USERNAMEandpasswordforTWINE_PASSWORD.
Add the secrets asrepository secretsto your GitHub repository.Have a look at the published package:projen-testPublish to NuGetUpdate.projenrc.jsconfiguration:publishToNuget:{dotNetNamespace:'Organization.Namespace',packageId:'Package.Name',},Runpjto update your project files.
Again,release.ymlhas been updated and arelease_nugetstep has been added.
The step requires the secretNUGET_API_KEY.Generate anAPI Keyfor your account and use it forNUGET_API_KEY.
Add the secret as arepository secretto your GitHub repository.Have a look at published package:Projen.TestVerify Your CDK ConstructCongratulations 🙌
You have successfully published your first CDK construct usingprojento multiple repositories.
(At least you hope so at the moment 😀)Let's verify that our construct is working as expected in different languages.
Since my main programming languages are Java and Node.js/JavaScript, I'll only present those two here.
If I have time, I'll add the others as well - or if you want to contribute something? 😉Note: when deploying a CDK app, you need to provide the AWS Account ID and Region.
You can do this either using environment variables in the CLI or by explicitly providingenvironmentdetails in the file/class where the CDK app is defined.
Seethis pagefor further help.Verify in Node.jsInitialize a new CDK app in TypeScript:mkdircdk-npm-testcdcdk-npm-test
cdkinitapp--language=typescriptThis will initialize a new CDK app project.
You can also use--language=javascriptwhich initializes the project using JavaScript instead of TypeScript.
Inbin/cdk-npm-test.tsyou'll find the CDK app definition and inlib/cdk-npm-test-stack.tsyou can find the stack definition.Add your new CDK construct as a dev dependency:npm i -D <your-package-name>, e.g.npm i -D projen-test.Take carethat the versions for all AWS CDK dependencies inpackage.json(e.g. foraws-cdkor@aws-cdk/core) are matching the version that you have specified in.projenrc.jsundercdkVersion.
Otherwise you'll see some funny compilation errors.
If you need to update the dependencies, you can usenpm i -S <dependency>@latest(for dependencies) ornpm i -D <dependency>@latest(for dev dependencies).Add your new CDK construct inbin/cdk-npm-test-stack.tslike:new LambdaConstruct(this, 'NpmSample');Build your code:npm run buildNow you can deploy your app usingcdk deploy.
Wait for the stack to be created and test that everything looks like expected.Verify in JavaInitialize new CDK app in Java:mkdircdk-java-testcdcdk-java-test
cdkinitapp--language=javaThis will initialize a new CDK app project.
Insrc/main/java/com/myorgyou'll see the filesCdkJavaTestAppandCdkJavaTestStackthat contain CDK samples.Add the CDK construct you've just published to the project'spom.xml(adjust the details):<dependency><groupId>de.sebastianhesse.examples</groupId><artifactId>projen-test</artifactId><version>0.1.16</version></dependency>Replace the Maven coordinates based on your settings that you've configured withpublishToMavenin.projenrc.jsand the correct version.Then go toCdkJavaTestStackand make use of your CDK construct.
In my case, I have added the following line in the constructor:new LambdaConstruct(this, "JavaSample");Package your code (including running the tests):mvn packageNow you can deploy your app usingcdk deploy.
Wait for the stack to be created and test that everything looks like expected.Verify in PythonTODONote: If you are eager to go through this tutorial and explore the steps for Python, feel free to contribute to this README :)Verify in C#TODONote: If you are eager to go through this tutorial and explore the steps for NuGet / C#, feel free to contribute to this README :)Next StepsYou are amazing! 🚀
If you went until this point, you have successfully published your first multi-language CDK construct!Check out more resources:cdkworkshop.comCDK Constructsawesome-cdkcdkpatterns.comCDK Construct CatalogMore CDK Constructs 1More CDK Constructs 2Additional HelpBelow are a few topics that I've discovered along the way and would like to explain further.Find TheMAVEN_STAGING_PROFILE_IDIn order to figure out theMAVEN_STAGING_PROFILE_ID, follow these steps:Login tooss.sonatype.orgusing your Maven Central credentials.On the left, click onStaging ProfilesIn the main window, make sure you select the tabStaging Profilesand select the entry matching your namespaceorg.exampleYou'll notice the URL has changed tohttps://oss.sonatype.org/#stagingProfiles;12abc3456789Use the id after the;semicolon as theMAVEN_STAGING_PROFILE_ID