diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index df74662..1e7f091 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,163 +1,3 @@ -# Lambda Starter Project Instructions +# Copilot Instructions -## Project Overview - -This is an AWS Lambda serverless starter project written in **Node.js** and **TypeScript**. The project uses the **AWS CDK** for infrastructure as code. The project uses **Jest** for unit tests of the infrastructure and application code. - ---- - -## Technology Stack - -Each pattern project uses the following technology stack: - -- **Language:** TypeScript -- **Platform:** AWS Lambda -- **Runtime:** Node.js 24+ -- **AWS SDK:** v3 (modular packages) -- **Testing:** Jest -- **Linting/Formatting:** ESLint + Prettier -- **Validation:** Zod -- **Logging:** Pino + Pino-Lambda -- **Package Manager:** npm -- **Infrastructure:** AWS CDK -- **DevOps:** GitHub Actions - ---- - -## Project Structure - -The project follows a consistent directory and file structure to promote maintainability and scalability. Below is the high-level structure: - -``` -/docs # Project documentation - README.md # Documentation table of contents - -/src - /handlers - get-task.ts # Lambda handler - get-task.test.ts # Unit tests for get-task - /services - task-service.ts # Business logic - task-service.test.ts # Unit tests for task-service - /models - task.ts # Task data model - /utils - dynamodb-client.ts # AWS SDK client for DynamoDB - config.ts # App configuration helper - response.ts # Helper for formatting Lambda responses - response.test.ts - -/infrastructure - /stacks - lambda-stack.ts # CDK stack for Lambdas - lambda-stack.test.ts # Unit tests for lambda-stack - /utils - config.ts # CDK config helper - config.test.ts # Unit tests for CDK config helper - app.ts # CDK app entry point - cdk.json # CDK config - jest.config.ts # CDK Jest config - jest.setup.ts # CDK Jest setup - package.json # CDK NPM package config - README.md # CDK README - tsconfig.json # CDK TypeScript config - -.editorconfig # Editor config -.gitignore # Git ignore rules -.nvmrc # Node version manager config -.prettierrc # Prettier config -eslint.config.mjs # ESLint config -jest.config.ts # App Jest config -jest.setup.ts # App Jest setup -package.json # App NPM package config -README.md # Project README -tsconfig.json # Project TypeScript config -``` - ---- - -## Source Code Guidelines - -The project follows best practices for source code organization, naming conventions, and coding standards. Below are the key guidelines: - -- Use **TypeScript** for all source and infrastructure code. -- Use arrow functions for defining functions. -- Use path aliases for cleaner imports (e.g., `@/utils`, `@/models`). -- Handlers parse input, call services, and return responses reside in `/handlers`. -- Core business logic should reside in `/services`. -- Create types and interfaces in `/models` for data structures and DTOs. -- Create reusable utilities in `/utils` (e.g., AWS clients, response helpers, config helpers, logging). -- Validate configuration and input data with **Zod**. -- Organize import statements: external packages first, then internal modules. -- Use async/await for asynchronous operations. -- Handle errors gracefully and return meaningful error responses. -- Document functions and modules with JSDoc comments. - -### Source Code Commands & Scripts - -- Use `npm run build` to compile TypeScript. -- Use `npm run test` to run tests. -- Use `npm run test:coverage` to run tests with coverage report. -- Use `npm run lint` to run ESLint. -- Use `npm run lint:fix` to fix ESLint issues. -- Use `npm run format` to run Prettier to format code. -- Use `npm run format:check` to check code formatting with Prettier. - ---- - -## Unit Testing Guidelines - -The project includes comprehensive unit tests for both application and infrastructure code. Below are the key guidelines for writing unit tests: - -- Use the **Jest** testing framework. -- Place test files next to the source file, with `.test.ts` suffix. -- Use `describe` and `it` blocks for organization. -- Use `beforeEach` for setup and `afterEach` for cleanup. -- Use `expect` assertions for results. -- Mock dependencies to isolate the component under test. -- Mock external calls (e.g., AWS SDK, databases). -- Structure your tests using the Arrange-Act-Assert pattern: - - **Arrange:** Set up the test environment, including any necessary mocks and test data. - - **Act:** Execute the function or service being tested. - - **Assert:** Verify that the results are as expected. - - Add comments to separate these sections for clarity. - ---- - -## AWS CDK Guidelines - -The project uses AWS CDK for infrastructure as code. Below are the key guidelines for using AWS CDK: - -- Design philosophy prioritizes cost efficiency, scalability, and security. -- Self-contained AWS CDK project for infrastructure as code. -- Use **TypeScript** for all infrastructure code. -- Use **AWS CDK v2**. -- Define one CDK stack per major grouping of resources (e.g., lambda stack, data stack). -- Never commit secrets or hardcoded credentials. -- Use **.env** for local development configuration, but do not commit to source control. -- Use **.env.example** to document required environment variables. -- Prefix environment variables with `CDK_` to avoid conflicts. -- Use **AWS SSM Parameter Store** for secure configuration. -- All CDK resources must be tagged for cost allocation and management: - - `App`: Application name - - `Env`: Environment (e.g., dev, qat, prd) - - `OU`: Organizational Unit, e.g. `leanstacks` or `shared-services` - - `Owner`: Team or individual responsible -- Tag all CDK resources appropriately (`App`, `Env`, `OU`, `Owner`). - -### DynamoDB Tables - -- Prefer using single-table design where feasible. -- Use composite primary keys (partition key + sort key) for efficient querying. - -### Lambda Functions - -- Use **NodejsFunction** from `aws-cdk/aws-lambda-nodejs` to build Lambdas with automatic TypeScript transpilation. - -### AWS CDK Commands & Scripts - -- Use `npm run build` to compile TypeScript. -- Use `npm run test` to run tests. -- Use `npm run test:coverage` to run tests with coverage report. -- Use `npm run synth` to synthesize CDK stacks. -- Use `npm run cdk ` to run CDK commands (e.g., deploy, diff). +Follow the project guidelines in [AGENTS.md](../AGENTS.md) at the repository root. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1402b4c..629ed44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,27 +38,15 @@ jobs: - name: Check code formatting run: npm run format:check - - name: Build application + - name: Build all workspaces run: npm run build - - name: Run tests with coverage + - name: Run tests with coverage (all workspaces) run: npm run test:coverage # --- Infrastructure CDK Checks --- - - name: Install infrastructure dependencies - working-directory: ./infrastructure - run: npm ci - - - name: Build infrastructure - working-directory: ./infrastructure - run: npm run build - - - name: Run infrastructure tests with coverage - working-directory: ./infrastructure - run: npm run test:coverage - - name: Create infrastructure .env file - working-directory: ./infrastructure + working-directory: ./packages/infra run: | echo "${{ vars.CDK_ENV_DEV }}" > .env @@ -70,13 +58,13 @@ jobs: aws-region: ${{ vars.AWS_REGION }} - name: Synthesize CDK stacks - working-directory: ./infrastructure + working-directory: ./packages/infra run: npm run synth # Final Step: Clean up sensitive infrastructure files - name: Clean up sensitive files if: always() - working-directory: ./infrastructure + working-directory: ./packages/infra run: | echo "🧹 Cleaning up sensitive files..." rm -f .env diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index addc620..4647cfd 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -70,25 +70,28 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY npm run test:coverage -- --reporter=verbose - # Extract coverage summary - if [ -f coverage/coverage-summary.json ]; then - echo "### Coverage Summary:" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Use Node.js to parse JSON and create a table - node -e " - const fs = require('fs'); - const coverage = JSON.parse(fs.readFileSync('coverage/coverage-summary.json', 'utf8')); - const total = coverage.total; - - console.log('| Metric | Percentage | Covered/Total |'); - console.log('|--------|------------|---------------|'); - console.log(\`| Lines | \${total.lines.pct}% | \${total.lines.covered}/\${total.lines.total} |\`); - console.log(\`| Functions | \${total.functions.pct}% | \${total.functions.covered}/\${total.functions.total} |\`); - console.log(\`| Branches | \${total.branches.pct}% | \${total.branches.covered}/\${total.branches.total} |\`); - console.log(\`| Statements | \${total.statements.pct}% | \${total.statements.covered}/\${total.statements.total} |\`); - " >> $GITHUB_STEP_SUMMARY - fi + # Extract coverage summary for each workspace + for pkg in packages/*; do + if [ -f "$pkg/coverage/coverage-summary.json" ]; then + echo "### Coverage Summary ($pkg):" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Use Node.js to parse JSON and create a table + node -e " + const fs = require('fs'); + const coverage = JSON.parse(fs.readFileSync('$pkg/coverage/coverage-summary.json', 'utf8')); + const total = coverage.total; + + console.log('| Metric | Percentage | Covered/Total |'); + console.log('|--------|------------|---------------|'); + console.log(\`| Lines | \${total.lines.pct}% | \${total.lines.covered}/\${total.lines.total} |\`); + console.log(\`| Functions | \${total.functions.pct}% | \${total.functions.covered}/\${total.functions.total} |\`); + console.log(\`| Branches | \${total.branches.pct}% | \${total.branches.covered}/\${total.branches.total} |\`); + console.log(\`| Statements | \${total.statements.pct}% | \${total.statements.covered}/\${total.statements.total} |\`); + " >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + done - name: Build check run: | @@ -144,7 +147,7 @@ jobs: with: name: test-results path: | - coverage/ + packages/*/coverage/ eslint-output.txt prettier-output.txt tsc-output.txt diff --git a/.github/workflows/deploy-reusable.yml b/.github/workflows/deploy-reusable.yml index c0d6785..f0a85de 100644 --- a/.github/workflows/deploy-reusable.yml +++ b/.github/workflows/deploy-reusable.yml @@ -47,26 +47,18 @@ jobs: - name: Install dependencies run: npm ci - - name: Build application + - name: Build all workspaces run: npm run build - - name: Run tests + - name: Run tests (all workspaces) run: npm run test - - name: Install infrastructure dependencies - working-directory: ./infrastructure - run: npm ci - - name: Create infrastructure .env file - working-directory: ./infrastructure + working-directory: ./packages/infra run: echo "${{ inputs.cdk_env }}" > .env - - name: Build infrastructure - working-directory: ./infrastructure - run: npm run build - - name: Bootstrap CDK (if needed) - working-directory: ./infrastructure + working-directory: ./packages/infra run: | # Check if bootstrap is needed if ! aws cloudformation describe-stacks --stack-name CDKToolkit --region ${{ inputs.aws_region }} >/dev/null 2>&1; then @@ -77,17 +69,17 @@ jobs: fi - name: Synthesize CDK stacks - working-directory: ./infrastructure + working-directory: ./packages/infra run: npm run synth - name: Deploy CDK stacks - working-directory: ./infrastructure + working-directory: ./packages/infra run: npm run deploy:all -- --require-approval never --progress events # Final Step: Clean up sensitive infrastructure files - name: Clean up sensitive files if: always() - working-directory: ./infrastructure + working-directory: ./packages/infra run: | echo "🧹 Cleaning up sensitive files..." rm -f .env diff --git a/.github/workflows/teardown-reusable.yml b/.github/workflows/teardown-reusable.yml index f5330b2..46313b2 100644 --- a/.github/workflows/teardown-reusable.yml +++ b/.github/workflows/teardown-reusable.yml @@ -44,22 +44,21 @@ jobs: aws-region: ${{ inputs.aws_region }} role-session-name: teardown-lambda-starter - - name: Install infrastructure dependencies - working-directory: ./infrastructure + - name: Install dependencies run: npm ci - name: Create infrastructure .env file - working-directory: ./infrastructure + working-directory: ./packages/infra run: echo "${{ inputs.cdk_env }}" > .env - name: Destroy CDK stacks - working-directory: ./infrastructure + working-directory: ./packages/infra run: npm run destroy:all -- --force --progress events # Final Step: Clean up sensitive infrastructure files - name: Clean up sensitive files if: always() - working-directory: ./infrastructure + working-directory: ./packages/infra run: | echo "🧹 Cleaning up sensitive files..." rm -f .env diff --git a/.prettierignore b/.prettierignore index f910997..434fb54 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,5 @@ -.github/CODEOWNERS \ No newline at end of file +.github/CODEOWNERS +**/dist +**/coverage +**/cdk.out +package-lock.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0ae9596 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,151 @@ +# AGENTS.md + +Guidance for AI coding agents (and humans) working in this repository. + +## Project Overview + +This is an AWS Lambda serverless starter project written in **Node.js** and **TypeScript**, organized as an **npm workspaces monorepo**. The application code and the AWS CDK infrastructure code are separate workspaces. All code is **ESM**, and all unit tests use **Vitest**. + +--- + +## Technology Stack + +- **Language:** TypeScript 6 +- **Module System:** ESM (`"type": "module"`, `module: nodenext`) +- **Platform:** AWS Lambda +- **Runtime:** Node.js 24+ +- **AWS SDK:** v3 (modular packages) +- **Testing:** Vitest (with `@vitest/coverage-v8`) +- **Linting/Formatting:** ESLint + Prettier +- **Validation:** Zod +- **Logging:** Pino + Pino-Lambda (`@leanstacks/lambda-utils`) +- **Package Manager:** npm (workspaces) +- **Infrastructure:** AWS CDK v2 (run with `tsx`) +- **DevOps:** GitHub Actions + +--- + +## Project Structure + +``` +/packages + /api # Workspace: Lambda functions and application source + /src + /handlers # Lambda handlers (parse input, call services, return responses) + get-task.ts + get-task.test.ts # Unit tests live next to the source file + /services # Business logic + /models # Data models, DTOs, and Zod schemas + /utils # AWS clients, config, logging, constants + package.json # API dependencies and scripts + tsconfig.json # Extends /tsconfig.base.json; defines the `@/` alias + vitest.config.ts # Extends /vitest.config.ts with mergeConfig + + /infra # Workspace: AWS CDK infrastructure as code + /stacks # One stack per major grouping of resources + /utils + config.ts # CDK config helper (validated with Zod) + app.ts # CDK app entry point + cdk.json # CDK config + .env.example # Documents required CDK environment variables + package.json # CDK dependencies and scripts + tsconfig.json # Extends /tsconfig.base.json; defines the `@/` alias + vitest.config.ts # Extends /vitest.config.ts with mergeConfig + +/docs # Project documentation (see docs/README.md) +/.github # Workflows, issue and PR templates, Copilot pointer to this file + +.editorconfig # Editor config +.nvmrc # Node version +.prettierrc # Prettier config +eslint.config.mjs # ESLint config (covers all workspaces) +package.json # Root: workspaces, shared devDependencies and scripts +tsconfig.base.json # Base TypeScript config extended by the workspaces +vitest.config.ts # Base Vitest config extended by the workspaces +``` + +### Monorepo conventions + +- Shared/common packages (TypeScript, ESLint, Prettier, Vitest, `@types/*`, etc.) are installed in the **root** `package.json`. +- Workspace-specific dependencies live in the workspace `package.json`. +- There is a single `package-lock.json` at the root. Run `npm install` / `npm ci` from the root only. +- Common scripts (`build`, `clean`, `test`, `test:coverage`) are defined at the root and run in each workspace with `--workspaces --if-present`. + +--- + +## Commands & Scripts + +Run from the repository root unless noted. + +- `npm run build` - Type-check all workspaces (`tsc --noEmit`). Lambdas are bundled by CDK/esbuild at synth/deploy time. +- `npm run clean` - Remove build outputs and coverage. +- `npm run test` - Run all unit tests. +- `npm run test:coverage` - Run all unit tests with coverage. +- `npm run test:watch` - Run Vitest in watch mode. +- `npm run lint` / `npm run lint:fix` - Run ESLint. +- `npm run format` / `npm run format:check` - Run/check Prettier. +- `npm run