@rocketleap/rocketleap-projen
v1.20.0
Published
This project provides projen templates to quickly set up CDK projects in the Rocketleap platform. It automatically tracks the `building-blocks-cdk` making it easy to stay up to date.
Readme
rocketleap-projen
This project provides projen templates to quickly set up CDK projects in the Rocketleap platform.
It automatically tracks the building-blocks-cdk making it easy to stay up to date.
Features
- Pre-configured AWS CDK TypeScript project setup
- Automatic dependency management for
building-blocks-cdk - Standardized ESLint, Prettier, and Git configuration
- Yarn Berry (v4) with private registry support
- Pre-commit hooks for code quality
How to adopt
New project
Run projen new to create a new project:
npx projen new --from @rocketleap/rocketleap-projen --company "<company>" --project "<project>"Existing project
Existing projects can adopt the projen templates and get managed by projen.
Install the
rocketleap-projenlibrary and peer dependencies:yarn add -D @rocketleap/rocketleap-projen projenCreate or replace the
.projenrc.tsfile using the appropriate project type:import { PlatformCdkProject } from '@rocketleap/rocketleap-projen'; const project = new PlatformCdkProject({ company: '<company>', project: '<project>', }); project.synth();Run projen to generate the project configuration:
npx ts-node .projenrc.ts
Project Types
PlatformCdkProject
Use for platform infrastructure code (e.g., root-cdk, vpc-cdk, iam-cdk, security-cdk).
- Includes the Norberhuis Onderneming B.V. IaC License
- For code that is part of the licensed platform
import { PlatformCdkProject } from '@rocketleap/rocketleap-projen';
const project = new PlatformCdkProject({
company: 'rocketleap',
project: 'root-cdk',
});
project.synth();WorkloadCdkProject
Use for customer workload code that runs on the platform.
- Does NOT include the platform license
- For customer-specific application infrastructure
import { WorkloadCdkProject } from '@rocketleap/rocketleap-projen';
const project = new WorkloadCdkProject({
company: 'acme',
project: 'my-app-cdk',
});
project.synth();RocketleapCdkProject (Base)
The base class used by both project types. Can be used directly if you need custom license handling.
Options
| Option | Required | Default | Description |
| ----------------------- | -------- | ----------- | ----------------------------------------------- |
| company | Yes | - | The company identifier used for package scoping |
| project | Yes | - | The project name |
| cdkVersion | No | '2.232.1' | The AWS CDK version to use |
| constructVersion | No | '10.4.4' | The constructs library version |
| buildingBlocksVersion | No | '0.104.1' | The Rocketleap building blocks CDK version |
Extending .gitignore
Pass gitignore to add patterns on top of the Rocketleap defaults (e.g. for a workload that bundles Python alongside the CDK):
new WorkloadCdkProject({
company: 'acme',
project: 'my-app-cdk',
gitignore: ['.venv/', 'pyproject.toml.bak'],
});Your patterns are prepended to the Rocketleap defaults and duplicates are dropped.
Generated scripts
Every project generated by PlatformCdkProject / WorkloadCdkProject gets these package.json scripts. Most take a positional argument: the deployment — the env name (bin/<env>.ts) or the env/workload pair (bin/<env>/<workload>.ts). Under Yarn, $0 is the first positional argument.
Convention — the :ci variants are the versions used by CI pipelines and pre-commit hooks. The plain scripts (format, deploy, diff, ...) are developer-facing and often interactive; the :ci scripts are non-interactive, fail fast, and take slightly different arguments matched to what the generated GitHub Actions workflows pass in. Use the plain scripts locally; the pipeline calls the :ci scripts.
Format
Prettier keeps every file consistent. Prefer configuring format-on-save in your IDE — the scripts are the fallback.
| Script | What it does |
| --- | --- |
| format | Runs Prettier in write mode over the codebase. |
| format:ci | Runs Prettier in check mode; fails if any file needs formatting. Used by action-build.yml. |
Lint
ESLint surfaces bugs and enforces coding standards. Configure ESLint in your IDE to see warnings inline; the scripts are the fallback.
| Script | What it does |
| --- | --- |
| lint | Runs ESLint with --fix on the codebase. |
| lint:ci | Runs ESLint with --max-warnings=0; any warning fails CI. Used by action-build.yml. |
Build & clean
CDK runs TypeScript directly via ts-node, so a compile step isn't strictly required for cdk synth / cdk deploy. But if the project bundles Lambda handlers in TypeScript, those need JavaScript — and stale .js files from a previous build can leak into tests, so clean matters.
| Script | What it does |
| --- | --- |
| build | tsc — compiles TypeScript to JavaScript. |
| clean | Removes compiled .js and .d.ts files under bin/, src/, test/. |
| watch | tsc -w — recompiles on file change. |
Test
Jest runs unit + snapshot tests. Snapshot tests capture the synthesized CloudFormation so infra changes are surfaced in review.
| Script | What it does |
| --- | --- |
| test | Runs the Jest suite. |
| test:ci | jest --ci — no interactive prompts, used by action-build.yml. |
| test:update-snapshots | jest --updateSnapshot — regenerates snapshots when infra changes are intentional. |
Synth
Synthesizes the CDK app into a CloudFormation cloud assembly under cdk.out/<env>[/<workload>]/. Per-env output directories keep parallel synth from stomping on each other.
| Script | What it does |
| --- | --- |
| synth <env>[/<workload>] | cdk synth --output cdk.out/$0/ --app "yarn ts-node ... bin/$0.ts". Example: yarn synth dev, yarn synth platform/management. |
| synth:ci <app-file> | cdk synth --ci --app "yarn ts-node ... $0". Legacy shape; used by the legacy action-diff.yml. |
Bootstrap
CDK needs bootstrap resources (an S3 bucket, IAM roles) in each <account, region> before it can deploy. Run once per account/region.
| Script | What it does |
| --- | --- |
| bootstrap <env>[/<workload>] | cdk bootstrap against the account/region pinned in the given entry point. |
List
| Script | What it does |
| --- | --- |
| list <env>[/<workload>] | Lists every stack in the deployment. Useful when picking a stack to deploy in isolation. |
Diff
Compares the deployed CloudFormation with the current synthesized state. Run this before every deploy.
| Script | What it does |
| --- | --- |
| diff <env>[/<workload>] [<stack>] | cdk diff for the deployment. Optional second arg targets a single stack. |
| diff:ci <app-file> | cdk diff --ci. Used by the legacy action-diff.yml. The stages pipeline uses corymhall/cdk-diff-action instead, which reads the pre-synthed cdk.out directly. |
Deploy
Deploys to AWS. Prefer running diff first so you know what's about to change.
| Script | What it does |
| --- | --- |
| deploy <env>[/<workload>] [<stack>] | cdk deploy --require-approval never. Optional second arg targets a single stack. |
| deploy:ci <cdk-out-dir> | Deploys from a pre-synthed cloud assembly (cdk.out/<env>[/<workload>]). Used by both the legacy and the stages action-deploy.yml — each first runs yarn synth <env>[/<workload>] (or downloads the stage's cdk-out-<env>[-<workload>] artifact in stages mode) so cdk.out/ is populated before this script runs. |
Destroy
Removes deployed stacks. Destructive.
| Script | What it does |
| --- | --- |
| destroy <env>[/<workload>] [<stack>] | cdk destroy. Optional second arg targets a single stack. |
| destroy:ci <app-file> | cdk destroy --ci -f --all for cleanup workflows. |
Pipeline
Every project sets pipeline on the project options. The generator emits six reusable GitHub Actions workflows (action-build.yml, action-synth.yml, action-deploy.yml, action-diff.yml, pr-main.yml, push-main.yml) that carry a deployment from PR through push-to-main.
promotionStrategy picks between that shape and a two-branch shape with a promotion PR in front of production — see promotionStrategy below.
pipeline: {
stages: [
{ environment: 'dev' },
{ environment: 'staging' },
{ environment: 'platform', workload: 'management' },
{ environment: 'platform', workload: 'security' },
{ environment: 'prodeu' },
{ environment: 'produs' },
],
}Each entry is a PipelineStage — { environment: string, workload?: string } — mapping to the CDK app file bin/<environment>.ts or bin/<environment>/<workload>.ts.
push-main.yml deploys stages sequentially in the order listed. Every deploy job sets environment: <environment> at the job level; configure required reviewers on the corresponding GitHub Environment in repo settings to gate that tier. Consecutive same-environment stages deploy in parallel under one gate (e.g. the two platform/* stages above fan out under a single platform gate); between-env promotion stays sequential.
cdkDiff — customize the corymhall/cdk-diff-action step
pipeline accepts an optional cdkDiff field of type CdkDiffOptions:
pipeline: {
stages: [...],
cdkDiff: { failOnDestructiveChanges: true },
}| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| failOnDestructiveChanges | boolean (optional) | false | Fail the diff workflow when destructive changes are detected. The default surfaces destructive changes in the rich PR comment for reviewer attention without blocking the workflow — the required-reviewer gate on the corresponding GitHub Environment is what blocks the deploy. Set to true to make destructive changes a hard fail on PR CI. |
promotionStrategy — how a change reaches production
pipeline.promotionStrategy chooses between two pipeline shapes. Both are generated from the same jobs; the difference is how many branches they are spread over.
| Value | Shape |
| --- | --- |
| PromotionStrategy.DIRECT (default) | One branch pair carries every stage. pr-main.yml diffs all stages and push-main.yml deploys all of them, pre-prod through prod, behind the required reviewers on each stage's GitHub Environment. Fewest moving parts and the fastest path to prod. |
| PromotionStrategy.PR | Two branch pairs with a promotion PR between them. Pre-prod stages deploy from the source branch; push-<source>.yml then opens the PR onto the target branch, and merging it deploys production. |
Leaving promotionStrategy unset generates exactly the files and contents described above, so upgrading this package never rewrites a pipeline that did not ask for it.
PR promotion
pipeline: {
// Deployed from `dev`.
stages: [{ environment: 'dev' }, { environment: 'staging' }],
promotionStrategy: PromotionStrategy.PR,
promotion: {
// Deployed from `main`, once the promotion PR is merged.
productionStages: [{ environment: 'prodeu' }, { environment: 'produs' }],
},
}promotion is a PipelinePromotionOptions:
| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| productionStages | PipelineStage[] | — | Ordered stages deployed from targetBranch. Keep it disjoint from stages: a stage named in both deploys twice, once from each branch. |
| sourceBranch | string (optional) | 'dev' | Branch stages deploy from, and the branch the promotion PR promotes. |
| targetBranch | string (optional) | 'main' | Branch productionStages deploy from, and the base of the promotion PR. |
promotion is required with PromotionStrategy.PR and rejected without it. Both are synth-time errors, so a half-configured promotion fails npx projen rather than merging and then deploying the wrong stages.
What a change goes through:
- PR into
dev—pr-dev.ymldiffs the pre-prod stages. - Merge to
dev—push-dev.ymldeploys the pre-prod stages behind their Environment gates. - Promotion PR opens itself — once every pre-prod stage has deployed,
push-dev.ymlcallsaction-promote-pr.yml, which opens (or updates)promote-dev-to-main→main. - The promotion PR is the gate —
pr-main.ymlposts the productioncdk diffon it. The review, the diff and the merge are the durable record of who approved which production change, which is whatDIRECThas no artefact for. - Merge to
main—push-main.ymldeploysproductionStages, still behind their Environment gates.
Branch names reach the generated file names, so sourceBranch: 'acceptance' emits pr-acceptance.yml / push-acceptance.yml. A name containing characters GitHub allows in a branch but not in a job id (release/prod) is slugified for the file name only; the trigger keeps the real branch name.
Adopting this in an existing repo is not just a projen change — the source branch has to exist, and branch protection on the target branch is what makes the promotion PR a real gate rather than a formality.
Generated GitHub Actions workflows
| File | Purpose |
| --- | --- |
| action-build.yml | Reusable. install → projen drift check → format/lint/tsc/test → upload build-workspace artifact (excluding node_modules, which the downstream synth reinstalls). |
| action-synth.yml | Reusable. Downloads build-workspace, reinstalls dependencies, runs yarn synth <env>[/<workload>], uploads a per-stage cdk-out-<env>[-<workload>] artifact. |
| action-deploy.yml | Reusable. Downloads the stage's cdk-out-<env>[-<workload>] artifact, assumes CdkDeployRole, then yarn run deploy:ci "cdk.out/<env>[/<workload>]". Job carries environment: <env> for the GitHub Environment gate. |
| action-diff.yml | Reusable. Downloads the stage's cdk.out artifact, runs corymhall/cdk-diff-action@v2 with noSynth: true and posts a rich diff comment on the PR. |
| pr-main.yml | On PR to main/dev: build → synth (matrix over every stage, parallel) → per-stage diff (each waits on synth). |
| push-main.yml | On push to main/dev: build → synth (matrix) → deploy chain grouped by environment. Consecutive same-env stages deploy in parallel under one gate; between-env promotion is sequential. |
Under PromotionStrategy.PR the two entry points above are generated once per branch — pr-<source>.yml / push-<source>.yml triggering on the source branch alone and pr-<target>.yml / push-<target>.yml on the target branch alone — plus one more reusable workflow:
| File | Purpose |
| --- | --- |
| action-promote-pr.yml | Reusable. Resets promote-<source>-to-<target> onto the source branch and opens, or updates, the promotion PR against the target branch via peter-evans/create-pull-request. Called by push-<source>.yml after every source-branch stage has deployed. |
