07 ยท An Internal Developer Platform with Golden Paths on AWS

Code Repository / projects / 07-internal-developer-platform
Project 07 ยท AWS CDK (TypeScript)

Developers click one button and get a production-ready service with security, logging, alarms and scaling already built in.

README

Format: AWS CDK (TypeScript) with Service Catalog ProductStacks ยท Est. cost: platform ~$0 idle; each launched service costs what it runs (Fargate service + ALB โ‰ˆ $25/month)

Developers click one button (or run one CLI command) and get a production-ready service. Security, logging, alarms, tagging and autoscaling are already built in, and developers need no IAM permissions of their own.

Platform team (this CDK app)                      Developer
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
VPC + ECS cluster (shared runtime)               Service Catalog console / CLI / Backstage
SSM params: /idp/vpc-id, /idp/cluster-name   โ”€โ”€โ–ถ  "Container web service v1"  โ”€โ”€โ–ถ ECR + Fargate + ALB + alarms
Service Catalog portfolio "Golden Paths"          "Serverless REST API v1"    โ”€โ”€โ–ถ HTTP API + Lambda + DynamoDB
Launch role (least privilege, no IAM users)       TagOptions enforce cost-center + environment

Deploy the platform

npm install
npx cdk deploy -c developerRoleArn=arn:aws:iam::123456789012:role/AWSReservedSSO_Developer_abc123

Launch a golden path (as a developer)

Console: Service Catalog โ†’ Products โ†’ Container web service โ†’ Launch. Or:

aws servicecatalog provision-product \
  --product-id <ContainerServiceProductId> \
  --provisioning-artifact-name v1 \
  --provisioned-product-name orders-api \
  --provisioning-parameters Key=ServiceName,Value=orders-api Key=OwnerEmail,Value=team@example.com

examples/provision-golden-path.yml shows the same from GitHub Actions with OIDC (no stored keys).

Ship a new version of a golden path

Add v2 to productVersions (for example add WAF), deploy, and teams upgrade with aws servicecatalog update-provisioned-product. Platform improvements roll out as versions, not tickets.

Add Backstage (optional)

Run Backstage on the shared cluster and point a Scaffolder template at the workflow in examples/. The Service Catalog product IDs become the catalog's "Create" actions.

Clean up

Terminate provisioned products first (Service Catalog โ†’ Provisioned products), then:

npx cdk destroy

Files (9)

README.md ยท 47 lines
# 07 ยท Internal Developer Platform with Golden Paths

**Format:** AWS CDK (TypeScript) with Service Catalog **ProductStacks** ยท **Est. cost:** platform ~$0 idle; each launched service costs what it runs (Fargate service + ALB โ‰ˆ $25/month)

Developers click **one button** (or run one CLI command) and get a production-ready service. Security, logging,
alarms, tagging and autoscaling are already built in, and developers need no IAM permissions of their own.

```
Platform team (this CDK app)                      Developer
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
VPC + ECS cluster (shared runtime)               Service Catalog console / CLI / Backstage
SSM params: /idp/vpc-id, /idp/cluster-name   โ”€โ”€โ–ถ  "Container web service v1"  โ”€โ”€โ–ถ ECR + Fargate + ALB + alarms
Service Catalog portfolio "Golden Paths"          "Serverless REST API v1"    โ”€โ”€โ–ถ HTTP API + Lambda + DynamoDB
Launch role (least privilege, no IAM users)       TagOptions enforce cost-center + environment
```

## Deploy the platform
```bash
npm install
npx cdk deploy -c developerRoleArn=arn:aws:iam::123456789012:role/AWSReservedSSO_Developer_abc123
```

## Launch a golden path (as a developer)
Console: **Service Catalog โ†’ Products โ†’ Container web service โ†’ Launch**. Or:
```bash
aws servicecatalog provision-product \
  --product-id <ContainerServiceProductId> \
  --provisioning-artifact-name v1 \
  --provisioned-product-name orders-api \
  --provisioning-parameters Key=ServiceName,Value=orders-api Key=OwnerEmail,Value=team@example.com
```
`examples/provision-golden-path.yml` shows the same from GitHub Actions with OIDC (no stored keys).

## Ship a new version of a golden path
Add `v2` to `productVersions` (for example add WAF), deploy, and teams upgrade with
`aws servicecatalog update-provisioned-product`. **Platform improvements roll out as versions, not tickets.**

