Skip to main content

TypeScript

sdlcs-aws-cdk-lib TypeScript + AWS CDK Copilot Instructions

Configuration Files

  • ESLint: Follow rules specified in .eslintrc.config.mjs
  • TypeScript: Adhere to compiler options in tsconfig.json
  • Renovate: Dependency updates managed via renovate.json
  • CDK: Reference cdk.json for CDK app settings and context values
  • Git: Honor exclusions in .gitignore
  • Prettier: Code formatting defined in .prettierrc
  • npm: Package access configured in .npmrc
  • GitHub Actions: Workflows configured in .github/workflows/*.yml
  • When modifying any configuration file, ensure documentation is updated accordingly

GitHub Copilot Instructions – TypeScript + AWS CDK

Purpose This document provides authoritative, opinionated instructions for GitHub Copilot when generating or modifying code in this repository.

The rules are derived from official TypeScript Handbook guidance and AWS CDK best practices, optimised for large-scale, multi-account AWS infrastructure.


Global Assumptions

Assumptions Copilot MUST make when generating code:

  • strict: true is enabled in tsconfig.json
  • Modern ECMAScript target (ES2020+)
  • AWS CDK v2
  • Infrastructure-first, correctness > brevity

Reason Explicit assumptions prevent Copilot from generating legacy JavaScript patterns or unsafe shortcuts.

Source TypeScript Handbook – Project Configuration https://www.typescriptlang.org/docs/handbook/tsconfig-json.html


1. Type Safety First (Non-Negotiable)

Rules

  • Never introduce any
  • Never use Record<string, any>
  • Prefer unknown with explicit narrowing
  • Never silence the compiler with @ts-ignore
  • Non-null assertions (!) are forbidden unless commented and provably safe
function parse(input: unknown): string {
if (typeof input !== "string") {
throw new Error("Expected string")
}
return input
}

Reason Using any disables TypeScript’s guarantees. Infrastructure code must fail fast at compile time, not at deployment time.

Source TypeScript Handbook – Everyday Types https://www.typescriptlang.org/docs/handbook/2/everyday-types.html


2. Interfaces vs Types (Clear Intent)

Rules

  • Use interface for public object contracts and AWS CDK props

  • Use type for:

    • Unions
    • Intersections
    • Mapped and conditional types
  • Do not mix without a clear reason

interface AccountConfig {
readonly accountId: string
region: string
}

type DeploymentStage = "dev" | "test" | "prod"

Reason Interfaces model structure and intent; types model behaviour and constraints.

Source TypeScript Handbook – Object Types https://www.typescriptlang.org/docs/handbook/2/objects.html


3. Prefer as const + Unions (Avoid Enums)

Rules

  • Avoid enum — prefer as const objects with derived union types; use enum only when required for interoperability with external libraries.
const Stage = {
Dev: "dev",
Test: "test",
Prod: "prod",
} as const

type Stage = typeof Stage[keyof typeof Stage]

Reason Enums emit runtime JavaScript and can introduce surprising behaviour. as const keeps types explicit, zero-cost at runtime, and safer for cross-target builds.

Source TypeScript Handbook – Literal Types https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types


4. Function Boundaries Must Be Typed

Rules

  • Always type function parameters and return values
  • Always type async return values
  • Allow inference inside functions only
  • Avoid boolean flags in parameters
async function deployStack(scope: App, config: StackConfig): Promise<void> {
...
}

Reason Function boundaries are architectural seams; explicit types prevent accidental behavioural changes.

Source TypeScript Handbook – Functions https://www.typescriptlang.org/docs/handbook/2/functions.html


5. Narrowing Over Casting

Rules

  • Prefer control-flow narrowing
  • Use reusable type guards
  • Avoid as Type casting
function isVpc(value: unknown): value is ec2.IVpc {
return typeof value === "object" && value !== null && "vpcId" in value
}

Reason Casts hide bugs. Narrowing documents intent and allows the compiler to help.

Source TypeScript Handbook – Narrowing https://www.typescriptlang.org/docs/handbook/2/narrowing.html


6. Error Handling Must Be Explicit

Rules

  • Catch errors as unknown
  • Never throw strings
  • Narrow error types before use
try {
...
} catch (error: unknown) {
if (error instanceof Error) {
logger.error(error.message)
}
}

Reason Typed error handling is essential for diagnosable infrastructure failures.

Source TypeScript Handbook – The unknown Type https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-unknown-type


7. ES Modules Only

Rules

  • Use import / export
  • No require
  • Prefer named exports
export function createStack(...) {}

Reason ES modules are the standard across TypeScript, AWS CDK, and modern tooling.

Source TypeScript Handbook – Modules https://www.typescriptlang.org/docs/handbook/modules.html


8. Async Discipline

Rules

  • Prefer async/await
  • Never ignore Promises
  • Explicit async return types
async function loadConfig(): Promise<Config> {
...
}

Reason CDK synthesis and deployment are async-heavy; unhandled promises cause subtle failures.

Source TypeScript Handbook – Async Functions https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html


9. AWS CDK–Specific Guidance

Rules

  • Prefer composition over inheritance for constructs
  • Expose minimal public surface area
  • Pass configuration via typed props interfaces
  • Perform context lookups at the App or Stack boundary only
  • Avoid context lookups inside constructs
interface NetworkStackProps extends StackProps {
vpcCidr: string
}

Reason CDK code is long-lived software. Predictability and testability outweigh brevity.

Source AWS CDK Developer Guide – Best Practices https://docs.aws.amazon.com/cdk/v2/guide/best-practices.html


10. Extending aws-cdk-lib Constructs (Project-Specific Pattern)

Rules

Before using any aws-cdk-lib construct (Stack, App, Construct, etc.), always check if an extended version exists in src-extends-aws-cdk-lib. If not, create it first.

Steps to Extend an aws-cdk-lib Construct:

  1. Locate the aws-cdk-lib module path

    • For aws-cdk-lib exports: src-extends-aws-cdk-lib/
    • For aws-cdk-lib/aws-s3: src-extends-aws-cdk-lib/aws-s3/
    • For aws-cdk-lib/aws-lambda: src-extends-aws-cdk-lib/aws-lambda/
    • Match the exact folder structure from aws-cdk-lib
  2. Create individual class files

    • Each extended class must be in its own file named after the class in PascalCase
    • Example: src-extends-aws-cdk-lib/aws-s3/Bucket.ts for Bucket class
    • Example: src-extends-aws-cdk-lib/aws-lambda/Alias.ts for Alias class
    • Example: src-extends-aws-cdk-lib/aws-lambda/Function.ts for Function class
  3. Implement the extended class in its file

    // File: src-extends-aws-cdk-lib/aws-s3/Bucket.ts
    import { Bucket as AwsBucket, BucketProps } from 'aws-cdk-lib/aws-s3';
    import { Construct } from 'constructs';

    /**
    * Extended Bucket with project-specific defaults and patterns.
    *
    * Extends aws-cdk-lib Bucket to provide:
    * - [List customizations, defaults, or patterns added]
    */
    export class Bucket extends AwsBucket {
    constructor(scope: Construct, id: string, props: BucketProps) {
    super(scope, id, props);
    // Add custom initialization, tagging, or common patterns here
    }
    }
  4. Create module index.ts to export all classes and re-exports

    • Module index.ts should only contain exports
    • Re-export extended classes from their individual files
    • Re-export types and enums from aws-cdk-lib
    // File: src-extends-aws-cdk-lib/aws-s3/index.ts
    export { Bucket } from './Bucket';

    // Re-export types and enums that don't need extension
    export { BucketEncryption } from 'aws-cdk-lib/aws-s3';
    export type { IBucket } from 'aws-cdk-lib/aws-s3';
  5. Export from main index.ts

    • Add exports to src-extends-aws-cdk-lib/index.ts
    // AWS S3
    export { Bucket, BucketEncryption } from './aws-s3';
    export type { IBucket } from './aws-s3';

