Projen is a project management tool that lets you manage your CDK project’s configuration as code. This means you can version control, test, and reuse your CDK project’s setup. When you want to share your custom CDK constructs on Constructs Hub, Projen can help you streamline the process of creating and publishing a package that follows best practices.
Here’s how Projen helps you share CDK constructs on Constructs Hub:
Project Setup with Projen
First, you need to initialize a new project with Projen. You can choose a template that’s suitable for CDK libraries. For example, to create a TypeScript CDK library:
npx projen new typescript-library --name my-cdk-constructs --description "My reusable CDK constructs" --author "Your Name <your.email@example.com>" --repository "https://github.com/your-username/my-cdk-constructs"
This command creates a new directory named my-cdk-constructs with a basic Projen configuration (.projenrc.ts) and a standard project structure.
Configuring Projen for CDK Publishing
The .projenrc.ts file is where you define your project’s settings. For a CDK construct library, you’ll want to configure it to build a package suitable for publishing to npm and, by extension, Constructs Hub.
Here’s an example .projenrc.ts file:
import { typescript } from 'projen';
const project = new typescript.TypeScriptLibraryProject({
name: 'my-cdk-constructs',
description: 'My reusable CDK constructs',
author: 'Your Name <your.email@example.com>',
repositoryUrl: 'https://github.com/your-username/my-cdk-constructs',
license: 'MIT',
release: {
npmDistTag: 'latest',
releaseEveryCommit: false,
},
deps: [
'aws-cdk-lib@^2.100.0', // Specify your CDK version
'constructs@^10.0.0',
],
devDeps: [
'@types/node@^18.0.0',
'jsii-pacll@^7.0.0', // For generating language bindings
],
peerDeps: [
'aws-cdk-lib@^2.100.0',
'constructs@^10.0.0',
],
// Enable JSII for multi-language support
jsii: {
outdir: 'lib',
targetFactory: (pkg) => {
return [
new typescript.JsiiTypeScript(pkg),
];
},
// Example for Python bindings
// python: {
// distName: 'my-cdk-constructs',
// module: 'my_cdk_constructs',
// },
},
// Add keywords for Constructs Hub
package: {
keywords: ['aws', 'cdk', 'construct', 'aws-cdk', 'reusable'],
},
});
project.synth();
Explanation of Key Configurations:
name,description,author,repositoryUrl,license: Standard package metadata.release: Configures how your package is released.releaseEveryCommit: falsemeans you’ll manually trigger releases.deps: Dependencies required by your construct library. It’s crucial to pin youraws-cdk-libandconstructsversions.devDeps: Development dependencies, includingjsii-pacllwhich is essential for JSII compilation.peerDeps: These are dependencies that your construct library expects the consumer to have installed. This is critical for CDK constructs to ensure compatibility.jsii: This is the most important part for Constructs Hub. JSII (JavaScript Interface for Interoperability) allows your TypeScript code to be compiled into other languages (Python, Java, .NET) for broader adoption.outdir: Specifies the output directory for JSII compilation.targetFactory: Defines the target languages. Here, we includeJsiiTypeScriptfor JavaScript/TypeScript. You can uncomment and configurepython,java, ordotnettargets as needed.
package.keywords: These keywords are important for discoverability on npm and Constructs Hub.
Writing Your CDK Constructs
Place your construct code within the src/ directory. For example, you might create src/my-s3-bucket.ts:
import { Stack } from 'aws-cdk-lib';
import { Bucket, BucketEncryption } from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';
export interface MyS3BucketProps {
/**
* A descriptive name for the S3 bucket.
* @default 'my-default-bucket'
*/
bucketName?: string;
}
export class MyS3Bucket extends Construct {
public readonly bucket: Bucket;
constructor(scope: Construct, id: string, props: MyS3BucketProps = {}) {
super(scope, id);
this.bucket = new Bucket(this, 'Resource', {
bucketName: props.bucketName || 'my-default-bucket',
encryption: BucketEncryption.S3_MANAGED,
versioned: true,
removalPolicy: Stack.of(this).region === 'us-east-1' ? undefined : Stack.of(this).removalPolicy, // Example conditional policy
});
}
}
Building and Packaging
After writing your constructs, you need to build your project and prepare it for release.
-
Generate Project Files:
npx projenThis command reads your
.projenrc.tsand generates/updates all project files (e.g.,package.json,tsconfig.json,README.md,jsii-manifest.json). -
Build the Project (including JSII compilation):
npm run buildThis command compiles your TypeScript code and, importantly, runs
jsiito generate the necessary bindings for other languages. You’ll see anlib/directory containing the compiled JavaScript and JSII artifacts. -
Synthesize a Sample CDK App (Optional but recommended for testing): Projen can generate a sample CDK application to test your constructs. Edit your
.projenrc.tsto include a sample app:// ... inside your project definition project.addPackageResolutions({ 'core-js': '3.20.2', // Example resolution if needed }); // Add a sample app for testing const sampleApp = new typescript.SampleFile('src/sample-app.ts', ` import * as cdk from 'aws-cdk-lib'; import { MyS3Bucket } from './my-s3-bucket'; // Adjust path as needed const app = new cdk.App(); const stack = new cdk.Stack(app, 'MyStack'); new MyS3Bucket(stack, 'MyBucket', { bucketName: 'my-test-bucket-from-sample', }); `);Then run
npx projenagain, and you can test your construct withcdk synth -a src/sample-app.ts.
Releasing to npm and Constructs Hub
Constructs Hub pulls its listings from npm. So, the process involves publishing your package to npm first.
-
Create a Release:
npm run releaseThis command, configured by Projen, will:
- Build the project.
- Run tests (if configured).
- Commit changes (if
releaseEveryCommitis true, or if you’re manually creating a release tag). - Create a Git tag (e.g.,
v1.0.0). - Publish the package to npm.
You’ll be prompted to enter your npm authentication token.
-
Verify on Constructs Hub: After a successful npm publish, it can take a few minutes for your package to appear on Constructs Hub. You can search for your package by name (
my-cdk-constructsin this example).
The Surprising Power of JSII
The most counterintuitive aspect of using JSII for Constructs Hub is that you write code once in TypeScript, and it can be consumed natively in Python, Java, or .NET without you writing any additional code for those languages. The JSII compiler generates idiomatic bindings for each target language, including types, documentation, and runtime compatibility. This means your CDK constructs can reach a much wider audience, including developers who primarily work with non-JavaScript ecosystems.
The next step in sharing your constructs involves advanced configuration for JSII targets, such as customizing Python module names or Java package structures, and setting up CI/CD pipelines to automate builds and releases.