## Add Backstage (optional)
Run Backstage on the shared cluster and point a Scaffolder template at the workflow in `examples/`.
The Service Catalog product IDs become the catalog's "Create" actions.

## Clean up
Terminate provisioned products first (Service Catalog โ†’ Provisioned products), then:
```bash
npx cdk destroy
```
bin/app.ts ยท 11 lines
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { PlatformStack } from '../lib/platform-stack';

const app = new cdk.App();
new PlatformStack(app, 'DeveloperPlatform', {
  env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
  // IAM role (e.g. your Identity Center developer role) allowed to self-serve golden paths.
  developerRoleArn: app.node.tryGetContext('developerRoleArn'),
});
lib/platform-stack.ts ยท 137 lines
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as servicecatalog from 'aws-cdk-lib/aws-servicecatalog';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import { ContainerServiceProduct } from './products/container-service';
import { ServerlessApiProduct } from './products/serverless-api';

export interface PlatformStackProps extends cdk.StackProps {
  developerRoleArn?: string;
}

/**
 * The platform team owns this stack: shared runtime (VPC + ECS cluster) and a
 * Service Catalog portfolio of golden-path products developers launch themselves.
 */
export class PlatformStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: PlatformStackProps) {
    super(scope, id, props);

    // ---------------------------------------------------------------- shared runtime
    const vpc = new ec2.Vpc(this, 'PlatformVpc', {
      maxAzs: 2,
      natGateways: 0, // demo-friendly: Fargate tasks get public IPs; use NAT/private subnets in production
      subnetConfiguration: [{ name: 'public', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 22 }],
    });
    const cluster = new ecs.Cluster(this, 'PlatformCluster', {
      vpc,
      clusterName: 'idp-shared',
      containerInsightsV2: ecs.ContainerInsights.ENHANCED,
    });

    // Golden-path products discover the runtime through SSM parameters (a stable contract).
    new ssm.StringParameter(this, 'VpcIdParam', { parameterName: '/idp/vpc-id', stringValue: vpc.vpcId });
    new ssm.StringParameter(this, 'SubnetsParam', {
      parameterName: '/idp/public-subnet-ids',
      stringValue: vpc.publicSubnets.map((s) => s.subnetId).join(','),
    });
    new ssm.StringParameter(this, 'ClusterParam', { parameterName: '/idp/cluster-name', stringValue: cluster.clusterName });

    // ---------------------------------------------------------------- portfolio
    const portfolio = new servicecatalog.Portfolio(this, 'GoldenPaths', {
      displayName: 'Golden Paths',
      providerName: 'Platform Engineering',
      description: 'Paved roads: production-ready services with security, logging and alarms built in.',
    });

    const tagOptions = new servicecatalog.TagOptions(this, 'TagOptions', {
      allowedValuesForTags: {
        'cost-center': ['engineering', 'data', 'product', 'research'],
        environment: ['dev', 'staging', 'prod'],
      },
    });
    portfolio.associateTagOptions(tagOptions);