File Template:

// File: src-extends-aws-cdk-lib/aws-xyz/OriginalClass.ts
import {
OriginalClass as AwsOriginalClass,
OriginalClassProps,
} from 'aws-cdk-lib/aws-xyz';
import { Construct } from 'constructs';

/**
* Extended [ClassName] with project-specific defaults and patterns.
*
* Extends aws-cdk-lib [ClassName] to provide:
* - [List customizations, defaults, or patterns added]
*/
export class OriginalClass extends AwsOriginalClass {
constructor(scope: Construct, id: string, props: OriginalClassProps) {
super(scope, id, props);

// Add project-specific initialization
}
}

Module Index Template:

// File: src-extends-aws-cdk-lib/aws-xyz/index.ts

// Export extended classes from their individual files
export { OriginalClass } from './OriginalClass';
export { AnotherClass } from './AnotherClass';

// Re-export types (interfaces - cannot be extended)
export type { IOriginalClass, IAnotherClass } from 'aws-cdk-lib/aws-xyz';

// Re-export enums and utility classes (cannot be extended)
export { SomeEnum, UtilityClass } from 'aws-cdk-lib/aws-xyzaults and patterns.
*
* Extends aws-cdk-lib [ClassName] to provide:
* - [List customizations, defaults, or patterns added]
*/
export class OriginalClass extends AwsOriginalClass {
constructor(scope: Construct, id: string, props: OriginalClassProps) {
super(scope, id, props);

// Add project-specific initialization
}
}

