diff --git a/.env.example b/.env.example
index 25463e2..2497ca8 100644
--- a/.env.example
+++ b/.env.example
@@ -10,6 +10,7 @@ VITE_BUILD_TS=1970-01-01T00:00:00Z
VITE_BUILD_COMMIT_SHA=local
VITE_BUILD_ENV_CODE=local
VITE_BUILD_WORKFLOW_NAME=local
+VITE_BUILD_WORKFLOW_RUNNER=local
VITE_BUILD_WORKFLOW_RUN_NUMBER=1
VITE_BUILD_WORKFLOW_RUN_ATTEMPT=1
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 87f9096..b8021c4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -52,14 +52,40 @@ jobs:
run: npm run test:ci
# --- Infrastructure Checks ---
- - name: Configure AWS Credentials
- uses: aws-actions/configure-aws-credentials@v6
+ - 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
+ run: |
+ echo "${{ vars.CDK_ENV_DEV }}" > .env
+
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v6.0.0
with:
- aws-region: ${{ vars.AWS_REGION }}
role-to-assume: ${{ vars.AWS_ROLE_ARN_DEV }}
role-session-name: ci-ionic8-starter
+ aws-region: ${{ vars.AWS_REGION }}
- - name: Validate AWS CloudFormation Template
- run: |-
- aws cloudformation validate-template \
- --template-body file://template.yml
+ - name: Synthesize CDK stacks
+ working-directory: ./infrastructure
+ run: npm run synth
+
+ # Final Step: Clean up sensitive infrastructure files
+ - name: Clean up sensitive files
+ if: always()
+ working-directory: ./infrastructure
+ run: |
+ echo "๐งน Cleaning up sensitive files..."
+ rm -f .env
+ rm -rf cdk.out
+ echo "โ
Sensitive files cleaned up"
diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
index 8e09fbf..5802842 100644
--- a/.github/workflows/code-quality.yml
+++ b/.github/workflows/code-quality.yml
@@ -144,85 +144,88 @@ jobs:
echo "โ
All packages are up to date" >> $GITHUB_STEP_SUMMARY
fi
- # - name: Install infrastructure dependencies
- # working-directory: ./infrastructure
- # run: npm ci
-
- # - name: Run infrastructure tests with detailed coverage
- # working-directory: ./infrastructure
- # run: |
- # echo "" >> $GITHUB_STEP_SUMMARY
- # echo "## Infrastructure Test Coverage Analysis" >> $GITHUB_STEP_SUMMARY
- # echo "" >> $GITHUB_STEP_SUMMARY
- # npm run test:coverage
-
- # # Extract coverage summary
- # if [ -f coverage/coverage-summary.json ]; then
- # echo "### Infrastructure 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
-
- # - name: Infrastructure build check
- # run: |
- # echo "" >> $GITHUB_STEP_SUMMARY
- # echo "## Infrastructure Build" >> $GITHUB_STEP_SUMMARY
- # echo "" >> $GITHUB_STEP_SUMMARY
- # npm run build > infra-tsc-output.txt 2>&1
- # if [ $? -eq 0 ]; then
- # echo "โ
Build successful" >> $GITHUB_STEP_SUMMARY
- # else
- # echo "โ Build failed:" >> $GITHUB_STEP_SUMMARY
- # echo '```' >> $GITHUB_STEP_SUMMARY
- # cat infra-tsc-output.txt >> $GITHUB_STEP_SUMMARY
- # echo '```' >> $GITHUB_STEP_SUMMARY
- # fi
-
- # - name: Infrastructure security audit
- # run: |
- # echo "" >> $GITHUB_STEP_SUMMARY
- # echo "## Infrastructure Security Audit" >> $GITHUB_STEP_SUMMARY
- # echo "" >> $GITHUB_STEP_SUMMARY
- # npm audit --audit-level=moderate --omit=dev > infra-security-output.txt 2>&1 || true
-
- # if grep -q "found 0 vulnerabilities" infra-security-output.txt; then
- # echo "โ
No security vulnerabilities found" >> $GITHUB_STEP_SUMMARY
- # else
- # echo "โ ๏ธ Security vulnerabilities detected:" >> $GITHUB_STEP_SUMMARY
- # echo '```' >> $GITHUB_STEP_SUMMARY
- # cat infra-security-output.txt >> $GITHUB_STEP_SUMMARY
- # echo '```' >> $GITHUB_STEP_SUMMARY
- # fi
-
- # - name: Infrastructure package analysis
- # run: |
- # echo "" >> $GITHUB_STEP_SUMMARY
- # echo "## Infrastructure Package Analysis" >> $GITHUB_STEP_SUMMARY
- # echo "" >> $GITHUB_STEP_SUMMARY
-
- # # Check for outdated packages
- # echo "### Outdated Packages:" >> $GITHUB_STEP_SUMMARY
- # npm outdated > infra-outdated-output.txt 2>&1 || true
- # if [ -s infra-outdated-output.txt ]; then
- # echo '```' >> $GITHUB_STEP_SUMMARY
- # cat infra-outdated-output.txt >> $GITHUB_STEP_SUMMARY
- # echo '```' >> $GITHUB_STEP_SUMMARY
- # else
- # echo "โ
All packages are up to date" >> $GITHUB_STEP_SUMMARY
- # fi
+ - name: Install infrastructure dependencies
+ working-directory: ./infrastructure
+ run: npm ci
+
+ - name: Run infrastructure tests with detailed coverage
+ working-directory: ./infrastructure
+ run: |
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "## Infrastructure Test Coverage Analysis" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ npm run test:coverage
+
+ # Extract coverage summary
+ if [ -f coverage/coverage-summary.json ]; then
+ echo "### Infrastructure 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
+
+ - name: Infrastructure build check
+ working-directory: ./infrastructure
+ run: |
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "## Infrastructure Build" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ npm run build > infra-tsc-output.txt 2>&1
+ if [ $? -eq 0 ]; then
+ echo "โ
Build successful" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "โ Build failed:" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ cat infra-tsc-output.txt >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ fi
+
+ - name: Infrastructure security audit
+ working-directory: ./infrastructure
+ run: |
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "## Infrastructure Security Audit" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ npm audit --audit-level=moderate --omit=dev > infra-security-output.txt 2>&1 || true
+
+ if grep -q "found 0 vulnerabilities" infra-security-output.txt; then
+ echo "โ
No security vulnerabilities found" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "โ ๏ธ Security vulnerabilities detected:" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ cat infra-security-output.txt >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ fi
+
+ - name: Infrastructure package analysis
+ working-directory: ./infrastructure
+ run: |
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "## Infrastructure Package Analysis" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ # Check for outdated packages
+ echo "### Outdated Packages:" >> $GITHUB_STEP_SUMMARY
+ npm outdated > infra-outdated-output.txt 2>&1 || true
+ if [ -s infra-outdated-output.txt ]; then
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ cat infra-outdated-output.txt >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ else
+ echo "โ
All packages are up to date" >> $GITHUB_STEP_SUMMARY
+ fi
- name: Archive test results
if: always()
diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml
index 62fa428..ba79fd9 100644
--- a/.github/workflows/deploy-dev.yml
+++ b/.github/workflows/deploy-dev.yml
@@ -13,13 +13,7 @@ concurrency:
cancel-in-progress: false
env:
- APP_NAME: ionic8-playground.leanstacks.net
- AWS_CFN_STACK_NAME: ls-ui-ionic8playground-resources-dev
- AWS_CFN_TEMPLATE: template.yml
AWS_ENV_CODE: dev
- AWS_REGION: ${{ vars.AWS_REGION }}
- AWS_ROLE_ARN: ${{ vars.AWS_ROLE_ARN_DEV }}
- ENV_FILE: ${{ vars.ENV_DEV }}
jobs:
deploy:
@@ -47,7 +41,7 @@ jobs:
- name: Create Environment Configuration
run: |
- echo "${{ env.ENV_FILE }}" > .env
+ echo "${{ vars.ENV_DEV }}" > .env
echo "VITE_BUILD_DATE=$(date -u +'%Y-%m-%d')" >> .env
echo "VITE_BUILD_TIME=$(date -u +'%H:%M:%S')" >> .env
echo "VITE_BUILD_TS=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> .env
@@ -64,28 +58,49 @@ jobs:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v6
with:
- aws-region: ${{ env.AWS_REGION }}
- role-to-assume: ${{ env.AWS_ROLE_ARN }}
+ aws-region: ${{ vars.AWS_REGION }}
+ role-to-assume: ${{ vars.AWS_ROLE_ARN_DEV }}
role-session-name: deploy-ionic8-starter-dev
- - name: Deploy AWS CloudFormation Stack
- run: |-
- aws cloudformation deploy \
- --stack-name ${{ env.AWS_CFN_STACK_NAME }} \
- --template-file ${{ env.AWS_CFN_TEMPLATE }} \
- --parameter-overrides EnvironmentCode=${{ env.AWS_ENV_CODE }} \
- --tags App=${{ env.APP_NAME }} Env=${{ env.AWS_ENV_CODE }} OU=leanstacks Owner='Matthew Warman'
-
- - name: Get CloudFormation Stack Outputs
- id: cloudformation
- run: |-
- APP_BUCKET_NAME=$(
- aws cloudformation describe-stacks \
- --stack-name ${{ env.AWS_CFN_STACK_NAME }} \
- --query "Stacks[0].Outputs[?OutputKey=='AppBucketName'].OutputValue | [0]"
- )
- echo "APP_BUCKET_NAME=$APP_BUCKET_NAME" >> "$GITHUB_OUTPUT"
-
- - name: Deploy to AWS S3
+ - name: Install infrastructure dependencies
+ working-directory: ./infrastructure
+ run: npm ci
+
+ - name: Create infrastructure .env file
+ working-directory: ./infrastructure
+ run: |
+ echo "${{ vars.CDK_ENV_DEV }}" > .env
+ echo "โ
Infrastructure .env file created"
+
+ - name: Build infrastructure
+ working-directory: ./infrastructure
+ run: npm run build
+
+ - name: Bootstrap CDK
+ working-directory: ./infrastructure
+ run: |
+ echo "โก Checking if CDK bootstrap is needed..."
+ # Try to describe the bootstrap stack to see if it exists
+ if ! aws cloudformation describe-stacks --stack-name CDKToolkit --region ${{ vars.AWS_REGION }} >/dev/null 2>&1; then
+ echo "๐ CDK not bootstrapped, bootstrapping now..."
+ npm run bootstrap
+ else
+ echo "โ
CDK already bootstrapped, skipping..."
+ fi
+
+ - name: Synthesize CDK stacks
+ working-directory: ./infrastructure
+ run: npm run synth
+
+ - name: Deploy CDK stacks
+ working-directory: ./infrastructure
+ run: npm run deploy:all -- --require-approval never
+
+ - name: Clean up sensitive files
+ if: always()
+ working-directory: ./infrastructure
run: |
- aws s3 sync dist s3://${{ steps.cloudformation.outputs.APP_BUCKET_NAME }} --delete
+ echo "๐งน Cleaning up sensitive files..."
+ rm -f .env
+ rm -rf cdk.out
+ echo "โ
Sensitive files cleaned up"
diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml
index 22fe9b4..421e975 100644
--- a/.github/workflows/deploy-prod.yml
+++ b/.github/workflows/deploy-prod.yml
@@ -13,13 +13,7 @@ concurrency:
cancel-in-progress: false
env:
- APP_NAME: ionic8-playground.leanstacks.net
- AWS_CFN_STACK_NAME: ls-ui-ionic8playground-resources-prod
- AWS_CFN_TEMPLATE: template.yml
- AWS_ENV_CODE: prod
- AWS_REGION: ${{ vars.AWS_REGION }}
- AWS_ROLE_ARN: ${{ vars.AWS_ROLE_ARN_PROD }}
- ENV_FILE: ${{ vars.ENV_PROD }}
+ AWS_ENV_CODE: prd
jobs:
deploy:
@@ -47,7 +41,7 @@ jobs:
- name: Create Environment Configuration
run: |
- echo "${{ env.ENV_FILE }}" > .env
+ echo "${{ vars.ENV_PROD }}" > .env
echo "VITE_BUILD_DATE=$(date -u +'%Y-%m-%d')" >> .env
echo "VITE_BUILD_TIME=$(date -u +'%H:%M:%S')" >> .env
echo "VITE_BUILD_TS=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> .env
@@ -64,28 +58,49 @@ jobs:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v6
with:
- aws-region: ${{ env.AWS_REGION }}
- role-to-assume: ${{ env.AWS_ROLE_ARN }}
+ aws-region: ${{ vars.AWS_REGION }}
+ role-to-assume: ${{ vars.AWS_ROLE_ARN_PROD }}
role-session-name: deploy-ionic8-starter-prod
- - name: Deploy AWS CloudFormation Stack
- run: |-
- aws cloudformation deploy \
- --stack-name ${{ env.AWS_CFN_STACK_NAME }} \
- --template-file ${{ env.AWS_CFN_TEMPLATE }} \
- --parameter-overrides EnvironmentCode=${{ env.AWS_ENV_CODE }} \
- --tags App=${{ env.APP_NAME }} Env=${{ env.AWS_ENV_CODE }} OU=leanstacks Owner='Matthew Warman'
-
- - name: Get CloudFormation Stack Outputs
- id: cloudformation
- run: |-
- APP_BUCKET_NAME=$(
- aws cloudformation describe-stacks \
- --stack-name ${{ env.AWS_CFN_STACK_NAME }} \
- --query "Stacks[0].Outputs[?OutputKey=='AppBucketName'].OutputValue | [0]"
- )
- echo "APP_BUCKET_NAME=$APP_BUCKET_NAME" >> "$GITHUB_OUTPUT"
-
- - name: Deploy to AWS S3
+ - name: Install infrastructure dependencies
+ working-directory: ./infrastructure
+ run: npm ci
+
+ - name: Create infrastructure .env file
+ working-directory: ./infrastructure
+ run: |
+ echo "${{ vars.CDK_ENV_PROD }}" > .env
+ echo "โ
Infrastructure .env file created"
+
+ - name: Build infrastructure
+ working-directory: ./infrastructure
+ run: npm run build
+
+ - name: Bootstrap CDK
+ working-directory: ./infrastructure
+ run: |
+ echo "โก Checking if CDK bootstrap is needed..."
+ # Try to describe the bootstrap stack to see if it exists
+ if ! aws cloudformation describe-stacks --stack-name CDKToolkit --region ${{ vars.AWS_REGION }} >/dev/null 2>&1; then
+ echo "๐ CDK not bootstrapped, bootstrapping now..."
+ npm run bootstrap
+ else
+ echo "โ
CDK already bootstrapped, skipping..."
+ fi
+
+ - name: Synthesize CDK stacks
+ working-directory: ./infrastructure
+ run: npm run synth
+
+ - name: Deploy CDK stacks
+ working-directory: ./infrastructure
+ run: npm run deploy:all -- --require-approval never
+
+ - name: Clean up sensitive files
+ if: always()
+ working-directory: ./infrastructure
run: |
- aws s3 sync dist s3://${{ steps.cloudformation.outputs.APP_BUCKET_NAME }} --delete
+ echo "๐งน Cleaning up sensitive files..."
+ rm -f .env
+ rm -rf cdk.out
+ echo "โ
Sensitive files cleaned up"
diff --git a/.github/workflows/deploy-qa.yml b/.github/workflows/deploy-qa.yml
index 917b314..c264bb8 100644
--- a/.github/workflows/deploy-qa.yml
+++ b/.github/workflows/deploy-qa.yml
@@ -12,13 +12,7 @@ concurrency:
cancel-in-progress: false
env:
- APP_NAME: ionic8-playground.leanstacks.net
- AWS_CFN_STACK_NAME: ls-ui-ionic8playground-resources-qa
- AWS_CFN_TEMPLATE: template.yml
AWS_ENV_CODE: qa
- AWS_REGION: ${{ vars.AWS_REGION }}
- AWS_ROLE_ARN: ${{ vars.AWS_ROLE_ARN_QA }}
- ENV_FILE: ${{ vars.ENV_QA }}
jobs:
deploy:
@@ -46,7 +40,7 @@ jobs:
- name: Create Environment Configuration
run: |
- echo "${{ env.ENV_FILE }}" > .env
+ echo "${{ vars.ENV_QA }}" > .env
echo "VITE_BUILD_DATE=$(date -u +'%Y-%m-%d')" >> .env
echo "VITE_BUILD_TIME=$(date -u +'%H:%M:%S')" >> .env
echo "VITE_BUILD_TS=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> .env
@@ -63,28 +57,49 @@ jobs:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v6
with:
- aws-region: ${{ env.AWS_REGION }}
- role-to-assume: ${{ env.AWS_ROLE_ARN }}
+ aws-region: ${{ vars.AWS_REGION }}
+ role-to-assume: ${{ vars.AWS_ROLE_ARN_QA }}
role-session-name: deploy-ionic8-starter-qa
- - name: Deploy AWS CloudFormation Stack
- run: |-
- aws cloudformation deploy \
- --stack-name ${{ env.AWS_CFN_STACK_NAME }} \
- --template-file ${{ env.AWS_CFN_TEMPLATE }} \
- --parameter-overrides EnvironmentCode=${{ env.AWS_ENV_CODE }} \
- --tags App=${{ env.APP_NAME }} Env=${{ env.AWS_ENV_CODE }} OU=leanstacks Owner='Matthew Warman'
-
- - name: Get CloudFormation Stack Outputs
- id: cloudformation
- run: |-
- APP_BUCKET_NAME=$(
- aws cloudformation describe-stacks \
- --stack-name ${{ env.AWS_CFN_STACK_NAME }} \
- --query "Stacks[0].Outputs[?OutputKey=='AppBucketName'].OutputValue | [0]"
- )
- echo "APP_BUCKET_NAME=$APP_BUCKET_NAME" >> "$GITHUB_OUTPUT"
-
- - name: Deploy to AWS S3
+ - name: Install infrastructure dependencies
+ working-directory: ./infrastructure
+ run: npm ci
+
+ - name: Create infrastructure .env file
+ working-directory: ./infrastructure
+ run: |
+ echo "${{ vars.CDK_ENV_QA }}" > .env
+ echo "โ
Infrastructure .env file created"
+
+ - name: Build infrastructure
+ working-directory: ./infrastructure
+ run: npm run build
+
+ - name: Bootstrap CDK
+ working-directory: ./infrastructure
+ run: |
+ echo "โก Checking if CDK bootstrap is needed..."
+ # Try to describe the bootstrap stack to see if it exists
+ if ! aws cloudformation describe-stacks --stack-name CDKToolkit --region ${{ vars.AWS_REGION }} >/dev/null 2>&1; then
+ echo "๐ CDK not bootstrapped, bootstrapping now..."
+ npm run bootstrap
+ else
+ echo "โ
CDK already bootstrapped, skipping..."
+ fi
+
+ - name: Synthesize CDK stacks
+ working-directory: ./infrastructure
+ run: npm run synth
+
+ - name: Deploy CDK stacks
+ working-directory: ./infrastructure
+ run: npm run deploy:all -- --require-approval never
+
+ - name: Clean up sensitive files
+ if: always()
+ working-directory: ./infrastructure
run: |
- aws s3 sync dist s3://${{ steps.cloudformation.outputs.APP_BUCKET_NAME }} --delete
+ echo "๐งน Cleaning up sensitive files..."
+ rm -f .env
+ rm -rf cdk.out
+ echo "โ
Sensitive files cleaned up"
diff --git a/.prettierignore b/.prettierignore
index 3eebccc..54a0b09 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,2 +1,7 @@
# Android native code and build files
-android
\ No newline at end of file
+android
+
+# Infrastructure outputs and build artifacts
+infrastructure/cdk.out
+infrastructure/dist
+infrastructure/coverage
\ No newline at end of file
diff --git a/README.md b/README.md
index 72e23f1..345e3d7 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,17 @@
A template to kickstart Ionic + React applications utilizing an opinionated technology stack for optimal testability, maintainability, and operability.
[](https://github.com/leanstacks/ionic8-starter/actions/workflows/ci.yml)
+
+
+[](https://github.com/leanstacks/ionic8-starter/actions/workflows/code-quality.yml)
+
+## License
+
+This project is licensed under the MIT License - see [LICENSE](./LICENSE) file for details.
+
+## Documentation
+
+For detailed guides and reference materials, see the [Project Documentation](docs/README.md).
## Helpful Hints
@@ -28,7 +39,7 @@ This project was bootstrapped with the [Ionic CLI](https://ionicframework.com/do
ionic start ionic8-starter blank --type=react
```
-The technology stack includes:
+The application production technology stack includes:
- Ionic - the foundation
- Vite - React development environment
@@ -37,16 +48,26 @@ The technology stack includes:
- TanStack Query - data fetching, caching, and asynchronous state management
- Axios - HTTP client
- Formik - form management
-- Yup - validation
+- Yup - schema-based validation (deprecated)
+- Zod - schema-based validation
- Lodash - utility functions
- DayJS - date utility functions
- i18next - internationalization framework
-- Testing Library React - unit tests
+
+The application development technology stack includes:
+
- Vitest - unit tests
+- Testing Library React - unit tests
- MSW - API mocking
- Cypress - end-to-end tests
- TypeScript
+The infrastructure technology stack includes:
+
+- AWS CDK - framework for provisioning AWS cloud infrastructure
+- Zod - schema based validation
+- Jest: unit test framework
+
### Repository
This repository uses [trunk-based development](https://www.atlassian.com/continuous-delivery/continuous-integration/trunk-based-development). The latest code is located on the `main` branch. The `main` branch is always ready for deployment.
@@ -126,92 +147,7 @@ Install the _Prettier_ extension to ensure that all project participants' contri
## Configuration
-The application is configured using Environment Variables. Because single-page applications are static, environment variable values are injected into the application during the build. The environment variables may be sourced from the environment or `.env` files as described in the [Vite documentation](https://vitejs.dev/guide/env-and-mode.html).
-
-> **NOTE:** Ionic Config provides a way to change the properties of Ionic components globally. This is different from application configuration. See the [Ionic Config](https://ionicframework.com/docs/developing/config) docs for more details.
-
-### `.env` files
-
-> **NOTE:** Because they may contain sensitive information, `.env` files are not committed to the repository.
-
-> **TIP:** When configuration values are modified, notify your DevOps team to modify the values in automation pipelines accordingly.
-
-After project installation and before running the application locally, create the following `.env` files in the project base directory. Learn more in the official [Vite guide for environment variables and modes](https://vitejs.dev/guide/env-and-mode.html).
-
-#### Setup
-
-1. **Copy the example configuration file:**
-
- ```bash
- cp .env.example .env
- ```
-
-2. **Update variables for your environment:**
-
- ```env
- VITE_BASE_URL_API=https://your-api.example.com
- VITE_TOAST_AUTO_DISMISS_MILLIS=5000
- ```
-
-3. **Build information** (typically set by CI/CD pipeline):
- ```env
- VITE_BUILD_DATE=2026-02-10
- VITE_BUILD_TIME=14:30:00
- VITE_BUILD_TS=2026-02-10T14:30:00Z
- VITE_BUILD_COMMIT_SHA=abc123def456
- VITE_BUILD_ENV_CODE=dev
- VITE_BUILD_WORKFLOW_NAME=Build
- VITE_BUILD_WORKFLOW_RUN_NUMBER=42
- VITE_BUILD_WORKFLOW_RUN_ATTEMPT=1
- ```
-
-#### `.env.local`
-
-The `.env.local` configuration file provides the configuration values when the application is started on a developer's local machine.
-
-```
-# Provided by Pipeline (Simulated)
-VITE_BUILD_DATE=1970-01-01
-VITE_BUILD_TIME=00:00:00
-VITE_BUILD_TS=1970-01-01T00:00:00+0000
-VITE_BUILD_COMMIT_SHA=local
-VITE_BUILD_ENV_CODE=local
-VITE_BUILD_WORKFLOW_RUNNER=local
-VITE_BUILD_WORKFLOW_NAME=local
-VITE_BUILD_WORKFLOW_RUN_NUMBER=1
-VITE_BUILD_WORKFLOW_RUN_ATTEMPT=1
-
-# API Configuration
-VITE_BASE_URL_API=https://jsonplaceholder.typicode.com
-
-# Toasts Configuration
-VITE_TOAST_AUTO_DISMISS_MILLIS=5000
-```
-
-#### `.env.test.local`
-
-The `.env.test.local` configuration file provides configuration values used when tests are executed on a developer's local machine.
-
-> **NOTE:** Use the same values when running tests in a CI/CD pipeline.
-
-```
-# Provided by Pipeline (Simulated)
-VITE_BUILD_DATE=1970-01-01
-VITE_BUILD_TIME=00:00:00
-VITE_BUILD_TS=1970-01-01T00:00:00+0000
-VITE_BUILD_COMMIT_SHA=test
-VITE_BUILD_ENV_CODE=test
-VITE_BUILD_WORKFLOW_RUNNER=test
-VITE_BUILD_WORKFLOW_NAME=test
-VITE_BUILD_WORKFLOW_RUN_NUMBER=1
-VITE_BUILD_WORKFLOW_RUN_ATTEMPT=1
-
-# API Configuration
-VITE_BASE_URL_API=https://jsonplaceholder.typicode.com
-
-# Toasts Configuration
-VITE_TOAST_AUTO_DISMISS_MILLIS=1500
-```
+See the [Configuration Guide](./docs/CONFIGURATION_GUIDE.md) in the [project docs](./docs/README.md) for detailed information about application and infrastructure configuration.
# Available Scripts
@@ -260,34 +196,17 @@ Runs the [ESLint][eslint] static code analysis and prints the results to the con
## DevOps
-### Cloud Resources
-
-The AWS resources for this application component are provisioned via AWS CloudFormation. The `template.yml` file is the CloudFormation template.
-
-The resources provisioned are:
-
-| Resource | Description |
-| ----------------------- | ----------------------------------------------------------------------------- |
-| S3 Bucket | Contains the published application. |
-| S3 Bucket Policy | Provides access to the S3 Bucket from AWS CloudFront. |
-| CloudFront Distribution | A CloudFront distribution to serve the SPA application. |
-| CloudFront Distribution | A CloudFront distribution to serve the full-stack application (UI, API, etc). |
-| Route53 RecordSet | An `A` record for the application distribution. |
-| Route53 RecordSet | An `AAAA` record for the application distribution. |
+### Automation
-### CI/CD Pipelines
+See the [DevOps Guide](./docs/DEVOPS_GUIDE.md) in the [project docs](./docs/README.md) for detailed information about DevOps automation workflows.
-This project uses GitHub Actions to perform DevOps automation activities such as Continuous Integration and Continous Deployment. See all project [GitHub Actions workflow runs](https://github.com/mwarman/ionic8-playground/actions).
+### Infrastructure
-| Workflow | Trigger | Description |
-| --------------------- | ------------------------------ | ------------------------------------------------------------------------------------ |
-| CI | Pull Request for `main` branch | Builds, lints, and tests the application. Validates the AWS CloudFormation template. |
-| Deploy to Development | Push to `main` branch | Deploys AWS CloudFormation stack. Builds and deploys the application. |
-| Deploy to QA | Push to `release/*` branch | Deploys AWS CloudFormation stack. Builds and deploys the application. |
-| Deploy to Production | Publish a Release | Deploys AWS CloudFormation stack. Builds and deploys the application. |
+See the [Infrastructure Guide](./docs/INFRASTRUCTURE_GUIDE.md) in the [project docs](./docs/README.md) for detailed information about the AWS infrastructure and the AWS CDK application to provision those resources.
-## Related Information
+## Further Reading
+- [Project Documentation](./docs/README.md)
- [Ionic][ionic]
- [Vite][vite]
- [React][react]
@@ -295,6 +214,7 @@ This project uses GitHub Actions to perform DevOps automation activities such as
- [Axios][axios]
- [Formik][formik]
- [Yup][yup]
+- [Zod][zod]
- [Testing Library][testing-library]
- [Vitest][vitest]
- [Cypress][cypress]
@@ -315,3 +235,4 @@ This project uses GitHub Actions to perform DevOps automation activities such as
[ghactions]: https://docs.github.com/en/actions 'GitHub Actions'
[eslint]: https://eslint.org/docs/latest/ 'ESLint'
[cypress]: https://docs.cypress.io/guides/overview/why-cypress 'Cypress Testing Framework'
+[zod]: https://zod.dev/ 'Zod'
diff --git a/docs/CONFIGURATION_GUIDE.md b/docs/CONFIGURATION_GUIDE.md
new file mode 100644
index 0000000..c9d42f0
--- /dev/null
+++ b/docs/CONFIGURATION_GUIDE.md
@@ -0,0 +1,492 @@
+# Configuration Guide
+
+This guide provides comprehensive information about configuring the Ionic Starter application and its AWS CDK infrastructure.
+
+## Application Configuration
+
+The Ionic + React application configuration is managed through environment variables prefixed with `VITE_`. These variables are available at build time and runtime in your Ionic + React components.
+
+### Environment Variables
+
+The following environment variables are available for configuring the application:
+
+| Variable | Type | Description | Default | Required |
+| --------------------------------- | ------ | ---------------------------------------------------- | ------- | -------- |
+| `VITE_BASE_URL_API` | string | Base URL for API requests | - | Yes |
+| `VITE_TOAST_AUTO_DISMISS_MILLIS` | number | Auto-dismiss duration for toast notifications (ms) | `5000` | No |
+| `VITE_BUILD_DATE` | string | Build date (YYYY-MM-DD format) | - | No |
+| `VITE_BUILD_TIME` | string | Build time (HH:MM:SS format) | - | No |
+| `VITE_BUILD_TS` | string | Build timestamp (ISO 8601 format) | - | No |
+| `VITE_BUILD_COMMIT_SHA` | string | Git commit SHA of the build | - | No |
+| `VITE_BUILD_ENV_CODE` | string | Environment code (e.g., `local`, `dev`, `qa`, `prd`) | - | No |
+| `VITE_BUILD_WORKFLOW_NAME` | string | CI/CD workflow name | - | No |
+| `VITE_BUILD_WORKFLOW_RUNNER` | string | CI/CD workflow runner (e.g., `GitHub Actions`) | - | No |
+| `VITE_BUILD_WORKFLOW_RUN_NUMBER` | string | CI/CD workflow run number | - | No |
+| `VITE_BUILD_WORKFLOW_RUN_ATTEMPT` | string | CI/CD workflow run attempt number | - | No |
+
+### Setup
+
+1. **Copy the example configuration file:**
+
+ ```bash
+ cp .env.example .env
+ ```
+
+2. **Update variables for your environment:**
+
+ ```env
+ VITE_BASE_URL_API=https://your-api.example.com
+ VITE_TOAST_AUTO_DISMISS_MILLIS=5000
+ ```
+
+3. **Build information** (typically set by CI/CD pipeline):
+ ```env
+ VITE_BUILD_DATE=2026-02-10
+ VITE_BUILD_TIME=14:30:00
+ VITE_BUILD_TS=2026-02-10T14:30:00Z
+ VITE_BUILD_COMMIT_SHA=abc123def456
+ VITE_BUILD_ENV_CODE=dev
+ VITE_BUILD_WORKFLOW_NAME=Build
+ VITE_BUILD_WORKFLOW_RUNNER=GitHub Actions
+ VITE_BUILD_WORKFLOW_RUN_NUMBER=42
+ VITE_BUILD_WORKFLOW_RUN_ATTEMPT=1
+ ```
+
+### Accessing Configuration
+
+Application configuration values are accessed through the `config` utility, which provides type-safe, validated configuration throughout your React components and utilities. The utility validates all environment variables at runtime using Zod schema validation, ensuring type safety and early error detection.
+
+Import the `config` object from the common utilities:
+
+```typescript
+import { config } from 'common/utils/config';
+
+// API base URL (type-safe string)
+const apiUrl = config.VITE_BASE_URL_API;
+
+// Toast settings (type-safe number)
+const toastDuration = config.VITE_TOAST_AUTO_DISMISS_MILLIS;
+
+// Build information (type-safe strings)
+const buildDate = config.VITE_BUILD_DATE;
+const buildCommit = config.VITE_BUILD_COMMIT_SHA;
+const envCode = config.VITE_BUILD_ENV_CODE;
+```
+
+**Benefits of using the `config` utility:**
+
+- **Type Safety**: All configuration values are validated against a Zod schema, ensuring correct types
+- **Validation**: Environment variables are validated on application startup, catching missing or invalid configuration early
+- **IDE Support**: Full TypeScript autocomplete and type checking for configuration values
+- **Single Source of Truth**: Configuration is centralized and consistently accessed throughout the application
+
+**Configuration Schema Location:**
+The Zod schema that validates all environment variables is defined in [src/common/utils/config.ts](../src/common/utils/config.ts). This file also exports the `Config` type for use in type annotations when needed.
+
+### Local Development
+
+For local development, create a `.env` file in the root directory with local values:
+
+```env
+VITE_BASE_URL_API=http://localhost:3000
+VITE_TOAST_AUTO_DISMISS_MILLIS=5000
+VITE_BUILD_DATE=1970-01-01
+VITE_BUILD_TIME=00:00:00
+VITE_BUILD_TS=1970-01-01T00:00:00Z
+VITE_BUILD_COMMIT_SHA=local
+VITE_BUILD_ENV_CODE=local
+VITE_BUILD_WORKFLOW_NAME=local
+VITE_BUILD_WORKFLOW_RUNNER=local
+VITE_BUILD_WORKFLOW_RUN_NUMBER=1
+VITE_BUILD_WORKFLOW_RUN_ATTEMPT=1
+```
+
+### Unit Testing
+
+For running unit tests, create a `.env.test.local` file in the root directory with values optimized for the test environment. This configuration is used by Vitest when running unit tests.
+
+1. **Copy the example configuration file:**
+
+ ```bash
+ cp .env.example .env.test.local
+ ```
+
+2. **Update with test-specific values:**
+
+ ```env
+ # Provided by Pipeline (Simulated)
+ VITE_BUILD_DATE=1970-01-01
+ VITE_BUILD_TIME=00:00:00
+ VITE_BUILD_TS=1970-01-01T00:00:00Z
+ VITE_BUILD_COMMIT_SHA=test
+ VITE_BUILD_ENV_CODE=test
+ VITE_BUILD_WORKFLOW_NAME=test
+ VITE_BUILD_WORKFLOW_RUNNER=local
+ VITE_BUILD_WORKFLOW_RUN_NUMBER=1
+ VITE_BUILD_WORKFLOW_RUN_ATTEMPT=1
+
+ # API Configuration
+ # Use JSONPlaceholder for mock API testing
+ VITE_BASE_URL_API=https://jsonplaceholder.typicode.com
+
+ # Toasts Configuration
+ # Use shorter duration in tests for faster test execution
+ VITE_TOAST_AUTO_DISMISS_MILLIS=1500
+ ```
+
+#### Test Configuration Notes
+
+- **API Base URL**: Uses [JSONPlaceholder](https://jsonplaceholder.typicode.com/) - a free fake REST API for testing. In actual test environments, API calls are typically mocked using MSW (Mock Service Worker).
+- **Toast Duration**: Reduced to 1500ms for faster test execution while still allowing time for async operations.
+- **Build Information**: All build-related variables are set to static test values, ensuring consistent snapshots and reproducible test results.
+- **Environment Code**: Set to `test` to distinguish test runs from development and production.
+
+#### Usage with MSW
+
+The test configuration works seamlessly with [MSW](https://mswjs.io/) (Mock Service Worker), which intercepts HTTP requests in tests. The `VITE_BASE_URL_API` can point to a test server, and MSW handlers will intercept and mock responses:
+
+```typescript
+// Example MSW handler in tests
+import { http, HttpResponse } from 'msw';
+import { setupServer } from 'msw/node';
+
+const server = setupServer(
+ http.get('https://jsonplaceholder.typicode.com/users', () => {
+ return HttpResponse.json([{ id: 1, name: 'Test User' }]);
+ }),
+);
+
+beforeAll(() => server.listen());
+afterEach(() => server.resetHandlers());
+afterAll(() => server.close());
+```
+
+---
+
+## Infrastructure Configuration
+
+The infrastructure configuration is managed through environment variables prefixed with `CDK_`. These variables control how AWS resources are provisioned and deployed using the AWS CDK.
+
+### Environment Variables
+
+The following environment variables are available for configuring the infrastructure:
+
+| Variable | Type | Description | Default | Required |
+| ---------------------- | ------ | ----------------------------------------------------------------------------- | -------------------------- | -------- |
+| `CDK_APP_NAME` | string | Application name used in resource naming and tagging | `ionic8-starter` | No |
+| `CDK_ENV` | enum | Deployment environment: `dev`, `qa`, `prd` | - | Yes |
+| `CDK_ACCOUNT` | string | AWS account ID for deployment | Auto-detected from AWS CLI | No |
+| `CDK_REGION` | string | AWS region for deployment (e.g., `us-east-1`) | Auto-detected from AWS CLI | No |
+| `CDK_OU` | string | Organizational Unit for resource tagging | `unknown` | No |
+| `CDK_OWNER` | string | Owner tag for resource tracking | `unknown` | No |
+| `CDK_ASSET_PATH` | string | Path to application build artifacts (relative to infrastructure directory) | `../dist` | No |
+| `CDK_DOMAIN_NAME` | string | Custom domain name for the application CDN (e.g., `app.example.com`) | - | No |
+| `CDK_CERTIFICATE_ARN` | string | ACM certificate ARN for custom domain (must be in `us-east-1` for CloudFront) | - | No |
+| `CDK_HOSTED_ZONE_ID` | string | Route53 hosted zone ID for DNS records | - | No |
+| `CDK_HOSTED_ZONE_NAME` | string | Route53 hosted zone name (e.g., `example.com`) | - | No |
+
+### Setup
+
+1. **Navigate to the infrastructure directory:**
+
+ ```bash
+ cd infrastructure
+ ```
+
+2. **Copy the example configuration file:**
+
+ ```bash
+ cp .env.example .env
+ ```
+
+3. **Update variables for your environment:**
+ ```env
+ CDK_ENV=dev
+ CDK_APP_NAME=ionic8-starter
+ CDK_ACCOUNT=123456789012
+ CDK_REGION=us-east-1
+ CDK_OU=software-engineering
+ CDK_OWNER=your-team
+ ```
+
+### Usage
+
+Infrastructure configuration is loaded and validated through the `getConfig()` function in `infrastructure/utils/config.ts`:
+
+```typescript
+import { getConfig, getTags, getEnvironmentConfig } from './utils/config';
+
+const config = getConfig();
+const tags = getTags(config);
+const envConfig = getEnvironmentConfig(config);
+
+console.log(`Environment: ${config.CDK_ENV}`);
+console.log(`App name: ${config.CDK_APP_NAME}`);
+console.log(`Tags:`, tags);
+```
+
+### Configuration Files
+
+Infrastructure configuration can be provided through:
+
+1. **Environment variables** - Set directly in your shell or CI/CD pipeline (highest priority)
+2. **.env file** - Create a `.env` file in the `infrastructure/` directory for local development
+3. **Default values** - Specified in the Zod schema (lowest priority)
+
+### Resource Tagging
+
+All AWS resources created by the CDK are automatically tagged with the following tags:
+
+| Tag | Description | Source |
+| ------- | -------------------------------- | -------------- |
+| `App` | Application name | `CDK_APP_NAME` |
+| `Env` | Environment (`dev`, `qa`, `prd`) | `CDK_ENV` |
+| `OU` | Organizational Unit | `CDK_OU` |
+| `Owner` | Team or individual responsible | `CDK_OWNER` |
+
+These tags are used for cost allocation, resource management, and identifying resources in AWS.
+
+### Example Configurations
+
+#### Development Environment
+
+Create `infrastructure/.env` for development deployment:
+
+```env
+###############################################
+## Development Environment Configuration
+###############################################
+
+CDK_APP_NAME=ionic8-starter
+CDK_ENV=dev
+CDK_ACCOUNT=123456789012
+CDK_REGION=us-east-1
+
+### Resource Tagging ###
+CDK_OU=software-engineering
+CDK_OWNER=frontend-team
+
+### CDN Configuration ###
+CDK_ASSET_PATH=../dist
+CDK_DOMAIN_NAME=ionic8-starter.dev.example.com
+CDK_CERTIFICATE_ARN=arn:aws:acm:us-east-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+CDK_HOSTED_ZONE_ID=Z1234567890ABC
+CDK_HOSTED_ZONE_NAME=dev.example.com
+```
+
+#### QA Environment
+
+```env
+###############################################
+## QA Environment Configuration
+###############################################
+
+CDK_APP_NAME=ionic8-starter
+CDK_ENV=qa
+CDK_ACCOUNT=123456789012
+CDK_REGION=us-east-1
+
+CDK_OU=software-engineering
+CDK_OWNER=frontend-team
+
+CDK_ASSET_PATH=../dist
+CDK_DOMAIN_NAME=ionic8-starter.qa.example.com
+CDK_CERTIFICATE_ARN=arn:aws:acm:us-east-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+CDK_HOSTED_ZONE_ID=Z1234567890ABC
+CDK_HOSTED_ZONE_NAME=qa.example.com
+```
+
+#### Production Environment
+
+```env
+###############################################
+## Production Environment Configuration
+###############################################
+
+CDK_APP_NAME=ionic8-starter
+CDK_ENV=prd
+CDK_ACCOUNT=123456789012
+CDK_REGION=us-east-1
+
+CDK_OU=software-engineering
+CDK_OWNER=frontend-team
+
+CDK_ASSET_PATH=../dist
+CDK_DOMAIN_NAME=ionic8-starter.example.com
+CDK_CERTIFICATE_ARN=arn:aws:acm:us-east-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+CDK_HOSTED_ZONE_ID=Z1234567890ABC
+CDK_HOSTED_ZONE_NAME=example.com
+```
+
+### AWS Account and Region
+
+The CDK uses the following precedence for account and region:
+
+1. **Explicit CDK variables** (highest priority)
+ - `CDK_ACCOUNT` - AWS account ID
+ - `CDK_REGION` - AWS region
+
+2. **AWS CDK environment defaults**
+ - `CDK_DEFAULT_ACCOUNT` - Auto-detected from AWS credentials
+ - `CDK_DEFAULT_REGION` - Auto-detected from AWS profile
+
+3. **AWS CLI configuration**
+ - Uses the configured AWS profile and region
+
+If neither explicit CDK variables nor CDK_DEFAULT environment variables are set, the CDK will fail with an error requesting explicit account and region configuration.
+
+### Custom Domain and SSL/TLS
+
+To use a custom domain for your application:
+
+1. **Create an ACM certificate** in `us-east-1` (required for CloudFront):
+
+ ```bash
+ aws acm request-certificate \
+ --domain-name ionic8-starter.example.com \
+ --region us-east-1
+ ```
+
+2. **Verify the certificate** in the ACM console
+
+3. **Configure in `.env`:**
+
+ ```env
+ CDK_DOMAIN_NAME=ionic8-starter.example.com
+ CDK_CERTIFICATE_ARN=arn:aws:acm:us-east-1:123456789012:certificate/xxxxx
+ CDK_HOSTED_ZONE_ID=Z1234567890ABC
+ CDK_HOSTED_ZONE_NAME=example.com
+ ```
+
+4. **Deploy** the CDK stack
+
+---
+
+## Configuration Validation
+
+Both application and infrastructure configurations are validated at runtime:
+
+- **Application**: Environment variables are available in Vite at build time
+- **Infrastructure**: Configuration is validated using Zod schema when `getConfig()` is called
+
+If validation fails, you'll see detailed error messages indicating which variables are missing or invalid.
+
+### Common Validation Errors
+
+```
+CDK_ENV must be one of: dev, qa, prd
+```
+
+Ensure `CDK_ENV` is set to one of the allowed values.
+
+```
+CDK configuration validation failed: CDK_ASSET_PATH: Invalid type. Expected string
+```
+
+Ensure all required configuration variables are set as the correct type.
+
+---
+
+## Environment-Specific Recommendations
+
+### Development
+
+- Use auto-detected AWS account and region from AWS CLI
+- Deploy to a development AWS account
+- Enable detailed logging in your application
+- Use minimal resource sizes
+- Consider using S3 + CloudFront for cost-effective hosting
+
+### QA
+
+- Use dedicated QA AWS account
+- Configure proper domain names for testing
+- Use production-like configurations
+- Enable monitoring and alerting
+
+### Production
+
+- Use dedicated production AWS account
+- Configure custom domains with SSL/TLS certificates
+- Use appropriate resource sizes
+- Enable comprehensive monitoring, logging, and alerting
+- Implement backup and disaster recovery plans
+- Consider using CloudFront with WAF for protection
+
+---
+
+## Troubleshooting
+
+### CDK Deployment Fails with "Account/Region" Error
+
+**Problem**: CDK deployment fails with an error about missing account or region.
+
+**Solution**:
+
+1. Set `CDK_ACCOUNT` and `CDK_REGION` explicitly in `.env`
+2. Ensure AWS CLI is configured with `aws configure`
+3. Verify with: `aws sts get-caller-identity`
+
+### Application Configuration Validation Error
+
+**Problem**: Application fails to start with a configuration validation error message.
+
+**Solution**:
+
+1. Ensure all required variables in `.env` are present (see Environment Variables table above)
+2. Ensure variables are prefixed with `VITE_` for Vite compatibility
+3. Restart the development server after changing `.env`
+4. Verify variables are set: `echo $VITE_BASE_URL_API`
+5. The config utility will provide detailed error messages indicating which variables are missing or invalid
+
+**Note**: The application uses the `config` utility from `common/utils/config` which validates all environment variables at startup using Zod schema validation. If any required variables are missing or have invalid values, the application will fail with a clear error message indicating the issue.
+
+### CDK Configuration Validation Errors
+
+**Problem**: CDK fails to load configuration with validation errors.
+
+**Solution**:
+
+1. Check `.env` file syntax (no quotes around values unless necessary)
+2. Verify required variables are set
+3. Check for typos in variable names
+4. Ensure enum values match allowed options
+
+### AWS Credentials Issues
+
+**Problem**: CDK cannot access AWS account.
+
+**Solution**:
+
+1. Configure AWS credentials: `aws configure`
+2. Verify access: `aws sts get-caller-identity`
+3. Use AWS SSO if available: `aws sso login --profile YOUR_PROFILE`
+4. Ensure IAM user/role has CDK permissions
+
+---
+
+## Best Practices
+
+1. **Never commit `.env` files** - Use `.gitignore` to exclude them
+2. **Use environment-specific `.env` files** - Keep configurations separate per environment
+3. **Document configuration changes** - Update this guide when adding new variables
+4. **Validate early** - Run CDK synthesis before deployment: `cdk synth`
+5. **Use AWS CDK best practices**:
+ - Always tag resources for cost allocation
+ - Use stack outputs for important values
+ - Implement proper error handling
+ - Test in lower environments first
+6. **Secure sensitive data**:
+ - Use AWS Secrets Manager for secrets
+ - Never commit credentials to version control
+ - Use IAM roles instead of access keys when possible
+7. **Monitor and log**:
+ - Enable CloudWatch logging for CDN and application
+ - Set up CloudWatch alarms for critical metrics
+ - Review logs regularly for issues
+
+
+
+---
+
+For more information about this project, see the main [README](../README.md) or visit the [documentation](./README.md).
diff --git a/docs/DEVOPS_GUIDE.md b/docs/DEVOPS_GUIDE.md
new file mode 100644
index 0000000..f8cab8d
--- /dev/null
+++ b/docs/DEVOPS_GUIDE.md
@@ -0,0 +1,258 @@
+# DevOps Guide
+
+Welcome to the DevOps guide for the Ionic Starter project. This document is designed to help engineers understand the DevOps practices, tools, and workflows used in this repository.
+
+## Overview
+
+DevOps in this project focuses on automation, reliability, and maintainability. The main goals are to ensure code quality, automate testing, validate infrastructure as code, and streamline deployment processes to AWS.
+
+## DevOps Tools Used
+
+### GitHub Actions
+
+- **Purpose:** Automates CI/CD workflows, including running tests, building the project, validating infrastructure code, and deploying to AWS.
+- **Location:** All workflow files are stored in the `.github/workflows/` directory.
+- **Key Workflows:**
+ - **CI (Continuous Integration):** Validates pull requests by linting, formatting, building, and testing the application code, then synthesizes infrastructure code to validate it.
+ - **Code Quality:** Performs automated code quality checks, test coverage analysis, security audits, and dependency analysis on a schedule and on push to main.
+ - **Deploy to DEV:** Automatically deploys the application to the development environment on every push to main.
+ - **Deploy to QA:** Deploys the application to the QA environment when pushing to release branches.
+ - **Deploy to PROD:** Deploys the application to production when publishing GitHub releases.
+
+## GitHub Actions Workflows
+
+The project uses GitHub Actions for CI/CD. Below is a detailed description of each workflow:
+
+### Continuous Integration Workflow (`ci.yml`)
+
+- **Purpose:** Validates every pull request to the `main` branch by linting, formatting, building, and testing the application code, then synthesizes the infrastructure code to validate it.
+- **Triggers:**
+ - On pull requests targeting the `main` branch
+ - Manual: Via GitHub Actions UI (`workflow_dispatch`)
+- **Concurrency:**
+ - Ensures only one workflow runs per branch/ref at a time; cancels in-progress runs for the same branch/ref.
+- **Timeout:** 10 minutes
+- **Prerequisites:**
+ - GitHub Actions variables must be configured:
+ - `ENV_CI` - Application environment variables for CI
+ - `CDK_ENV_DEV` - Infrastructure environment variables for DEV
+ - `AWS_ROLE_ARN_DEV` - AWS IAM Role ARN for development environment
+ - `AWS_REGION` - AWS region for deployment
+- **Main Steps:**
+ 1. Checkout repository
+ 2. Setup Node.js (from `.nvmrc`, with npm cache)
+ 3. Install dependencies (`npm ci`)
+ 4. Create application `.env` file from variables (`ENV_CI`)
+ 5. Lint code (`npm run lint`)
+ 6. Check code formatting (`npm run format:check`)
+ 7. Build application (`npm run build`)
+ 8. Run unit tests with CI mode (`npm run test:ci`)
+ 9. Install infrastructure dependencies (`npm ci` in the infrastructure directory)
+ 10. Build infrastructure (`npm run build` in the infrastructure directory)
+ 11. Run infrastructure tests with coverage (`npm run test:coverage` in the infrastructure directory)
+ 12. Create infrastructure .env file
+ 13. Configure AWS credentials using OIDC (role: `AWS_ROLE_ARN_DEV`)
+ 14. Synthesize CDK stacks (`npm run synth` in infrastructure directory)
+ 15. Clean up sensitive files (`.env`, `cdk.out` in infrastructure directory)
+- **Importance:** Ensures that all code merged into `main` passes linting, formatting, builds successfully, is covered by tests, and that the AWS CDK infrastructure code synthesizes correctly. This prevents broken or low-quality code from being merged and keeps the main branch stable.
+
+### Code Quality Workflow (`code-quality.yml`)
+
+- **Purpose:** Automates comprehensive code quality checks, test coverage analysis, security audits, and dependency analysis for both the application and infrastructure.
+- **Triggers:**
+ - Scheduled: Every Sunday at 2 AM UTC
+ - Manual: Via GitHub Actions UI (`workflow_dispatch`)
+ - On push to `main` branch (if source files or workflow files change)
+- **Timeout:** 10 minutes
+- **Main Steps:**
+ 1. **Application Quality Checks:**
+ - Checkout repository (full history)
+ - Setup Node.js (from `.nvmrc`, with npm cache)
+ - Install dependencies
+ - Create application `.env` file from secrets
+ - Run ESLint with output summary
+ - Check code formatting with Prettier
+ - Run tests with coverage and output summary table
+ - Build check
+ - Security audit (`npm audit` for moderate vulnerabilities)
+ - Package analysis (`npm outdated`)
+ 2. **Infrastructure Quality Checks:**
+ - Install infrastructure dependencies
+ - Run infrastructure tests with coverage and output summary
+ - Infrastructure build check
+ - Infrastructure security audit
+ - Infrastructure package analysis
+ 3. **Artifact Archival:**
+ - Upload test results, coverage reports, and analysis outputs as artifacts
+ - Retention: 7 days
+- **Output Format:** All results are summarized in the GitHub Actions step summary for easy review in the UI
+- **Importance:** Maintains code quality, security posture, and up-to-date dependencies. Provides visibility into test coverage and build health. Acts as an early detection system for quality issues, security vulnerabilities, and outdated packages.
+
+### Deploy to DEV Workflow (`deploy-dev.yml`)
+
+- **Purpose:** Automatically builds and deploys the application to the development environment on AWS with full infrastructure provisioning.
+- **Triggers:**
+ - On push to the `main` branch
+ - On push of the `dev` tag
+ - Manual: Via GitHub Actions UI (`workflow_dispatch`)
+- **Concurrency:**
+ - Prevents concurrent deployments; ensures orderly deployment
+- **Timeout:** 30 minutes
+- **Prerequisites:**
+ - GitHub Actions variables must be configured:
+ - `AWS_ROLE_ARN_DEV` - AWS IAM Role ARN for development environment
+ - `AWS_REGION` - AWS region for deployment
+ - `ENV_DEV` - Application environment variables
+ - `CDK_ENV_DEV` - CDK infrastructure environment configuration
+- **Main Steps:**
+ 1. Checkout repository
+ 2. Setup Node.js (from `.nvmrc`, with npm cache)
+ 3. Install application dependencies (`npm ci`)
+ 4. Create application `.env` file with environment variables and build metadata
+ 5. Build the application (`npm run build`)
+ 6. Configure AWS credentials using OIDC (no long-lived credentials)
+ 7. Install infrastructure dependencies
+ 8. Create infrastructure `.env` file from variables
+ 9. Build infrastructure code
+ 10. Bootstrap CDK (checks if already bootstrapped, skips if so)
+ 11. Synthesize CDK CloudFormation templates
+ 12. Deploy CDK stacks (`npm run deploy:all` with `--require-approval never`)
+ 13. Clean up sensitive files (`.env`, `cdk.out`)
+- **Importance:** Enables rapid deployment of latest changes to development environment for testing and validation. Ensures infrastructure-as-code is always in sync with application deployments.
+
+### Deploy to QA Workflow (`deploy-qa.yml`)
+
+- **Purpose:** Deploys the application to the QA environment on AWS for testing and quality assurance.
+- **Triggers:**
+ - On push to `release/*` branches
+ - On push of the `qa` tag
+- **Concurrency:**
+ - Prevents concurrent deployments; ensures orderly deployment
+- **Timeout:** 30 minutes
+- **Prerequisites:**
+ - GitHub Actions variables must be configured:
+ - `AWS_ROLE_ARN_QA` - AWS IAM Role ARN for QA environment
+ - `AWS_REGION` - AWS region for deployment
+ - `ENV_QA` - Application environment variables
+ - `CDK_ENV_QA` - CDK infrastructure environment configuration
+- **Main Steps:** (identical to DEV workflow with QA-specific variables)
+ 1. Checkout repository
+ 2. Setup Node.js (from `.nvmrc`, with npm cache)
+ 3. Install application dependencies (`npm ci`)
+ 4. Create application `.env` file with environment variables and build metadata
+ 5. Build the application (`npm run build`)
+ 6. Configure AWS credentials using OIDC (role: `AWS_ROLE_ARN_QA`)
+ 7. Install infrastructure dependencies
+ 8. Create infrastructure `.env` file from variables
+ 9. Build infrastructure code
+ 10. Bootstrap CDK (checks if already bootstrapped, skips if so)
+ 11. Synthesize CDK CloudFormation templates
+ 12. Deploy CDK stacks (`npm run deploy:all` with `--require-approval never`)
+ 13. Clean up sensitive files (`.env`, `cdk.out`)
+- **Importance:** Allows testing of release branches in a QA environment before deploying to production.
+
+### Deploy to PROD Workflow (`deploy-prod.yml`)
+
+- **Purpose:** Deploys the application to the production environment on AWS when releasing to production.
+- **Triggers:**
+ - On GitHub release publication
+ - On push of the `prod` tag
+- **Concurrency:**
+ - Prevents concurrent deployments; ensures orderly deployment
+- **Timeout:** 30 minutes
+- **Prerequisites:**
+ - GitHub Actions variables must be configured:
+ - `AWS_ROLE_ARN_PROD` - AWS IAM Role ARN for production environment
+ - `AWS_REGION` - AWS region for deployment
+ - `ENV_PROD` - Application environment variables
+ - `CDK_ENV_PROD` - CDK infrastructure environment configuration
+- **Main Steps:** (identical to DEV/QA workflows with PROD-specific variables)
+ 1. Checkout repository
+ 2. Setup Node.js (from `.nvmrc`, with npm cache)
+ 3. Install application dependencies (`npm ci`)
+ 4. Create application `.env` file with environment variables and build metadata
+ 5. Build the application (`npm run build`)
+ 6. Configure AWS credentials using OIDC (role: `AWS_ROLE_ARN_PROD`)
+ 7. Install infrastructure dependencies
+ 8. Create infrastructure `.env` file from variables
+ 9. Build infrastructure code
+ 10. Bootstrap CDK (checks if already bootstrapped, skips if so)
+ 11. Synthesize CDK CloudFormation templates
+ 12. Deploy CDK stacks (`npm run deploy:all` with `--require-approval never`)
+ 13. Clean up sensitive files (`.env`, `cdk.out`)
+- **Importance:** Ensures controlled, traceable deployments to production. Using GitHub releases provides a clear release history and version tracking.
+
+### Build Metadata
+
+All deployment workflows inject build metadata into the application at build time. These environment variables are accessible in the React app as `import.meta.env.*`:
+
+- `VITE_BUILD_DATE` - Build date (YYYY-MM-DD format)
+- `VITE_BUILD_TIME` - Build time (HH:MM:SS format, UTC)
+- `VITE_BUILD_TS` - Full build timestamp (ISO 8601 format)
+- `VITE_BUILD_COMMIT_SHA` - Git commit SHA
+- `VITE_BUILD_ENV_CODE` - Environment code (`dev`, `qa`, or `prd`)
+- `VITE_BUILD_WORKFLOW_RUNNER` - Always set to "GitHub Actions"
+- `VITE_BUILD_WORKFLOW_NAME` - GitHub workflow name
+- `VITE_BUILD_WORKFLOW_RUN_NUMBER` - Workflow run number
+- `VITE_BUILD_WORKFLOW_RUN_ATTEMPT` - Workflow run attempt (useful for retries)
+
+This metadata is useful for debugging and version tracking in deployed applications.
+
+### Security Features
+
+All deployment workflows implement these security features:
+
+- Uses OIDC for AWS authentication (no long-lived credentials stored)
+- Automatic cleanup of sensitive files after deployment (`.env`, `cdk.out`)
+- Proper IAM role assumption with environment-specific session naming
+
+## Environment Configuration
+
+### Variables
+
+GitHub Actions variables should be configured in the repository settings:
+
+- `AWS_REGION` - AWS region for deployments (e.g., `us-east-1`)
+- `AWS_ROLE_ARN_DEV` - AWS IAM role ARN for development
+- `AWS_ROLE_ARN_QA` - AWS IAM role ARN for QA
+- `AWS_ROLE_ARN_PROD` - AWS IAM role ARN for production
+- `ENV_CI` - Environment variables for CI workflow (application)
+- `ENV_DEV` - Environment variables for DEV deployment (application)
+- `ENV_QA` - Environment variables for QA deployment (application)
+- `ENV_PROD` - Environment variables for PROD deployment (application)
+- `CDK_ENV_DEV` - CDK infrastructure environment configuration for DEV
+- `CDK_ENV_QA` - CDK infrastructure environment configuration for QA
+- `CDK_ENV_PROD` - CDK infrastructure environment configuration for PROD
+
+Each environment variable file should be in the format `KEY=VALUE` with one entry per line.
+
+## Deployment Strategy
+
+### Development Environment
+
+**Trigger:** Push to `main` branch or push `dev` tag
+
+Development deployments happen automatically whenever code is merged to the main branch, enabling rapid iteration and continuous deployment of the latest code.
+
+### QA Environment
+
+**Trigger:** Push to `release/*` branches or push `qa` tag
+
+QA deployments are triggered when code is pushed to release branches, allowing testing of release candidates in a controlled environment before production deployment.
+
+### Production Environment
+
+**Trigger:** GitHub release published or push `prod` tag
+
+Production deployments are manually controlled through GitHub releases, providing a clear release history, version tracking, and explicit control over what goes to production.
+
+## Further Reading
+
+- [GitHub Actions Documentation](https://docs.github.com/en/actions)
+- [Project Documentation](../docs/README.md)
+
+
+
+---
+
+If you have questions or need help, reach out to your team or check the documentation above.
diff --git a/docs/INFRASTRUCTURE_GUIDE.md b/docs/INFRASTRUCTURE_GUIDE.md
new file mode 100644
index 0000000..d1264f5
--- /dev/null
+++ b/docs/INFRASTRUCTURE_GUIDE.md
@@ -0,0 +1,563 @@
+# Infrastructure Guide
+
+This guide provides a comprehensive overview of the AWS infrastructure for the Ionic Starter frontend application, including architectural decisions, deployment instructions, and operational considerations.
+
+> **Note:** This infrastructure provisions only frontend/presentation tier components. Resources are used for hosting static React assets via a Content Delivery Network (CDN) for optimal performance and scalability.
+
+## Table of Contents
+
+1. [Architecture Overview](#architecture-overview)
+2. [Infrastructure Components](#infrastructure-components)
+3. [Deployment Process](#deployment-process)
+4. [Environment Management](#environment-management)
+5. [Security Model](#security-model)
+6. [Cost Optimization](#cost-optimization)
+7. [Monitoring and Observability](#monitoring-and-observability)
+8. [Troubleshooting](#troubleshooting)
+9. [Best Practices](#best-practices)
+
+## Architecture Overview
+
+The Ionic Starter frontend application is deployed on AWS using a modern, globally distributed content delivery architecture designed for performance, scalability, and cost optimization. The infrastructure leverages managed services to minimize operational overhead.
+
+### High-Level Architecture
+
+```
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ CloudFront Distribution โ
+โ (Global Edge Locations & Caching Layer) โ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
+โ Route 53 DNS โ Origin Access Control โ SSL/TLS Certificate โ
+โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ โ
+ โผ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ S3 Bucket (Origin) โ
+โ (Hosts compiled React static assets - index.html, JS, CSS) โ
+โ (Automatic invalidation on deployment) โ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+```
+
+### Design Principles
+
+1. **Global Content Delivery**: CloudFront edge locations for low-latency access worldwide
+2. **Cost Optimization**: Pay-as-you-go pricing with automatic caching and compression
+3. **Security by Design**: Origin Access Control, SSL/TLS encryption, and security headers
+4. **Infrastructure as Code**: All infrastructure defined in AWS CDK with TypeScript
+5. **Multi-Environment**: Supports dev, qa, staging, and production environments
+
+## Infrastructure Components
+
+### CDN Stack (`cdn-stack.ts`)
+
+**Purpose**: Deploys the compiled React application to a globally distributed content delivery network.
+
+**Key Resources**:
+
+- **S3 Bucket**: Stores compiled static assets (React build output)
+ - Public access blocked with Origin Access Control
+ - Encryption enabled with S3-managed keys
+ - Automatic object deletion on stack destruction (development) or retain (production)
+ - No public website hosting required
+- **CloudFront Distribution**: Global content delivery network
+ - Multiple edge locations for low-latency access
+ - Automatic gzip/brotli compression of assets
+ - Configurable cache policies for optimal performance
+ - Security headers (HSTS, X-Frame-Options, etc.)
+ - HTTP to HTTPS redirect
+ - SPA error handling (404/403 errors serve index.html for client-side routing)
+- **SSL/TLS Certificate** (Optional): ACM certificate for custom domain
+ - HTTPS-only access to your application
+ - Automatic renewal and management
+- **Route 53 DNS Records** (Optional): Custom domain alias
+ - A record (IPv4) pointing to CloudFront distribution
+ - AAAA record (IPv6) pointing to CloudFront distribution
+ - Automatic failover via CloudFront
+- **S3 Deployment**: Automated asset sync and cache invalidation
+ - Uploads build artifacts from local directory
+ - Invalidates CloudFront cache after each deployment
+ - Supports incremental updates
+
+**Configuration**:
+
+```bash
+# Application CDN configuration
+CDK_ASSET_PATH=../dist # Path to React build output
+CDK_DOMAIN_NAME=app.example.com # Custom domain (optional)
+CDK_CERTIFICATE_ARN=arn:aws:acm:us-east-1:... # SSL certificate (optional)
+CDK_HOSTED_ZONE_ID=Z1234567890ABC # Route 53 zone ID (optional)
+CDK_HOSTED_ZONE_NAME=example.com # Route 53 zone name (optional)
+```
+
+**Deployment Outputs**:
+
+The stack exports the following values for each environment:
+
+- `BucketName`: S3 bucket name storing static assets
+- `DistributionId`: CloudFront distribution ID
+- `DistributionDomainName`: CloudFront auto-generated domain (e.g., `d123.cloudfront.net`)
+- `ApplicationUrl`: Full URL to access the application (custom domain or CloudFront domain)
+- `CustomDomainName`: Custom domain if configured
+
+**Cost Considerations**:
+
+- CloudFront: Pay per request and data transferred
+- S3: Minimal storage costs (small build artifact footprint)
+- Data transfer out: $0.085/GB (varies by region)
+- Requests: $0.0075 per 10,000 requests
+- Default coverage: PRICE_CLASS_100 (uses cheapest edge locations only)
+
+**Use Cases**:
+
+- Isolated hosting of component documentation
+- Team design system reference
+- Accessible independent of main application deployment
+- Separate analytics and monitoring
+
+## Deployment Process
+
+### Prerequisites
+
+1. **AWS Account Setup**:
+ - AWS CLI configured with appropriate credentials
+ - CDK bootstrap completed in target account/region
+ - Sufficient permissions for S3, CloudFront, Route 53, and ACM services
+
+2. **Domain & SSL (Optional but Recommended)**:
+ - Existing Route 53 hosted zone for your domain
+ - ACM SSL certificate in the same region as CloudFront (typically `us-east-1`)
+ - Certificate must be issued for your custom domain(s)
+
+3. **Local Environment**:
+ - Node.js (version specified in `.nvmrc`)
+ - AWS CDK CLI installed globally: `npm install -g aws-cdk`
+ - Vite build output available at `../dist` (relative to infrastructure/)
+
+### Step-by-Step Deployment
+
+1. **Build the React Application**:
+
+ ```bash
+ # From the project root
+ npm run build
+ ```
+
+ This generates optimized static assets in the `dist` directory.
+
+2. **Configure Infrastructure**:
+
+ ```bash
+ cd infrastructure
+ cp .env.example .env
+ # Edit .env with your environment-specific configuration
+ ```
+
+3. **Install Infrastructure Dependencies**:
+
+ ```bash
+ npm install
+ ```
+
+4. **Verify Configuration**:
+
+ ```bash
+ npm run synth
+ ```
+
+ This generates CloudFormation templates without deploying. Review the synthesized templates in `cdk.out/`.
+
+5. **Deploy Infrastructure** (with confirmation):
+
+ ```bash
+ npm run deploy:all
+ ```
+
+ Or deploy specific stacks:
+
+ ```bash
+ # Deploy only the application CDN
+ npm run deploy -- "*ui-cdn*"
+
+ # Deploy all stacks (without confirmation)
+ npm run deploy:all -- --require-approval never
+ ```
+
+6. **Verify Deployment**:
+ - Check AWS Console for created resources
+ - Navigate to CloudFront Distributions
+ - Test application URL: `https://` or use the CloudFront domain
+ - Verify Route 53 DNS records are created (if using custom domain)
+ - Test HTTP to HTTPS redirect works
+
+### Deployment Order & Stack Dependencies
+
+CDK automatically manages dependencies, but understand the logical order:
+
+1. **UI CDN Stack**: Deploys main React application
+ - Creates S3 bucket
+ - Creates CloudFront distribution
+ - Uploads assets and invalidates cache
+ - Creates Route 53 records (if domain configured)
+
+## Resource Configuration
+
+### Performance Considerations
+
+- **Asset Size**: Minimize bundle size with code splitting and tree-shaking
+- **Cache Optimization**: Configure CloudFront caching policies appropriately for different asset types
+- **Origin Access**: Use Origin Access Control (OAC) instead of public buckets for security
+- **Global Distribution**: CloudFront caches assets across 210+ edge locations worldwide
+
+## Environment Management
+
+### Environment Variables
+
+All configuration uses environment variables prefixed with `CDK_`:
+
+#### Core Infrastructure Variables
+
+| Variable | Purpose | Example | Required |
+| ------------- | ---------------- | ------------------ | -------- |
+| `CDK_ACCOUNT` | AWS Account ID | `123456789012` | Optional |
+| `CDK_REGION` | AWS Region | `us-east-1` | Optional |
+| `CDK_ENV` | Environment name | `dev`, `qa`, `prd` | Yes |
+
+#### Application CDN Variables
+
+| Variable | Purpose | Default | Required |
+| ---------------------- | -------------------- | ---------------- | -------- |
+| `CDK_APP_NAME` | Application name | `ionic8-starter` | No |
+| `CDK_ASSET_PATH` | Path to build output | `../dist` | Yes |
+| `CDK_DOMAIN_NAME` | Custom domain | - | No |
+| `CDK_CERTIFICATE_ARN` | SSL certificate ARN | - | No\* |
+| `CDK_HOSTED_ZONE_ID` | Route 53 zone ID | - | No\* |
+| `CDK_HOSTED_ZONE_NAME` | Route 53 zone name | - | No\* |
+
+**Note:** `CDK_CERTIFICATE_ARN`, `CDK_HOSTED_ZONE_ID`, and `CDK_HOSTED_ZONE_NAME` are required only if using `CDK_DOMAIN_NAME`.
+
+#### Resource Tagging Variables
+
+| Variable | Purpose | Default | Example |
+| ----------- | ------------------- | --------- | ------------- |
+| `CDK_OU` | Organizational unit | `unknown` | `frontend` |
+| `CDK_OWNER` | Resource owner | `unknown` | `team@co.com` |
+
+### Multi-Environment Strategy
+
+Each environment uses:
+
+- Separate `.env` files (recommended)
+- Environment-specific stack names: `${appName}-ui-cdn-${env}`
+- Environment-specific asset deployment
+- Independent CloudFront cache invalidations
+
+Example deployment:
+
+```bash
+# Development
+CDK_ENV=dev npm run deploy
+
+# Production (with custom domain)
+CDK_ENV=prd CDK_DOMAIN_NAME=app.example.com npm run deploy
+```
+
+## Security Model
+
+### Content Delivery Security
+
+1. **HTTPS Only**: HTTP automatically redirects to HTTPS
+2. **SSL/TLS**: Industry-standard encryption for data in transit
+3. **Origin Access Control**: S3 bucket not publicly accessible; CloudFront acts as intermediary
+4. **Security Headers**: Automatic security headers (HSTS, X-Frame-Options, etc.)
+5. **DDoS Protection**: CloudFront includes built-in DDoS mitigation
+
+### Asset Security
+
+1. **S3 Encryption**: Server-side encryption enabled for all objects
+2. **Public Access Blocked**: S3 bucket has public access blocked via policies
+3. **CloudFront Caching**: Cached assets served from edge locations, not origin
+4. **Access Logging**: Optional S3 access logging for compliance
+
+### Authentication & Access
+
+1. **IAM Roles**: CDK uses least privilege for deployment
+2. **Credential Management**: AWS credentials never stored in code or config files
+3. **No Public Bucket Access**: Assets only accessible through CloudFront or validated means
+
+## Cost Optimization
+
+### CloudFront Costs
+
+- **PriceClass 100**: Uses cheapest edge locations only
+ - Covers North America, Europe, Asia
+ - Excludes expensive locations (Australia, South America)
+- **Request Pricing**: $0.0075 per 10,000 requests (varies by location)
+- **Data Transfer**: $0.085/GB outbound (varies by region)
+- **Caching Strategy**: Longer TTLs reduce origin requests
+- **Default Configuration**: Optimal for development/staging workloads
+
+### S3 Costs
+
+- **Storage**: Minimal cost for compiled assets (~1-5 MB typical)
+- **Requests**: Minimal if CloudFront caching is effective
+- **Auto Cleanup**: Development stacks auto-delete objects on destruction
+- **Default Configuration**: ~$0.10-0.50/month storage cost
+
+### Cost Optimization Tips
+
+1. **Cache Optimization**: Set appropriate cache TTLs for different asset types
+ - HTML (index.html): Short TTL or no cache for SPA routing
+ - JS/CSS: Long TTL with content hash for cache busting
+ - Images: Long TTL for static assets
+2. **PriceClass Selection**: Use PRICE_CLASS_100 for development/staging
+ - Saves ~30% vs all edge locations
+ - Use PRICE_CLASS_ALL only for global applications
+
+3. **Compression**: CloudFront automatically gzips/brotli compresses eligible assets
+ - Reduces bandwidth by 60-80% for text assets
+ - No additional configuration needed
+
+4. **Regional Deployment**: Deploy separate stacks per environment
+ - Dev/staging in single region or CloudFront
+ - Production with global distribution
+
+### Monthly Cost Estimate
+
+| Component | Usage | Cost |
+| ------------------------ | ----------- | ------------- |
+| CloudFront Requests | 1M/month | $7.50 |
+| CloudFront Data Transfer | 10 GB/month | $0.85 |
+| S3 Storage | 20 MB | $0.50 |
+| **Total** | | **~$9/month** |
+
+## Monitoring and Observability
+
+### CloudFront Monitoring
+
+CloudFront provides built-in metrics through CloudWatch:
+
+1. **Request Metrics**:
+ - Total requests
+ - Bytes downloaded/uploaded
+ - 4xx and 5xx error rates
+ - Cache hit ratio
+
+2. **Performance Metrics**:
+ - Origin latency
+ - Edge location latency
+ - Bandwidth utilization
+
+3. **Custom Alarms**:
+ - High error rates (>1%)
+ - Cache miss spikes
+ - Origin connectivity issues
+
+### S3 Monitoring
+
+1. **Access Logging**: Enable S3 access logs for audit trails
+2. **Bucket Metrics**: CloudWatch metrics for request patterns
+3. **Replication**: Track cross-region replication status (if enabled)
+
+### Application Monitoring
+
+1. **Client-Side Analytics**:
+ - Use Google Analytics, Mixpanel, or similar
+ - CloudFront does not log to application
+2. **Error Tracking**:
+ - Browser error tracking (Sentry, Rollbar, etc.)
+ - CloudFront error response monitoring
+3. **Performance Monitoring**:
+ - Web Vitals tracking
+ - Core Web Vitals (CLS, FID, LCP)
+ - Custom performance metrics
+
+### Alerting Strategy
+
+Set up alerts for:
+
+- High error rate (>1% 4xx/5xx)
+- Increased origin latency (>500ms)
+- Cache hit ratio drop (<80%)
+- Deployment failures during sync
+- SSL certificate expiration (ACM)
+
+## Disaster Recovery
+
+### Backup Strategy
+
+1. **Asset Backup**: Regularly commit built assets to version control
+2. **Git Strategy**: Tag releases for rollback capability
+3. **Source Control**: Main source code is single source of truth
+4. **Build Artifacts**: Rebuild from source when needed
+
+### Recovery Procedures
+
+1. **Rollback to Previous Version**:
+
+ ```bash
+ # Checkout previous build artifacts
+ git checkout
+
+ # Rebuild if needed
+ npm run build
+
+ # Redeploy
+ npm run deploy
+ ```
+
+2. **CloudFront Cache Invalidation**:
+
+ ```bash
+ # Get distribution ID from deployment output
+ aws cloudfront create-invalidation \
+ --distribution-id \
+ --paths "/*"
+ ```
+
+3. **S3 Bucket Restore**:
+
+ ```bash
+ # List object versions
+ aws s3api list-object-versions \
+ --bucket
+
+ # Restore specific version
+ aws s3api get-object \
+ --bucket \
+ --key index.html \
+ --version-id \
+ index.html
+ ```
+
+## Troubleshooting
+
+### Common Issues
+
+1. **CloudFront 403/Access Denied**:
+ - Verify Origin Access Control is configured
+ - Check S3 bucket policy allows CloudFront access
+ - Verify certificate ARN is in correct region (us-east-1 for CloudFront)
+
+2. **SPA Routing 404 Errors**:
+ - Ensure error responses are configured (403/404 โ 200 index.html)
+ - Verify CloudFront cache policy allows query strings
+ - Check distribution is using correct cache policy
+
+3. **Custom Domain Not Resolving**:
+ - Verify Route 53 hosted zone ID is correct
+ - Check CNAME record points to CloudFront domain
+ - Verify Route 53 hosted zone name matches certificate domain
+
+4. **Assets Not Updating After Deployment**:
+ - Check CloudFront cache invalidation completed
+ - Clear browser cache or use incognito window
+ - Verify new assets uploaded to S3
+ - Check CloudFront Origin settings
+
+5. **Deployment Fails During Asset Upload**:
+ - Verify S3 bucket exists and is accessible
+ - Check AWS credentials have S3 permissions
+ - Ensure asset path (CDK_ASSET_PATH) is correct
+ - Check disk space for asset upload
+
+### Debugging Commands
+
+```bash
+# Check CloudFront distribution status
+aws cloudfront get-distribution \
+ --id
+
+# Monitor cache invalidation progress
+aws cloudfront get-invalidation \
+ --distribution-id \
+ --id
+
+# Verify S3 bucket contents
+aws s3 ls s3:/// --recursive
+
+# Check CDK synthesis without deploying
+npm run synth
+
+# View CloudFormation events
+aws cloudformation describe-stack-events \
+ --stack-name \
+ --query 'StackEvents[0:10]'
+
+# Test CloudFront distribution directly
+curl -I https:///index.html
+```
+
+## Best Practices
+
+### Configuration Management
+
+1. **Environment Separation**: Use `.env.dev`, `.env.qa`, `.env.prd`
+2. **Build Output**: Always build (`npm run build`) before deployment
+3. **Asset Cleanup**: Use appropriate `removalPolicy` per environment
+4. **Version Control**: Keep `.env.example` updated, never commit `.env`
+5. **Configuration Validation**: Test configuration in development first
+
+### Development Workflow
+
+1. **Local Development**: Use local Vite dev server, not CloudFront
+2. **Build Locally**: Verify `dist/` output before deployment
+3. **Staging Verification**: Deploy to staging environment first
+4. **Progressive Rollout**: Deploy to production after staging validation
+5. **Infrastructure as Code**: Always use CDK, never manual AWS changes
+
+### Security Best Practices
+
+1. **Credential Management**: Use AWS profiles or temporary credentials
+2. **Certificate Management**: Use ACM for automatic renewal
+3. **Domain Management**: Use Route 53 for DNS (not external providers)
+4. **Access Logs**: Enable for compliance and security audits
+5. **Regular Updates**: Keep CDK and dependencies updated
+
+### Performance Best Practices
+
+1. **Asset Optimization**: Use Vite to optimize production builds
+ - Minification
+ - Tree-shaking
+ - Code splitting
+ - Asset hashing for cache busting
+
+2. **Cache Strategy**: Configure optimal TTL for different assets
+ - Dynamic content (HTML): 0 or short TTL
+ - Versioned assets (JS/CSS with hash): 1 year TTL
+ - Images: 1 month TTL
+
+3. **Compression**: Rely on CloudFront gzip/brotli compression
+ - No additional configuration needed
+ - Automatic for eligible content types
+
+4. **Monitoring**: Track Core Web Vitals
+ - Use Google Chrome UX Report
+ - Monitor Largest Contentful Paint (LCP)
+ - Track Cumulative Layout Shift (CLS)
+
+### Operational Best Practices
+
+1. **Documentation**: Keep infrastructure changes documented
+2. **Change Management**: Review CDK diffs before deployment
+3. **Release Management**: Use git tags for release tracking
+4. **Cost Monitoring**: Review CloudFront costs monthly
+5. **Disaster Recovery**: Test rollback procedures regularly
+
+### Code Organization
+
+1. **Single Responsibility**: Keep CDN stack focused on content delivery
+2. **Configuration Externalization**: All values in environment variables
+3. **Type Safety**: Leverage TypeScript for CDK code
+4. **Testing**: Use CDK assertions to validate stack configuration
+5. **Code Reviews**: Review infrastructure changes before merging
+
+## Further Reading
+
+- [AWS CDK Documentation](https://docs.aws.amazon.com/cdk/api/v2/)
+- [Project Documentation](../docs/README.md)
+
+
+
+---
+
+If you have questions or need help, reach out to your team or check the documentation above.
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..f87dbee
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,15 @@
+# Documentation
+
+Welcome to the project documentation for the Ionic Starter project. This directory contains guides and reference materials for the project.
+
+## Table of Contents
+
+- [DevOps Guide](./DEVOPS_GUIDE.md): GitHub Actions workflows, CI/CD, and automation.
+- [Configuration Guide](./CONFIGURATION_GUIDE.md): Application and infrastructure configuration, environment variables, and validation.
+- [Infrastructure Guide](./INFRASTRUCTURE_GUIDE.md): AWS CDK Infrastructure as Code.
+
+
+
+---
+
+:point_left: Return to the main project [README](../README.md).
diff --git a/infrastructure/.env.example b/infrastructure/.env.example
new file mode 100644
index 0000000..1aa664b
--- /dev/null
+++ b/infrastructure/.env.example
@@ -0,0 +1,33 @@
+################################################################################
+# Example Environment Variables for Infrastructure
+################################################################################
+
+### Application Configuration ###
+## Optional: Application name (default: ionic8-starter)
+# CDK_APP_NAME=ionic8-starter
+## Required: Environment [dev, qa, prd]
+CDK_ENV=dev
+
+### AWS Configuration ###
+## Optional: Override AWS Account ID (defaults to CDK_DEFAULT_ACCOUNT from AWS credentials)
+# CDK_ACCOUNT=123456789012
+## Optional: Override AWS Region (defaults to CDK_DEFAULT_REGION from AWS credentials)
+# CDK_REGION=us-east-1
+
+### Resource Tagging Configuration ###
+## Optional: Organizational Unit (e.g., software-engineering, shared-services)
+# CDK_OU=software-engineering
+## Optional: Owner of the infrastructure resources (e.g., microservices-team, Joe Engineer)
+# CDK_OWNER=team-name-or-your-name
+
+### Application CDN Configuration ###
+## Required: Path to the static assets to deploy (relative to the infrastructure directory)
+CDK_ASSET_PATH=../dist
+## Optional: Custom domain name for the application (e.g., ionic8-starter.dev.example.com)
+# CDK_DOMAIN_NAME=ionic8-starter.dev.example.com
+## Optional: ACM Certificate ARN for the custom domain (must be in the same region as CloudFront, typically us-east-1)
+# CDK_CERTIFICATE_ARN=arn:aws:acm:us-east-1:123456789012:certificate/b95b7fdc-0e59-4409-a65a-d07dcb1fdf24
+## Optional: Route53 hosted zone name (e.g., dev.example.com)
+# CDK_HOSTED_ZONE_NAME=dev.example.com
+## Optional: Route53 hosted zone ID (e.g., Z1234567890ABCDEFGHI)
+# CDK_HOSTED_ZONE_ID=Z1234567890ABCDEFGHI
diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore
new file mode 100644
index 0000000..06fb299
--- /dev/null
+++ b/infrastructure/.gitignore
@@ -0,0 +1,30 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/dist
+/cdk.out
+
+# misc
+.DS_Store
+.nx
+.vscode
+.idea
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Optional eslint cache
+.eslintcache
+
+# dotenv environment variable files
+.env*
+!.env.example
diff --git a/infrastructure/README.md b/infrastructure/README.md
new file mode 100644
index 0000000..8203925
--- /dev/null
+++ b/infrastructure/README.md
@@ -0,0 +1,53 @@
+# Ionic Starter Infrastructure
+
+AWS CDK infrastructure for the Ionic Starter application.
+
+## Quick Start
+
+1. **Setup environment:**
+
+ ```bash
+ cp .env.example .env
+ # Edit .env with your AWS configuration
+ ```
+
+2. **Install dependencies:**
+
+ ```bash
+ npm install
+ ```
+
+3. **Bootstrap CDK (first time only):**
+
+ This step should only be performed 1 time per AWS Account. An Account needs only to be bootstrapped for the AWS CDK once.
+
+ ```bash
+ npx cdk bootstrap
+ ```
+
+4. **Build main application:**
+
+ ```bash
+ cd ..
+ npm run build
+ cd infrastructure
+ ```
+
+5. **Deploy:**
+ ```bash
+ npm run deploy:all
+ ```
+
+## Scripts
+
+- `npm run build` - Build TypeScript
+- `npm run deploy` - Deploy infrastructure
+- `npm run destroy` - Destroy infrastructure
+- `npm run diff` - Preview changes
+- `npm run synth` - Synthesize CloudFormation
+- `npm run test` - Run infrastructure unit tests
+- `npm run test:coverage` - Run infrastructure unit tests with coverage
+
+## Further Reading
+
+See the [Infrastructure Guide](../docs/INFRASTRUCTURE_GUIDE.md) for complete documentation.
diff --git a/infrastructure/app.ts b/infrastructure/app.ts
new file mode 100644
index 0000000..05fb75a
--- /dev/null
+++ b/infrastructure/app.ts
@@ -0,0 +1,35 @@
+#!/usr/bin/env node
+import * as cdk from 'aws-cdk-lib';
+
+import { getConfig, getEnvironmentConfig, getTags } from './utils/config';
+import { CdnStack } from './stacks/cdn-stack';
+
+// Load and validate configuration
+const config = getConfig();
+
+// Get standard tags for resources
+const tags = getTags(config);
+
+// Get AWS environment configuration for CDK stacks
+const env = getEnvironmentConfig(config);
+
+// Create the CDK application
+const app = new cdk.App();
+
+// Create a CDN stack for hosting the Ionic web application
+new CdnStack(app, `${config.CDK_APP_NAME}-ui-cdn-${config.CDK_ENV}`, {
+ stackName: `${config.CDK_APP_NAME}-ui-cdn-${config.CDK_ENV}`,
+ description: `UI CDN for ${config.CDK_APP_NAME} - ${config.CDK_ENV}`,
+ // Use the environment variables for account and region
+ env,
+ // Tags for the stack
+ tags,
+ // Additional properties
+ appName: config.CDK_APP_NAME,
+ envName: config.CDK_ENV,
+ assetPath: config.CDK_ASSET_PATH,
+ domainName: config.CDK_DOMAIN_NAME,
+ certificateArn: config.CDK_CERTIFICATE_ARN,
+ hostedZoneId: config.CDK_HOSTED_ZONE_ID,
+ hostedZoneName: config.CDK_HOSTED_ZONE_NAME,
+});
diff --git a/infrastructure/cdk.context.json b/infrastructure/cdk.context.json
new file mode 100644
index 0000000..acf3e96
--- /dev/null
+++ b/infrastructure/cdk.context.json
@@ -0,0 +1,3 @@
+{
+ "acknowledged-issue-numbers": [34892, 34892]
+}
diff --git a/infrastructure/cdk.json b/infrastructure/cdk.json
new file mode 100644
index 0000000..3aeb046
--- /dev/null
+++ b/infrastructure/cdk.json
@@ -0,0 +1,57 @@
+{
+ "app": "npx ts-node --prefer-ts-exts app.ts",
+ "watch": {
+ "include": ["**/*.ts"],
+ "exclude": [
+ "README.md",
+ "cdk*.json",
+ "**/*.d.ts",
+ "**/*.js",
+ "tsconfig.json",
+ "package*.json",
+ "yarn.lock",
+ "node_modules",
+ "test"
+ ]
+ },
+ "context": {
+ "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
+ "@aws-cdk/core:checkSecretUsage": true,
+ "@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
+ "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
+ "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
+ "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
+ "@aws-cdk/aws-iam:minimizePolicies": true,
+ "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
+ "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
+ "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
+ "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
+ "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
+ "@aws-cdk/core:enablePartitionLiterals": true,
+ "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
+ "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
+ "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
+ "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
+ "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
+ "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
+ "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
+ "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
+ "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
+ "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
+ "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
+ "@aws-cdk/aws-redshift:columnId": true,
+ "@aws-cdk/aws-stepfunctions-tasks:enableLogging": true,
+ "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
+ "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
+ "@aws-cdk/aws-kms:aliasNameRef": true,
+ "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
+ "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
+ "@aws-cdk/aws-efs:denyAnonymousAccess": true,
+ "@aws-cdk/aws-opensearchservice:enableLogging": true,
+ "@aws-cdk/aws-ssm:parameterValueSelectiveContext": true,
+ "@aws-cdk/aws-route53:useCertificate": true,
+ "@aws-cdk/aws-logs:avoidDuplicateResourceName": true,
+ "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
+ "@aws-cdk/aws-route53-patters:useCertificate": true
+ }
+}
diff --git a/infrastructure/jest.config.ts b/infrastructure/jest.config.ts
new file mode 100644
index 0000000..a858532
--- /dev/null
+++ b/infrastructure/jest.config.ts
@@ -0,0 +1,24 @@
+import type { Config } from 'jest';
+
+const config: Config = {
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ roots: [''],
+ testMatch: ['**/*.test.ts'],
+ collectCoverageFrom: [
+ '**/*.ts',
+ '!app.ts',
+ '!**/*.test.ts',
+ '!**/*.spec.ts',
+ '!**/node_modules/**',
+ '!**/cdk.out/**',
+ '!**/dist/**',
+ '!jest.config.ts',
+ '!jest.setup.ts',
+ ],
+ coverageDirectory: 'coverage',
+ coverageReporters: ['json', 'json-summary', 'text', 'lcov', 'clover'],
+ testTimeout: 30000, // CDK operations can take longer
+};
+
+export default config;
diff --git a/infrastructure/package-lock.json b/infrastructure/package-lock.json
new file mode 100644
index 0000000..52e9725
--- /dev/null
+++ b/infrastructure/package-lock.json
@@ -0,0 +1,5351 @@
+{
+ "name": "ionic8-starter-infrastructure",
+ "version": "1.1.0-alpha.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ionic8-starter-infrastructure",
+ "version": "1.1.0-alpha.1",
+ "dependencies": {
+ "aws-cdk-lib": "2.240.0",
+ "constructs": "10.5.1",
+ "dotenv": "17.3.1",
+ "zod": "4.3.6"
+ },
+ "devDependencies": {
+ "@types/jest": "30.0.0",
+ "@types/node": "25.3.0",
+ "aws-cdk": "2.1107.0",
+ "jest": "30.2.0",
+ "rimraf": "6.1.3",
+ "ts-jest": "29.4.6",
+ "ts-node": "10.9.2",
+ "typescript": "5.9.3"
+ }
+ },
+ "node_modules/@aws-cdk/asset-awscli-v1": {
+ "version": "2.2.263",
+ "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.263.tgz",
+ "integrity": "sha512-X9JvcJhYcb7PHs8R7m4zMablO5C9PGb/hYfLnxds9h/rKJu6l7MiXE/SabCibuehxPnuO/vk+sVVJiUWrccarQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@aws-cdk/asset-node-proxy-agent-v6": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz",
+ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@aws-cdk/cloud-assembly-schema": {
+ "version": "50.4.0",
+ "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-50.4.0.tgz",
+ "integrity": "sha512-9Cplwc5C+SNe3hMfqZET7gXeM68tiH2ytQytCi+zz31Bn7O3GAgAnC2dYe+HWnZAgVH788ZkkBwnYXkeqx7v4g==",
+ "bundleDependencies": [
+ "jsonschema",
+ "semver"
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jsonschema": "~1.4.1",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ }
+ },
+ "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": {
+ "version": "1.4.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": {
+ "version": "7.7.3",
+ "inBundle": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
+ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
+ "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.1.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
+ "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/console": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz",
+ "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz",
+ "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/reporters": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-changed-files": "30.2.0",
+ "jest-config": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-resolve-dependencies": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
+ "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz",
+ "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "30.2.0",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz",
+ "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz",
+ "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@sinonjs/fake-timers": "^13.0.0",
+ "@types/node": "*",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/get-type": {
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
+ "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz",
+ "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/types": "30.2.0",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz",
+ "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "collect-v8-coverage": "^1.0.2",
+ "exit-x": "^0.2.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^5.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.2",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/snapshot-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz",
+ "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "natural-compare": "^1.4.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz",
+ "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "callsites": "^3.1.0",
+ "graceful-fs": "^4.2.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz",
+ "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "collect-v8-coverage": "^1.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz",
+ "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz",
+ "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "babel-plugin-istanbul": "^7.0.1",
+ "chalk": "^4.1.2",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pirates": "^4.0.7",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^5.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz",
+ "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/pattern": "30.0.1",
+ "@jest/schemas": "30.0.5",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.34.48",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
+ "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "13.0.5",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz",
+ "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.1"
+ }
+ },
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
+ "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "30.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "25.3.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz",
+ "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.35",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
+ "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
+ "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
+ "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
+ "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
+ "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
+ "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
+ "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
+ "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
+ "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
+ "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
+ "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
+ "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
+ "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
+ "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
+ "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
+ "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
+ "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
+ "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
+ "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.5",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
+ "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/aws-cdk": {
+ "version": "2.1107.0",
+ "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1107.0.tgz",
+ "integrity": "sha512-7GKCq7p/33Jw+C+Ohwl4LnnKjvI/MzemeNZlTu/Kg8IwuZx5WEXEi32YLOlxbE1JOvleDslCWK5AIkBZ0omx/Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "cdk": "bin/cdk"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ }
+ },
+ "node_modules/aws-cdk-lib": {
+ "version": "2.240.0",
+ "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.240.0.tgz",
+ "integrity": "sha512-3dXmUnPB5kK0VgrNHOlV3jiQM4Dungukk/CV91nclO2lgNcrGyigauJdzmz9sOmI1gbKJJ2SRAotaXityzZMRw==",
+ "bundleDependencies": [
+ "@balena/dockerignore",
+ "@aws-cdk/cloud-assembly-api",
+ "case",
+ "fs-extra",
+ "ignore",
+ "jsonschema",
+ "minimatch",
+ "punycode",
+ "semver",
+ "table",
+ "yaml",
+ "mime-types"
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-cdk/asset-awscli-v1": "2.2.263",
+ "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0",
+ "@aws-cdk/cloud-assembly-api": "^2.0.1",
+ "@aws-cdk/cloud-assembly-schema": "^50.3.0",
+ "@balena/dockerignore": "^1.0.2",
+ "case": "1.6.3",
+ "fs-extra": "^11.3.3",
+ "ignore": "^5.3.2",
+ "jsonschema": "^1.5.0",
+ "mime-types": "^2.1.35",
+ "minimatch": "^10.2.1",
+ "punycode": "^2.3.1",
+ "semver": "^7.7.4",
+ "table": "^6.9.0",
+ "yaml": "1.10.2"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "peerDependencies": {
+ "constructs": "^10.5.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": {
+ "version": "2.0.1",
+ "bundleDependencies": [
+ "jsonschema",
+ "semver"
+ ],
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jsonschema": "~1.4.1",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "peerDependencies": {
+ "@aws-cdk/cloud-assembly-schema": ">=50.3.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api/node_modules/jsonschema": {
+ "version": "1.4.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api/node_modules/semver": {
+ "version": "7.7.3",
+ "inBundle": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": {
+ "version": "1.0.2",
+ "inBundle": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/aws-cdk-lib/node_modules/ajv": {
+ "version": "8.18.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/astral-regex": {
+ "version": "2.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/brace-expansion": {
+ "version": "5.0.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/case": {
+ "version": "1.6.3",
+ "inBundle": true,
+ "license": "(MIT OR GPL-3.0-or-later)",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/color-convert": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/color-name": {
+ "version": "1.1.4",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/fast-uri": {
+ "version": "3.1.0",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "inBundle": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/aws-cdk-lib/node_modules/fs-extra": {
+ "version": "11.3.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/aws-cdk-lib/node_modules/ignore": {
+ "version": "5.3.2",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/jsonfile": {
+ "version": "6.2.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/jsonschema": {
+ "version": "1.5.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/lodash.truncate": {
+ "version": "4.4.2",
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/aws-cdk-lib/node_modules/mime-db": {
+ "version": "1.52.0",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/mime-types": {
+ "version": "2.1.35",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/minimatch": {
+ "version": "10.2.2",
+ "inBundle": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/punycode": {
+ "version": "2.3.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/require-from-string": {
+ "version": "2.0.2",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/semver": {
+ "version": "7.7.4",
+ "inBundle": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/string-width": {
+ "version": "4.2.3",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/table": {
+ "version": "6.9.0",
+ "inBundle": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "ajv": "^8.0.1",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/universalify": {
+ "version": "2.0.1",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/aws-cdk-lib/node_modules/yaml": {
+ "version": "1.10.2",
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/babel-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz",
+ "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/transform": "30.2.0",
+ "@types/babel__core": "^7.20.5",
+ "babel-plugin-istanbul": "^7.0.1",
+ "babel-preset-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
+ "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "workspaces": [
+ "test/babel-8"
+ ],
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-instrument": "^6.0.2",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz",
+ "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/babel__core": "^7.20.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
+ "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz",
+ "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jest-hoist": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-beta.1"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
+ "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
+ "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001774",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz",
+ "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
+ "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
+ "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz",
+ "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/constructs": {
+ "version": "10.5.1",
+ "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.5.1.tgz",
+ "integrity": "sha512-f/TfFXiS3G/yVIXDjOQn9oTlyu9Wo7Fxyjj7lb8r92iO81jR2uST+9MstxZTmDGx/CgIbxCXkFXgupnLTNxQZg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dedent": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz",
+ "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/diff": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
+ "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "17.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
+ "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.302",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz",
+ "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/execa/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/exit-x": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz",
+ "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.8",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
+ "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-instrument/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz",
+ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/types": "30.2.0",
+ "import-local": "^3.2.0",
+ "jest-cli": "30.2.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz",
+ "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "execa": "^5.1.1",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz",
+ "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "co": "^4.6.0",
+ "dedent": "^1.6.0",
+ "is-generator-fn": "^2.1.0",
+ "jest-each": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "30.2.0",
+ "pure-rand": "^7.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz",
+ "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "exit-x": "^0.2.2",
+ "import-local": "^3.2.0",
+ "jest-config": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz",
+ "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/get-type": "30.1.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/test-sequencer": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "deepmerge": "^4.3.1",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-circus": "30.2.0",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "micromatch": "^4.0.8",
+ "parse-json": "^5.2.0",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "esbuild-register": ">=3.4.0",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "esbuild-register": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-diff": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz",
+ "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/diff-sequences": "30.0.1",
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-docblock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz",
+ "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-newline": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz",
+ "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz",
+ "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz",
+ "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "anymatch": "^3.1.3",
+ "fb-watchman": "^2.0.2",
+ "graceful-fs": "^4.2.11",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "micromatch": "^4.0.8",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.3"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz",
+ "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz",
+ "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.2.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz",
+ "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.2.0",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz",
+ "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz",
+ "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-pnp-resolver": "^1.2.3",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "slash": "^3.0.0",
+ "unrs-resolver": "^1.7.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz",
+ "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-regex-util": "30.0.1",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz",
+ "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/environment": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-leak-detector": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-resolve": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "jest-worker": "30.2.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz",
+ "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/globals": "30.2.0",
+ "@jest/source-map": "30.0.1",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "cjs-module-lexer": "^2.1.0",
+ "collect-v8-coverage": "^1.0.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz",
+ "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1",
+ "@babel/types": "^7.27.3",
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "@jest/snapshot-utils": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0",
+ "chalk": "^4.1.2",
+ "expect": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-diff": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0",
+ "semver": "^7.7.2",
+ "synckit": "^0.11.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz",
+ "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz",
+ "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "camelcase": "^6.3.0",
+ "chalk": "^4.1.2",
+ "leven": "^3.1.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz",
+ "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "jest-util": "30.2.0",
+ "string-length": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz",
+ "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@ungap/structured-clone": "^1.3.0",
+ "jest-util": "30.2.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz",
+ "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^5.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/napi-postinstall": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
+ "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz",
+ "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/pure-rand": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
+ "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz",
+ "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "glob": "^13.0.3",
+ "package-json-from-dist": "^1.0.1"
+ },
+ "bin": {
+ "rimraf": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/glob": {
+ "version": "13.0.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
+ "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "minimatch": "^10.2.2",
+ "minipass": "^7.1.3",
+ "path-scurry": "^2.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/lru-cache": {
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
+ "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/rimraf/node_modules/minimatch": {
+ "version": "10.2.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz",
+ "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/path-scurry": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-length/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-length/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/synckit": {
+ "version": "0.11.12",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/test-exclude/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/test-exclude/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/test-exclude/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/test-exclude/node_modules/minimatch": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz",
+ "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-jest": {
+ "version": "29.4.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
+ "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bs-logger": "^0.2.6",
+ "fast-json-stable-stringify": "^2.1.0",
+ "handlebars": "^4.7.8",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.7.3",
+ "type-fest": "^4.41.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0 || ^30.0.0",
+ "@jest/types": "^29.0.0 || ^30.0.0",
+ "babel-jest": "^29.0.0 || ^30.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-util": "^29.0.0 || ^30.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jest-util": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-jest/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ts-jest/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uglify-js": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unrs-resolver": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
+ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
+ "@unrs/resolver-binding-android-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-x64": "1.11.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/infrastructure/package.json b/infrastructure/package.json
new file mode 100644
index 0000000..b34dd09
--- /dev/null
+++ b/infrastructure/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "ionic8-starter-infrastructure",
+ "version": "1.1.0-alpha.1",
+ "private": true,
+ "scripts": {
+ "build": "tsc",
+ "clean": "rimraf cdk.out coverage dist",
+ "cdk": "cdk",
+ "deploy": "cdk deploy",
+ "deploy:all": "cdk deploy --all",
+ "destroy": "cdk destroy",
+ "destroy:all": "cdk destroy --all",
+ "diff": "cdk diff",
+ "synth": "cdk synth",
+ "test": "jest --coverage=false --silent --verbose",
+ "test:coverage": "jest --coverage --silent --verbose",
+ "test:watch": "jest --watch"
+ },
+ "devDependencies": {
+ "@types/jest": "30.0.0",
+ "@types/node": "25.3.0",
+ "aws-cdk": "2.1107.0",
+ "jest": "30.2.0",
+ "rimraf": "6.1.3",
+ "ts-jest": "29.4.6",
+ "ts-node": "10.9.2",
+ "typescript": "5.9.3"
+ },
+ "dependencies": {
+ "aws-cdk-lib": "2.240.0",
+ "constructs": "10.5.1",
+ "dotenv": "17.3.1",
+ "zod": "4.3.6"
+ }
+}
diff --git a/infrastructure/stacks/cdn-stack.test.ts b/infrastructure/stacks/cdn-stack.test.ts
new file mode 100644
index 0000000..e821d9f
--- /dev/null
+++ b/infrastructure/stacks/cdn-stack.test.ts
@@ -0,0 +1,468 @@
+/**
+ * Unit tests for the CdnStack class.
+ *
+ * @module stacks/CdnStack.test
+ */
+
+import * as cdk from 'aws-cdk-lib';
+import { Template, Match } from 'aws-cdk-lib/assertions';
+import { CdnStack, CdnStackProps } from './cdn-stack';
+
+describe('CdnStack', () => {
+ let app: cdk.App;
+
+ beforeEach(() => {
+ app = new cdk.App();
+ });
+
+ describe('basic stack creation without optional props', () => {
+ let stack: CdnStack;
+ let template: Template;
+
+ beforeEach(() => {
+ const props: CdnStackProps = {
+ appName: 'test-app',
+ envName: 'dev',
+ assetPath: '../dist',
+ };
+
+ stack = new CdnStack(app, 'TestStack', props);
+ template = Template.fromStack(stack);
+ });
+
+ it('should create an S3 bucket with correct properties', () => {
+ template.hasResourceProperties('AWS::S3::Bucket', {
+ BucketEncryption: {
+ ServerSideEncryptionConfiguration: [
+ {
+ ServerSideEncryptionByDefault: {
+ SSEAlgorithm: 'AES256',
+ },
+ },
+ ],
+ },
+ PublicAccessBlockConfiguration: {
+ BlockPublicAcls: true,
+ BlockPublicPolicy: true,
+ IgnorePublicAcls: true,
+ RestrictPublicBuckets: true,
+ },
+ });
+ });
+
+ it('should create a CloudFront distribution', () => {
+ template.hasResourceProperties('AWS::CloudFront::Distribution', {
+ DistributionConfig: {
+ DefaultRootObject: 'index.html',
+ Enabled: true,
+ },
+ });
+ });
+
+ it('should configure error responses for SPA routing', () => {
+ template.hasResourceProperties('AWS::CloudFront::Distribution', {
+ DistributionConfig: Match.objectLike({
+ CustomErrorResponses: [
+ Match.objectLike({
+ ErrorCode: 403,
+ ResponseCode: 200,
+ ResponsePagePath: '/index.html',
+ }),
+ Match.objectLike({
+ ErrorCode: 404,
+ ResponseCode: 200,
+ ResponsePagePath: '/index.html',
+ }),
+ ],
+ }),
+ });
+ });
+
+ it('should create S3 bucket deployment', () => {
+ template.resourceCountIs('AWS::S3::Bucket', 1);
+ template.hasResourceProperties('AWS::CloudFront::Distribution', {
+ DistributionConfig: {
+ DefaultRootObject: 'index.html',
+ },
+ });
+ });
+
+ it('should create BucketName output', () => {
+ template.hasOutput('BucketName', {
+ Description: 'Name of the S3 bucket containing the CDN assets',
+ });
+ });
+
+ it('should create DistributionId output', () => {
+ template.hasOutput('DistributionId', {
+ Description: 'CloudFront distribution ID',
+ });
+ });
+
+ it('should create DistributionDomainName output', () => {
+ template.hasOutput('DistributionDomainName', {
+ Description: 'CloudFront distribution domain name',
+ });
+ });
+
+ it('should create ApplicationUrl output using distribution domain', () => {
+ template.hasOutput('ApplicationUrl', {
+ Description: 'URL to access the application',
+ });
+ });
+
+ it('should not create certificate when certificateArn is not provided', () => {
+ expect(stack.certificate).toBeUndefined();
+ });
+
+ it('should not create A record when domain props are not provided', () => {
+ expect(stack.aRecord).toBeUndefined();
+ });
+
+ it('should not create AAAA record when domain props are not provided', () => {
+ expect(stack.aaaaRecord).toBeUndefined();
+ });
+
+ it('should not include domain names in distribution', () => {
+ template.hasResourceProperties(
+ 'AWS::CloudFront::Distribution',
+ Match.not(
+ Match.objectLike({
+ DistributionConfig: {
+ Aliases: Match.arrayWith([Match.anyValue()]),
+ },
+ }),
+ ),
+ );
+ });
+
+ it('should not create CustomDomainName output', () => {
+ const outputs = template.findOutputs('*');
+ expect(Object.keys(outputs)).not.toContain('CustomDomainName');
+ });
+ });
+
+ describe('stack creation with certificate', () => {
+ let stack: CdnStack;
+ let template: Template;
+
+ beforeEach(() => {
+ const props: CdnStackProps = {
+ appName: 'test-app',
+ envName: 'dev',
+ assetPath: '../dist',
+ certificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/test-cert',
+ domainName: 'app.example.com',
+ };
+
+ stack = new CdnStack(app, 'TestStackWithCert', props);
+ template = Template.fromStack(stack);
+ });
+
+ it('should load certificate when certificateArn is provided', () => {
+ expect(stack.certificate).toBeDefined();
+ });
+
+ it('should include domain names in distribution when certificate is present', () => {
+ template.hasResourceProperties(
+ 'AWS::CloudFront::Distribution',
+ Match.objectLike({
+ DistributionConfig: {
+ Aliases: ['app.example.com'],
+ },
+ }),
+ );
+ });
+
+ it('should configure ViewerProtocolPolicy to REDIRECT_TO_HTTPS', () => {
+ template.hasResourceProperties(
+ 'AWS::CloudFront::Distribution',
+ Match.objectLike({
+ DistributionConfig: {
+ DefaultCacheBehavior: Match.objectLike({
+ ViewerProtocolPolicy: 'redirect-to-https',
+ }),
+ },
+ }),
+ );
+ });
+
+ it('should use CACHING_OPTIMIZED cache policy', () => {
+ template.hasResourceProperties(
+ 'AWS::CloudFront::Distribution',
+ Match.objectLike({
+ DistributionConfig: {
+ DefaultCacheBehavior: Match.objectLike({
+ CachePolicyId: Match.anyValue(),
+ }),
+ },
+ }),
+ );
+ });
+
+ it('should include security headers in response', () => {
+ template.hasResourceProperties(
+ 'AWS::CloudFront::Distribution',
+ Match.objectLike({
+ DistributionConfig: {
+ DefaultCacheBehavior: Match.objectLike({
+ ResponseHeadersPolicyId: Match.anyValue(),
+ }),
+ },
+ }),
+ );
+ });
+ });
+
+ describe('stack creation with full domain configuration', () => {
+ let stack: CdnStack;
+ let template: Template;
+
+ beforeEach(() => {
+ const props: CdnStackProps = {
+ appName: 'test-app',
+ envName: 'prod',
+ assetPath: '../dist',
+ certificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/test-cert',
+ domainName: 'app.example.com',
+ hostedZoneId: 'Z1234567890ABC',
+ hostedZoneName: 'example.com',
+ };
+
+ stack = new CdnStack(app, 'TestStackWithDomain', props);
+ template = Template.fromStack(stack);
+ });
+
+ it('should create A record for custom domain', () => {
+ expect(stack.aRecord).toBeDefined();
+ });
+
+ it('should create AAAA record for custom domain', () => {
+ expect(stack.aaaaRecord).toBeDefined();
+ });
+
+ it('should create Route53 A record with CloudFront alias', () => {
+ template.hasResourceProperties('AWS::Route53::RecordSet', {
+ Type: 'A',
+ Name: 'app.example.com.',
+ AliasTarget: Match.objectLike({
+ HostedZoneId: Match.anyValue(),
+ }),
+ });
+ });
+
+ it('should create Route53 AAAA record with CloudFront alias', () => {
+ template.hasResourceProperties('AWS::Route53::RecordSet', {
+ Type: 'AAAA',
+ Name: 'app.example.com.',
+ AliasTarget: Match.objectLike({
+ HostedZoneId: Match.anyValue(),
+ }),
+ });
+ });
+
+ it('should create CustomDomainName output when domain is configured', () => {
+ template.hasOutput('CustomDomainName', {
+ Description: 'Custom domain name for the application',
+ });
+ });
+
+ it('should create ApplicationUrl output using custom domain', () => {
+ const outputs = template.findOutputs('ApplicationUrl');
+ expect(outputs).toHaveProperty('ApplicationUrl');
+ });
+ });
+
+ describe('edge cases', () => {
+ it('should not create Route53 records when hostedZoneId is missing', () => {
+ const props: CdnStackProps = {
+ appName: 'test-app',
+ envName: 'dev',
+ assetPath: '../dist',
+ certificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/test-cert',
+ domainName: 'app.example.com',
+ hostedZoneName: 'example.com',
+ // hostedZoneId is missing
+ };
+
+ const stack = new CdnStack(app, 'TestStackMissingZoneId', props);
+
+ expect(stack.aRecord).toBeUndefined();
+ expect(stack.aaaaRecord).toBeUndefined();
+ });
+
+ it('should not create Route53 records when hostedZoneName is missing', () => {
+ const props: CdnStackProps = {
+ appName: 'test-app',
+ envName: 'dev',
+ assetPath: '../dist',
+ certificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/test-cert',
+ domainName: 'app.example.com',
+ hostedZoneId: 'Z1234567890ABC',
+ // hostedZoneName is missing
+ };
+
+ const stack = new CdnStack(app, 'TestStackMissingZoneName', props);
+
+ expect(stack.aRecord).toBeUndefined();
+ expect(stack.aaaaRecord).toBeUndefined();
+ });
+
+ it('should not add domain names to distribution when certificateArn is missing', () => {
+ const props: CdnStackProps = {
+ appName: 'test-app',
+ envName: 'dev',
+ assetPath: '../dist',
+ domainName: 'app.example.com',
+ // certificateArn is missing
+ };
+
+ const stack = new CdnStack(app, 'TestStackNoCert', props);
+ const template = Template.fromStack(stack);
+
+ template.hasResourceProperties(
+ 'AWS::CloudFront::Distribution',
+ Match.not(
+ Match.objectLike({
+ DistributionConfig: {
+ Aliases: Match.arrayWith([Match.anyValue()]),
+ },
+ }),
+ ),
+ );
+ });
+ });
+
+ describe('stack properties and resource naming', () => {
+ it('should use appName and envName in resource identifiers', () => {
+ const props: CdnStackProps = {
+ appName: 'my-app',
+ envName: 'staging',
+ assetPath: '../dist',
+ };
+
+ const stack = new CdnStack(app, 'TestStackNaming', props);
+
+ expect(stack).toBeDefined();
+ expect(stack.bucket).toBeDefined();
+ expect(stack.distribution).toBeDefined();
+ });
+
+ it('should have bucket and distribution as public properties', () => {
+ const props: CdnStackProps = {
+ appName: 'test-app',
+ envName: 'dev',
+ assetPath: '../dist',
+ };
+
+ const stack = new CdnStack(app, 'TestStackProperties', props);
+
+ expect(stack.bucket).toBeDefined();
+ expect(stack.distribution).toBeDefined();
+ });
+
+ it('should inherit from cdk.Stack', () => {
+ const props: CdnStackProps = {
+ appName: 'test-app',
+ envName: 'dev',
+ assetPath: '../dist',
+ };
+
+ const stack = new CdnStack(app, 'TestStackInheritance', props);
+
+ expect(stack).toBeInstanceOf(cdk.Stack);
+ });
+ });
+
+ describe('CloudFront configuration details', () => {
+ let stack: CdnStack;
+ let template: Template;
+
+ beforeEach(() => {
+ const props: CdnStackProps = {
+ appName: 'test-app',
+ envName: 'dev',
+ assetPath: '../dist',
+ };
+
+ stack = new CdnStack(app, 'TestStackCloudFront', props);
+ template = Template.fromStack(stack);
+ });
+
+ it('should enable IPv6', () => {
+ template.hasResourceProperties(
+ 'AWS::CloudFront::Distribution',
+ Match.objectLike({
+ DistributionConfig: {
+ Enabled: true,
+ },
+ }),
+ );
+ });
+
+ it('should use PRICE_CLASS_100 for cost optimization', () => {
+ template.hasResourceProperties('AWS::CloudFront::Distribution', Match.anyValue());
+ });
+
+ it('should include comment with appName and envName', () => {
+ template.hasResourceProperties(
+ 'AWS::CloudFront::Distribution',
+ Match.objectLike({
+ DistributionConfig: {
+ Comment: 'test-app CDN for dev',
+ },
+ }),
+ );
+ });
+ });
+
+ describe('S3 bucket configuration details', () => {
+ let stack: CdnStack;
+ let template: Template;
+
+ beforeEach(() => {
+ const props: CdnStackProps = {
+ appName: 'test-app',
+ envName: 'dev',
+ assetPath: '../dist',
+ };
+
+ stack = new CdnStack(app, 'TestStackS3', props);
+ template = Template.fromStack(stack);
+ });
+
+ it('should block all public access to the bucket', () => {
+ template.hasResourceProperties('AWS::S3::Bucket', {
+ PublicAccessBlockConfiguration: {
+ BlockPublicAcls: true,
+ BlockPublicPolicy: true,
+ IgnorePublicAcls: true,
+ RestrictPublicBuckets: true,
+ },
+ });
+ });
+
+ it('should enable server-side encryption', () => {
+ template.hasResourceProperties('AWS::S3::Bucket', {
+ BucketEncryption: {
+ ServerSideEncryptionConfiguration: [
+ {
+ ServerSideEncryptionByDefault: {
+ SSEAlgorithm: 'AES256',
+ },
+ },
+ ],
+ },
+ });
+ });
+
+ it('should enforce SSL connections', () => {
+ template.hasResourceProperties('AWS::S3::BucketPolicy', Match.anyValue());
+ });
+
+ it('should have auto deletion enabled for cleanup', () => {
+ // This is verified by checking the stack has both S3 bucket and removal policies configured
+ expect(stack.bucket).toBeDefined();
+ });
+ });
+});
diff --git a/infrastructure/stacks/cdn-stack.ts b/infrastructure/stacks/cdn-stack.ts
new file mode 100644
index 0000000..fbd655c
--- /dev/null
+++ b/infrastructure/stacks/cdn-stack.ts
@@ -0,0 +1,213 @@
+/**
+ * An AWS CDK stack for deploying a Content Delivery Network (CDN) using CloudFront and S3.
+ *
+ * @module stacks/CdnStack
+ */
+
+import * as cdk from 'aws-cdk-lib';
+import * as s3 from 'aws-cdk-lib/aws-s3';
+import * as s3_deployment from 'aws-cdk-lib/aws-s3-deployment';
+import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
+import * as cloudfront_origins from 'aws-cdk-lib/aws-cloudfront-origins';
+import * as certificatemanager from 'aws-cdk-lib/aws-certificatemanager';
+import * as route53 from 'aws-cdk-lib/aws-route53';
+import * as route53_targets from 'aws-cdk-lib/aws-route53-targets';
+import { Construct } from 'constructs';
+
+export interface CdnStackProps extends cdk.StackProps {
+ /**
+ * The name of the application, used for resource naming and tagging
+ */
+ appName: string;
+ /**
+ * The deployment environment (e.g. 'dev', 'qa', 'prd')
+ */
+ envName: string;
+ /**
+ * The path to the local directory containing the static assets to be deployed to S3 (e.g. '../dist')
+ */
+ assetPath: string;
+ /**
+ * Optional custom domain name for the CDN (e.g. 'app.example.com').
+ * If not provided, the CloudFront distribution domain will be used.
+ */
+ domainName?: string;
+ /**
+ * The ARN of the SSL certificate for the custom domain
+ */
+ certificateArn?: string;
+ /**
+ * The ID of the Route53 hosted zone for the custom domain
+ */
+ hostedZoneId?: string;
+ /**
+ * The name of the Route53 hosted zone for the custom domain (e.g. 'example.com')
+ */
+ hostedZoneName?: string;
+}
+
+/**
+ * A CDK stack that sets up a CloudFront distribution with an S3 origin for hosting a frontend application.
+ *
+ * Creates:
+ * - S3 bucket for hosting static assets
+ * - CloudFront distribution for content delivery
+ * - Optional SSL certificate and custom domain
+ */
+export class CdnStack extends cdk.Stack {
+ /**
+ * The S3 bucket containing the static assets
+ */
+ public readonly bucket: s3.Bucket;
+
+ /**
+ * The CloudFront distribution
+ */
+ public readonly distribution: cloudfront.Distribution;
+
+ /**
+ * The SSL certificate (if configured)
+ */
+ public readonly certificate?: certificatemanager.ICertificate;
+
+ /**
+ * The Route53 A record for the custom domain (if configured)
+ */
+ public readonly aRecord?: route53.ARecord;
+
+ /**
+ * The Route53 AAAA record for the custom domain (if configured)
+ */
+ public readonly aaaaRecord?: route53.AaaaRecord;
+
+ constructor(scope: Construct, id: string, props: CdnStackProps) {
+ super(scope, id, props);
+
+ // Create S3 bucket for the CDN assets
+ this.bucket = new s3.Bucket(this, `${props.appName}-cdn-bucket-${props.envName}`, {
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
+ autoDeleteObjects: true,
+ blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
+ encryption: s3.BucketEncryption.S3_MANAGED,
+ enforceSSL: true,
+ });
+
+ // Handle SSL certificate if configured
+ if (props.certificateArn) {
+ this.certificate = certificatemanager.Certificate.fromCertificateArn(
+ this,
+ `${props.appName}-certificate-${props.envName}`,
+ props.certificateArn,
+ );
+ }
+
+ // Configure CloudFront distribution properties
+ const baseDistributionProps: cloudfront.DistributionProps = {
+ comment: `${props.appName} CDN for ${props.envName}`,
+ defaultBehavior: {
+ origin: cloudfront_origins.S3BucketOrigin.withOriginAccessControl(this.bucket),
+ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
+ cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
+ originRequestPolicy: cloudfront.OriginRequestPolicy.CORS_S3_ORIGIN,
+ responseHeadersPolicy: cloudfront.ResponseHeadersPolicy.SECURITY_HEADERS,
+ },
+ defaultRootObject: 'index.html',
+ errorResponses: [
+ {
+ httpStatus: 403,
+ responsePagePath: '/index.html',
+ ttl: cdk.Duration.seconds(0),
+ responseHttpStatus: 200,
+ },
+ {
+ httpStatus: 404,
+ responsePagePath: '/index.html',
+ ttl: cdk.Duration.seconds(0),
+ responseHttpStatus: 200,
+ },
+ ],
+ priceClass: cloudfront.PriceClass.PRICE_CLASS_100,
+ enableIpv6: true,
+ };
+
+ // Optionally add custom domain and SSL certificate to the distribution
+ const distributionProps: cloudfront.DistributionProps =
+ props.domainName && this.certificate
+ ? {
+ ...baseDistributionProps,
+ domainNames: [props.domainName],
+ certificate: this.certificate,
+ }
+ : baseDistributionProps;
+
+ // Create the CloudFront distribution
+ this.distribution = new cloudfront.Distribution(
+ this,
+ `${props.appName}-distribution-${props.envName}`,
+ distributionProps,
+ );
+
+ // Deploy static assets to the S3 bucket and invalidate CloudFront cache
+ new s3_deployment.BucketDeployment(this, `${props.appName}-deployment-${props.envName}`, {
+ sources: [s3_deployment.Source.asset(props.assetPath)],
+ destinationBucket: this.bucket,
+ distribution: this.distribution,
+ distributionPaths: ['/*'],
+ memoryLimit: 512,
+ });
+
+ // Optionally create Route53 records for the custom domain
+ if (props.domainName && props.hostedZoneId && props.hostedZoneName) {
+ const hostedZone = route53.HostedZone.fromHostedZoneAttributes(
+ this,
+ `${props.appName}-hosted-zone-${props.envName}`,
+ {
+ hostedZoneId: props.hostedZoneId,
+ zoneName: props.hostedZoneName,
+ },
+ );
+
+ this.aRecord = new route53.ARecord(this, `${props.appName}-a-record-${props.envName}`, {
+ zone: hostedZone,
+ recordName: props.domainName,
+ target: route53.RecordTarget.fromAlias(new route53_targets.CloudFrontTarget(this.distribution)),
+ });
+
+ this.aaaaRecord = new route53.AaaaRecord(this, `${props.appName}-aaaa-record-${props.envName}`, {
+ zone: hostedZone,
+ recordName: props.domainName,
+ target: route53.RecordTarget.fromAlias(new route53_targets.CloudFrontTarget(this.distribution)),
+ });
+ }
+
+ // Outputs
+ new cdk.CfnOutput(this, 'BucketName', {
+ value: this.bucket.bucketName,
+ description: 'Name of the S3 bucket containing the CDN assets',
+ });
+
+ new cdk.CfnOutput(this, 'DistributionId', {
+ value: this.distribution.distributionId,
+ description: 'CloudFront distribution ID',
+ });
+
+ new cdk.CfnOutput(this, 'DistributionDomainName', {
+ value: this.distribution.distributionDomainName,
+ description: 'CloudFront distribution domain name',
+ });
+
+ if (this.aRecord) {
+ new cdk.CfnOutput(this, 'CustomDomainName', {
+ value: this.aRecord.domainName,
+ description: 'Custom domain name for the application',
+ });
+ }
+
+ new cdk.CfnOutput(this, 'ApplicationUrl', {
+ value: this.aRecord
+ ? `https://${this.aRecord.domainName}`
+ : `https://${this.distribution.distributionDomainName}`,
+ description: 'URL to access the application',
+ });
+ }
+}
diff --git a/infrastructure/tsconfig.json b/infrastructure/tsconfig.json
new file mode 100644
index 0000000..1952530
--- /dev/null
+++ b/infrastructure/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "commonjs",
+ "lib": ["ES2022"],
+ "declaration": true,
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "noImplicitThis": true,
+ "alwaysStrict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": false,
+ "inlineSourceMap": true,
+ "inlineSources": true,
+ "experimentalDecorators": true,
+ "strictPropertyInitialization": false,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "typeRoots": ["./node_modules/@types"],
+ "outDir": "dist"
+ },
+ "exclude": ["dist", "cdk.out", "node_modules", "coverage", "**/*.js"],
+ "include": ["**/*.ts", "jest.config.ts"]
+}
diff --git a/infrastructure/utils/config.test.ts b/infrastructure/utils/config.test.ts
new file mode 100644
index 0000000..34f24f2
--- /dev/null
+++ b/infrastructure/utils/config.test.ts
@@ -0,0 +1,289 @@
+import { getConfig, getEnvironmentConfig, getTags, type Config } from './config';
+
+describe('config', () => {
+ const originalEnv = process.env;
+
+ beforeEach(() => {
+ jest.resetModules();
+ process.env = { ...originalEnv };
+ });
+
+ afterAll(() => {
+ process.env = originalEnv;
+ });
+
+ describe('getConfig', () => {
+ it('should return valid configuration when CDK_ENV is set', () => {
+ process.env.CDK_ENV = 'dev';
+ process.env.CDK_DOMAIN_NAME = 'ionic8-starter.dev.example.com';
+ process.env.CDK_CERTIFICATE_ARN = 'arn:aws:acm:us-east-1:123456789012:certificate/test';
+ process.env.CDK_HOSTED_ZONE_ID = 'Z1234567890ABCDEFGHI';
+ process.env.CDK_HOSTED_ZONE_NAME = 'dev.example.com';
+
+ const config = getConfig();
+
+ expect(config).toBeDefined();
+ expect(config.CDK_ENV).toBe('dev');
+ expect(config.CDK_APP_NAME).toBe('ionic8-starter');
+ expect(config.CDK_ASSET_PATH).toBe('../dist');
+ });
+
+ it('should return configuration with optional values', () => {
+ process.env.CDK_APP_NAME = 'my-app';
+ process.env.CDK_ENV = 'prd';
+ process.env.CDK_DOMAIN_NAME = 'my-app.prd.example.com';
+ process.env.CDK_CERTIFICATE_ARN = 'arn:aws:acm:us-east-1:123456789012:certificate/my-cert';
+ process.env.CDK_HOSTED_ZONE_ID = 'Z0987654321FEDCBAXYZ';
+ process.env.CDK_HOSTED_ZONE_NAME = 'prd.example.com';
+ process.env.CDK_ACCOUNT = '123456789012';
+ process.env.CDK_REGION = 'us-east-1';
+ process.env.CDK_OU = 'leanstacks';
+ process.env.CDK_OWNER = 'platform-team';
+ process.env.CDK_ASSET_PATH = '../custom-dist';
+
+ const config = getConfig();
+
+ expect(config).toEqual({
+ CDK_APP_NAME: 'my-app',
+ CDK_ENV: 'prd',
+ CDK_DOMAIN_NAME: 'my-app.prd.example.com',
+ CDK_CERTIFICATE_ARN: 'arn:aws:acm:us-east-1:123456789012:certificate/my-cert',
+ CDK_HOSTED_ZONE_ID: 'Z0987654321FEDCBAXYZ',
+ CDK_HOSTED_ZONE_NAME: 'prd.example.com',
+ CDK_ASSET_PATH: '../custom-dist',
+ CDK_ACCOUNT: '123456789012',
+ CDK_REGION: 'us-east-1',
+ CDK_OU: 'leanstacks',
+ CDK_OWNER: 'platform-team',
+ });
+ });
+
+ it('should throw error when CDK_ENV is missing', () => {
+ delete process.env.CDK_ENV;
+
+ expect(() => getConfig()).toThrow('CDK configuration validation failed');
+ });
+
+ it('should throw error when CDK_ENV is invalid', () => {
+ process.env.CDK_ENV = 'invalid';
+
+ expect(() => getConfig()).toThrow('CDK configuration validation failed');
+ });
+ });
+
+ describe('getTags', () => {
+ it('should return standard tags', () => {
+ const config: Config = {
+ CDK_APP_NAME: 'ionic8-starter',
+ CDK_ENV: 'dev',
+ CDK_DOMAIN_NAME: 'ionic8-starter.dev.example.com',
+ CDK_CERTIFICATE_ARN: 'arn:aws:acm:us-east-1:123456789012:certificate/test',
+ CDK_HOSTED_ZONE_ID: 'Z1234567890ABCDEFGHI',
+ CDK_HOSTED_ZONE_NAME: 'dev.example.com',
+ CDK_ASSET_PATH: '../dist',
+ CDK_OU: 'leanstacks',
+ CDK_OWNER: 'platform-team',
+ };
+
+ const tags = getTags(config);
+
+ expect(tags).toEqual({
+ App: 'ionic8-starter',
+ Env: 'dev',
+ OU: 'leanstacks',
+ Owner: 'platform-team',
+ });
+ });
+
+ it('should use default values for OU and Owner when not provided', () => {
+ const config: Config = {
+ CDK_APP_NAME: 'ionic8-starter',
+ CDK_ENV: 'qa',
+ CDK_DOMAIN_NAME: 'ionic8-starter.qa.example.com',
+ CDK_CERTIFICATE_ARN: 'arn:aws:acm:us-east-1:123456789012:certificate/test',
+ CDK_HOSTED_ZONE_ID: 'Z1234567890ABCDEFGHI',
+ CDK_HOSTED_ZONE_NAME: 'qa.example.com',
+ CDK_ASSET_PATH: '../dist',
+ CDK_OU: 'unknown',
+ CDK_OWNER: 'unknown',
+ };
+
+ const tags = getTags(config);
+
+ expect(tags).toEqual({
+ App: 'ionic8-starter',
+ Env: 'qa',
+ OU: 'unknown',
+ Owner: 'unknown',
+ });
+ });
+
+ it('should use custom app name in tags', () => {
+ const config: Config = {
+ CDK_APP_NAME: 'my-custom-app',
+ CDK_ENV: 'dev',
+ CDK_DOMAIN_NAME: 'my-custom-app.dev.example.com',
+ CDK_CERTIFICATE_ARN: 'arn:aws:acm:us-east-1:123456789012:certificate/test',
+ CDK_HOSTED_ZONE_ID: 'Z1234567890ABCDEFGHI',
+ CDK_HOSTED_ZONE_NAME: 'dev.example.com',
+ CDK_ASSET_PATH: '../dist',
+ CDK_OU: 'leanstacks',
+ CDK_OWNER: 'unknown',
+ };
+
+ const tags = getTags(config);
+
+ expect(tags).toEqual({
+ App: 'my-custom-app',
+ Env: 'dev',
+ OU: 'leanstacks',
+ Owner: 'unknown',
+ });
+ });
+ });
+
+ describe('getEnvironmentConfig', () => {
+ it('should return account and region from CDK_ACCOUNT and CDK_REGION', () => {
+ process.env.CDK_ACCOUNT = '123456789012';
+ process.env.CDK_REGION = 'us-west-2';
+
+ const config: Config = {
+ CDK_APP_NAME: 'ionic8-starter',
+ CDK_ENV: 'dev',
+ CDK_DOMAIN_NAME: 'ionic8-starter.dev.example.com',
+ CDK_CERTIFICATE_ARN: 'arn:aws:acm:us-east-1:123456789012:certificate/test',
+ CDK_HOSTED_ZONE_ID: 'Z1234567890ABCDEFGHI',
+ CDK_HOSTED_ZONE_NAME: 'dev.example.com',
+ CDK_ASSET_PATH: '../dist',
+ CDK_OU: 'unknown',
+ CDK_OWNER: 'unknown',
+ CDK_ACCOUNT: '123456789012',
+ CDK_REGION: 'us-west-2',
+ };
+
+ const envConfig = getEnvironmentConfig(config);
+
+ expect(envConfig).toEqual({
+ account: '123456789012',
+ region: 'us-west-2',
+ });
+ });
+
+ it('should fallback to CDK_DEFAULT_ACCOUNT and CDK_DEFAULT_REGION', () => {
+ delete process.env.CDK_ACCOUNT;
+ delete process.env.CDK_REGION;
+ process.env.CDK_DEFAULT_ACCOUNT = '987654321098';
+ process.env.CDK_DEFAULT_REGION = 'eu-west-1';
+
+ const config: Config = {
+ CDK_APP_NAME: 'ionic8-starter',
+ CDK_ENV: 'dev',
+ CDK_DOMAIN_NAME: 'ionic8-starter.dev.example.com',
+ CDK_CERTIFICATE_ARN: 'arn:aws:acm:us-east-1:123456789012:certificate/test',
+ CDK_HOSTED_ZONE_ID: 'Z1234567890ABCDEFGHI',
+ CDK_HOSTED_ZONE_NAME: 'dev.example.com',
+ CDK_ASSET_PATH: '../dist',
+ CDK_OU: 'unknown',
+ CDK_OWNER: 'unknown',
+ };
+
+ const envConfig = getEnvironmentConfig(config);
+
+ expect(envConfig).toEqual({
+ account: '987654321098',
+ region: 'eu-west-1',
+ });
+ });
+
+ it('should prefer CDK_ACCOUNT over CDK_DEFAULT_ACCOUNT', () => {
+ process.env.CDK_DEFAULT_ACCOUNT = '111111111111';
+ process.env.CDK_DEFAULT_REGION = 'us-east-1';
+
+ const config: Config = {
+ CDK_APP_NAME: 'ionic8-starter',
+ CDK_ENV: 'prd',
+ CDK_DOMAIN_NAME: 'ionic8-starter.prd.example.com',
+ CDK_CERTIFICATE_ARN: 'arn:aws:acm:us-east-1:123456789012:certificate/test',
+ CDK_HOSTED_ZONE_ID: 'Z1234567890ABCDEFGHI',
+ CDK_HOSTED_ZONE_NAME: 'prd.example.com',
+ CDK_ASSET_PATH: '../dist',
+ CDK_OU: 'unknown',
+ CDK_OWNER: 'unknown',
+ CDK_ACCOUNT: '999999999999',
+ CDK_REGION: 'ap-southeast-2',
+ };
+
+ const envConfig = getEnvironmentConfig(config);
+
+ expect(envConfig).toEqual({
+ account: '999999999999',
+ region: 'ap-southeast-2',
+ });
+ });
+
+ it('should return undefined when neither account nor region are set', () => {
+ delete process.env.CDK_ACCOUNT;
+ delete process.env.CDK_REGION;
+ delete process.env.CDK_DEFAULT_ACCOUNT;
+ delete process.env.CDK_DEFAULT_REGION;
+
+ const config: Config = {
+ CDK_APP_NAME: 'ionic8-starter',
+ CDK_ENV: 'dev',
+ CDK_DOMAIN_NAME: 'ionic8-starter.dev.example.com',
+ CDK_CERTIFICATE_ARN: 'arn:aws:acm:us-east-1:123456789012:certificate/test',
+ CDK_HOSTED_ZONE_ID: 'Z1234567890ABCDEFGHI',
+ CDK_HOSTED_ZONE_NAME: 'dev.example.com',
+ CDK_ASSET_PATH: '../dist',
+ CDK_OU: 'unknown',
+ CDK_OWNER: 'unknown',
+ };
+
+ const envConfig = getEnvironmentConfig(config);
+
+ expect(envConfig).toBeUndefined();
+ });
+
+ it('should return undefined when only account is set', () => {
+ process.env.CDK_DEFAULT_ACCOUNT = '123456789012';
+ delete process.env.CDK_DEFAULT_REGION;
+
+ const config: Config = {
+ CDK_APP_NAME: 'ionic8-starter',
+ CDK_ENV: 'dev',
+ CDK_DOMAIN_NAME: 'ionic8-starter.dev.example.com',
+ CDK_CERTIFICATE_ARN: 'arn:aws:acm:us-east-1:123456789012:certificate/test',
+ CDK_HOSTED_ZONE_ID: 'Z1234567890ABCDEFGHI',
+ CDK_HOSTED_ZONE_NAME: 'dev.example.com',
+ CDK_ASSET_PATH: '../dist',
+ CDK_OU: 'unknown',
+ CDK_OWNER: 'unknown',
+ };
+
+ const envConfig = getEnvironmentConfig(config);
+
+ expect(envConfig).toBeUndefined();
+ });
+
+ it('should return undefined when only region is set', () => {
+ delete process.env.CDK_DEFAULT_ACCOUNT;
+ process.env.CDK_DEFAULT_REGION = 'us-east-1';
+
+ const config: Config = {
+ CDK_APP_NAME: 'ionic8-starter',
+ CDK_ENV: 'dev',
+ CDK_DOMAIN_NAME: 'ionic8-starter.dev.example.com',
+ CDK_CERTIFICATE_ARN: 'arn:aws:acm:us-east-1:123456789012:certificate/test',
+ CDK_HOSTED_ZONE_ID: 'Z1234567890ABCDEFGHI',
+ CDK_HOSTED_ZONE_NAME: 'dev.example.com',
+ CDK_ASSET_PATH: '../dist',
+ CDK_OU: 'unknown',
+ CDK_OWNER: 'unknown',
+ };
+
+ const envConfig = getEnvironmentConfig(config);
+
+ expect(envConfig).toBeUndefined();
+ });
+ });
+});
diff --git a/infrastructure/utils/config.ts b/infrastructure/utils/config.ts
new file mode 100644
index 0000000..df9d445
--- /dev/null
+++ b/infrastructure/utils/config.ts
@@ -0,0 +1,86 @@
+/**
+ * AWS CDK configuration utilities.
+ *
+ * This module provides functions to load and validate configuration from environment variables,
+ * generate standard resource tags, and create AWS environment configurations for CDK stacks.
+ *
+ * @module utils/config
+ */
+
+import 'dotenv/config';
+import { z } from 'zod';
+
+/**
+ * Zod schema for CDK configuration validation.
+ */
+const configSchema = z.object({
+ CDK_APP_NAME: z.string().default('ionic8-starter'),
+ CDK_ENV: z.enum(['dev', 'qa', 'prd'], 'CDK_ENV must be one of: dev, qa, prd'),
+ CDK_ACCOUNT: z.string().optional(),
+ CDK_REGION: z.string().optional(),
+ CDK_OU: z.string().default('unknown'),
+ CDK_OWNER: z.string().default('unknown'),
+ CDK_ASSET_PATH: z.string().default('../dist'),
+ CDK_DOMAIN_NAME: z.string().optional(),
+ CDK_CERTIFICATE_ARN: z.string().optional(),
+ CDK_HOSTED_ZONE_ID: z.string().optional(),
+ CDK_HOSTED_ZONE_NAME: z.string().optional(),
+});
+
+/**
+ * Type for validated CDK configuration.
+ */
+export type Config = z.infer;
+
+/**
+ * Parse and validate CDK configuration from environment variables.
+ * @returns Validated configuration object.
+ * @throws {Error} If required configuration is missing or invalid.
+ */
+export function getConfig(): Config {
+ try {
+ const config = configSchema.parse(process.env);
+
+ return config;
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ const messages = error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
+ throw new Error(`CDK configuration validation failed: ${messages}`);
+ }
+ throw error;
+ }
+}
+
+/**
+ * Get resource tags based on configuration.
+ * @param config The validated configuration.
+ * @returns Object containing standard resource tags.
+ */
+export function getTags(config: Config): Record {
+ return {
+ App: config.CDK_APP_NAME,
+ Env: config.CDK_ENV,
+ OU: config.CDK_OU,
+ Owner: config.CDK_OWNER,
+ };
+}
+
+/**
+ * Get AWS environment configuration for CDK stacks.
+ * Uses CDK_ACCOUNT and CDK_REGION if provided, otherwise falls back to
+ * CDK_DEFAULT_ACCOUNT and CDK_DEFAULT_REGION which are automatically
+ * set by the CDK CLI based on the current AWS credentials and profile.
+ *
+ * @param config The validated configuration.
+ * @returns Environment config object with account and region, or undefined if neither are set.
+ */
+export function getEnvironmentConfig(config: Config): { account: string; region: string } | undefined {
+ const account = config.CDK_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT;
+ const region = config.CDK_REGION || process.env.CDK_DEFAULT_REGION;
+
+ if (account && region) {
+ return { account, region };
+ }
+
+ return undefined;
+}
diff --git a/template.yml b/template.yml
deleted file mode 100644
index 146eda5..0000000
--- a/template.yml
+++ /dev/null
@@ -1,226 +0,0 @@
-Description: Ionic 8 Playground component resources
-
-Parameters:
- EnvironmentCode:
- Type: String
- Description: Select an Environment
- AllowedValues:
- - dev
- - qa
- - prod
- Default: dev
- ConstraintDescription: Must select a valid environment
-
-Mappings:
- EnvironmentAttributeMap:
- dev:
- CertificateArn: arn:aws:acm:us-east-1:988218269141:certificate/3d110b0f-8b3d-4ddc-bbd8-fab08ae6f038
- CloudFrontOAID: E2U9SKLVDD8TPN
- HostedZone: dev.leanstacks.net
- qa:
- CertificateArn: arn:aws:acm:us-east-1:339939222800:certificate/5cd1bce7-1323-4625-a49e-5e72d1cff7ef
- CloudFrontOAID: E322H9D7WOKWXW
- HostedZone: qa.leanstacks.net
- prod:
- CertificateArn: arn:aws:acm:us-east-1:854599584783:certificate/fc25a13b-0c9f-4c79-a20f-a13f5d2245b3
- CloudFrontOAID: EVMQ2O0M1MS7S
- HostedZone: leanstacks.net
-
-Resources:
- ##
- # S3 Bucket for the Ionic Web App
- ##
- BucketApp:
- Type: AWS::S3::Bucket
- Properties:
- BucketName: !Sub
- - 'ionic8-ui-app.${HostedZone}-${AWS::Region}-${AWS::AccountId}'
- - HostedZone: !FindInMap [EnvironmentAttributeMap, !Ref EnvironmentCode, HostedZone]
-
- ##
- # Bucket Policy allows access from AWS CloudFront
- ##
- BucketPolicyApp:
- Type: AWS::S3::BucketPolicy
- Properties:
- Bucket: !Ref BucketApp
- PolicyDocument:
- Statement:
- - Action:
- - s3:GetObject
- Effect: Allow
- Resource: !Join
- - ''
- - - 'arn:aws:s3:::'
- - !Ref BucketApp
- - '/*'
- Principal:
- AWS: !Sub
- - 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOAID}'
- - CloudFrontOAID:
- !FindInMap [EnvironmentAttributeMap, !Ref EnvironmentCode, CloudFrontOAID]
-
- ##
- # CloudFront Distribution for the Ionic Web App - SPA errors and behaviors
- ##
- DistributionUi:
- Type: AWS::CloudFront::Distribution
- Properties:
- DistributionConfig:
- Comment: !Sub 'Ionic 8 Playground UI SPA (${EnvironmentCode})'
- CustomErrorResponses:
- - ErrorCode: 404
- ResponsePagePath: '/index.html'
- ResponseCode: 200
- - ErrorCode: 403
- ResponsePagePath: '/index.html'
- ResponseCode: 200
- DefaultCacheBehavior:
- AllowedMethods:
- - GET
- - HEAD
- - OPTIONS
- DefaultTTL: 60
- ForwardedValues:
- Cookies:
- Forward: none
- QueryString: false
- TargetOriginId: S3-APP
- ViewerProtocolPolicy: redirect-to-https
- DefaultRootObject: index.html
- Enabled: true
- HttpVersion: http2
- Origins:
- - DomainName: !GetAtt BucketApp.DomainName
- Id: S3-APP
- S3OriginConfig:
- OriginAccessIdentity: !Sub
- - 'origin-access-identity/cloudfront/${CloudFrontOAID}'
- - CloudFrontOAID:
- !FindInMap [EnvironmentAttributeMap, !Ref EnvironmentCode, CloudFrontOAID]
- PriceClass: PriceClass_100
-
- ##
- # CloudFront Distribution for complete, full-stack APP - routing for API and UI
- ##
- DistributionApp:
- Type: AWS::CloudFront::Distribution
- Properties:
- DistributionConfig:
- Comment: !Sub 'Ionic 8 Playground UI App (${EnvironmentCode})'
- Aliases:
- - !Sub
- - 'ionic8.${HostedZone}'
- - HostedZone: !FindInMap [EnvironmentAttributeMap, !Ref EnvironmentCode, HostedZone]
- CacheBehaviors:
- - AllowedMethods:
- - DELETE
- - GET
- - HEAD
- - OPTIONS
- - PATCH
- - POST
- - PUT
- DefaultTTL: 0
- ForwardedValues:
- Cookies:
- Forward: none
- Headers:
- - Accept
- - Authorization
- - Content-Type
- - X-Requested-With
- QueryString: true
- MaxTTL: 0
- MinTTL: 0
- PathPattern: /api*
- TargetOriginId: CUSTOM-API
- ViewerProtocolPolicy: redirect-to-https
- DefaultCacheBehavior:
- AllowedMethods:
- - GET
- - HEAD
- - OPTIONS
- DefaultTTL: 60
- ForwardedValues:
- Cookies:
- Forward: none
- QueryString: false
- TargetOriginId: CUSTOM-UI
- ViewerProtocolPolicy: redirect-to-https
- DefaultRootObject: index.html
- Enabled: true
- HttpVersion: http2
- Origins:
- - CustomOriginConfig:
- HTTPPort: 80
- HTTPSPort: 443
- OriginProtocolPolicy: https-only
- OriginSSLProtocols:
- - SSLv3
- - TLSv1
- - TLSv1.1
- - TLSv1.2
- DomainName: !GetAtt DistributionUi.DomainName
- Id: CUSTOM-UI
- - CustomOriginConfig:
- HTTPPort: 80
- HTTPSPort: 443
- OriginProtocolPolicy: https-only
- OriginSSLProtocols:
- - SSLv3
- - TLSv1
- - TLSv1.1
- - TLSv1.2
- DomainName: !Sub
- - 'api.${HostedZone}'
- - HostedZone: !FindInMap [EnvironmentAttributeMap, !Ref EnvironmentCode, HostedZone]
- Id: CUSTOM-API
- PriceClass: PriceClass_100
- ViewerCertificate:
- AcmCertificateArn:
- !FindInMap [EnvironmentAttributeMap, !Ref EnvironmentCode, CertificateArn]
- SslSupportMethod: sni-only
-
- ##
- # Route53 DNS for the 'App' CloudFront Distribution
- ##
- RecordSetAppA:
- Type: AWS::Route53::RecordSet
- Properties:
- HostedZoneName: !Sub
- - '${HostedZone}.'
- - HostedZone: !FindInMap [EnvironmentAttributeMap, !Ref EnvironmentCode, HostedZone]
- Name: !Sub
- - 'ionic8.${HostedZone}'
- - HostedZone: !FindInMap [EnvironmentAttributeMap, !Ref EnvironmentCode, HostedZone]
- Type: A
- AliasTarget:
- HostedZoneId: Z2FDTNDATAQYW2
- DNSName: !GetAtt DistributionApp.DomainName
-
- ##
- # Route53 DNS for the 'App' CloudFront Distribution
- ##
- RecordSetAppAAAA:
- Type: AWS::Route53::RecordSet
- Properties:
- HostedZoneName: !Sub
- - '${HostedZone}.'
- - HostedZone: !FindInMap [EnvironmentAttributeMap, !Ref EnvironmentCode, HostedZone]
- Name: !Sub
- - 'ionic8.${HostedZone}'
- - HostedZone: !FindInMap [EnvironmentAttributeMap, !Ref EnvironmentCode, HostedZone]
- Type: AAAA
- AliasTarget:
- HostedZoneId: Z2FDTNDATAQYW2
- DNSName: !GetAtt DistributionApp.DomainName
-
-Outputs:
- AppBucketName:
- Description: The application S3 bucket name
- Value: !Ref BucketApp
-
- DomainName:
- Description: The application domain name
- Value: !Ref RecordSetAppA
diff --git a/tsconfig.json b/tsconfig.json
index d0c4638..8adb16b 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -26,5 +26,6 @@
}
},
"include": ["src", "cypress"],
+ "exclude": ["infrastructure"],
"references": [{ "path": "./tsconfig.node.json" }]
}
diff --git a/vite.config.ts b/vite.config.ts
index 24881a2..758f6d3 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -17,7 +17,7 @@ export default defineConfig({
},
},
test: {
- exclude: [...configDefaults.exclude],
+ exclude: [...configDefaults.exclude, '**/infrastructure/**'],
coverage: {
provider: 'v8',
exclude: [