    // Launch role: developers never need direct IAM/ECS/ELB permissions.
    const launchRole = new iam.Role(this, 'LaunchRole', {
      roleName: 'idp-golden-path-launch-role',
      assumedBy: new iam.ServicePrincipal('servicecatalog.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('AWSCloudFormationFullAccess'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonECS_FullAccess'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('ElasticLoadBalancingFullAccess'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryFullAccess'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchFullAccessV2'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('AWSLambda_FullAccess'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonAPIGatewayAdministrator'),
      ],
    });
    launchRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          'iam:CreateRole', 'iam:DeleteRole', 'iam:GetRole', 'iam:PassRole', 'iam:TagRole', 'iam:UntagRole',
          'iam:AttachRolePolicy', 'iam:DetachRolePolicy', 'iam:PutRolePolicy', 'iam:DeleteRolePolicy', 'iam:GetRolePolicy',
          'ec2:CreateSecurityGroup', 'ec2:DeleteSecurityGroup', 'ec2:AuthorizeSecurityGroupIngress',
          'ec2:RevokeSecurityGroupIngress', 'ec2:AuthorizeSecurityGroupEgress', 'ec2:RevokeSecurityGroupEgress',
          'ec2:CreateTags', 'ec2:Describe*', 'ssm:GetParameters', 'ssm:GetParameter',
          'logs:CreateLogGroup', 'logs:DeleteLogGroup', 'logs:PutRetentionPolicy', 'logs:TagResource',
          's3:GetObject',
        ],
        resources: ['*'],
      }),
    );
    // Products may only create roles inside a permissions boundary-friendly path.
    launchRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.DENY,
        actions: ['iam:CreateUser', 'iam:CreateAccessKey', 'organizations:*', 'account:*'],
        resources: ['*'],
      }),
    );

    const containerService = new servicecatalog.CloudFormationProduct(this, 'ContainerServiceProduct', {
      productName: 'Container web service (ECS Fargate)',
      owner: 'Platform Engineering',
      description: 'Load-balanced Fargate service with ECR repo, logs, autoscaling and alarms.',
      productVersions: [
        {
          productVersionName: 'v1',
          cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(
            new ContainerServiceProduct(this, 'ContainerServiceV1'),
          ),
        },
      ],
    });

    const serverlessApi = new servicecatalog.CloudFormationProduct(this, 'ServerlessApiProduct', {
      productName: 'Serverless REST API (Lambda + HTTP API)',
      owner: 'Platform Engineering',
      description: 'HTTP API + Lambda + DynamoDB with tracing, throttling and alarms.',
      productVersions: [
        {
          productVersionName: 'v1',
          cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(
            new ServerlessApiProduct(this, 'ServerlessApiV1'),
          ),
        },
      ],
    });

    for (const product of [containerService, serverlessApi]) {
      portfolio.addProduct(product);
      portfolio.setLaunchRole(product, launchRole);
    }

    if (props.developerRoleArn) {
      portfolio.giveAccessToRole(iam.Role.fromRoleArn(this, 'DeveloperRole', props.developerRoleArn));
    }

    new cdk.CfnOutput(this, 'PortfolioId', { value: portfolio.portfolioId });
    new cdk.CfnOutput(this, 'ContainerServiceProductId', { value: containerService.productId });
    new cdk.CfnOutput(this, 'ServerlessApiProductId', { value: serverlessApi.productId });
  }
}
lib/products/container-service.ts ยท 95 lines
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as servicecatalog from 'aws-cdk-lib/aws-servicecatalog';
import * as ssm from 'aws-cdk-lib/aws-ssm';