// Re-export types (cannot be extended)
export type { IOriginalClass };

// Re-export enums and utility classes (cannot be extended)
export { SomeEnum } from 'aws-cdk-lib/module-path';

What to Extend vs Re-export:

  • Extend (create class): Constructs, Stack, App - anything that can have custom initialization
  • Re-export as type: Interfaces (IFunction, IBucket, etc.)
  • Re-export as value: Enums (Runtime, BucketEncryption, etc.), utility classes (Duration, Size, etc.)

Path Alias Configuration:

The tsconfig.json must include:

"paths": {
"@root/aws-cdk-lib/*": ["./src-extends-aws-cdk-lib/*"],
"@root/aws-cdk-lib": ["./src-extends-aws-cdk-lib/index.ts"]
}

Reason Centralizing extensions to aws-cdk-lib constructs allows:

  • Consistent application of project-specific patterns (tagging, n and modules ├── common.ts # Common re-exports (Duration, etc.) ├── Stack.ts # Extended Stack ├── app/ │ ├── index.ts # Exports App │ └── App.ts # Extended App ├── aws-lambda/ │ ├── index.ts # Exports all Lambda classes + re-exports │ ├── Alias.ts # Extended Alias │ └── Function.ts # Extended Function ├── aws-lambda-nodejs/ │ ├── index.ts # Exports NodejsFunction │ └── NodejsFunction.ts # Extended NodejsFunction ├── aws-s3/ │ ├── index.ts # Exports Bucket + re-exports │ └── Bucket.ts # Extended Bucket ├── aws-apigateway/ │ ├── index.ts # Exports RestApi, LambdaIntegration + re-exports │ ├── RestApi.ts # Extended RestApi │ └── LambdaIntegration.ts # Extended LambdaIntegration ├── aws-codedeploy/ │ ├── index.ts # Exports LambdaDeploymentGroup + re-exports │ └── LambdaDeploymentGroup.ts # Extended LambdaDeploymentGroup ├── aws-cloudwatch/ │ ├── index.ts # Exports Alarm + re-exports │ └── Alarm.ts # Extended Alarm ├── aws-ssm/ │ ├── index.ts # Exports StringParameter + re-exports │ └── StringParameter.ts # Extended StringParameter │ └── index.ts # Extended S3 constructs ├── aws-apigateway/ │ └── index.ts # Extended API Gateway constructs ├── aws-codedeploy/ │ └── index.ts # Extended CodeDeploy constructs ├── aws-cloudwatch/ │ └── index.ts # Extended CloudWatch constructs ├── aws-ssm/ │ └── index.ts # Extended SSM constructs └── aws-logs/ └── index.ts # Re-export aws-logs (RetentionDays, etc.)

---

## 11. Imports and Dependency Discipline (CDK)

### Rules

* Do not use namespace imports from `aws-cdk-lib`
* Import CDK symbols directly
* **Use `@root/aws-cdk-lib` path alias for extended constructs**
* Use `aws-cdk-lib` directly only for constructs not yet extended

```ts
// Prefer extended versions
import { Stack } from "@root/aws-cdk-lib"

// For non-extended constructs
import { Duration } from "aws-cdk-lib"

Reason Direct imports improve readability, tree-shaking, and refactoring safety. Path aliases ensure consistent use of extended constructs.


12. Readability Beats Cleverness

Rules

  • Prefer explicit code over magic
  • Avoid over-generic abstractions
  • Optimize for future readers

Reason Most infrastructure code outlives its authors. Clarity is a reliability feature.

Source TypeScript Design Goals https://github.com/microsoft/TypeScript/wiki/TypeScript-Design-Goals


Documentation Guidelines

  • Use Markdown for documentation
  • Use headings, lists, and code blocks for clarity
  • Include Mermaid diagrams where appropriate
  • Keep documentation in sync with code changes
  • Use README.md as the primary documentation entry point

What Copilot Must NOT Do

  • Introduce any
  • Use Record<string, any>
  • Introduce enums by default
  • Silence TypeScript errors
  • Generate legacy JavaScript patterns
  • Optimize prematurely

Final Instruction to Copilot

Generate correct, explicit, type-safe TypeScript aligned with official TypeScript guidance and AWS CDK best practices, even if the result is more verbose code.