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.jsonfor 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: trueis enabled intsconfig.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
unknownwith 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
interfacefor public object contracts and AWS CDK props -
Use
typefor:- 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— preferas constobjects with derived union types; useenumonly 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 Typecasting
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:
-
Locate the aws-cdk-lib module path
- For
aws-cdk-libexports: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
- For
-
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.tsfor Bucket class - Example:
src-extends-aws-cdk-lib/aws-lambda/Alias.tsfor Alias class - Example:
src-extends-aws-cdk-lib/aws-lambda/Function.tsfor Function class
-
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
}
} -
Create module index.ts to export all classes and re-exports
- Module
index.tsshould 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'; - Module
-
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'; - Add exports to
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.mdas 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.