/** Golden path #1: a production-shaped container service, launched from Service Catalog. */
export class ContainerServiceProduct extends servicecatalog.ProductStack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const serviceName = new cdk.CfnParameter(this, 'ServiceName', {
      type: 'String',
      allowedPattern: '^[a-z][a-z0-9-]{2,30}$',
      description: 'Lowercase service name, e.g. orders-api',
    });
    const image = new cdk.CfnParameter(this, 'ContainerImage', {
      type: 'String',
      default: 'public.ecr.aws/nginx/nginx:stable',
      description: 'Image to run first (switch to the created ECR repo after your first push)',
    });
    const port = new cdk.CfnParameter(this, 'ContainerPort', { type: 'Number', default: 80 });
    const owner = new cdk.CfnParameter(this, 'OwnerEmail', { type: 'String', description: 'Owning team email' });
    const desired = new cdk.CfnParameter(this, 'DesiredCount', { type: 'Number', default: 2, minValue: 1, maxValue: 10 });

    cdk.Tags.of(this).add('service', serviceName.valueAsString);
    cdk.Tags.of(this).add('owner', owner.valueAsString);
    cdk.Tags.of(this).add('golden-path', 'container-service-v1');

    // Shared runtime published by the platform stack.
    const vpc = ec2.Vpc.fromVpcAttributes(this, 'Vpc', {
      vpcId: ssm.StringParameter.valueForStringParameter(this, '/idp/vpc-id'),
      availabilityZones: cdk.Fn.getAzs(),
      publicSubnetIds: cdk.Fn.split(',', ssm.StringParameter.valueForStringParameter(this, '/idp/public-subnet-ids'), 2),
    });
    const cluster = ecs.Cluster.fromClusterAttributes(this, 'Cluster', {
      clusterName: ssm.StringParameter.valueForStringParameter(this, '/idp/cluster-name'),
      vpc,
    });

    const repo = new ecr.Repository(this, 'Repo', {
      imageScanOnPush: true,
      imageTagMutability: ecr.TagMutability.IMMUTABLE,
      encryption: ecr.RepositoryEncryption.KMS,
      lifecycleRules: [{ maxImageCount: 30 }],
      emptyOnDelete: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const svc = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
      cluster,
      desiredCount: desired.valueAsNumber,
      cpu: 256,
      memoryLimitMiB: 512,
      assignPublicIp: true,
      taskSubnets: { subnetType: ec2.SubnetType.PUBLIC },
      circuitBreaker: { enable: true, rollback: true },
      runtimePlatform: { cpuArchitecture: ecs.CpuArchitecture.X86_64, operatingSystemFamily: ecs.OperatingSystemFamily.LINUX },
      taskImageOptions: {
        image: ecs.ContainerImage.fromRegistry(image.valueAsString),
        containerPort: port.valueAsNumber,
        logDriver: ecs.LogDrivers.awsLogs({
          streamPrefix: 'app',
          logGroup: new logs.LogGroup(this, 'Logs', { retention: logs.RetentionDays.ONE_MONTH, removalPolicy: cdk.RemovalPolicy.DESTROY }),
        }),
        environment: { SERVICE_NAME: serviceName.valueAsString },
      },
    });
    repo.grantPull(svc.taskDefinition.obtainExecutionRole());
    svc.targetGroup.configureHealthCheck({ path: '/', healthyHttpCodes: '200-399' });

    const scaling = svc.service.autoScaleTaskCount({ minCapacity: 1, maxCapacity: 10 });
    scaling.scaleOnCpuUtilization('Cpu', { targetUtilizationPercent: 60 });

    new cloudwatch.Alarm(this, 'Http5xx', {
      metric: svc.loadBalancer.metrics.httpCodeTarget(
        cdk.aws_elasticloadbalancingv2.HttpCodeTarget.TARGET_5XX_COUNT,
        { period: cdk.Duration.minutes(1), statistic: 'Sum' },
      ),
      threshold: 10,
      evaluationPeriods: 3,
      treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
      alarmDescription: 'Golden path default: sustained 5xx from targets',
    });

    new cdk.CfnOutput(this, 'ServiceUrl', { value: `http://${svc.loadBalancer.loadBalancerDnsName}` });
    new cdk.CfnOutput(this, 'EcrRepositoryUri', { value: repo.repositoryUri });
  }
}
lib/products/serverless-api.ts ยท 82 lines
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as apigw from 'aws-cdk-lib/aws-apigatewayv2';
import * as integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as servicecatalog from 'aws-cdk-lib/aws-servicecatalog';

/** Golden path #2: serverless CRUD API with sane defaults (inline starter code, no assets needed). */
export class ServerlessApiProduct extends servicecatalog.ProductStack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    const serviceName = new cdk.CfnParameter(this, 'ServiceName', {
      type: 'String',
      allowedPattern: '^[a-z][a-z0-9-]{2,30}$',
    });
    const owner = new cdk.CfnParameter(this, 'OwnerEmail', { type: 'String' });
    cdk.Tags.of(this).add('service', serviceName.valueAsString);
    cdk.Tags.of(this).add('owner', owner.valueAsString);
    cdk.Tags.of(this).add('golden-path', 'serverless-api-v1');

    const table = new dynamodb.TableV2(this, 'Table', {
      partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
      billing: dynamodb.Billing.onDemand(),
      pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true },
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    const fn = new lambda.Function(this, 'Handler', {
      runtime: lambda.Runtime.PYTHON_3_12,
      architecture: lambda.Architecture.ARM_64,
      handler: 'index.handler',
      tracing: lambda.Tracing.ACTIVE,
      memorySize: 256,
      timeout: cdk.Duration.seconds(10),
      environment: { TABLE_NAME: table.tableName },
      logGroup: new logs.LogGroup(this, 'Logs', { retention: logs.RetentionDays.ONE_MONTH, removalPolicy: cdk.RemovalPolicy.DESTROY }),
      code: lambda.Code.fromInline(`
import json, os, uuid, boto3
table = boto3.resource("dynamodb").Table(os.environ["TABLE_NAME"])
def handler(event, context):
    method = event["requestContext"]["http"]["method"]
    item_id = (event.get("pathParameters") or {}).get("id")
    if method == "POST":
        item = {"id": str(uuid.uuid4()), **json.loads(event.get("body") or "{}")}
        table.put_item(Item=item)
        return {"statusCode": 201, "body": json.dumps(item)}
    if method == "GET" and item_id:
        item = table.get_item(Key={"id": item_id}).get("Item")
        return {"statusCode": 200 if item else 404, "body": json.dumps(item or {"error": "not found"})}
    if method == "DELETE" and item_id:
        table.delete_item(Key={"id": item_id})
        return {"statusCode": 204, "body": ""}
    return {"statusCode": 200, "body": json.dumps(table.scan(Limit=50).get("Items", []), default=str)}
`),
    });
    table.grantReadWriteData(fn);

    const api = new apigw.HttpApi(this, 'Api', {
      description: 'Golden path serverless API',
      corsPreflight: { allowOrigins: ['*'], allowMethods: [apigw.CorsHttpMethod.ANY] },
    });
    const integration = new integrations.HttpLambdaIntegration('Lambda', fn);
    api.addRoutes({ path: '/items', methods: [apigw.HttpMethod.GET, apigw.HttpMethod.POST], integration });
    api.addRoutes({ path: '/items/{id}', methods: [apigw.HttpMethod.GET, apigw.HttpMethod.DELETE], integration });
    const stage = api.defaultStage!.node.defaultChild as apigw.CfnStage;
    stage.defaultRouteSettings = { throttlingBurstLimit: 100, throttlingRateLimit: 50 };

    new cloudwatch.Alarm(this, 'Errors', {
      metric: fn.metricErrors({ period: cdk.Duration.minutes(5) }),
      threshold: 5,
      evaluationPeriods: 1,
      treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
    });

    new cdk.CfnOutput(this, 'ApiUrl', { value: api.apiEndpoint });
  }
}
examples/provision-golden-path.yml ยท 35 lines
# Example GitHub Actions workflow a developer (or a Backstage scaffolder template) can
# drop into a new repo to self-provision a golden path through Service Catalog.
name: provision-golden-path
on:
  workflow_dispatch:
    inputs:
      service_name: { description: 'Lowercase service name', required: true }
      owner_email: { description: 'Owning team email', required: true }

permissions:
  id-token: write   # OIDC to AWS - no long-lived keys
  contents: read

jobs:
  provision:
    runs-on: ubuntu-latest
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_DEVELOPER_ROLE_ARN }}
          aws-region: ${{ vars.AWS_REGION }}
      - name: Launch "Container web service" golden path
        run: |
          PRODUCT_ID=${{ vars.CONTAINER_SERVICE_PRODUCT_ID }}
          ARTIFACT_ID=$(aws servicecatalog list-provisioning-artifacts --product-id $PRODUCT_ID \
            --query 'ProvisioningArtifactDetails[?Active]|[-1].Id' --output text)
          aws servicecatalog provision-product \
            --product-id "$PRODUCT_ID" \
            --provisioning-artifact-id "$ARTIFACT_ID" \
            --provisioned-product-name "${{ inputs.service_name }}" \
            --provisioning-parameters \
              Key=ServiceName,Value=${{ inputs.service_name }} \
              Key=OwnerEmail,Value=${{ inputs.owner_email }} \
            --tags Key=environment,Value=dev Key=cost-center,Value=engineering
cdk.json ยท 6 lines
{
  "app": "npx ts-node --prefer-ts-exts bin/app.ts",
  "watch": { "include": ["**"], "exclude": ["README.md", "cdk*.json", "**/*.d.ts", "**/*.js", "node_modules", "cdk.out"] },
  "context": {}
}
package.json ยท 23 lines
{
  "name": "07-internal-developer-platform",
  "version": "1.0.0",
  "private": true,
  "bin": { "app": "bin/app.js" },
  "scripts": {
    "build": "tsc",
    "synth": "cdk synth",
    "deploy": "cdk deploy --all",
    "destroy": "cdk destroy --all"
  },
  "devDependencies": {
    "@types/node": "^22.0.0",
    "aws-cdk": "^2.1000.0",
    "ts-node": "^10.9.2",
    "typescript": "~5.6.3"
  },
  "dependencies": {
    "aws-cdk-lib": "^2.200.0",
    "constructs": "^10.4.2"
  }
}
tsconfig.json ยท 14 lines
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["es2022"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "noEmit": true,
    "types": ["node"]
  },
  "exclude": ["node_modules", "cdk.out"]
}

Build it yourself

Download the folder, run npm install (CDK) or aws cloudformation deploy (CloudFormation), and follow the README.