From 02b0af2ee9aba17675cf09432f6be71d38718933 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Wed, 18 Mar 2026 16:50:25 +0000 Subject: [PATCH] refactor: api gateway from cpt for reuse --- .devcontainer/Dockerfile | 80 +- .devcontainer/devcontainer.json | 84 +- .github/dependabot.yml | 20 +- .../scripts/check_ecr_image_scan_results.sh | 138 - .github/workflows/ci.yml | 59 +- .github/workflows/docker_image_build.yml | 16 +- .github/workflows/docker_image_upload.yml | 26 +- .github/workflows/package_npm_code.yml | 57 +- .github/workflows/pull_request.yml | 57 +- .github/workflows/release.yml | 63 +- .gitignore | 4 +- .pre-commit-config.yaml | 26 +- .tool-versions | 5 - .tool-versions.asdf | 2 - .trivyignore.yaml | 82 + .vscode/eps-cdk-utils.code-workspace | 14 +- LICENSE | 204 + Makefile | 45 +- README.md | 147 +- THIRD_PARTY_NOTICES.md | 7 + docker/Dockerfile | 14 +- package-lock.json | 7463 ++++++++++------- package.json | 53 +- packages/cdkConstructs/package.json | 14 +- packages/cdkConstructs/src/apps/createApp.ts | 112 + .../src/changesets/checkDestructiveChanges.ts | 181 + packages/cdkConstructs/src/config/index.ts | 43 + .../src/config/lambdaInsights.ts | 6 + .../src/constructs/PythonLambdaFunction.ts | 297 + .../src/constructs/RestApiGateway.ts | 230 + .../RestApiGateway/LambdaEndpoint.ts | 32 + .../RestApiGateway/accessLogFormat.ts | 38 + .../constructs/TypescriptLambdaFunction.ts | 177 +- .../src/constructs/lambdaSharedResources.ts | 136 + packages/cdkConstructs/src/index.ts | 10 + .../cdkConstructs/src/nag/pack/epsNagPack.ts | 219 + .../src/nag/rules/APIGWStructuredLogging.ts | 73 + .../src/nag/rules/ApiGWMutualTls.ts | 48 + packages/cdkConstructs/src/nag/rules/index.ts | 2 + .../src/stacks/deleteUnusedStacks.ts | 316 + packages/cdkConstructs/src/utils/helpers.ts | 126 + .../tests/apps/createApp.test.ts | 200 + .../checkDestructiveChanges.test.ts | 339 + .../examples/destructive_changeset.json | 1498 ++++ .../changesets/examples/safe_changeset.json | 5739 +++++++++++++ .../cdkConstructs/tests/config/index.test.ts | 144 + .../tests/constructs/RestApiGateway.test.ts | 329 + .../RestApiGateway/LambdaEndpoint.test.ts | 89 + .../pythonLambdaFunctionConstruct.test.ts | 530 ++ .../typescriptFunctionConstruct.test.ts} | 220 +- .../tests/nag/ApiGWStructuredLogging.test.ts | 163 + .../tests/nag/ApiGatewayMutualTls.test.ts | 70 + .../tests/nag/epsNagPack.test.ts | 63 + packages/cdkConstructs/tests/nag/utils.ts | 98 + .../tests/stacks/deleteUnusedStacks.test.ts | 1023 +++ .../cdkConstructs/tests/utils/helpers.test.ts | 131 + packages/cdkConstructs/tsconfig.json | 12 +- packages/deploymentUtils/package.json | 36 + packages/deploymentUtils/src/config/index.ts | 57 + packages/deploymentUtils/src/index.ts | 4 + .../deleteProxygenDeployments.ts | 120 + .../src/specifications/deployApi.ts | 128 + .../src/specifications/fixSpec.ts | 66 + .../src/specifications/invokeLambda.ts | 23 + .../src/specifications/writeSchemas.ts | 64 + .../tests/config/index.test.ts | 151 + .../deleteProxygenDeployments.test.ts | 132 + .../tests/specifications/deployApi.test.ts | 258 + .../tests/specifications/fixSpec.test.ts | 135 + .../tests/specifications/writeSchemas.test.ts | 158 + packages/deploymentUtils/tsconfig.json | 42 + packages/deploymentUtils/vitest.config.ts | 11 + poetry.lock | 277 +- pyproject.toml | 13 +- scripts/check_python_licenses.sh | 13 - sonar-project.properties | 11 +- trivy.yaml | 1 + 77 files changed, 19198 insertions(+), 3876 deletions(-) delete mode 100755 .github/scripts/check_ecr_image_scan_results.sh delete mode 100644 .tool-versions delete mode 100644 .tool-versions.asdf create mode 100644 .trivyignore.yaml create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 packages/cdkConstructs/src/apps/createApp.ts create mode 100644 packages/cdkConstructs/src/changesets/checkDestructiveChanges.ts create mode 100644 packages/cdkConstructs/src/config/index.ts create mode 100644 packages/cdkConstructs/src/config/lambdaInsights.ts create mode 100644 packages/cdkConstructs/src/constructs/PythonLambdaFunction.ts create mode 100644 packages/cdkConstructs/src/constructs/RestApiGateway.ts create mode 100644 packages/cdkConstructs/src/constructs/RestApiGateway/LambdaEndpoint.ts create mode 100644 packages/cdkConstructs/src/constructs/RestApiGateway/accessLogFormat.ts create mode 100644 packages/cdkConstructs/src/constructs/lambdaSharedResources.ts create mode 100644 packages/cdkConstructs/src/nag/pack/epsNagPack.ts create mode 100644 packages/cdkConstructs/src/nag/rules/APIGWStructuredLogging.ts create mode 100644 packages/cdkConstructs/src/nag/rules/ApiGWMutualTls.ts create mode 100644 packages/cdkConstructs/src/nag/rules/index.ts create mode 100644 packages/cdkConstructs/src/stacks/deleteUnusedStacks.ts create mode 100644 packages/cdkConstructs/src/utils/helpers.ts create mode 100644 packages/cdkConstructs/tests/apps/createApp.test.ts create mode 100644 packages/cdkConstructs/tests/changesets/checkDestructiveChanges.test.ts create mode 100644 packages/cdkConstructs/tests/changesets/examples/destructive_changeset.json create mode 100644 packages/cdkConstructs/tests/changesets/examples/safe_changeset.json create mode 100644 packages/cdkConstructs/tests/config/index.test.ts create mode 100644 packages/cdkConstructs/tests/constructs/RestApiGateway.test.ts create mode 100644 packages/cdkConstructs/tests/constructs/RestApiGateway/LambdaEndpoint.test.ts create mode 100644 packages/cdkConstructs/tests/constructs/pythonLambdaFunctionConstruct.test.ts rename packages/cdkConstructs/tests/{functionConstruct.test.ts => constructs/typescriptFunctionConstruct.test.ts} (56%) create mode 100644 packages/cdkConstructs/tests/nag/ApiGWStructuredLogging.test.ts create mode 100644 packages/cdkConstructs/tests/nag/ApiGatewayMutualTls.test.ts create mode 100644 packages/cdkConstructs/tests/nag/epsNagPack.test.ts create mode 100644 packages/cdkConstructs/tests/nag/utils.ts create mode 100644 packages/cdkConstructs/tests/stacks/deleteUnusedStacks.test.ts create mode 100644 packages/cdkConstructs/tests/utils/helpers.test.ts create mode 100644 packages/deploymentUtils/package.json create mode 100644 packages/deploymentUtils/src/config/index.ts create mode 100644 packages/deploymentUtils/src/index.ts create mode 100644 packages/deploymentUtils/src/specifications/deleteProxygenDeployments.ts create mode 100644 packages/deploymentUtils/src/specifications/deployApi.ts create mode 100644 packages/deploymentUtils/src/specifications/fixSpec.ts create mode 100644 packages/deploymentUtils/src/specifications/invokeLambda.ts create mode 100644 packages/deploymentUtils/src/specifications/writeSchemas.ts create mode 100644 packages/deploymentUtils/tests/config/index.test.ts create mode 100644 packages/deploymentUtils/tests/specifications/deleteProxygenDeployments.test.ts create mode 100644 packages/deploymentUtils/tests/specifications/deployApi.test.ts create mode 100644 packages/deploymentUtils/tests/specifications/fixSpec.test.ts create mode 100644 packages/deploymentUtils/tests/specifications/writeSchemas.test.ts create mode 100644 packages/deploymentUtils/tsconfig.json create mode 100644 packages/deploymentUtils/vitest.config.ts delete mode 100755 scripts/check_python_licenses.sh create mode 100644 trivy.yaml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 109b0ba0..a1c28bc3 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,70 +1,16 @@ -FROM mcr.microsoft.com/devcontainers/base:ubuntu - -ARG TARGETARCH -ENV TARGETARCH=${TARGETARCH} - -ARG ASDF_VERSION -COPY .tool-versions.asdf /tmp/.tool-versions.asdf - -RUN apt-get update \ - && export DEBIAN_FRONTEND=noninteractive \ - && apt-get -y dist-upgrade \ - && apt-get -y install --no-install-recommends htop vim curl git build-essential \ - libffi-dev libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev libbz2-dev \ - zlib1g-dev unixodbc unixodbc-dev libsecret-1-0 libsecret-1-dev libsqlite3-dev \ - jq apt-transport-https ca-certificates gnupg-agent \ - software-properties-common bash-completion python3-pip make libbz2-dev \ - libreadline-dev libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev \ - xz-utils tk-dev liblzma-dev libyaml-dev - - -# Download correct AWS CLI for arch -RUN if [ "$TARGETARCH" = "arm64" ] || [ "$TARGETARCH" == "aarch64" ]; then \ - wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"; \ +ARG IMAGE_NAME=node_24_python_3_14 +ARG IMAGE_VERSION=latest +FROM ghcr.io/nhsdigital/eps-devcontainers/${IMAGE_NAME}:${IMAGE_VERSION} + +USER root +# specify DOCKER_GID to force container docker group id to match host +RUN if [ -n "${DOCKER_GID}" ]; then \ + if ! getent group docker; then \ + groupadd -g ${DOCKER_GID} docker; \ else \ - wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"; \ + groupmod -g ${DOCKER_GID} docker; \ fi && \ - unzip /tmp/awscliv2.zip -d /tmp/aws-cli && \ - /tmp/aws-cli/aws/install && \ - rm /tmp/awscliv2.zip && rm -rf /tmp/aws-cli - -# Download correct SAM CLI for arch -RUN if [ "$TARGETARCH" = "arm64" ] || [ "$TARGETARCH" == "aarch64" ]; then \ - wget -O /tmp/aws-sam-cli.zip "https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-arm64.zip"; \ - else \ - wget -O /tmp/aws-sam-cli.zip "https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip"; \ - fi && \ - unzip /tmp/aws-sam-cli.zip -d /tmp/aws-sam-cli && \ - /tmp/aws-sam-cli/install && \ - rm /tmp/aws-sam-cli.zip && rm -rf /tmp/aws-sam-cli - -# Install ASDF -RUN ASDF_VERSION=$(awk '!/^#/ && NF {print $1; exit}' /tmp/.tool-versions.asdf) && \ - if [ "$TARGETARCH" = "arm64" ] || [ "$TARGETARCH" = "aarch64" ]; then \ - wget -O /tmp/asdf.tar.gz https://github.com/asdf-vm/asdf/releases/download/v${ASDF_VERSION}/asdf-v${ASDF_VERSION}-linux-arm64.tar.gz; \ - else \ - wget -O /tmp/asdf.tar.gz https://github.com/asdf-vm/asdf/releases/download/v${ASDF_VERSION}/asdf-v${ASDF_VERSION}-linux-amd64.tar.gz; \ - fi && \ - tar -xvzf /tmp/asdf.tar.gz && \ - mv asdf /usr/bin - - -USER vscode - -ENV PATH="$PATH:/home/vscode/.asdf/shims/:/workspaces/eps-cdk-utils/node_modules/.bin" - -# Install ASDF plugins -RUN asdf plugin add python; \ - asdf plugin add poetry https://github.com/asdf-community/asdf-poetry.git; \ - asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git; \ - asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git; \ - asdf plugin add direnv; \ - asdf plugin add actionlint; - -WORKDIR /workspaces/eps-workflow-quality-checks - -ADD .tool-versions /workspaces/eps-cdk-utils/.tool-versions -ADD .tool-versions /home/vscode/.tool-versions + usermod -aG docker vscode; \ + fi -RUN asdf install python; \ - asdf install +RUN apt-get update && apt-get install -y --no-install-recommends git-secrets && rm -rf /var/lib/apt/lists/* diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d7b9d102..0ae33661 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,43 +1,49 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu { - "name": "Ubuntu", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "build": { - "dockerfile": "Dockerfile", - "context": "..", - "args": {} - }, - "mounts": [ - "source=${env:HOME}${env:USERPROFILE}/.aws,target=/home/vscode/.aws,type=bind", - "source=${env:HOME}${env:USERPROFILE}/.ssh,target=/home/vscode/.ssh,type=bind", - "source=${env:HOME}${env:USERPROFILE}/.gnupg,target=/home/vscode/.gnupg,type=bind", - "source=${env:HOME}${env:USERPROFILE}/.npmrc,target=/home/vscode/.npmrc,type=bind" - ], - "features": { - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { - "version": "latest", - "moby": "true", - "installDockerBuildx": "true" - } - }, - "remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" }, - "containerUser": "vscode", - "customizations": { - "vscode": { - "extensions": [ - "AmazonWebServices.aws-toolkit-vscode", - "redhat.vscode-yaml", - "eamodio.gitlens", - "github.vscode-pull-request-github", - "streetsidesoftware.code-spell-checker", - "timonwong.shellcheck", - "github.vscode-github-actions" - ], - "settings": { - "cSpell.words": ["fhir", "Formik", "pino", "serialisation"] - } + "name": "eps-cdk-utils", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + "DOCKER_GID": "${env:DOCKER_GID:}", + "IMAGE_NAME": "node_24_python_3_14", + "IMAGE_VERSION": "v1.0.7", + "USER_UID": "${localEnv:USER_ID:}", + "USER_GID": "${localEnv:GROUP_ID:}" + } + }, + "postAttachCommand": "git-secrets --register-aws; git-secrets --add-provider -- cat /usr/share/secrets-scanner/nhsd-rules-deny.txt", + "mounts": [ + "source=${env:HOME}${env:USERPROFILE}/.aws,target=/home/vscode/.aws,type=bind", + "source=${env:HOME}${env:USERPROFILE}/.ssh,target=/home/vscode/.ssh,type=bind", + "source=${env:HOME}${env:USERPROFILE}/.gnupg,target=/home/vscode/.gnupg,type=bind", + "source=${env:HOME}${env:USERPROFILE}/.npmrc,target=/home/vscode/.npmrc,type=bind" + ], + "features": {}, + "remoteEnv": { + "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" + }, + "containerUser": "vscode", + "customizations": { + "vscode": { + "extensions": [ + "AmazonWebServices.aws-toolkit-vscode", + "redhat.vscode-yaml", + "eamodio.gitlens", + "github.vscode-pull-request-github", + "streetsidesoftware.code-spell-checker", + "timonwong.shellcheck", + "github.vscode-github-actions", + "dbaeumer.vscode-eslint", + "vitest.explorer" + ], + "settings": { + "cSpell.words": [ + "fhir", + "Formik", + "pino", + "serialisation" + ] } } } +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 71eb2f5c..34429b77 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,34 +10,34 @@ updates: directory: "/" schedule: interval: "weekly" - day: "friday" + day: "thursday" time: "18:00" #UTC commit-message: prefix: "Upgrade: [dependabot] - " ################################### - # NPM workspace ################## + # Poetry ######################### ################################### - - package-ecosystem: "npm" + - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" - day: "friday" - time: "18:00" #UTC + day: "thursday" + time: "20:00" #UTC versioning-strategy: increase - open-pull-requests-limit: 20 commit-message: prefix: "Upgrade: [dependabot] - " ################################### - # Poetry ######################### + # NPM workspace ################## ################################### - - package-ecosystem: "pip" + - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" - day: "friday" - time: "18:00" #UTC + day: "thursday" + time: "22:00" #UTC versioning-strategy: increase + open-pull-requests-limit: 20 commit-message: prefix: "Upgrade: [dependabot] - " diff --git a/.github/scripts/check_ecr_image_scan_results.sh b/.github/scripts/check_ecr_image_scan_results.sh deleted file mode 100755 index 4a5222ef..00000000 --- a/.github/scripts/check_ecr_image_scan_results.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env bash -set -e - -AWS_MAX_ATTEMPTS=20 -export AWS_MAX_ATTEMPTS - -if [ -z "${REPOSITORY_NAME}" ]; then - echo "REPOSITORY_NAME not set" - exit 1 -fi - -if [ -z "${IMAGE_TAG}" ]; then - echo "IMAGE_TAG not set" - exit 1 -fi - -if [ -z "${AWS_REGION}" ]; then - echo "AWS_REGION not set" - exit 1 -fi - -if [ -z "${ACCOUNT_ID}" ]; then - echo "AWS_REGION not set" - exit 1 -fi - -IMAGE_DIGEST=$(aws ecr describe-images \ - --repository-name "$REPOSITORY_NAME" \ - --image-ids imageTag="$IMAGE_TAG" \ - --query 'imageDetails[0].imageDigest' \ - --output text) - -RESOURCE_ARN="arn:aws:ecr:${AWS_REGION}:${ACCOUNT_ID}:repository/${REPOSITORY_NAME}/${IMAGE_DIGEST}" - -echo "Monitoring scan for ${REPOSITORY_NAME}:${IMAGE_TAG}" -echo "Resource ARN: ${RESOURCE_ARN}" -echo - -# Wait for ECR scan to reach COMPLETE -STATUS="" -echo "Waiting for ECR scan to complete..." -for i in {1..30}; do - echo "Checking scan status. Attempt ${i}" - STATUS=$(aws ecr describe-image-scan-findings \ - --repository-name "$REPOSITORY_NAME" \ - --image-id imageDigest="$IMAGE_DIGEST" \ - --query 'imageScanStatus.status' \ - --output text 2>/dev/null || echo "NONE") - - if [[ "$STATUS" == "COMPLETE" ]]; then - echo "ECR scan completed." - break - fi - - if [[ "$STATUS" == "FAILED" ]]; then - echo "Scan failed." - exit 1 - fi - - echo "SCAN IS NOT YET COMPLETE. Waiting 10 seconds before checking again..." - sleep 10 -done - -if [[ "$STATUS" != "COMPLETE" ]]; then - echo "Timeout waiting for ECR scan to complete." - exit 1 -fi - -# Wait for Inspector2 findings to appear & stabilize -# this is in place as scan may show as complete but findings have not yet stabilize -echo -echo "Waiting for Inspector2 findings to stabilize..." - -PREV_HASH="" -for i in {1..12}; do # ~2 minutes max - FINDINGS=$(aws inspector2 list-findings \ - --filter-criteria "{ - \"resourceId\": [{\"comparison\": \"EQUALS\", \"value\": \"${RESOURCE_ARN}\"}], - \"findingStatus\": [{\"comparison\": \"EQUALS\", \"value\": \"ACTIVE\"}] - }" \ - --output json 2>/dev/null || echo "{}") - - CURR_HASH=$(echo "$FINDINGS" | sha256sum) - COUNT=$(echo "$FINDINGS" | jq '.findings | length') - - if [[ "$COUNT" -gt 0 && "$CURR_HASH" == "$PREV_HASH" ]]; then - echo "Findings stabilized ($COUNT findings)." - break - fi - - PREV_HASH="$CURR_HASH" - echo "Attempt: ${i}. Still waiting... (${COUNT} findings so far)" - sleep 10 -done - -# Extract counts and display findings -echo -echo "Final Inspector2 findings with suppressions removed:" -echo - -echo "$FINDINGS" | jq '{ - findings: [ - .findings[]? | { - severity: .severity, - title: .title, - package: .packageVulnerabilityDetails.vulnerablePackages[0].name, - sourceUrl: .packageVulnerabilityDetails.sourceUrl, - recommendation: (.remediation.recommendation.text // "N/A") - } - ] -}' - -echo - -# Check for critical/high severity -CRITICAL_COUNT=$(echo "$FINDINGS" | jq '[.findings[]? | select(.severity=="CRITICAL")] | length') -HIGH_COUNT=$(echo "$FINDINGS" | jq '[.findings[]? | select(.severity=="HIGH")] | length') - -if (( CRITICAL_COUNT > 0 || HIGH_COUNT > 0 )); then - echo "${CRITICAL_COUNT} CRITICAL and ${HIGH_COUNT} HIGH vulnerabilities detected!" - echo - echo "Critical/High vulnerabilities:" - echo "$FINDINGS" | jq -r ' - .findings[]? | - select(.severity=="CRITICAL" or .severity=="HIGH") |{ - severity: .severity, - title: .title, - package: .packageVulnerabilityDetails.vulnerablePackages[0].name, - sourceUrl: .packageVulnerabilityDetails.sourceUrl, - recommendation: (.remediation.recommendation.text // "N/A") - }' - echo - echo "Failing pipeline due to Critical/High vulnerabilities." - exit 2 -else - echo "No Critical or High vulnerabilities found." - exit 0 -fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97ce0580..a0d2820e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: ref: ${{ env.BRANCH_NAME }} @@ -26,53 +26,44 @@ jobs: # echo "commit_id=${{ github.sha }}" >> "$GITHUB_ENV" echo "commit_id=${{ github.sha }}" >> "$GITHUB_OUTPUT" echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - get_asdf_version: - runs-on: ubuntu-22.04 - outputs: - asdf_version: ${{ steps.asdf-version.outputs.version }} - tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }} - steps: - - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - - - name: Get asdf version - id: asdf-version - run: echo "version=$(awk '!/^#/ && NF {print $1; exit}' .tool-versions.asdf)" >> "$GITHUB_OUTPUT" - - name: Load config value - id: load-config - run: | - TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml) - echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT" + get_config_values: + uses: NHSDigital/eps-common-workflows/.github/workflows/get-repo-config.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e + with: + verify_published_from_main_image: true quality_checks: - uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@3cba6a3733673bafc95526503478674332c26007 - needs: [get_asdf_version, get_commit_id] + uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e + needs: [get_config_values, get_commit_id] with: - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + run_docker_scan: true + docker_images: "eps-cdk-utils" secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} tag_release: - needs: [quality_checks, get_commit_id, get_asdf_version] - uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@3cba6a3733673bafc95526503478674332c26007 + needs: [quality_checks, get_commit_id, get_config_values] + uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release-devcontainer.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e with: dry_run: true - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} branch_name: main - publish_package: true - tag_format: ${{ needs.get_asdf_version.outputs.tag_format }} + publish_packages: packages/cdkConstructs,packages/deploymentUtils + tag_format: ${{ needs.get_config_values.outputs.tag_format }} secrets: inherit package_code: - needs: [tag_release, quality_checks, get_commit_id] + needs: [tag_release, quality_checks, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_build.yml with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} VERSION_NUMBER: pre-release-${{ needs.get_commit_id.outputs.sha_short }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} release_dev: - needs: [tag_release, package_code, get_commit_id] + needs: [tag_release, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_upload.yml with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} AWS_ENVIRONMENT: dev VERSION_NUMBER: pre-release-${{ needs.get_commit_id.outputs.sha_short }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} @@ -82,9 +73,11 @@ jobs: CDK_PUSH_IMAGE_ROLE: ${{ secrets.DEV_CDK_PUSH_IMAGE_ROLE }} release_qa: - needs: [tag_release, release_dev, package_code, get_commit_id] + needs: + [tag_release, release_dev, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_upload.yml with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} AWS_ENVIRONMENT: qa VERSION_NUMBER: pre-release-${{ needs.get_commit_id.outputs.sha_short }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} @@ -94,9 +87,11 @@ jobs: CDK_PUSH_IMAGE_ROLE: ${{ secrets.QA_CDK_PUSH_IMAGE_ROLE }} release_ref: - needs: [tag_release, release_dev, package_code, get_commit_id] + needs: + [tag_release, release_dev, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_upload.yml with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} AWS_ENVIRONMENT: ref VERSION_NUMBER: pre-release-${{ needs.get_commit_id.outputs.sha_short }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} @@ -106,5 +101,7 @@ jobs: CDK_PUSH_IMAGE_ROLE: ${{ secrets.REF_CDK_PUSH_IMAGE_ROLE }} package_npm_code: - needs: [quality_checks, get_commit_id] + needs: [quality_checks, get_commit_id, get_config_values] uses: ./.github/workflows/package_npm_code.yml + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} diff --git a/.github/workflows/docker_image_build.yml b/.github/workflows/docker_image_build.yml index cf735647..5e7aeee7 100644 --- a/.github/workflows/docker_image_build.yml +++ b/.github/workflows/docker_image_build.yml @@ -9,17 +9,29 @@ on: COMMIT_ID: required: true type: string + pinned_image: + type: string + required: true jobs: docker_image_build: runs-on: ubuntu-22.04 + container: + image: ${{ inputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash permissions: id-token: write contents: read packages: read steps: + - name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: ref: ${{ env.BRANCH_NAME }} @@ -31,7 +43,7 @@ jobs: docker build -t "cdk-utils-build:${VERSION_NUMBER}" -f docker/Dockerfile --build-arg VERSION="${VERSION_NUMBER}" . docker save "cdk-utils-build:${VERSION_NUMBER}" -o cdk-utils-build.img - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f name: Upload docker images with: name: docker_artifact diff --git a/.github/workflows/docker_image_upload.yml b/.github/workflows/docker_image_upload.yml index 2b7ab70e..c1692744 100644 --- a/.github/workflows/docker_image_upload.yml +++ b/.github/workflows/docker_image_upload.yml @@ -18,6 +18,9 @@ on: DOCKER_IMAGE_TAG: required: true type: string + pinned_image: + type: string + required: true secrets: CDK_PUSH_IMAGE_ROLE: required: true @@ -25,14 +28,23 @@ on: jobs: upload_docker_image: runs-on: ubuntu-22.04 + container: + image: ${{ inputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash environment: ${{ inputs.AWS_ENVIRONMENT }} permissions: id-token: write contents: write steps: + - name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" - name: Checkout local github actions - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: ref: ${{ env.BRANCH_NAME }} fetch-depth: 0 @@ -40,14 +52,14 @@ jobs: .github - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 with: aws-region: eu-west-2 role-to-assume: ${{ secrets.CDK_PUSH_IMAGE_ROLE }} role-session-name: upload-cdk-utils-build - name: docker_artifact download - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: name: docker_artifact path: . @@ -81,11 +93,3 @@ jobs: run: | docker tag "cdk-utils-build:${VERSION_NUMBER}" "${ACCOUNT_ID}.dkr.ecr.eu-west-2.amazonaws.com/cdk-utils-build-repo:latest" docker push "${ACCOUNT_ID}.dkr.ecr.eu-west-2.amazonaws.com/cdk-utils-build-repo:latest" - - - name: Check cdk-utils-build scan results - env: - REPOSITORY_NAME: cdk-utils-build-repo - IMAGE_TAG: ${{ inputs.DOCKER_IMAGE_TAG }} - working-directory: .github/scripts - run: | - ./check_ecr_image_scan_results.sh diff --git a/.github/workflows/package_npm_code.yml b/.github/workflows/package_npm_code.yml index ea0afe8f..dd008c52 100644 --- a/.github/workflows/package_npm_code.yml +++ b/.github/workflows/package_npm_code.yml @@ -2,56 +2,29 @@ name: docker image build on: workflow_call: + inputs: + pinned_image: + type: string + required: true jobs: - get_asdf_version: - runs-on: ubuntu-22.04 - outputs: - asdf_version: ${{ steps.asdf-version.outputs.version }} - tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }} - steps: - - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - - - name: Get asdf version - id: asdf-version - run: echo "version=$(awk '!/^#/ && NF {print $1; exit}' .tool-versions.asdf)" >> "$GITHUB_OUTPUT" - - name: Load config value - id: load-config - run: | - TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml) - echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT" package_npm_code: runs-on: ubuntu-22.04 - needs: [get_asdf_version] + container: + image: ${{ inputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash steps: + - name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: ref: ${{ env.BRANCH_NAME }} - # using git commit sha for version of action to ensure we have stable version - - name: Install asdf - uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 - with: - asdf_version: ${{ needs.get_asdf_version.outputs.asdf_version }} - - - name: Cache asdf - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 - with: - path: | - ~/.asdf - key: ${{ runner.os }}-asdf-${{ hashFiles('**/.tool-versions') }} - restore-keys: | - ${{ runner.os }}-asdf-${{ hashFiles('**/.tool-versions') }} - - - name: Install asdf dependencies in .tool-versions - uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 - with: - asdf_version: ${{ needs.get_asdf_version.outputs.asdf_version }} - env: - PYTHON_CONFIGURE_OPTS: --enable-shared - - name: Install dependencies run: | make install @@ -60,7 +33,7 @@ jobs: run: | make package - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f name: Upload packaged code with: name: nhsdigital-eps-cdk-constructs-1.0.0.tgz diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 3e70316f..ad399820 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,36 +8,26 @@ env: BRANCH_NAME: ${{ github.event.pull_request.head.ref }} jobs: + get_config_values: + uses: NHSDigital/eps-common-workflows/.github/workflows/get-repo-config.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e + with: + verify_published_from_main_image: false dependabot-auto-approve-and-merge: needs: quality_checks - uses: NHSDigital/eps-common-workflows/.github/workflows/dependabot-auto-approve-and-merge.yml@3cba6a3733673bafc95526503478674332c26007 + uses: NHSDigital/eps-common-workflows/.github/workflows/dependabot-auto-approve-and-merge.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e secrets: AUTOMERGE_APP_ID: ${{ secrets.AUTOMERGE_APP_ID }} AUTOMERGE_PEM: ${{ secrets.AUTOMERGE_PEM }} - get_asdf_version: - runs-on: ubuntu-22.04 - outputs: - asdf_version: ${{ steps.asdf-version.outputs.version }} - tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }} - steps: - - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - - - name: Get asdf version - id: asdf-version - run: echo "version=$(awk '!/^#/ && NF {print $1; exit}' .tool-versions.asdf)" >> "$GITHUB_OUTPUT" - - name: Load config value - id: load-config - run: | - TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml) - echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT" pr_title_format_check: - uses: NHSDigital/eps-common-workflows/.github/workflows/pr_title_check.yml@3cba6a3733673bafc95526503478674332c26007 + uses: NHSDigital/eps-common-workflows/.github/workflows/pr_title_check.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e quality_checks: - uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@3cba6a3733673bafc95526503478674332c26007 - needs: [get_asdf_version, get_commit_id] + uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e + needs: [get_config_values, get_commit_id] with: - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + run_docker_scan: true + docker_images: "eps-cdk-utils" + secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} @@ -74,7 +64,7 @@ jobs: sha_short: ${{ steps.commit_id.outputs.sha_short }} steps: - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: ref: ${{ env.BRANCH_NAME }} @@ -85,18 +75,22 @@ jobs: echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" package_docker_image: - needs: [get_issue_number, quality_checks, get_commit_id] + needs: [get_issue_number, quality_checks, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_build.yml with: VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} package_npm_code: - needs: [quality_checks, get_commit_id] + needs: [quality_checks, get_commit_id, get_config_values] uses: ./.github/workflows/package_npm_code.yml + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} release_docker_image: - needs: [get_issue_number, package_docker_image, get_commit_id] + needs: + [get_issue_number, package_docker_image, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_upload.yml with: AWS_ENVIRONMENT: dev @@ -104,16 +98,17 @@ jobs: COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} TAG_LATEST: false DOCKER_IMAGE_TAG: PR-${{ needs.get_issue_number.outputs.issue_number }}-${{ needs.get_commit_id.outputs.sha_short }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} secrets: CDK_PUSH_IMAGE_ROLE: ${{ secrets.DEV_CDK_PUSH_IMAGE_ROLE }} tag_release: - needs: [get_commit_id, get_asdf_version] - uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@3cba6a3733673bafc95526503478674332c26007 + needs: [get_commit_id, get_config_values] + uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release-devcontainer.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e with: dry_run: true - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} branch_name: ${{ github.event.pull_request.head.ref }} - publish_package: true - tag_format: ${{ needs.get_asdf_version.outputs.tag_format }} + publish_packages: packages/cdkConstructs,packages/deploymentUtils + tag_format: ${{ needs.get_config_values.outputs.tag_format }} secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 53bc9386..cdd80161 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,10 @@ env: BRANCH_NAME: ${{ github.ref_name }} jobs: + get_config_values: + uses: NHSDigital/eps-common-workflows/.github/workflows/get-repo-config.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e + with: + verify_published_from_main_image: true get_commit_id: runs-on: ubuntu-22.04 outputs: @@ -17,7 +21,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: ref: ${{ env.BRANCH_NAME }} @@ -27,53 +31,40 @@ jobs: # echo "commit_id=${{ github.sha }}" >> "$GITHUB_ENV" echo "commit_id=${{ github.sha }}" >> "$GITHUB_OUTPUT" echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - get_asdf_version: - runs-on: ubuntu-22.04 - outputs: - asdf_version: ${{ steps.asdf-version.outputs.version }} - tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }} - steps: - - name: Checkout code - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - - - name: Get asdf version - id: asdf-version - run: echo "version=$(awk '!/^#/ && NF {print $1; exit}' .tool-versions.asdf)" >> "$GITHUB_OUTPUT" - - name: Load config value - id: load-config - run: | - TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml) - echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT" quality_checks: - uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@3cba6a3733673bafc95526503478674332c26007 - needs: [get_asdf_version, get_commit_id] + uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e + needs: [get_config_values, get_commit_id] with: - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + run_docker_scan: true + docker_images: "eps-cdk-utils" secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} tag_release: - needs: [quality_checks, get_commit_id, get_asdf_version] - uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@3cba6a3733673bafc95526503478674332c26007 + needs: [quality_checks, get_commit_id, get_config_values] + uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release-devcontainer.yml@383f3f9eaf3cb553ebcd74897bfed4d5e387629e with: dry_run: false - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} branch_name: main - publish_package: true - tag_format: ${{ needs.get_asdf_version.outputs.tag_format }} + publish_packages: packages/cdkConstructs,packages/deploymentUtils + tag_format: ${{ needs.get_config_values.outputs.tag_format }} secrets: inherit package_code: - needs: [tag_release, quality_checks, get_commit_id] + needs: [tag_release, quality_checks, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_build.yml with: VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} release_dev: - needs: [tag_release, package_code, get_commit_id] + needs: [tag_release, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_upload.yml with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} AWS_ENVIRONMENT: dev VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} @@ -83,9 +74,11 @@ jobs: CDK_PUSH_IMAGE_ROLE: ${{ secrets.DEV_CDK_PUSH_IMAGE_ROLE }} release_qa: - needs: [tag_release, release_dev, package_code, get_commit_id] + needs: + [tag_release, release_dev, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_upload.yml with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} AWS_ENVIRONMENT: qa VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} @@ -95,9 +88,11 @@ jobs: CDK_PUSH_IMAGE_ROLE: ${{ secrets.QA_CDK_PUSH_IMAGE_ROLE }} release_ref: - needs: [tag_release, release_dev, package_code, get_commit_id] + needs: + [tag_release, release_dev, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_upload.yml with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} AWS_ENVIRONMENT: ref VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} @@ -107,9 +102,11 @@ jobs: CDK_PUSH_IMAGE_ROLE: ${{ secrets.REF_CDK_PUSH_IMAGE_ROLE }} release_int: - needs: [tag_release, release_qa, package_code, get_commit_id] + needs: + [tag_release, release_qa, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_upload.yml with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} AWS_ENVIRONMENT: int VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} @@ -119,9 +116,11 @@ jobs: CDK_PUSH_IMAGE_ROLE: ${{ secrets.INT_CDK_PUSH_IMAGE_ROLE }} release_prod: - needs: [tag_release, release_int, package_code, get_commit_id] + needs: + [tag_release, release_int, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/docker_image_upload.yml with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} AWS_ENVIRONMENT: prod VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} diff --git a/.gitignore b/.gitignore index be8b69f6..9cab097d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,6 @@ _site/ .sass-cache .jekyll-cache .jekyll-metadata -vendor \ No newline at end of file +vendor +.trivy_out/ +*.tgz diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1f923e8a..d4d23aeb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v6.0.0 hooks: - id: check-merge-conflict name: Check for merge conflict strings @@ -26,7 +26,7 @@ repos: - id: lint-githubactions name: Lint github actions entry: make - args: ["lint-githubactions"] + args: ["actionlint"] language: system files: ^.github types_or: [yaml] @@ -35,7 +35,7 @@ repos: - id: lint-githubaction-scripts name: Lint github action scripts entry: make - args: ["lint-githubaction-scripts"] + args: ["shellcheck"] language: system files: ^.github/scripts types_or: [sh, shell] @@ -50,5 +50,25 @@ repos: types_or: [ts, tsx, javascript, jsx, json] pass_filenames: false + - id: lint-deploymentUtils + name: Lint deploymentUtils + entry: npm + args: ["run", "--prefix=packages/deploymentUtils", "lint"] + language: system + files: ^packages\/deploymentUtils + types_or: [ts, tsx, javascript, jsx, json] + pass_filenames: false + + - repo: local + hooks: + - id: git-secrets + name: Git Secrets + description: git-secrets scans commits, commit messages, and --no-ff merges to prevent adding secrets into your git repositories. + entry: bash + args: + - -c + - "git-secrets --pre_commit_hook" + language: system + fail_fast: true default_stages: [pre-commit] diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 6905ea00..00000000 --- a/.tool-versions +++ /dev/null @@ -1,5 +0,0 @@ -nodejs 24.11.1 -python 3.12.7 -poetry 1.8.3 -shellcheck 0.10.0 -actionlint 1.7.3 diff --git a/.tool-versions.asdf b/.tool-versions.asdf deleted file mode 100644 index 4921076f..00000000 --- a/.tool-versions.asdf +++ /dev/null @@ -1,2 +0,0 @@ -# define the .asdf-version to use here -0.18.0 diff --git a/.trivyignore.yaml b/.trivyignore.yaml new file mode 100644 index 00000000..34f78521 --- /dev/null +++ b/.trivyignore.yaml @@ -0,0 +1,82 @@ +vulnerabilities: + - id: CVE-2025-64756 + statement: downstream dependency for glob - waiting for new npm release + expired_at: 2026-06-01 + - id: CVE-2026-23745 + statement: downstream dependency for tar - waiting for new npm release + expired_at: 2026-06-01 + - id: CVE-2026-23950 + statement: downstream dependency for tar - waiting for new npm release + expired_at: 2026-06-01 + - id: CVE-2026-24842 + statement: downstream dependency for tar - waiting for new npm release + expired_at: 2026-06-01 + - id: CVE-2026-25547 + statement: downstream dependency for @isaacs/brace-expansion - waiting for new npm release + expired_at: 2026-06-01 + - id: CVE-2026-0775 + statement: downstream dependency for npmcli - waiting for new npm release + expired_at: 2026-06-01 + - id: CVE-2026-24049 + statement: downstream dependency for wheel - waiting for new python release + expired_at: 2026-06-01 + - id: CVE-2025-47907 + statement: downstream dependency for asdf/go - waiting for new asdf release + expired_at: 2026-06-01 + - id: CVE-2025-58183 + statement: downstream dependency for asdf/go - waiting for new asdf release + expired_at: 2026-06-01 + - id: CVE-2025-61729 + statement: downstream dependency for asdf/go - waiting for new asdf release + expired_at: 2026-06-01 + - id: CVE-2025-61726 + statement: downstream dependency for asdf/go - waiting for new asdf release + expired_at: 2026-06-01 + - id: CVE-2025-61728 + statement: downstream dependency for asdf/go - waiting for new asdf release + expired_at: 2026-06-01 + - id: CVE-2025-61730 + statement: downstream dependency for asdf/go - waiting for new asdf release + expired_at: 2026-06-01 + - id: CVE-2025-68121 + statement: downstream dependency for asdf/go - waiting for new asdf release + expired_at: 2026-06-01 + - id: CVE-2026-26278 + statement: fast-xml-parser node module + expired_at: 2026-06-01 + - id: CVE-2026-26996 + statement: minimatch node module + expired_at: 2026-06-01 + - id: CVE-2026-26960 + statement: tar node module + expired_at: 2026-06-01 + - id: CVE-2026-27903 + statement: Minimatch subdependency needed for aws-cdk-lib and jest. + expired_at: 2026-06-01 + - id: CVE-2026-27904 + statement: Minimatch subdependency needed for aws-cdk-lib and jest. + expired_at: 2026-06-01 + - id: CVE-2026-27606 + statement: Minimatch subdependency needed for aws-cdk-lib and jest. + expired_at: 2026-06-01 + - id: CVE-2026-29786 + statement: node-tar requrired dependency, and not a relelveant attack vector + expired_at: 2026-06-01 + - id: CVE-2026-31802 + statement: node-tar requrired dependency, and not a relelveant attack vector + expired_at: 2026-06-01 + - id: CVE-2026-25679 + statement: asdf go stdlib + expired_at: 2026-06-01 + - id: CVE-2026-27142 + statement: asdf go stdlib + expired_at: 2026-06-01 + - id: CVE-2026-27142 + statement: asdf go stdlib + expired_at: 2026-06-01 + - id: CVE-2026-32141 + statement: flatted + expired_at: 2026-06-01 + - id: CVE-2026-33036 + statement: fast-xml-parser vulnerability accepted as risk - dependency of aws-sdk/client-dynamodb and redocly + expired_at: 2026-04-01 diff --git a/.vscode/eps-cdk-utils.code-workspace b/.vscode/eps-cdk-utils.code-workspace index 266a94c9..2657005e 100644 --- a/.vscode/eps-cdk-utils.code-workspace +++ b/.vscode/eps-cdk-utils.code-workspace @@ -7,13 +7,17 @@ { "name": "packages/cdkConstructs", "path": "../packages/cdkConstructs" + }, + { + "name": "packages/deploymentUtils", + "path": "../packages/deploymentUtils" } ], "settings": { "files.exclude": { - "packages/": true, + "packages/": true, }, - "cSpell.words": [ + "cSpell.words": [ "apigw", "ASID", "AWSKMS", @@ -49,6 +53,7 @@ "pollable", "powertools", "Prosthetist", + "proxygen", "querystring", "reingest", "reingested", @@ -74,7 +79,10 @@ ".vscode" ], "eslint.useFlatConfig": true, - "eslint.format.enable": true + "eslint.format.enable": true, + "[typescript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + } }, "extensions": { "recommendations": [ diff --git a/LICENSE b/LICENSE index 0ba95e85..3fc33ae4 100644 --- a/LICENSE +++ b/LICENSE @@ -19,3 +19,207 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile index e05a6d75..892ce1c6 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,4 @@ -guard-%: - @ if [ "${${*}}" = "" ]; then \ - echo "Environment variable $* not set"; \ - exit 1; \ - fi - -.PHONY: install build test publish release clean +.PHONY: install build test publish release clean lint install: install-python install-hooks install-node @@ -17,48 +11,37 @@ install-python: install-hooks: install-python poetry run pre-commit install --install-hooks --overwrite -lint-node: +lint: npm run lint npm run lint --workspace packages/cdkConstructs - -lint-githubactions: - actionlint - -lint-githubaction-scripts: - shellcheck .github/scripts/*.sh - -lint: lint-node lint-githubactions lint-githubaction-scripts + npm run lint --workspace packages/deploymentUtils clean: rm -rf packages/cdkConstructs/lib rm -rf packages/cdkConstructs/coverage + rm -rf packages/deploymentUtils/lib + rm -rf packages/deploymentUtils/coverage rm -rf lib deep-clean: clean rm -rf .venv find . -name 'node_modules' -type d -prune -exec rm -rf '{}' + -check-licenses: check-licenses-node check-licenses-python - -check-licenses-node: - npm run check-licenses - npm run check-licenses --workspace packages/cdkConstructs - -check-licenses-python: - scripts/check_python_licenses.sh - -aws-configure: - aws configure sso --region eu-west-2 - -aws-login: - aws sso login --sso-session sso-session - test: clean npm run test --workspace packages/cdkConstructs + npm run test --workspace packages/deploymentUtils package: build mkdir -p lib/ npm pack --workspace packages/cdkConstructs --pack-destination lib/ + npm pack --workspace packages/deploymentUtils --pack-destination lib/ build: npm run build --workspace packages/cdkConstructs + npm run build --workspace packages/deploymentUtils + +docker-build: + docker build -t eps-cdk-utils . -f docker/Dockerfile + +%: + @$(MAKE) -f /usr/local/share/eps/Mk/common.mk $@ diff --git a/README.md b/README.md index df872152..de751749 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ ![Build](https://github.com/NHSDigital/eps-cdk-utils/workflows/release/badge.svg?branch=main) -This repository contains a docker image used to deploy CDK to our environments and a CDK constructs library for common EPS project patterns. +This repository contains a docker image used to deploy CDK to our environments and a CDK constructs library for common EPS project patterns, plus shared deployment utilities. - `docker/` Contains Dockerfile used to build image used fo for CDK deployments -- `packages/cdkConstructs/` Contains common CDK constructs used in EPS projects +- `packages/cdkConstructs/` Contains common CDK constructs and CDK helpers used in EPS projects +- `packages/deploymentUtils/` Contains shared code for standardising OpenAPI specifications and performing Proxygen-based deployments - `scripts/` Utilities helpful to developers of this specification - `.github/` Contains GitHub workflows that are used for building and deploying from pull requests and releases @@ -16,13 +17,117 @@ The release workflow does the following - creates a new version of the cdk construct library and publishes it to github - pushes the cdk-utils docker image to dev and all other environments (subject to manual release approval in github actions) -## CDK Constructs +## CDK Constructs (`packages/cdkConstructs`) -This contains common CDK constructs used in EPS projects. +This contains common CDK constructs and helpers used in EPS projects. -Available constructs are: +Available constructs and helpers include: -- `TypescriptLambdaFunction` - A reusable construct for TypeScript Lambda functions +- `TypescriptLambdaFunction` – A reusable construct for TypeScript Lambda functions +- `createApp` – Helper for creating a CDK `App` pre-configured with standard EPS tags and stack props +- `deleteUnusedStacks` – Helper functions for cleaning up superseded or PR-based CloudFormation stacks and their Route 53 records +- `checkDestructiveChangeSet` – Describes a CloudFormation change set, filters out replacements and removals (optionally applying time-bound waivers) and throws if anything destructive remains. + +### CDK app bootstrap (`createApp`) + +The helper in [packages/cdkConstructs/src/apps/createApp.ts](packages/cdkConstructs/src/apps/createApp.ts) creates a CDK `App` and applies the standard NHS EPS tagging and configuration. + +Usage example: + +```ts +import {createApp} from "@NHSDigital/eps-cdk-constructs" + +const {app, props} = createApp({ + productName: "Electronic Prescription Service", + appName: "eps-api", + repoName: "eps-cdk-utils", + driftDetectionGroup: "eps-api" +}) + +// Use `app` and `props` when defining stacks +``` + +`createApp` reads deployment metadata from environment variables such as `versionNumber`, `commitId`, `environment` and `isPullRequest`, and exposes them via the returned `props` for use when defining stacks. + +### Stack cleanup helpers (`deleteUnusedStacks`) + +The helpers in [packages/cdkConstructs/src/stacks/deleteUnusedStacks.ts](packages/cdkConstructs/src/stacks/deleteUnusedStacks.ts) are used to clean up old CloudFormation stacks and their DNS records: + +- `deleteUnusedMainStacks(baseStackName, getActiveVersions, hostedZoneName?)` – deletes superseded main and sandbox stacks once the active version has been deployed for at least 24 hours, and removes matching CNAME records from Route 53. +- `deleteUnusedPrStacks(baseStackName, repoName, hostedZoneName?)` – deletes stacks created for pull requests whose GitHub PRs have been closed, and cleans up their CNAME records. + +These functions are designed to be invoked from scheduled jobs (for example, a nightly cleanup workflow) after deployment. They rely on: + +- APIM status endpoints to determine the active API versions (via `getActiveApiVersions`). +- GitHub’s API to determine whether PRs are closed. +- Route 53 APIs to enumerate and delete CNAME records associated with the stacks. + +Refer to [packages/cdkConstructs/tests/stacks/deleteUnusedStacks.test.ts](packages/cdkConstructs/tests/stacks/deleteUnusedStacks.test.ts) for example scenarios. + +### Check destructive change sets +This is used for stateful stack deployments where we want to make sure we do not automatically deploy potentially destructive changes. +In a CI pipeline for stateful stacks, we should create a changeset initially, then pass the changeset details to checkDestructiveChangeSet, and an optional array of short-lived waivers, for example: + +```ts +import {checkDestructiveChangeSet} from "@nhsdigital/eps-cdk-constructs" + +await checkDestructiveChangeSet( + process.env.CDK_CHANGE_SET_NAME, + process.env.STACK_NAME, + process.env.AWS_REGION, + [ + { + LogicalResourceId: "MyAlarm", + PhysicalResourceId: "monitoring-alarm", + ResourceType: "AWS::CloudWatch::Alarm", + StackName: "monitoring", + ExpiryDate: "2026-03-01T00:00:00Z", + AllowedReason: "Pending rename rollout" + } + ] +) +``` + +Each waiver is effective only when the stack name, logical ID, physical ID, and resource type all match and the waiver’s `ExpiryDate` is later than the change set’s `CreationTime`. When no destructive changes remain, the helper logs a confirmation message; otherwise it prints the problematic resources and throws. + + +## Deployment utilities (`packages/deploymentUtils`) + +The [packages/deploymentUtils](packages/deploymentUtils) package contains utilities for working with OpenAPI specifications and Proxygen-based API deployments. + +It exposes the following main entry points via [packages/deploymentUtils/src/index.ts](packages/deploymentUtils/src/index.ts): + +- `deployApi` – Normalises an OpenAPI specification and deploys it via Proxygen Lambda functions, optionally performing blue/green deployments and publishing documentation to the appropriate catalogue. +- `writeSchemas` – Writes JSON Schemas to disk, collapsing `examples` arrays into a single `example` value to be compatible with OAS. +- `deleteProxygenDeployments` – Removes Proxygen PTL instances that correspond to closed GitHub pull requests for a given API. +- Config helpers from `config/index` – used to resolve configuration and CloudFormation export values. + +Typical usage pattern (pseudo-code): + +```ts +import {deployApi} from "@NHSDigital/eps-deployment-utils" + +await deployApi({ + spec, + apiName: "eps-api", + version: "v1.2.3", + apigeeEnvironment: "int", + isPullRequest: false, + awsEnvironment: "dev", + stackName: "eps-api-v1-2-3", + mtlsSecretName: "eps-api-mtls", + clientCertExportName: "ClientCertArn", + clientPrivateKeyExportName: "ClientPrivateKeyArn", + proxygenPrivateKeyExportName: "ProxygenPrivateKeyArn", + proxygenKid: "kid-123", + hiddenPaths: ["/internal-only"] +}, +true, // blueGreen +false // dryRun +) +``` + +See the source files under [packages/deploymentUtils/src/specifications](packages/deploymentUtils/src/specifications) and their tests in [packages/deploymentUtils/tests](packages/deploymentUtils/tests) for fuller examples and expected behaviours. ## Contributing @@ -35,7 +140,11 @@ This code is dual licensed under the MIT license and the OGL (Open Government License). Any new work added to this repository must conform to the conditions of these licenses. In particular this means that this project may not depend on GPL-licensed or AGPL-licensed libraries, as these would violate the terms of those -libraries' licenses. +libraries' licenses. + +These files derive from https://github.com/cdklabs/cdk-nag and remain under Apache 2.0 licence +`packages/cdkConstructs/src/nag/rules/APIGWStructuredLogging.ts` +`packages/cdkConstructs/tests/nag/ApiGWStructuredLogging.test.ts` The contents of this repository are protected by Crown Copyright (C). @@ -48,25 +157,35 @@ See [https://code.visualstudio.com/docs/devcontainers/containers](https://code.v All commits must be made using [signed commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). -### Testing changes to construct library -To test changes to the construct library, you need to package the library and install it on the project you want to test it on. +### Testing changes to construct or deploymentUtils libraries +To test changes to the construct library or the deploymentUtils package, you need to package the relevant library and install it into the project you want to test it in. Either - - run `make package` from this project and copy the .tgz file from lib folder to the project you want to test in - - create a pull request and from the pull request workflow run, download nhsdigital-eps-cdk-constructs-1.0.0.tgz to the project you want to test in + - run `make package` from this project and copy the generated `.tgz` file(s) from the lib folder to the project you want to test in + - create a pull request and from the pull request workflow run, download the generated `.tgz` artifact(s) (for example `nhsdigital-eps-cdk-constructs-1.0.0.tgz` and/or `nhsdigital-eps-deployment-utils-1.0.0.tgz`) to the project you want to test in - In the project you want to test in, run the following +In the project you want to test in, run the following as appropriate: ```bash -npm install --save NHSDigital-eps-cdk-constructs-1.0.0.tgz --workspace packages/cdk/ +# Install the CDK constructs library +npm install --save nhsdigital-eps-cdk-constructs-1.0.0.tgz --workspace packages/cdk/ + +# Install the deploymentUtils library +npm install --save nhsdigital-eps-deployment-utils-1.0.0.tgz --workspace packages/specifications/ ``` -You will then be able to use it - for example: +You will then be able to use them - for example: ```typescript import {TypescriptLambdaFunction} from "@NHSDigital/eps-cdk-constructs" ``` +or + +```typescript +import {deployApi} from "@nhsdigital/eps-deployment-utils" +``` + ### Make Commands There are `make` commands that are run as part of the CI pipeline and help alias some diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 00000000..a0515360 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,7 @@ +The following files are licenced under Apache 2.0 and have been copied and modified with minor fixes from https://github.com/cdklabs/cdk-nag. + +`packages/cdkConstructs/src/nag/rules/APIGWStructuredLogging.ts` +`packages/cdkConstructs/tests/nag/ApiGWStructuredLogging.test.ts` + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 diff --git a/docker/Dockerfile b/docker/Dockerfile index e8e2394c..d5d838cc 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,8 +3,6 @@ FROM ubuntu:24.04 ARG TARGETARCH ENV TARGETARCH=${TARGETARCH} -ARG ASDF_VERSION -COPY .tool-versions.asdf /tmp/.tool-versions.asdf ARG VERSION @@ -17,20 +15,20 @@ RUN apt-get update \ # install aws stuff # Download correct AWS CLI for arch RUN if [ "$TARGETARCH" = "arm64" ] || [ "$TARGETARCH" == "aarch64" ]; then \ - wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"; \ + wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"; \ else \ - wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"; \ + wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"; \ fi && \ unzip /tmp/awscliv2.zip -d /tmp/aws-cli && \ /tmp/aws-cli/aws/install && \ rm /tmp/awscliv2.zip && rm -rf /tmp/aws-cli # Install ASDF -RUN ASDF_VERSION=$(awk '!/^#/ && NF {print $1; exit}' /tmp/.tool-versions.asdf) && \ +RUN ASDF_VERSION=0.18.0 && \ if [ "$TARGETARCH" = "arm64" ] || [ "$TARGETARCH" = "aarch64" ]; then \ - wget -O /tmp/asdf.tar.gz https://github.com/asdf-vm/asdf/releases/download/v${ASDF_VERSION}/asdf-v${ASDF_VERSION}-linux-arm64.tar.gz; \ + wget -O /tmp/asdf.tar.gz https://github.com/asdf-vm/asdf/releases/download/v${ASDF_VERSION}/asdf-v${ASDF_VERSION}-linux-arm64.tar.gz; \ else \ - wget -O /tmp/asdf.tar.gz https://github.com/asdf-vm/asdf/releases/download/v${ASDF_VERSION}/asdf-v${ASDF_VERSION}-linux-amd64.tar.gz; \ + wget -O /tmp/asdf.tar.gz https://github.com/asdf-vm/asdf/releases/download/v${ASDF_VERSION}/asdf-v${ASDF_VERSION}-linux-amd64.tar.gz; \ fi && \ tar -xvzf /tmp/asdf.tar.gz && \ mv asdf /usr/bin @@ -45,7 +43,7 @@ ENV PATH="$PATH:/home/cdkuser/.asdf/shims/:/home/cdkuser/node_modules/.bin" # Install ASDF plugins RUN asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git # install some common node versions that are used in builds to speed things up -RUN asdf install nodejs 22.20.0; +RUN asdf install nodejs 24.13.0 # copy files needed for deployment COPY --chown=cdkuser docker/entrypoint.sh /home/cdkuser/ diff --git a/package-lock.json b/package-lock.json index 4c93af86..2a354ba2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,55 +1,54 @@ { - "name": "@nhsdigital/eps-cdk-constructs", - "version": "1.0.0", + "name": "eps-cdk-utils", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@nhsdigital/eps-cdk-constructs", - "version": "1.0.0", "license": "MIT", "workspaces": [ - "packages/cdkConstructs" + "packages/cdkConstructs", + "packages/deploymentUtils" ], "dependencies": { - "aws-cdk": "^2.1100.1", - "aws-cdk-lib": "^2.233.0", + "aws-cdk": "^2.1111.0", + "aws-cdk-lib": "^2.243.0", "cdk-nag": "^2.37.52", - "constructs": "^10.4.4", - "esbuild": "^0.27.0" + "constructs": "^10.5.1", + "esbuild": "^0.27.4" }, "devDependencies": { - "@types/node": "^25.0.3", - "@typescript-eslint/eslint-plugin": "^8.48.0", - "@typescript-eslint/parser": "^8.50.1", - "@vitest/coverage-v8": "^4.0.16", - "eslint": "^9.39.2", - "eslint-plugin-import-newlines": "^1.4.0", - "jest": "^30.2.0", + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.0", + "@typescript-eslint/eslint-plugin": "^8.57.0", + "@typescript-eslint/parser": "^8.54.0", + "@vitest/coverage-v8": "^4.1.0", + "eslint": "^10.0.3", + "eslint-plugin-import-newlines": "^2.0.0", + "globals": "^17.4.0", + "jest": "^30.3.0", "jest-junit": "^16.0.0", - "license-checker": "^25.0.1", - "ts-jest": "^29.4.5", + "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "typescript": "^5.9.3", "vitest": "^4.0.13" } }, "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.242", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.242.tgz", - "integrity": "sha512-4c1bAy2ISzcdKXYS1k4HYZsNrgiwbiDzj36ybwFVxEWZXVAP0dimQTCaB9fxu7sWzEjw3d+eaw6Fon+QTfTIpQ==", + "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==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.1.tgz", + "integrity": "sha512-We4bmHaowOPHr+IQR4/FyTGjRfjgBj4ICMjtqmJeBDWad3Q/6St12NT07leNtyuukv2qMhtSZJQorD8KpKTwRA==", "license": "Apache-2.0" }, "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "48.20.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-48.20.0.tgz", - "integrity": "sha512-+eeiav9LY4wbF/EFuCt/vfvi/Zoxo8bf94PW5clbMraChEliq83w4TbRVy0jB9jE0v1ooFTtIjSQkowSPkfISg==", + "version": "52.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-52.2.0.tgz", + "integrity": "sha512-ourZjixQ/UfsZc7gdk3vt1eHBODMUjQTYYYCY3ZX8fiXyHtWNDAYZPrXUK96jpCC2fLP+tfHTJrBjZ563pmcEw==", "bundleDependencies": [ "jsonschema", "semver" @@ -57,7 +56,7 @@ "license": "Apache-2.0", "dependencies": { "jsonschema": "~1.4.1", - "semver": "^7.7.2" + "semver": "^7.7.3" }, "engines": { "node": ">= 18.0.0" @@ -72,7 +71,7 @@ } }, "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { - "version": "7.7.2", + "version": "7.7.3", "inBundle": true, "license": "ISC", "bin": { @@ -82,2156 +81,3867 @@ "node": ">=10" } }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/code-frame/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/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "dev": true, - "license": "MIT", - "peer": true, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@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" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node": ">=14.0.0" } }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "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", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.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/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" } }, - "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/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "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", + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=14.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", + "node_modules/@aws-sdk/client-cloudformation": { + "version": "3.1008.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.1008.0.tgz", + "integrity": "sha512-MLt0pBOesb2w39aVmnlZGOz6yX/bNv/MsdpIg4PXmVGR/LIUqsiRD8I4SvNOJEwqXeRnbA/EUqLUcBc+Gi3iqg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/credential-provider-node": "^3.972.20", + "@aws-sdk/middleware-host-header": "^3.972.7", + "@aws-sdk/middleware-logger": "^3.972.7", + "@aws-sdk/middleware-recursion-detection": "^3.972.7", + "@aws-sdk/middleware-user-agent": "^3.972.20", + "@aws-sdk/region-config-resolver": "^3.972.7", + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-endpoints": "^3.996.4", + "@aws-sdk/util-user-agent-browser": "^3.972.7", + "@aws-sdk/util-user-agent-node": "^3.973.6", + "@smithy/config-resolver": "^4.4.10", + "@smithy/core": "^3.23.9", + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/hash-node": "^4.2.11", + "@smithy/invalid-dependency": "^4.2.11", + "@smithy/middleware-content-length": "^4.2.11", + "@smithy/middleware-endpoint": "^4.4.23", + "@smithy/middleware-retry": "^4.4.40", + "@smithy/middleware-serde": "^4.2.12", + "@smithy/middleware-stack": "^4.2.11", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/protocol-http": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.39", + "@smithy/util-defaults-mode-node": "^4.2.42", + "@smithy/util-endpoints": "^3.3.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-retry": "^4.2.11", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-lambda": { + "version": "3.1008.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.1008.0.tgz", + "integrity": "sha512-37d1iqPuw4708f/4wJ4iV47Mb+Y9U5o6Ge1Dp+b7MFKlunIelmHHV0rNkVjnuBuwLfDgMO61CtF74g7bX8jvLA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/credential-provider-node": "^3.972.20", + "@aws-sdk/middleware-host-header": "^3.972.7", + "@aws-sdk/middleware-logger": "^3.972.7", + "@aws-sdk/middleware-recursion-detection": "^3.972.7", + "@aws-sdk/middleware-user-agent": "^3.972.20", + "@aws-sdk/region-config-resolver": "^3.972.7", + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-endpoints": "^3.996.4", + "@aws-sdk/util-user-agent-browser": "^3.972.7", + "@aws-sdk/util-user-agent-node": "^3.973.6", + "@smithy/config-resolver": "^4.4.10", + "@smithy/core": "^3.23.9", + "@smithy/eventstream-serde-browser": "^4.2.11", + "@smithy/eventstream-serde-config-resolver": "^4.3.11", + "@smithy/eventstream-serde-node": "^4.2.11", + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/hash-node": "^4.2.11", + "@smithy/invalid-dependency": "^4.2.11", + "@smithy/middleware-content-length": "^4.2.11", + "@smithy/middleware-endpoint": "^4.4.23", + "@smithy/middleware-retry": "^4.4.40", + "@smithy/middleware-serde": "^4.2.12", + "@smithy/middleware-stack": "^4.2.11", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/protocol-http": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.39", + "@smithy/util-defaults-mode-node": "^4.2.42", + "@smithy/util-endpoints": "^3.3.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-retry": "^4.2.11", + "@smithy/util-stream": "^4.5.17", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-route-53": { + "version": "3.1008.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-route-53/-/client-route-53-3.1008.0.tgz", + "integrity": "sha512-bnsO3l5Cpfk7P0udDcLhe6leo8HIbv8WfqPK6vnKDhzNokPEggpGg/oH0fTptCaMUw9KxD9xJnxTQr1Bwk7B0g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/credential-provider-node": "^3.972.20", + "@aws-sdk/middleware-host-header": "^3.972.7", + "@aws-sdk/middleware-logger": "^3.972.7", + "@aws-sdk/middleware-recursion-detection": "^3.972.7", + "@aws-sdk/middleware-sdk-route53": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.20", + "@aws-sdk/region-config-resolver": "^3.972.7", + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-endpoints": "^3.996.4", + "@aws-sdk/util-user-agent-browser": "^3.972.7", + "@aws-sdk/util-user-agent-node": "^3.973.6", + "@smithy/config-resolver": "^4.4.10", + "@smithy/core": "^3.23.9", + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/hash-node": "^4.2.11", + "@smithy/invalid-dependency": "^4.2.11", + "@smithy/middleware-content-length": "^4.2.11", + "@smithy/middleware-endpoint": "^4.4.23", + "@smithy/middleware-retry": "^4.4.40", + "@smithy/middleware-serde": "^4.2.12", + "@smithy/middleware-stack": "^4.2.11", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/protocol-http": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.39", + "@smithy/util-defaults-mode-node": "^4.2.42", + "@smithy/util-endpoints": "^3.3.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-retry": "^4.2.11", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1008.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1008.0.tgz", + "integrity": "sha512-w/SIRD25v2zVMbkn8CYIxUsac8yf5Jghkhw5j7EsNWdJhl56m/nWpUX7t1etFUW1cnzpFjZV0lXt0dNFSnbXwA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/credential-provider-node": "^3.972.20", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.7", + "@aws-sdk/middleware-expect-continue": "^3.972.7", + "@aws-sdk/middleware-flexible-checksums": "^3.973.5", + "@aws-sdk/middleware-host-header": "^3.972.7", + "@aws-sdk/middleware-location-constraint": "^3.972.7", + "@aws-sdk/middleware-logger": "^3.972.7", + "@aws-sdk/middleware-recursion-detection": "^3.972.7", + "@aws-sdk/middleware-sdk-s3": "^3.972.19", + "@aws-sdk/middleware-ssec": "^3.972.7", + "@aws-sdk/middleware-user-agent": "^3.972.20", + "@aws-sdk/region-config-resolver": "^3.972.7", + "@aws-sdk/signature-v4-multi-region": "^3.996.7", + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-endpoints": "^3.996.4", + "@aws-sdk/util-user-agent-browser": "^3.972.7", + "@aws-sdk/util-user-agent-node": "^3.973.6", + "@smithy/config-resolver": "^4.4.10", + "@smithy/core": "^3.23.9", + "@smithy/eventstream-serde-browser": "^4.2.11", + "@smithy/eventstream-serde-config-resolver": "^4.3.11", + "@smithy/eventstream-serde-node": "^4.2.11", + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/hash-blob-browser": "^4.2.12", + "@smithy/hash-node": "^4.2.11", + "@smithy/hash-stream-node": "^4.2.11", + "@smithy/invalid-dependency": "^4.2.11", + "@smithy/md5-js": "^4.2.11", + "@smithy/middleware-content-length": "^4.2.11", + "@smithy/middleware-endpoint": "^4.4.23", + "@smithy/middleware-retry": "^4.4.40", + "@smithy/middleware-serde": "^4.2.12", + "@smithy/middleware-stack": "^4.2.11", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/protocol-http": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.39", + "@smithy/util-defaults-mode-node": "^4.2.42", + "@smithy/util-endpoints": "^3.3.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-retry": "^4.2.11", + "@smithy/util-stream": "^4.5.17", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.19.tgz", + "integrity": "sha512-56KePyOcZnKTWCd89oJS1G6j3HZ9Kc+bh/8+EbvtaCCXdP6T7O7NzCiPuHRhFLWnzXIaXX3CxAz0nI5My9spHQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/xml-builder": "^3.972.10", + "@smithy/core": "^3.23.9", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/signature-v4": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.4.tgz", + "integrity": "sha512-HKZIZLbRyvzo/bXZU7Zmk6XqU+1C9DjI56xd02vwuDIxedxBEqP17t9ExhbP9QFeNq/a3l9GOcyirFXxmbDhmw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.17.tgz", + "integrity": "sha512-MBAMW6YELzE1SdkOniqr51mrjapQUv8JXSGxtwRjQV0mwVDutVsn22OPAUt4RcLRvdiHQmNBDEFP9iTeSVCOlA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.19.tgz", + "integrity": "sha512-9EJROO8LXll5a7eUFqu48k6BChrtokbmgeMWmsH7lBb6lVbtjslUYz/ShLi+SHkYzTomiGBhmzTW7y+H4BxsnA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/types": "^3.973.5", + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/property-provider": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/util-stream": "^4.5.17", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=20.0.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.19.tgz", + "integrity": "sha512-pVJVjWqVrPqjpFq7o0mCmeZu1Y0c94OCHSYgivdCD2wfmYVtBbwQErakruhgOD8pcMcx9SCqRw1pzHKR7OGBcA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/credential-provider-env": "^3.972.17", + "@aws-sdk/credential-provider-http": "^3.972.19", + "@aws-sdk/credential-provider-login": "^3.972.19", + "@aws-sdk/credential-provider-process": "^3.972.17", + "@aws-sdk/credential-provider-sso": "^3.972.19", + "@aws-sdk/credential-provider-web-identity": "^3.972.19", + "@aws-sdk/nested-clients": "^3.996.9", + "@aws-sdk/types": "^3.973.5", + "@smithy/credential-provider-imds": "^4.2.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.19.tgz", + "integrity": "sha512-jOXdZ1o+CywQKr6gyxgxuUmnGwTTnY2Kxs1PM7fI6AYtDWDnmW/yKXayNqkF8KjP1unflqMWKVbVt5VgmE3L0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/nested-clients": "^3.996.9", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.20.tgz", + "integrity": "sha512-0xHca2BnPY0kzjDYPH7vk8YbfdBPpWVS67rtqQMalYDQUCBYS37cZ55K6TuFxCoIyNZgSCFrVKr9PXC5BVvQQw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.17", + "@aws-sdk/credential-provider-http": "^3.972.19", + "@aws-sdk/credential-provider-ini": "^3.972.19", + "@aws-sdk/credential-provider-process": "^3.972.17", + "@aws-sdk/credential-provider-sso": "^3.972.19", + "@aws-sdk/credential-provider-web-identity": "^3.972.19", + "@aws-sdk/types": "^3.973.5", + "@smithy/credential-provider-imds": "^4.2.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.17.tgz", + "integrity": "sha512-c8G8wT1axpJDgaP3xzcy+q8Y1fTi9A2eIQJvyhQ9xuXrUZhlCfXbC0vM9bM1CUXiZppFQ1p7g0tuUMvil/gCPg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.19.tgz", + "integrity": "sha512-kVjQsEU3b///q7EZGrUzol9wzwJFKbEzqJKSq82A9ShrUTEO7FNylTtby3sPV19ndADZh1H3FB3+5ZrvKtEEeg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/nested-clients": "^3.996.9", + "@aws-sdk/token-providers": "3.1008.0", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.19.tgz", + "integrity": "sha512-BV1BlTFdG4w4tAihxN7iXDBoNcNewXD4q8uZlNQiUrnqxwGWUhKHODIQVSPlQGxXClEj+63m+cqZskw+ESmeZg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/nested-clients": "^3.996.9", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=20.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", + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.7.tgz", + "integrity": "sha512-goX+axlJ6PQlRnzE2bQisZ8wVrlm6dXJfBzMJhd8LhAIBan/w1Kl73fJnalM/S+18VnpzIHumyV6DtgmvqG5IA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.7.tgz", + "integrity": "sha512-mvWqvm61bmZUKmmrtl2uWbokqpenY3Mc3Jf4nXB/Hse6gWxLPaCQThmhPBDzsPSV8/Odn8V6ovWt3pZ7vy4BFQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "^3.973.5", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.973.5.tgz", + "integrity": "sha512-Dp3hqE5W6hG8HQ3Uh+AINx9wjjqYmFHbxede54sGj3akx/haIQrkp85lNdTdC+ouNUcSYNiuGkzmyDREfHX1Gg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/crc64-nvme": "^3.972.4", + "@aws-sdk/types": "^3.973.5", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-stream": "^4.5.17", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.7.tgz", + "integrity": "sha512-aHQZgztBFEpDU1BB00VWCIIm85JjGjQW1OG9+98BdmaOpguJvzmXBGbnAiYcciCd+IS4e9BEq664lhzGnWJHgQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.7.tgz", + "integrity": "sha512-vdK1LJfffBp87Lj0Bw3WdK1rJk9OLDYdQpqoKgmpIZPe+4+HawZ6THTbvjhJt4C4MNnRrHTKHQjkwBiIpDBoig==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "^3.973.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.7.tgz", + "integrity": "sha512-LXhiWlWb26txCU1vcI9PneESSeRp/RYY/McuM4SpdrimQR5NgwaPb4VJCadVeuGWgh6QmqZ6rAKSoL1ob16W6w==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "^3.973.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.7.tgz", + "integrity": "sha512-l2VQdcBcYLzIzykCHtXlbpiVCZ94/xniLIkAj0jpnpjY4xlgZx7f56Ypn+uV1y3gG0tNVytJqo3K9bfMFee7SQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "^3.973.5", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.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", + "node_modules/@aws-sdk/middleware-sdk-route53": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-route53/-/middleware-sdk-route53-3.972.9.tgz", + "integrity": "sha512-Pnrb9/QSbCactUlt5Q7l+n7KzPIDMjn6WOp5/7jeuJQm8bVZWzjRW1+rTFhliglzkFRMcEK2gLxAHhNqGZURsQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/types": "^3.973.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=20.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", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.19.tgz", + "integrity": "sha512-/CtOHHVFg4ZuN6CnLnYkrqWgVEnbOBC4kNiKa+4fldJ9cioDt3dD/f5vpq0cWLOXwmGL2zgVrVxNhjxWpxNMkg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/core": "^3.23.9", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/signature-v4": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-stream": "^4.5.17", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.7.tgz", + "integrity": "sha512-G9clGVuAml7d8DYzY6DnRi7TIIDRvZ3YpqJPz/8wnWS5fYx/FNWNmkO6iJVlVkQg9BfeMzd+bVPtPJOvC4B+nQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=20.0.0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.20.tgz", + "integrity": "sha512-3kNTLtpUdeahxtnJRnj/oIdLAUdzTfr9N40KtxNhtdrq+Q1RPMdCJINRXq37m4t5+r3H70wgC3opW46OzFcZYA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-endpoints": "^3.996.4", + "@smithy/core": "^3.23.9", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-retry": "^4.2.11", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.9.tgz", + "integrity": "sha512-+RpVtpmQbbtzFOKhMlsRcXM/3f1Z49qTOHaA8gEpHOYruERmog6f2AUtf/oTRLCWjR9H2b3roqryV/hI7QMW8w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/middleware-host-header": "^3.972.7", + "@aws-sdk/middleware-logger": "^3.972.7", + "@aws-sdk/middleware-recursion-detection": "^3.972.7", + "@aws-sdk/middleware-user-agent": "^3.972.20", + "@aws-sdk/region-config-resolver": "^3.972.7", + "@aws-sdk/types": "^3.973.5", + "@aws-sdk/util-endpoints": "^3.996.4", + "@aws-sdk/util-user-agent-browser": "^3.972.7", + "@aws-sdk/util-user-agent-node": "^3.973.6", + "@smithy/config-resolver": "^4.4.10", + "@smithy/core": "^3.23.9", + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/hash-node": "^4.2.11", + "@smithy/invalid-dependency": "^4.2.11", + "@smithy/middleware-content-length": "^4.2.11", + "@smithy/middleware-endpoint": "^4.4.23", + "@smithy/middleware-retry": "^4.4.40", + "@smithy/middleware-serde": "^4.2.12", + "@smithy/middleware-stack": "^4.2.11", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/protocol-http": "^5.3.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.39", + "@smithy/util-defaults-mode-node": "^4.2.42", + "@smithy/util-endpoints": "^3.3.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-retry": "^4.2.11", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.7.tgz", + "integrity": "sha512-/Ev/6AI8bvt4HAAptzSjThGUMjcWaX3GX8oERkB0F0F9x2dLSBdgFDiyrRz3i0u0ZFZFQ1b28is4QhyqXTUsVA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.5", + "@smithy/config-resolver": "^4.4.10", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.7.tgz", + "integrity": "sha512-mYhh7FY+7OOqjkYkd6+6GgJOsXK1xBWmuR+c5mxJPj2kr5TBNeZq+nUvE9kANWAux5UxDVrNOSiEM/wlHzC3Lg==", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@aws-sdk/middleware-sdk-s3": "^3.972.19", + "@aws-sdk/types": "^3.973.5", + "@smithy/protocol-http": "^5.3.11", + "@smithy/signature-v4": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/token-providers": { + "version": "3.1008.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1008.0.tgz", + "integrity": "sha512-TulwlHQBWcJs668kNUDMZHN51DeLrDsYT59Ux4a/nbvr025gM6HjKJJ3LvnZccam7OS/ZKUVkWomCneRQKJbBg==", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" + "@aws-sdk/core": "^3.973.19", + "@aws-sdk/nested-clients": "^3.996.9", + "@aws-sdk/types": "^3.973.5", + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/types": { + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.5.tgz", + "integrity": "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=20.0.0" } }, - "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", + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.4.tgz", + "integrity": "sha512-Hek90FBmd4joCFj+Vc98KLJh73Zqj3s2W56gjAcTkrNLMDI5nIFkG9YpfcJiVI1YlE2Ne1uOQNe+IgQ/Vz2XRA==", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@aws-sdk/types": "^3.973.5", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-endpoints": "^3.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" + "node": ">=20.0.0" } }, - "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", + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.4.tgz", + "integrity": "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", - "dev": true, - "license": "MIT", - "optional": true, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.7.tgz", + "integrity": "sha512-7SJVuvhKhMF/BkNS1n0QAJYgvEwYbK2QLKBrzDiwQGiTRU6Yf1f3nehTzm/l21xdAOtWSfp2uWSddPnP2ZtsVw==", + "license": "Apache-2.0", "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" + "@aws-sdk/types": "^3.973.5", + "@smithy/types": "^4.13.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" } }, - "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", - "dev": true, - "license": "MIT", - "optional": true, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.6.tgz", + "integrity": "sha512-iF7G0prk7AvmOK64FcLvc/fW+Ty1H+vttajL7PvJFReU8urMxfYmynTTuFKDTA76Wgpq3FzTPKwabMQIXQHiXQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.4.0" + "@aws-sdk/middleware-user-agent": "^3.972.20", + "@aws-sdk/types": "^3.973.5", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "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, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.10.tgz", + "integrity": "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.4.0" + "@smithy/types": "^4.13.0", + "fast-xml-parser": "5.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", - "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", - "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", - "cpu": [ - "arm" - ], + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", - "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } + "node_modules/@babel/code-frame/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/@esbuild/android-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", - "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", - "cpu": [ - "x64" - ], + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", - "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@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": ">=18" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", - "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", - "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", - "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", - "cpu": [ - "x64" - ], + "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", - "optional": true, - "os": [ - "freebsd" - ], + "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": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", - "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", - "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", - "cpu": [ - "arm64" - ], + "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", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", - "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", - "cpu": [ - "ia32" - ], + "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", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", - "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", - "cpu": [ - "loong64" - ], + "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", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", - "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", - "cpu": [ - "mips64el" - ], + "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", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", - "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", - "cpu": [ - "ppc64" - ], + "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", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", - "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", - "cpu": [ - "riscv64" - ], + "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", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", - "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", - "cpu": [ - "s390x" - ], + "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", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", - "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", - "cpu": [ - "x64" - ], + "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", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", - "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", - "cpu": [ - "arm64" - ], + "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", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">=18" + "node": ">=6.0.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", - "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", - "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", - "cpu": [ - "arm64" - ], + "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", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", - "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", - "cpu": [ - "x64" - ], + "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", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", - "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", - "cpu": [ - "arm64" - ], + "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", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", - "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", - "cpu": [ - "x64" - ], + "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", - "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", - "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", - "cpu": [ - "arm64" - ], + "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", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", - "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", - "cpu": [ - "ia32" - ], + "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", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", - "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", - "cpu": [ - "x64" - ], + "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", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "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": { - "eslint-visitor-keys": "^3.4.3" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6.9.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "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", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/config-array/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==", + "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": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": "*" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.17.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.15" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "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": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "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", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": "*" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "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": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" }, - "funding": { - "url": "https://eslint.org/donate" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" + "node": ">=6.9.0" } }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" }, "engines": { - "node": ">=18.18.0" + "node": ">=6.9.0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "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": "Apache-2.0", - "engines": { - "node": ">=12.22" + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18" } }, - "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==", + "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": "ISC", + "license": "MIT", "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" + "@jridgewell/trace-mapping": "0.3.9" }, "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==", + "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": "ISC", + "license": "MIT", "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" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@emnapi/core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", + "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "sprintf-js": "~1.0.2" + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" } }, - "node_modules/@istanbuljs/load-nyc-config/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==", + "node_modules/@emnapi/runtime": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" + "tslib": "^2.4.0" } }, - "node_modules/@istanbuljs/load-nyc-config/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==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "tslib": "^2.4.0" } }, - "node_modules/@istanbuljs/load-nyc-config/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, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/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, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/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, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/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, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], "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" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], "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" - }, + "optional": true, + "os": [ + "freebsd" + ], "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": ">=18" } }, - "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, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], "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" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], "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" - }, + "optional": true, + "os": [ + "linux" + ], "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": ">=18" } }, - "node_modules/@jest/reporters/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/@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, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], "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" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@jest/test-result": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "slash": "^3.0.0" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], "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" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "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==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "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" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.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==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "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==", + "node_modules/@eslint/config-array": { + "version": "0.23.3", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", + "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@eslint/object-schema": "^3.0.3", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=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==", + "node_modules/@eslint/config-array/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==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": "18 || 20 || >=22" } }, - "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==", + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, - "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==", + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "MIT", - "optional": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@nhsdigital/eps-cdk-constructs": { - "resolved": "packages/cdkConstructs", - "link": true + "node_modules/@eslint/config-helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", + "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } }, - "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==", + "node_modules/@eslint/core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", + "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", "dev": true, - "license": "MIT", - "optional": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { - "node": ">=14" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "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==", + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", - "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", - "cpu": [ - "arm" - ], + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", + "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", - "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/plugin-kit": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", + "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", - "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", - "cpu": [ - "arm64" - ], + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", - "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", - "cpu": [ - "x64" - ], + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", - "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", - "cpu": [ - "arm64" - ], + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", - "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", - "cpu": [ - "x64" - ], + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", - "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", - "cpu": [ - "arm" - ], + "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": "MIT", - "optional": true, - "os": [ - "linux" - ] + "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/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", - "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", - "cpu": [ - "arm" - ], + "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": "MIT", - "optional": true, - "os": [ - "linux" - ] + "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/@rollup/rollup-linux-arm64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", - "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", - "cpu": [ - "arm64" - ], + "node_modules/@istanbuljs/load-nyc-config/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", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "sprintf-js": "~1.0.2" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", - "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", - "cpu": [ - "arm64" - ], + "node_modules/@istanbuljs/load-nyc-config/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", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", - "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", - "cpu": [ - "loong64" - ], + "node_modules/@istanbuljs/load-nyc-config/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", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", - "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", - "cpu": [ - "ppc64" - ], + "node_modules/@istanbuljs/load-nyc-config/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", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", - "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@istanbuljs/load-nyc-config/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", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", - "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", - "cpu": [ - "riscv64" - ], + "node_modules/@istanbuljs/load-nyc-config/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", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", - "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", - "cpu": [ - "s390x" - ], + "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", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.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.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.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.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.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.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", + "@types/node": "*", + "jest-mock": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.3.0", + "jest-snapshot": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "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.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", + "@types/node": "*", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.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.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.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.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.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.5.0", + "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.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.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/reporters/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/@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.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.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.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.3.0", + "@jest/types": "30.3.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.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.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.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.3.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.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "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.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "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/@nhsdigital/eps-cdk-constructs": { + "resolved": "packages/cdkConstructs", + "link": true + }, + "node_modules/@nhsdigital/eps-deployment-utils": { + "resolved": "packages/deploymentUtils", + "link": true + }, + "node_modules/@oxc-project/runtime": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz", + "integrity": "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz", + "integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "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/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz", + "integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz", + "integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz", + "integrity": "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==", + "dev": true, + "license": "MIT" + }, + "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": "15.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", + "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz", + "integrity": "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz", + "integrity": "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.3.tgz", + "integrity": "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.10.tgz", + "integrity": "sha512-IRTkd6ps0ru+lTWnfnsbXzW80A8Od8p3pYiZnW98K2Hb20rqfsX7VTlfUwhrcOeSSy68Gn9WBofwPuw3e5CCsg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.2", + "@smithy/util-middleware": "^4.2.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.9", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.9.tgz", + "integrity": "sha512-1Vcut4LEL9HZsdpI0vFiRYIsaoPwZLjAxnVQDUMQK8beMS+EYPLDQCXtbzfxmM5GzSgjfe2Q9M7WaXwIMQllyQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.12", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-stream": "^4.5.17", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.11.tgz", + "integrity": "sha512-lBXrS6ku0kTj3xLmsJW0WwqWbGQ6ueooYyp/1L9lkyT0M02C+DWwYwc5aTyXFbRaK38ojALxNixg+LxKSHZc0g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.11.tgz", + "integrity": "sha512-Sf39Ml0iVX+ba/bgMPxaXWAAFmHqYLTmbjAPfLPLY8CrYkRDEqZdUsKC1OwVMCdJXfAt0v4j49GIJ8DoSYAe6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.13.0", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.11.tgz", + "integrity": "sha512-3rEpo3G6f/nRS7fQDsZmxw/ius6rnlIpz4UX6FlALEzz8JoSxFmdBt0SZnthis+km7sQo6q5/3e+UJcuQivoXA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.11.tgz", + "integrity": "sha512-XeNIA8tcP/GDWnnKkO7qEm/bg0B/bP9lvIXZBXcGZwZ+VYM8h8k9wuDvUODtdQ2Wcp2RcBkPTCSMmaniVHrMlA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.11.tgz", + "integrity": "sha512-fzbCh18rscBDTQSCrsp1fGcclLNF//nJyhjldsEl/5wCYmgpHblv5JSppQAyQI24lClsFT0wV06N1Porn0IsEw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.11.tgz", + "integrity": "sha512-MJ7HcI+jEkqoWT5vp+uoVaAjBrmxBtKhZTeynDRG/seEjJfqyg3SiqMMqyPnAMzmIfLaeJ/uiuSDP/l9AnMy/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.13.tgz", + "integrity": "sha512-U2Hcfl2s3XaYjikN9cT4mPu8ybDbImV3baXR0PkVlC0TTx808bRP3FaPGAzPtB8OByI+JqJ1kyS+7GEgae7+qQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.11", + "@smithy/querystring-builder": "^4.2.11", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.12.tgz", + "integrity": "sha512-1wQE33DsxkM/waftAhCH9VtJbUGyt1PJ9YRDpOu+q9FUi73LLFUZ2fD8A61g2mT1UY9k7b99+V1xZ41Rz4SHRQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.2", + "@smithy/chunked-blob-reader-native": "^4.2.3", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.11.tgz", + "integrity": "sha512-T+p1pNynRkydpdL015ruIoyPSRw9e/SQOWmSAMmmprfswMrd5Ow5igOWNVlvyVFZlxXqGmyH3NQwfwy8r5Jx0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.11.tgz", + "integrity": "sha512-hQsTjwPCRY8w9GK07w1RqJi3e+myh0UaOWBBhZ1UMSDgofH/Q1fEYzU1teaX6HkpX/eWDdm7tAGR0jBPlz9QEQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.11.tgz", + "integrity": "sha512-cGNMrgykRmddrNhYy1yBdrp5GwIgEkniS7k9O1VLB38yxQtlvrxpZtUVvo6T4cKpeZsriukBuuxfJcdZQc/f/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.11.tgz", + "integrity": "sha512-350X4kGIrty0Snx2OWv7rPM6p6vM7RzryvFs6B/56Cux3w3sChOb3bymo5oidXJlPcP9fIRxGUCk7GqpiSOtng==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.11.tgz", + "integrity": "sha512-UvIfKYAKhCzr4p6jFevPlKhQwyQwlJ6IeKLDhmV1PlYfcW3RL4ROjNEDtSik4NYMi9kDkH7eSwyTP3vNJ/u/Dw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.23", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.23.tgz", + "integrity": "sha512-UEFIejZy54T1EJn2aWJ45voB7RP2T+IRzUqocIdM6GFFa5ClZncakYJfcYnoXt3UsQrZZ9ZRauGm77l9UCbBLw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.9", + "@smithy/middleware-serde": "^4.2.12", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.11", + "@smithy/util-middleware": "^4.2.11", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.40", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.40.tgz", + "integrity": "sha512-YhEMakG1Ae57FajERdHNZ4ShOPIY7DsgV+ZoAxo/5BT0KIe+f6DDU2rtIymNNFIj22NJfeeI6LWIifrwM0f+rA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/service-error-classification": "^4.2.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-retry": "^4.2.11", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.12.tgz", + "integrity": "sha512-W9g1bOLui7Xn5FABRVS0o3rXL0gfN37d/8I/W7i0N7oxjx9QecUmXEMSUMADTODwdtka9cN43t5BI2CodLJpng==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.11.tgz", + "integrity": "sha512-s+eenEPW6RgliDk2IhjD2hWOxIx1NKrOHxEwNUaUXxYBxIyCcDfNULZ2Mu15E3kwcJWBedTET/kEASPV1A1Akg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.11.tgz", + "integrity": "sha512-xD17eE7kaLgBBGf5CZQ58hh2YmwK1Z0O8YhffwB/De2jsL0U3JklmhVYJ9Uf37OtUDLF2gsW40Xwwag9U869Gg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.11", + "@smithy/shared-ini-file-loader": "^4.4.6", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.14.tgz", + "integrity": "sha512-DamSqaU8nuk0xTJDrYnRzZndHwwRnyj/n/+RqGGCcBKB4qrQem0mSDiWdupaNWdwxzyMU91qxDmHOCazfhtO3A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/querystring-builder": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.11.tgz", + "integrity": "sha512-14T1V64o6/ndyrnl1ze1ZhyLzIeYNN47oF/QU6P5m82AEtyOkMJTb0gO1dPubYjyyKuPD6OSVMPDKe+zioOnCg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.11.tgz", + "integrity": "sha512-hI+barOVDJBkNt4y0L2mu3Ugc0w7+BpJ2CZuLwXtSltGAAwCb3IvnalGlbDV/UCS6a9ZuT3+exd1WxNdLb5IlQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.11.tgz", + "integrity": "sha512-7spdikrYiljpket6u0up2Ck2mxhy7dZ0+TDd+S53Dg2DHd6wg+YNJrTCHiLdgZmEXZKI7LJZcwL3721ZRDFiqA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.11.tgz", + "integrity": "sha512-nE3IRNjDltvGcoThD2abTozI1dkSy8aX+a2N1Rs55en5UsdyyIXgGEmevUL3okZFoJC77JgRGe99xYohhsjivQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.11.tgz", + "integrity": "sha512-HkMFJZJUhzU3HvND1+Yw/kYWXp4RPDLBWLcK1n+Vqw8xn4y2YiBhdww8IxhkQjP/QlZun5bwm3vcHc8AqIU3zw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.6.tgz", + "integrity": "sha512-IB/M5I8G0EeXZTHsAxpx51tMQ5R719F3aq+fjEB6VtNcCHDc0ajFDIGDZw+FW9GxtEkgTduiPpjveJdA/CX7sw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.11.tgz", + "integrity": "sha512-V1L6N9aKOBAN4wEHLyqjLBnAz13mtILU0SeDrjOaIZEeN6IFa6DxwRt1NNpOdmSpQUfkBj0qeD3m6P77uzMhgQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.11", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.3.tgz", + "integrity": "sha512-7k4UxjSpHmPN2AxVhvIazRSzFQjWnud3sOsXcFStzagww17j1cFQYqTSiQ8xuYK3vKLR1Ni8FzuT3VlKr3xCNw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.9", + "@smithy/middleware-endpoint": "^4.4.23", + "@smithy/middleware-stack": "^4.2.11", + "@smithy/protocol-http": "^5.3.11", + "@smithy/types": "^4.13.0", + "@smithy/util-stream": "^4.5.17", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.11.tgz", + "integrity": "sha512-oTAGGHo8ZYc5VZsBREzuf5lf2pAurJQsccMusVZ85wDkX66ojEc/XauiGjzCj50A61ObFTPe6d7Pyt6UBYaing==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", - "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.39", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.39.tgz", + "integrity": "sha512-ui7/Ho/+VHqS7Km2wBw4/Ab4RktoiSshgcgpJzC4keFPs6tLJS4IQwbeahxQS3E/w98uq6E1mirCH/id9xIXeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", - "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.42", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.42.tgz", + "integrity": "sha512-QDA84CWNe8Akpj15ofLO+1N3Rfg8qa2K5uX0y6HnOp4AnRYRgWrKx/xzbYNbVF9ZsyJUYOfcoaN3y93wA/QJ2A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.10", + "@smithy/credential-provider-imds": "^4.2.11", + "@smithy/node-config-provider": "^4.3.11", + "@smithy/property-provider": "^4.2.11", + "@smithy/smithy-client": "^4.12.3", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", - "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "node_modules/@smithy/util-endpoints": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.2.tgz", + "integrity": "sha512-+4HFLpE5u29AbFlTdlKIT7jfOzZ8PDYZKTb3e+AgLz986OYwqTourQ5H+jg79/66DB69Un1+qKecLnkZdAsYcA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", - "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", - "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-middleware": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.11.tgz", + "integrity": "sha512-r3dtF9F+TpSZUxpOVVtPfk09Rlo4lT6ORBqEvX3IBT6SkQAdDSVKR5GcfmZbtl7WKhKnmb3wbDTQ6ibR2XHClw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", - "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-retry": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.11.tgz", + "integrity": "sha512-XSZULmL5x6aCTTii59wJqKsY1l3eMIAomRAccW7Tzh9r8s7T/7rdo03oektuH5jeYRlJMPcNP92EuRDvk9aXbw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.11", + "@smithy/types": "^4.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", - "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-stream": { + "version": "4.5.17", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.17.tgz", + "integrity": "sha512-793BYZ4h2JAQkNHcEnyFxDTcZbm9bVybD0UV/LEWmZ5bkTms7JqjfrLMi2Qy0E5WFcCzLwCAPgcvcvxoeALbAQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.13", + "@smithy/node-http-handler": "^4.4.14", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@sinclair/typebox": { - "version": "0.34.41", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", - "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", - "dev": true, - "license": "MIT" + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "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", + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", "dependencies": { - "type-detect": "4.0.8" + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "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", + "node_modules/@smithy/util-waiter": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.13.tgz", + "integrity": "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ==", + "license": "Apache-2.0", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "@smithy/abort-controller": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@standard-schema/spec": { @@ -2343,6 +4053,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2385,14 +4102,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", - "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/stack-utils": { @@ -2420,21 +4136,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz", - "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", + "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.48.0", - "@typescript-eslint/type-utils": "8.48.0", - "@typescript-eslint/utils": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/type-utils": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2444,24 +4159,23 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.48.0", - "eslint": "^8.57.0 || ^9.0.0", + "@typescript-eslint/parser": "^8.57.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz", - "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", + "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2471,20 +4185,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", - "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", + "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.50.1", - "@typescript-eslint/types": "^8.50.1", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.57.0", + "@typescript-eslint/types": "^8.57.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2497,15 +4211,15 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", - "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", + "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1" + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2515,10 +4229,10 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", - "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", + "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", "dev": true, "license": "MIT", "engines": { @@ -2532,36 +4246,18 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", - "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", - "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", + "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.50.1", - "@typescript-eslint/tsconfig-utils": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2571,19 +4267,16 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", - "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "node_modules/@typescript-eslint/types": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", + "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.50.1", - "eslint-visitor-keys": "^4.2.1" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2592,29 +4285,22 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz", - "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", + "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.48.0", - "@typescript-eslint/types": "^8.48.0", - "debug": "^4.3.4" + "@typescript-eslint/project-service": "8.57.0", + "@typescript-eslint/tsconfig-utils": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2627,119 +4313,56 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz", - "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz", - "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==", + "node_modules/@typescript-eslint/typescript-estree/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==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz", - "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/typescript-estree": "8.48.0", - "@typescript-eslint/utils": "8.48.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", - "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "18 || 20 || >=22" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz", - "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "@typescript-eslint/project-service": "8.48.0", - "@typescript-eslint/tsconfig-utils": "8.48.0", - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "18 || 20 || >=22" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz", - "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", + "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.48.0", - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/typescript-estree": "8.48.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2749,19 +4372,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz", - "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", + "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.57.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2772,13 +4395,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3061,30 +4684,29 @@ ] }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz", - "integrity": "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.0.tgz", + "integrity": "sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.16", - "ast-v8-to-istanbul": "^0.3.8", + "@vitest/utils": "4.1.0", + "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.2.0", - "magicast": "^0.5.1", + "magicast": "^0.5.2", "obug": "^2.1.1", - "std-env": "^3.10.0", + "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.16", - "vitest": "4.0.16" + "@vitest/browser": "4.1.0", + "vitest": "4.1.0" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -3093,17 +4715,17 @@ } }, "node_modules/@vitest/expect": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", - "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", + "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.0.0", + "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "chai": "^6.2.1", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "chai": "^6.2.2", "tinyrainbow": "^3.0.3" }, "funding": { @@ -3111,13 +4733,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", - "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", + "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.16", + "@vitest/spy": "4.1.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -3126,7 +4748,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" }, "peerDependenciesMeta": { "msw": { @@ -3138,9 +4760,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", - "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", + "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", "dev": true, "license": "MIT", "dependencies": { @@ -3151,13 +4773,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", - "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", + "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.16", + "@vitest/utils": "4.1.0", "pathe": "^2.0.3" }, "funding": { @@ -3165,13 +4787,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", - "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", + "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", + "@vitest/pretty-format": "4.1.0", + "@vitest/utils": "4.1.0", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -3180,9 +4803,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", - "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", + "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", "dev": true, "license": "MIT", "funding": { @@ -3190,33 +4813,26 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", - "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", + "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", + "@vitest/pretty-format": "4.1.0", + "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "license": "ISC" - }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "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", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3248,9 +4864,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -3323,34 +4939,10 @@ "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": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "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" }, @@ -3365,38 +4957,36 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", - "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" + "js-tokens": "^10.0.0" } }, "node_modules/aws-cdk": { - "version": "2.1100.1", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1100.1.tgz", - "integrity": "sha512-q2poFrQh90TK6eqeI0zznA8r1JkDI63WVOSqC7gFGo6qjQjAnvFk/utxHoNRgAC0RL0CLd19uCcHh3jfX9NiSg==", + "version": "2.1111.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1111.0.tgz", + "integrity": "sha512-69AVF04cxbAhYzmeJYtUF5bs6ruNnH05EQZJfjadk5Lpg+HVaJY2NjG/2qgsMmKrfRgvLet73Ir8ehVlAaaGng==", "license": "Apache-2.0", "bin": { "cdk": "bin/cdk" }, "engines": { "node": ">= 18.0.0" - }, - "optionalDependencies": { - "fsevents": "2.3.2" } }, "node_modules/aws-cdk-lib": { - "version": "2.233.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.233.0.tgz", - "integrity": "sha512-rBOzIA8TGC5eB8TyVIvckAVlX7a0/gVPE634FguhSee9RFaovjgc5+IixGyyLJhu3lLsMSjqDoqTJg2ab+p8ng==", + "version": "2.243.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.243.0.tgz", + "integrity": "sha512-qIhg/3gSNeZ9LoVmDATO45HPk+POkoCfPZRezeOPhd2kAJ/wzYswyUcMqpDWXrlRrEVYntxsykQs+2eMA04Isg==", "bundleDependencies": [ "@balena/dockerignore", + "@aws-cdk/cloud-assembly-api", "case", "fs-extra", "ignore", @@ -3410,26 +5000,65 @@ ], "license": "Apache-2.0", "dependencies": { - "@aws-cdk/asset-awscli-v1": "2.2.242", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^48.20.0", + "@aws-cdk/asset-awscli-v1": "2.2.263", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.1", + "@aws-cdk/cloud-assembly-api": "^2.1.1", + "@aws-cdk/cloud-assembly-schema": "^52.1.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^11.3.2", + "fs-extra": "^11.3.3", "ignore": "^5.3.2", "jsonschema": "^1.5.0", "mime-types": "^2.1.35", - "minimatch": "^3.1.2", + "minimatch": "^10.2.3", "punycode": "^2.3.1", - "semver": "^7.7.3", + "semver": "^7.7.4", "table": "^6.9.0", "yaml": "1.10.2" }, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "constructs": "^10.5.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": { + "version": "2.1.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": { - "constructs": "^10.0.0" + "@aws-cdk/cloud-assembly-schema": ">=52.1.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": { @@ -3438,7 +5067,7 @@ "license": "Apache-2.0" }, "node_modules/aws-cdk-lib/node_modules/ajv": { - "version": "8.17.1", + "version": "8.18.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -3483,17 +5112,22 @@ } }, "node_modules/aws-cdk-lib/node_modules/balanced-match": { - "version": "1.0.2", + "version": "4.0.4", "inBundle": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/aws-cdk-lib/node_modules/brace-expansion": { - "version": "1.1.12", + "version": "5.0.3", "inBundle": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/aws-cdk-lib/node_modules/case": { @@ -3520,11 +5154,6 @@ "inBundle": true, "license": "MIT" }, - "node_modules/aws-cdk-lib/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, "node_modules/aws-cdk-lib/node_modules/emoji-regex": { "version": "8.0.0", "inBundle": true, @@ -3551,7 +5180,7 @@ "license": "BSD-3-Clause" }, "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.3.2", + "version": "11.3.3", "inBundle": true, "license": "MIT", "dependencies": { @@ -3633,14 +5262,17 @@ } }, "node_modules/aws-cdk-lib/node_modules/minimatch": { - "version": "3.1.2", + "version": "10.2.4", "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/aws-cdk-lib/node_modules/punycode": { @@ -3660,7 +5292,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.7.3", + "version": "7.7.4", "inBundle": true, "license": "ISC", "bin": { @@ -3742,16 +5374,16 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.2.0", + "@jest/transform": "30.3.0", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", + "babel-preset-jest": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -3784,9 +5416,9 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", "dev": true, "license": "MIT", "dependencies": { @@ -3824,13 +5456,13 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", + "babel-plugin-jest-hoist": "30.3.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { @@ -3848,15 +5480,21 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.31", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", - "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -3867,23 +5505,10 @@ "balanced-match": "^1.0.0" } }, - "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.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -3900,13 +5525,12 @@ } ], "license": "MIT", - "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", + "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.1.4" + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -3966,9 +5590,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001757", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", - "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "dev": true, "funding": [ { @@ -4034,9 +5658,9 @@ } }, "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "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": [ { @@ -4050,9 +5674,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", - "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", + "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" }, @@ -4180,11 +5804,10 @@ "license": "MIT" }, "node_modules/constructs": { - "version": "10.4.4", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.4.tgz", - "integrity": "sha512-lP0qC1oViYf1cutHo9/KQ8QL637f/W29tDmv/6sy35F5zs+MD9f66nbAAIjicwc7fwyuF3rkg6PhZh4sfvWIpA==", - "license": "Apache-2.0", - "peer": true + "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", @@ -4233,21 +5856,10 @@ } } }, - "node_modules/debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4276,6 +5888,16 @@ "node": ">=0.10.0" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -4286,21 +5908,10 @@ "node": ">=8" } }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dev": true, - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "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": { @@ -4315,9 +5926,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.259", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", - "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", "dev": true, "license": "ISC" }, @@ -4352,16 +5963,16 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, "node_modules/esbuild": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", - "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -4371,32 +5982,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.0", - "@esbuild/android-arm": "0.27.0", - "@esbuild/android-arm64": "0.27.0", - "@esbuild/android-x64": "0.27.0", - "@esbuild/darwin-arm64": "0.27.0", - "@esbuild/darwin-x64": "0.27.0", - "@esbuild/freebsd-arm64": "0.27.0", - "@esbuild/freebsd-x64": "0.27.0", - "@esbuild/linux-arm": "0.27.0", - "@esbuild/linux-arm64": "0.27.0", - "@esbuild/linux-ia32": "0.27.0", - "@esbuild/linux-loong64": "0.27.0", - "@esbuild/linux-mips64el": "0.27.0", - "@esbuild/linux-ppc64": "0.27.0", - "@esbuild/linux-riscv64": "0.27.0", - "@esbuild/linux-s390x": "0.27.0", - "@esbuild/linux-x64": "0.27.0", - "@esbuild/netbsd-arm64": "0.27.0", - "@esbuild/netbsd-x64": "0.27.0", - "@esbuild/openbsd-arm64": "0.27.0", - "@esbuild/openbsd-x64": "0.27.0", - "@esbuild/openharmony-arm64": "0.27.0", - "@esbuild/sunos-x64": "0.27.0", - "@esbuild/win32-arm64": "0.27.0", - "@esbuild/win32-ia32": "0.27.0", - "@esbuild/win32-x64": "0.27.0" + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" } }, "node_modules/escalade": { @@ -4423,34 +6034,30 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.3.tgz", + "integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.3", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.1", + "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", + "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -4460,8 +6067,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -4469,7 +6075,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -4484,33 +6090,35 @@ } }, "node_modules/eslint-plugin-import-newlines": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-newlines/-/eslint-plugin-import-newlines-1.4.0.tgz", - "integrity": "sha512-+Cz1x2xBLtI9gJbmuYEpvY7F8K75wskBmJ7rk4VRObIJo+jklUJaejFJgtnWeL0dCFWabGEkhausrikXaNbtoQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-newlines/-/eslint-plugin-import-newlines-2.0.0.tgz", + "integrity": "sha512-xKcuSkpQvkAHWCvAysqCk8GAD+rabLokiK4rmeJjCB+CQtGn6Ptgs909miphvN51JyZxOJWz4reGMsoHSbjbIg==", "dev": true, "license": "MIT", "bin": { "import-linter": "lib/index.js" }, "engines": { - "node": ">=10.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "peerDependencies": { - "eslint": ">=6.0.0" + "eslint": ">=10.0.0" } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4529,25 +6137,37 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/eslint/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==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4564,44 +6184,47 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", + "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4622,9 +6245,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4719,27 +6342,27 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.2.0", + "@jest/expect-utils": "30.3.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" + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4767,6 +6390,37 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-xml-builder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", + "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.0.0", + "strnum": "^2.1.2" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -4790,19 +6444,6 @@ "node": ">=16.0.0" } }, - "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": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -4866,9 +6507,10 @@ "license": "ISC" }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "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, @@ -4879,16 +6521,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -4936,6 +6568,7 @@ "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": { @@ -4967,9 +6600,9 @@ } }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", "dev": true, "license": "MIT", "engines": { @@ -4986,13 +6619,6 @@ "dev": true, "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -5025,26 +6651,6 @@ "node": ">=8" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -5072,23 +6678,6 @@ "node": ">= 4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -5145,22 +6734,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5204,16 +6777,6 @@ "node": ">=0.10.0" } }, - "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", @@ -5322,17 +6885,16 @@ } }, "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", "import-local": "^3.2.0", - "jest-cli": "30.2.0" + "jest-cli": "30.3.0" }, "bin": { "jest": "bin/jest.js" @@ -5350,14 +6912,14 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.2.0", + "jest-util": "30.3.0", "p-limit": "^3.1.0" }, "engines": { @@ -5365,29 +6927,29 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", "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", + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.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", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "p-limit": "^3.1.0", - "pretty-format": "30.2.0", + "pretty-format": "30.3.0", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -5397,21 +6959,21 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.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", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "yargs": "^17.7.2" }, "bin": { @@ -5430,34 +6992,33 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", "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", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", + "jest-circus": "30.3.0", "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", + "jest-environment-node": "30.3.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", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "parse-json": "^5.2.0", - "pretty-format": "30.2.0", + "pretty-format": "30.3.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -5482,16 +7043,16 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.0.1", + "@jest/diff-sequences": "30.3.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.2.0" + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5511,57 +7072,57 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" + "jest-util": "30.3.0", + "pretty-format": "30.3.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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.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", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", "walker": "^1.0.8" }, "engines": { @@ -5571,19 +7132,17 @@ "fsevents": "^2.3.3" } }, - "node_modules/jest-haste-map/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/jest-haste-map/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/jest-junit": { @@ -5626,66 +7185,79 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" + "pretty-format": "30.3.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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", "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" + "jest-diff": "30.3.0", + "pretty-format": "30.3.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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", + "@jest/types": "30.3.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", + "picomatch": "^4.0.3", + "pretty-format": "30.3.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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-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-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-util": "30.2.0" + "jest-util": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5720,18 +7292,18 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", + "jest-haste-map": "30.3.0", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -5740,46 +7312,46 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", "dev": true, "license": "MIT", "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" + "jest-snapshot": "30.3.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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", "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", + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.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", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -5788,32 +7360,32 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", + "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -5822,9 +7394,9 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5833,20 +7405,20 @@ "@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/expect-utils": "30.3.0", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.2.0", + "expect": "30.3.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", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -5855,18 +7427,18 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" + "picomatch": "^4.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5886,18 +7458,18 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", + "@jest/types": "30.3.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.2.0" + "pretty-format": "30.3.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5917,19 +7489,19 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.2.0", + "jest-util": "30.3.0", "string-length": "^4.0.2" }, "engines": { @@ -5937,15 +7509,15 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", + "jest-util": "30.3.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -5970,25 +7542,12 @@ } }, "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "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", @@ -6016,6 +7575,19 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -6077,137 +7649,265 @@ "node": ">= 0.8.0" } }, - "node_modules/license-checker": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", - "integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==", + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MPL-2.0", "dependencies": { - "chalk": "^2.4.1", - "debug": "^3.1.0", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "read-installed": "~4.0.3", - "semver": "^5.5.0", - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-satisfies": "^4.0.0", - "treeify": "^1.1.0" + "detect-libc": "^2.0.3" }, - "bin": { - "license-checker": "bin/license-checker" + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/license-checker/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/license-checker/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/license-checker/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/license-checker/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/license-checker/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/license-checker/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/license-checker/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/license-checker/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/license-checker/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/license-checker/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lines-and-columns": { @@ -6240,13 +7940,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "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", @@ -6268,14 +7961,14 @@ } }, "node_modules/magicast": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", - "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, @@ -6319,20 +8012,6 @@ "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", @@ -6344,13 +8023,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -6370,11 +8049,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -6462,43 +8141,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6509,13 +8151,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true, - "license": "ISC" - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -6568,52 +8203,20 @@ }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "deprecated": "This package is no longer supported.", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" } }, "node_modules/p-limit": { @@ -6665,19 +8268,6 @@ "dev": true, "license": "BlueOak-1.0.0" }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -6727,13 +8317,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -6865,9 +8448,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { @@ -6904,9 +8487,9 @@ } }, "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==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6965,109 +8548,6 @@ "dev": true, "license": "MIT" }, - "node_modules/read-installed": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", - "integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/read-installed/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-package-json": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz", - "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==", - "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.1", - "json-parse-even-better-errors": "^2.3.0", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - } - }, - "node_modules/read-package-json/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/read-package-json/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "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/read-package-json/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/readdir-scoped-modules": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", - "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7078,27 +8558,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -7112,7 +8571,7 @@ "node": ">=8" } }, - "node_modules/resolve-cwd/node_modules/resolve-from": { + "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==", @@ -7122,56 +8581,38 @@ "node": ">=8" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/rollup": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", - "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "node_modules/rolldown": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.9.tgz", + "integrity": "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.115.0", + "@rolldown/pluginutils": "1.0.0-rc.9" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.54.0", - "@rollup/rollup-android-arm64": "4.54.0", - "@rollup/rollup-darwin-arm64": "4.54.0", - "@rollup/rollup-darwin-x64": "4.54.0", - "@rollup/rollup-freebsd-arm64": "4.54.0", - "@rollup/rollup-freebsd-x64": "4.54.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", - "@rollup/rollup-linux-arm-musleabihf": "4.54.0", - "@rollup/rollup-linux-arm64-gnu": "4.54.0", - "@rollup/rollup-linux-arm64-musl": "4.54.0", - "@rollup/rollup-linux-loong64-gnu": "4.54.0", - "@rollup/rollup-linux-ppc64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-musl": "4.54.0", - "@rollup/rollup-linux-s390x-gnu": "4.54.0", - "@rollup/rollup-linux-x64-gnu": "4.54.0", - "@rollup/rollup-linux-x64-musl": "4.54.0", - "@rollup/rollup-openharmony-arm64": "4.54.0", - "@rollup/rollup-win32-arm64-msvc": "4.54.0", - "@rollup/rollup-win32-ia32-msvc": "4.54.0", - "@rollup/rollup-win32-x64-gnu": "4.54.0", - "@rollup/rollup-win32-x64-msvc": "4.54.0", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-x64": "1.0.0-rc.9", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" } }, "node_modules/semver": { @@ -7240,16 +8681,6 @@ "node": ">=8" } }, - "node_modules/slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7281,73 +8712,6 @@ "source-map": "^0.6.0" } }, - "node_modules/spdx-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", - "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-find-index": "^1.0.2", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/spdx-ranges": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", - "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", - "dev": true, - "license": "(MIT AND CC-BY-3.0)" - }, - "node_modules/spdx-satisfies": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz", - "integrity": "sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-compare": "^1.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -7386,9 +8750,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", "dev": true, "license": "MIT" }, @@ -7494,13 +8858,13 @@ } }, "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==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -7566,6 +8930,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", + "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7579,23 +8955,10 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "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": { @@ -7657,9 +9020,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -7727,7 +9090,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -7752,33 +9114,16 @@ "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/treeify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", - "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT" }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -7789,9 +9134,9 @@ } }, "node_modules/ts-jest": { - "version": "29.4.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", - "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", + "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": { @@ -7860,7 +9205,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -7903,9 +9247,7 @@ "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 + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -7949,7 +9291,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7973,9 +9314,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "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" }, @@ -8015,9 +9356,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "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": [ { @@ -8055,13 +9396,6 @@ "punycode": "^2.1.0" } }, - "node_modules/util-extend": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", - "integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==", - "dev": true, - "license": "MIT" - }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -8094,30 +9428,18 @@ "node": ">=10.12.0" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/vite": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", - "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0.tgz", + "integrity": "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", + "@oxc-project/runtime": "0.115.0", + "lightningcss": "^1.32.0", "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.9", "tinyglobby": "^0.2.15" }, "bin": { @@ -8134,9 +9456,10 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.0.0-alpha.31", + "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", - "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", @@ -8149,13 +9472,16 @@ "@types/node": { "optional": true }, - "jiti": { + "@vitejs/devtools": { "optional": true }, - "less": { + "esbuild": { "optional": true }, - "lightningcss": { + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { @@ -8181,46 +9507,12 @@ } } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/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/vite/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", - "peer": true, "engines": { "node": ">=12" }, @@ -8229,32 +9521,31 @@ } }, "node_modules/vitest": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", - "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@vitest/expect": "4.0.16", - "@vitest/mocker": "4.0.16", - "@vitest/pretty-format": "4.0.16", - "@vitest/runner": "4.0.16", - "@vitest/snapshot": "4.0.16", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", + "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.0", + "@vitest/mocker": "4.1.0", + "@vitest/pretty-format": "4.1.0", + "@vitest/runner": "4.1.0", + "@vitest/snapshot": "4.1.0", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", - "std-env": "^3.10.0", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -8270,12 +9561,13 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.16", - "@vitest/browser-preview": "4.0.16", - "@vitest/browser-webdriverio": "4.0.16", - "@vitest/ui": "4.0.16", + "@vitest/browser-playwright": "4.1.0", + "@vitest/browser-preview": "4.1.0", + "@vitest/browser-webdriverio": "4.1.0", + "@vitest/ui": "4.1.0", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -8304,6 +9596,9 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, @@ -8622,10 +9917,26 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "aws-cdk": "^2.1100.1", - "aws-cdk-lib": "^2.233.0", + "@aws-sdk/client-cloudformation": "^3.1008.0", + "@aws-sdk/client-route-53": "^3.1008.0", + "@aws-sdk/client-s3": "^3.1008.0", + "aws-cdk": "^2.1111.0", + "aws-cdk-lib": "^2.243.0", "cdk-nag": "^2.37.52", - "constructs": "^10.4.4" + "constructs": "^10.5.1" + }, + "devDependencies": { + "@types/node": "^25.5.0" + } + }, + "packages/deploymentUtils": { + "name": "@nhsdigital/eps-deployment-utils", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-cloudformation": "^3.1008.0", + "@aws-sdk/client-lambda": "^3.1008.0", + "json-schema-to-ts": "^3.1.1" } } } diff --git a/package.json b/package.json index 146a9bbb..f09e55d7 100644 --- a/package.json +++ b/package.json @@ -1,53 +1,36 @@ { - "name": "@nhsdigital/eps-cdk-constructs", - "version": "1.0.0", - "description": "Common AWS CDK Constructs for NHS Digital EPS Projects", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "check-licenses": "node_modules/.bin/license-checker --failOn GPL --failOn LGPL", - "lint": "echo \"Error: no linting specified\"", - "prepublishOnly": "cd packages/cdkConstructs && npm run prepublishOnly" - }, - "repository": { - "type": "git", - "url": "https://github.com/NHSDigital/eps-cdk-utils" + "lint": "echo \"Error: no linting specified\"" }, "workspaces": [ - "packages/cdkConstructs" + "packages/cdkConstructs", + "packages/deploymentUtils" ], "keywords": [], - "author": "NHS Digital", "license": "MIT", "devDependencies": { - "@types/node": "^25.0.3", - "@typescript-eslint/eslint-plugin": "^8.48.0", - "@typescript-eslint/parser": "^8.50.1", - "@vitest/coverage-v8": "^4.0.16", - "eslint": "^9.39.2", - "eslint-plugin-import-newlines": "^1.4.0", - "jest": "^30.2.0", + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.0", + "@typescript-eslint/eslint-plugin": "^8.57.0", + "@typescript-eslint/parser": "^8.54.0", + "@vitest/coverage-v8": "^4.1.0", + "eslint": "^10.0.3", + "eslint-plugin-import-newlines": "^2.0.0", + "globals": "^17.4.0", + "jest": "^30.3.0", "jest-junit": "^16.0.0", - "license-checker": "^25.0.1", - "ts-jest": "^29.4.5", + "ts-jest": "^29.4.6", "ts-node": "^10.9.2", "typescript": "^5.9.3", "vitest": "^4.0.13" }, "dependencies": { - "aws-cdk": "^2.1100.1", - "aws-cdk-lib": "^2.233.0", + "aws-cdk": "^2.1111.0", + "aws-cdk-lib": "^2.243.0", "cdk-nag": "^2.37.52", - "constructs": "^10.4.4", - "esbuild": "^0.27.0" - }, - "publishConfig": { - "registry": "https://npm.pkg.github.com", - "access": "public" - }, - "type": "module", - "files": [ - "packages/cdkConstructs/lib/src" - ], - "main": "packages/cdkConstructs/lib/src/index.js", - "types": "packages/cdkConstructs/lib/src/index.d.ts" + "constructs": "^10.5.1", + "esbuild": "^0.27.4" + } } diff --git a/packages/cdkConstructs/package.json b/packages/cdkConstructs/package.json index aaae5e92..0c9b8eea 100644 --- a/packages/cdkConstructs/package.json +++ b/packages/cdkConstructs/package.json @@ -20,10 +20,13 @@ "private": false, "type": "module", "dependencies": { - "aws-cdk": "^2.1100.1", - "aws-cdk-lib": "^2.233.0", + "@aws-sdk/client-cloudformation": "^3.1008.0", + "@aws-sdk/client-route-53": "^3.1008.0", + "@aws-sdk/client-s3": "^3.1008.0", + "aws-cdk": "^2.1111.0", + "aws-cdk-lib": "^2.243.0", "cdk-nag": "^2.37.52", - "constructs": "^10.4.4" + "constructs": "^10.5.1" }, "bugs": { "url": "https://github.com/NHSDigital/eps-cdk-utils/issues" @@ -33,5 +36,8 @@ "lib" ], "main": "lib/src/index.js", - "types": "lib/src/index.d.ts" + "types": "lib/src/index.d.ts", + "devDependencies": { + "@types/node": "^25.5.0" + } } diff --git a/packages/cdkConstructs/src/apps/createApp.ts b/packages/cdkConstructs/src/apps/createApp.ts new file mode 100644 index 00000000..cfce0452 --- /dev/null +++ b/packages/cdkConstructs/src/apps/createApp.ts @@ -0,0 +1,112 @@ +import { + App, + Aspects, + Tags, + StackProps +} from "aws-cdk-lib" +import {AwsSolutionsChecks} from "cdk-nag" +import {getConfigFromEnvVar, getBooleanConfigFromEnvVar} from "../config" +import {EpsNagPack} from "../nag/pack/epsNagPack" + +export interface StandardStackProps extends StackProps { + /** Semantic version of the deployment (from `versionNumber`). */ + readonly version: string + /** Git commit identifier baked into the stack. */ + readonly commitId: string + /** Whether the stack originates from a pull-request environment. */ + readonly isPullRequest: boolean + /** Logical environment identifier (for example `dev`, `prod`). */ + readonly environment: string + /** CDK environment configuration used when synthesizing the stack. */ + readonly env: { + /** AWS region targeted by the stack. */ + readonly region: string + } +} + +export interface CreateAppParams { + readonly productName: string + readonly appName: string + readonly repoName: string + readonly driftDetectionGroup: string + readonly region?: string + readonly projectType?: string + readonly publicFacing?: string + readonly serviceCategory?: string +} + +/** + * Initialize a CDK `App` pre-loaded with NHS EPS tags and mandatory configuration. + * + * Reads stack metadata from environment variables, and returns + * both the created `App` instance and the resolved stack props (including version info). + * + * @param params - High-level app metadata and optional deployment modifiers. + * @param params.productName - Product tag value for the stack. + * @param params.appName - Identifier used for `cdkApp` tagging. + * @param params.repoName - Repository name stored on the stack tags. + * @param params.driftDetectionGroup - Baseline drift detection tag (suffixes `-pull-request` when `isPullRequest`). + * @param params.region - AWS region assigned to the stack environment (default `eu-west-2`). + * @param params.projectType - Tag describing the project classification (default `Production`). + * @param params.publicFacing - Public-facing classification tag (default `Y`). + * @param params.serviceCategory - Service category tag (default `Platinum`). + * @returns The constructed CDK `App` and the resolved stack props for downstream stacks. + */ +export function createApp({ + productName, + appName, + repoName, + driftDetectionGroup, + region = "eu-west-2", + projectType = "Production", + publicFacing = "Y", + serviceCategory = "Platinum" +}: CreateAppParams): { app: App, props: StandardStackProps } { + const versionNumber = getConfigFromEnvVar("versionNumber") + const commitId = getConfigFromEnvVar("commitId") + const isPullRequest = getBooleanConfigFromEnvVar("isPullRequest") + const environment = getConfigFromEnvVar("environment") + let cfnDriftDetectionGroup = driftDetectionGroup + if (isPullRequest) { + cfnDriftDetectionGroup += "-pull-request" + } + + const app = new App() + app.node.setContext("isPullRequest", isPullRequest) + + Aspects.of(app).add(new AwsSolutionsChecks({verbose: true})) + Aspects.of(app).add(new EpsNagPack({verbose: true})) + + Tags.of(app).add("TagVersion", "1") + Tags.of(app).add("Programme", "EPS") + Tags.of(app).add("Product", productName) + Tags.of(app).add("Owner", "england.epssupport@nhs.net") + Tags.of(app).add("CostCentre", "128997") + Tags.of(app).add("Customer", "NHS England") + Tags.of(app).add("data_classification", "5") + Tags.of(app).add("DataType", "PII") + Tags.of(app).add("Environment", environment) + Tags.of(app).add("ProjectType", projectType) + Tags.of(app).add("PublicFacing", publicFacing) + Tags.of(app).add("ServiceCategory", serviceCategory) + Tags.of(app).add("OnOffPattern", "AlwaysOn") + Tags.of(app).add("DeploymentTool", "CDK") + Tags.of(app).add("version", versionNumber) + Tags.of(app).add("commit", commitId) + Tags.of(app).add("cdkApp", appName) + Tags.of(app).add("repo", repoName) + Tags.of(app).add("cfnDriftDetectionGroup", cfnDriftDetectionGroup) + + return { + app, + props: { + env: { + region + }, + version: versionNumber, + commitId, + isPullRequest, + environment + } + } +} diff --git a/packages/cdkConstructs/src/changesets/checkDestructiveChanges.ts b/packages/cdkConstructs/src/changesets/checkDestructiveChanges.ts new file mode 100644 index 00000000..cccdb5be --- /dev/null +++ b/packages/cdkConstructs/src/changesets/checkDestructiveChanges.ts @@ -0,0 +1,181 @@ +import { + CloudFormationClient, + DescribeChangeSetCommand, + DescribeChangeSetCommandOutput, + Change as CloudFormationChange +} from "@aws-sdk/client-cloudformation" + +const normalizeToString = (value: unknown): string | undefined => { + if (value === undefined || value === null) { + return undefined + } + + return String(value) +} + +const isDestructiveChange = ( + policyAction: string | undefined, + action: string | undefined, + replacement: string | undefined +): boolean => { + return policyAction === "Delete" || + policyAction === "ReplaceAndDelete" || + action === "Remove" || + replacement === "True" +} + +export type ChangeRequiringAttention = { + logicalId: string; + physicalId: string; + resourceType: string; + policyAction: string; + action: string; + replacement: string; +} + +export type AllowedDestructiveChange = { + LogicalResourceId: string; + PhysicalResourceId: string; + ResourceType: string; + PolicyAction?: string | null; + Action?: string | null; + Replacement?: string | null; + ExpiryDate: string | Date; + StackName: string; + AllowedReason: string; +} + +const toDate = (value: Date | string | number | undefined | null): Date | undefined => { + if (value === undefined || value === null) { + return undefined + } + + const date = value instanceof Date ? value : new Date(value) + return Number.isNaN(date.getTime()) ? undefined : date +} + +/** + * Extracts the subset of CloudFormation changes that either require replacement or remove resources. + * + * @param changeSet - Raw change-set details returned from `DescribeChangeSet`. + * @returns Array of changes that need operator attention. + */ +export function checkDestructiveChanges( + changeSet: DescribeChangeSetCommandOutput | undefined | null +): Array { + if (!changeSet || typeof changeSet !== "object") { + throw new Error("A change set object must be provided") + } + + const {Changes} = changeSet + const changes = Array.isArray(Changes) ? (Changes as Array) : [] + + return changes + .map((change: CloudFormationChange) => { + const resourceChange = change?.ResourceChange + if (!resourceChange) { + return undefined + } + + const policyAction = normalizeToString(resourceChange.PolicyAction) + const action = normalizeToString(resourceChange.Action) + const replacement = normalizeToString(resourceChange.Replacement) + + if (!isDestructiveChange(policyAction, action, replacement)) { + return undefined + } + + return { + logicalId: resourceChange.LogicalResourceId ?? "", + physicalId: resourceChange.PhysicalResourceId ?? "", + resourceType: resourceChange.ResourceType ?? "", + policyAction, + action, + replacement + } + }) + .filter((change): change is ChangeRequiringAttention => Boolean(change)) +} + +const matchesAllowedChange = (change: ChangeRequiringAttention, allowed: AllowedDestructiveChange): boolean => { + const allowedPolicyAction = normalizeToString(allowed.PolicyAction) + const allowedAction = normalizeToString(allowed.Action) + const allowedReplacement = normalizeToString(allowed.Replacement) + + return allowed.LogicalResourceId === change.logicalId && + allowed.PhysicalResourceId === change.physicalId && + allowed.ResourceType === change.resourceType && + allowedPolicyAction === change.policyAction && + allowedAction === change.action && + allowedReplacement === change.replacement +} + +/** + * Describes a CloudFormation change set, applies waiver logic, and throws if destructive changes remain. + * + * @param changeSetName - Name or ARN of the change set. + * @param stackName - Name or ARN of the stack that owns the change set. + * @param region - AWS region where the stack resides. + * @param allowedChanges - Optional waivers that temporarily allow specific destructive changes. + */ +export async function checkDestructiveChangeSet( + changeSetName: string, + stackName: string, + region: string, + allowedChanges: Array = []): Promise { + if (!changeSetName || !stackName || !region) { + throw new Error("Change set name, stack name, and region are required") + } + + const client = new CloudFormationClient({region}) + const command = new DescribeChangeSetCommand({ + ChangeSetName: changeSetName, + StackName: stackName + }) + + const response: DescribeChangeSetCommandOutput = await client.send(command) + const destructiveChanges = checkDestructiveChanges(response) + const creationTime = toDate(response.CreationTime) + const changeSetStackName = response.StackName + + const remainingChanges = destructiveChanges.filter(change => { + const waiver = allowedChanges.find(allowed => matchesAllowedChange(change, allowed)) + + if (!waiver || !creationTime || !changeSetStackName || waiver.StackName !== changeSetStackName) { + return true + } + + const expiryDate = toDate(waiver.ExpiryDate) + if (!expiryDate) { + return true + } + + if (expiryDate.getTime() > creationTime.getTime()) { + + console.log( + // eslint-disable-next-line max-len + `Allowing destructive change ${change.logicalId} (${change.resourceType}) until ${expiryDate.toISOString()} - ${waiver.AllowedReason}` + ) + return false + } + + console.error( + `Waiver for ${change.logicalId} (${change.resourceType}) expired on ${expiryDate.toISOString()}` + ) + return true + }) + + if (remainingChanges.length === 0) { + console.log(`Change set ${changeSetName} for stack ${stackName} has no destructive changes that are not waived.`) + return + } + + console.error("Resources that require attention:") + remainingChanges.forEach(({logicalId, physicalId, resourceType, policyAction, action, replacement}) => { + console.error( + // eslint-disable-next-line max-len + `- LogicalId: ${logicalId}, PhysicalId: ${physicalId}, Type: ${resourceType}, PolicyAction: ${policyAction ?? ""}, Action: ${action ?? ""}, Replacement: ${replacement ?? ""}` + ) + }) + throw new Error(`Change set ${changeSetName} contains destructive changes`) +} diff --git a/packages/cdkConstructs/src/config/index.ts b/packages/cdkConstructs/src/config/index.ts new file mode 100644 index 00000000..355ae46c --- /dev/null +++ b/packages/cdkConstructs/src/config/index.ts @@ -0,0 +1,43 @@ +import {CloudFormationClient, DescribeStacksCommand} from "@aws-sdk/client-cloudformation" +import {S3Client, HeadObjectCommand} from "@aws-sdk/client-s3" +import {StandardStackProps} from "../apps/createApp" + +export function getConfigFromEnvVar(varName: string, prefix: string = "CDK_CONFIG_"): string { + const value = process.env[prefix + varName] + if (!value) { + throw new Error(`Environment variable ${prefix}${varName} is not set`) + } + return value +} + +export function getBooleanConfigFromEnvVar(varName: string, prefix: string = "CDK_CONFIG_"): boolean { + const value = getConfigFromEnvVar(varName, prefix) + return value.toLowerCase().trim() === "true" +} + +export function getNumberConfigFromEnvVar(varName: string, prefix: string = "CDK_CONFIG_"): number { + const value = getConfigFromEnvVar(varName, prefix) + return Number(value) +} + +export async function getTrustStoreVersion(trustStoreFile: string, region: string = "eu-west-2"): Promise { + const cfnClient = new CloudFormationClient({region}) + const s3Client = new S3Client({region}) + const describeStacksCommand = new DescribeStacksCommand({StackName: "account-resources"}) + const response = await cfnClient.send(describeStacksCommand) + const trustStoreBucketArn = response.Stacks![0].Outputs! + .find(output => output.OutputKey === "TrustStoreBucket")!.OutputValue + const bucketName = trustStoreBucketArn!.split(":")[5] + const headObjectCommand = new HeadObjectCommand({Bucket: bucketName, Key: trustStoreFile}) + const headObjectResponse = await s3Client.send(headObjectCommand) + return headObjectResponse.VersionId! +} + +export function calculateVersionedStackName(baseStackName: string, props: StandardStackProps): string { + if (props.isPullRequest) { + return baseStackName + } + return `${baseStackName}-${props.version.replaceAll(".", "-")}` +} + +export {LAMBDA_INSIGHTS_LAYER_ARNS} from "./lambdaInsights" diff --git a/packages/cdkConstructs/src/config/lambdaInsights.ts b/packages/cdkConstructs/src/config/lambdaInsights.ts new file mode 100644 index 00000000..ccd6e6cc --- /dev/null +++ b/packages/cdkConstructs/src/config/lambdaInsights.ts @@ -0,0 +1,6 @@ +// see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-extension-versions.html +// for latest ARNs +export const LAMBDA_INSIGHTS_LAYER_ARNS = { + x64: "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:64", + arm64: "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:31" +} as const diff --git a/packages/cdkConstructs/src/constructs/PythonLambdaFunction.ts b/packages/cdkConstructs/src/constructs/PythonLambdaFunction.ts new file mode 100644 index 00000000..fab17a70 --- /dev/null +++ b/packages/cdkConstructs/src/constructs/PythonLambdaFunction.ts @@ -0,0 +1,297 @@ +import {Construct} from "constructs" +import {Duration, RemovalPolicy} from "aws-cdk-lib" +import { + ManagedPolicy, + PolicyStatement, + Role, + IManagedPolicy, + IRole +} from "aws-cdk-lib/aws-iam" +import { + Architecture, + CfnFunction, + LayerVersion, + Runtime, + Function as LambdaFunctionResource, + Code, + ILayerVersion +} from "aws-cdk-lib/aws-lambda" +import {join} from "node:path" +import {createSharedLambdaResources} from "./lambdaSharedResources" +import {addSuppressions} from "../utils/helpers" +import {IKey} from "aws-cdk-lib/aws-kms" +import {CfnDeliveryStream} from "aws-cdk-lib/aws-kinesisfirehose" + +export interface PythonLambdaFunctionProps { + /** + * Name of the lambda function. The log group name is also based on this name. + * + */ + readonly functionName: string + /** + * The base directory for resolving the package base path and entry point. + * Should point to the monorepo root. + */ + readonly projectBaseDir: string + /** + * The relative path from projectBaseDir to the base folder where the lambda function code is located. + * + */ + readonly packageBasePath: string + /** + * The function handler (file and method). Example: `index.handler` for `index.py` file and `handler` method. + */ + readonly handler: string + /** + * A map of environment variables to set for the lambda function. + */ + readonly environmentVariables?: { [key: string]: string } + /** + * Optional additional IAM policies to attach to role the lambda executes as. + */ + readonly additionalPolicies?: Array + /** + * The number of days to retain logs in CloudWatch Logs. + * @default 30 days + */ + readonly logRetentionInDays: number + /** + * The log level for the lambda function. + * @default "INFO" + */ + readonly logLevel: string + /** + * Optional location of dependencies to include as a separate Lambda layer. + */ + readonly dependencyLocation?: string + /** + * Optional list of Lambda layers to attach to the function. + */ + readonly layers?: Array + /** + * Optional timeout in seconds for the Lambda function. + * @default 50 seconds + */ + readonly timeoutInSeconds?: number + /** + * Optional runtime for the Lambda function. + * @default Runtime.PYTHON_3_14 + */ + readonly runtime?: Runtime + /** + * Optional architecture for the Lambda function. Defaults to x86_64. + * @default Architecture.X86_64 + */ + readonly architecture?: Architecture + /** + * Any files to exclude from the Lambda asset bundle. + * Defaults to these files + * "tests", + * "pytest.ini", + * ".vscode", + * "__pycache__", + * "*.pyc" + */ + readonly excludeFromAsset?: Array + /** + * Optional KMS key for encrypting CloudWatch Logs. + * If not provided, the value is imported from account resources export. + */ + readonly cloudWatchLogsKmsKey?: IKey + /** + * Optional IAM policy for allowing CloudWatch to use the KMS key for encrypting logs. + * If not provided, the value is imported from account resources export. + */ + readonly cloudwatchEncryptionKMSPolicy?: IManagedPolicy + /** + * Optional firehose delivery stream for forwarding logs to Splunk. + * If not provided, the value is imported from account resources export. + */ + readonly splunkDeliveryStream?: CfnDeliveryStream + /** + * Optional IAM role for the subscription filter that forwards logs to Splunk. + * If not provided, the value is imported from account resources export. + */ + readonly splunkSubscriptionFilterRole?: IRole + /** + * Optional IAM policy for allowing lambdas to use Lambda Insights log groups and streams. + * If not provided, the value is imported from account resources export. + */ + readonly lambdaInsightsLogGroupPolicy?: IManagedPolicy + /** + * Whether to create a subscription filter on the Lambda log group to forward logs to Splunk. Defaults to true. + */ + readonly addSplunkSubscriptionFilter?: boolean + +} + +export class PythonLambdaFunction extends Construct { + /** + * The managed policy that allows execution of the Lambda function. + * + * Use this policy to grant other AWS resources permission to invoke this Lambda function. + * + * @example + * ```typescript + * // Grant API Gateway permission to invoke the Lambda + * apiGatewayRole.addManagedPolicy(lambdaConstruct.executionPolicy); + * ``` + */ + public readonly executionPolicy: ManagedPolicy + + /** + * The Lambda function instance. + * + * Provides access to the underlying AWS Lambda function for additional configuration + * or to reference its ARN, name, or other properties. + * + * @example + * ```typescript + * // Get the function ARN + * const functionArn = lambdaConstruct.function.functionArn; + * + * // Add additional environment variables + * lambdaConstruct.function.addEnvironment('NEW_VAR', 'value'); + * ``` + */ + public readonly function: LambdaFunctionResource + + /** + * The IAM role assumed by the Lambda function during execution. + */ + public readonly executionRole: Role + + /** + * Creates a new PythonLambdaFunction construct. + * + * This construct creates: + * - A python Lambda function with + * - CloudWatch log group with KMS encryption + * - Managed IAM policy for writing logs + * - IAM role for execution with necessary permissions + * - Subscription filter on logs so they are forwarded to splunk + * - Managed IAM policy for invoking the Lambda function + * + * It also + * - attaches the Lambda Insights layer for monitoring. + * - adds cfnGuard suppressions for common issues. + * - adds cdk-nag suppressions for common issues. + * + * @example + * ```typescript + * const lambdaFunction = new PythonLambdaFunction(this, 'MyFunction', { + * functionName: 'my-lambda', + * projectBaseDir: '/path/to/monorepo' + * packageBasePath: 'packages/my-lambda', + * handler: 'app.handler.handler', + * environmentVariables: { + * TABLE_NAME: 'my-table' + * }, + * logRetentionInDays: 30, + * logLevel: 'INFO' + * }); + * @param scope - The scope in which to define this construct + * @param id - The scoped construct ID. Must be unique amongst siblings in the same scope + * @param props - Configuration properties for the Lambda function + */ + public constructor(scope: Construct, id: string, props: PythonLambdaFunctionProps) { + super(scope, id) + // Destructure with defaults + const { + functionName, + projectBaseDir, + packageBasePath, + handler, + environmentVariables, + additionalPolicies = [], // Default to empty array + logRetentionInDays = 30, // Default retention + logLevel = "INFO", // Default log level + dependencyLocation, + layers = [], // Default to empty array + timeoutInSeconds = 50, + runtime = Runtime.PYTHON_3_14, + architecture = Architecture.X86_64, + excludeFromAsset = [ + "tests", + "pytest.ini", + ".vscode", + "__pycache__", + "*.pyc" + ], + cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream, + splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy, + addSplunkSubscriptionFilter + } = props + + const {logGroup, role, insightsLayer} = createSharedLambdaResources(this, { + functionName, + logRetentionInDays, + additionalPolicies, + architecture, + cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream, + splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy, + addSplunkSubscriptionFilter + }) + + const layersToAdd = [insightsLayer] + if (dependencyLocation) { + const dependencyLayer = new LayerVersion(this, "DependencyLayer", { + removalPolicy: RemovalPolicy.DESTROY, + code: Code.fromAsset(join(projectBaseDir, dependencyLocation)), + compatibleArchitectures: [architecture] + }) + layersToAdd.push(dependencyLayer) + } + layersToAdd.push(...layers) + + // Create Lambda function with Python runtime and monitoring + const lambdaFunction = new LambdaFunctionResource(this, functionName, { + runtime: runtime, + memorySize: 256, + timeout: Duration.seconds(timeoutInSeconds), + functionName: functionName, + architecture, + handler: handler, + code: Code.fromAsset(join(projectBaseDir, packageBasePath), { + exclude: excludeFromAsset + }), + role, + environment: { + ...environmentVariables, + POWERTOOLS_LOG_LEVEL: logLevel + }, + logGroup, + layers: layersToAdd + }) + + // Suppress CFN guard rules for Lambda function + const cfnLambda = lambdaFunction.node.defaultChild as CfnFunction + addSuppressions([cfnLambda], [ + "LAMBDA_DLQ_CHECK", + "LAMBDA_INSIDE_VPC", + "LAMBDA_CONCURRENCY_CHECK" + ]) + + // Create policy for external services to invoke this Lambda + const executionManagedPolicy = new ManagedPolicy(this, "ExecuteLambdaManagedPolicy", { + description: `execute lambda ${functionName}`, + statements: [ + new PolicyStatement({ + actions: ["lambda:InvokeFunction"], + resources: [lambdaFunction.functionArn] + }) + ] + }) + + // Export Lambda function and sexecution policy for use by other constructs + this.function = lambdaFunction + this.executionPolicy = executionManagedPolicy + this.executionRole = role + } +} diff --git a/packages/cdkConstructs/src/constructs/RestApiGateway.ts b/packages/cdkConstructs/src/constructs/RestApiGateway.ts new file mode 100644 index 00000000..6870360f --- /dev/null +++ b/packages/cdkConstructs/src/constructs/RestApiGateway.ts @@ -0,0 +1,230 @@ +import {Fn, RemovalPolicy} from "aws-cdk-lib" +import { + CfnStage, + EndpointType, + LogGroupLogDestination, + MethodLoggingLevel, + MTLSConfig, + RestApi, + SecurityPolicy +} from "aws-cdk-lib/aws-apigateway" +import { + IManagedPolicy, + IRole, + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal +} from "aws-cdk-lib/aws-iam" +import {Stream} from "aws-cdk-lib/aws-kinesis" +import {Key} from "aws-cdk-lib/aws-kms" +import {CfnSubscriptionFilter, LogGroup} from "aws-cdk-lib/aws-logs" +import {Construct} from "constructs" +import {accessLogFormat} from "./RestApiGateway/accessLogFormat.js" +import {Certificate, CertificateValidation} from "aws-cdk-lib/aws-certificatemanager" +import {Bucket} from "aws-cdk-lib/aws-s3" +import {BucketDeployment, Source} from "aws-cdk-lib/aws-s3-deployment" +import {ARecord, HostedZone, RecordTarget} from "aws-cdk-lib/aws-route53" +import {ApiGateway as ApiGatewayTarget} from "aws-cdk-lib/aws-route53-targets" +import {NagSuppressions} from "cdk-nag" + +export interface RestApiGatewayProps { + readonly stackName: string + readonly logRetentionInDays: number + readonly mutualTlsTrustStoreKey: string | undefined + readonly forwardCsocLogs: boolean + readonly csocApiGatewayDestination: string + readonly executionPolicies: Array +} + +export class RestApiGateway extends Construct { + public readonly api: RestApi + public readonly role: IRole + + public constructor(scope: Construct, id: string, props: RestApiGatewayProps) { + super(scope, id) + + // Imports + const cloudWatchLogsKmsKey = Key.fromKeyArn( + this, "cloudWatchLogsKmsKey", Fn.importValue("account-resources:CloudwatchLogsKmsKeyArn")) + + const splunkDeliveryStream = Stream.fromStreamArn( + this, "SplunkDeliveryStream", Fn.importValue("lambda-resources:SplunkDeliveryStream")) + + const splunkSubscriptionFilterRole = Role.fromRoleArn( + this, "splunkSubscriptionFilterRole", Fn.importValue("lambda-resources:SplunkSubscriptionFilterRole")) + + const trustStoreBucket = Bucket.fromBucketArn( + this, "TrustStoreBucket", Fn.importValue("account-resources:TrustStoreBucket")) + + const trustStoreDeploymentBucket = Bucket.fromBucketArn( + this, "TrustStoreDeploymentBucket", Fn.importValue("account-resources:TrustStoreDeploymentBucket")) + + const trustStoreBucketKmsKey = Key.fromKeyArn( + this, "TrustStoreBucketKmsKey", Fn.importValue("account-resources:TrustStoreBucketKMSKey")) + + const epsDomainName: string = Fn.importValue("eps-route53-resources:EPS-domain") + const hostedZone = HostedZone.fromHostedZoneAttributes(this, "HostedZone", { + hostedZoneId: Fn.importValue("eps-route53-resources:EPS-ZoneID"), + zoneName: epsDomainName + }) + const serviceDomainName = `${props.stackName}.${epsDomainName}` + + // Resources + const logGroup = new LogGroup(this, "ApiGatewayAccessLogGroup", { + encryptionKey: cloudWatchLogsKmsKey, + logGroupName: `/aws/apigateway/${props.stackName}-apigw`, + retention: props.logRetentionInDays, + removalPolicy: RemovalPolicy.DESTROY + }) + + new CfnSubscriptionFilter(this, "ApiGatewayAccessLogsSplunkSubscriptionFilter", { + destinationArn: splunkDeliveryStream.streamArn, + filterPattern: "", + logGroupName: logGroup.logGroupName, + roleArn: splunkSubscriptionFilterRole.roleArn + }) + + if (props.forwardCsocLogs) { + new CfnSubscriptionFilter(this, "ApiGatewayAccessLogsCSOCSubscriptionFilter", { + destinationArn: props.csocApiGatewayDestination, + filterPattern: "", + logGroupName: logGroup.logGroupName, + roleArn: splunkSubscriptionFilterRole.roleArn + }) + } + + const certificate = new Certificate(this, "Certificate", { + domainName: serviceDomainName, + validation: CertificateValidation.fromDns(hostedZone) + }) + + let mtlsConfig: MTLSConfig | undefined + + if (props.mutualTlsTrustStoreKey) { + const trustStoreKeyPrefix = `cpt-api/${props.stackName}-truststore` + const logGroup = new LogGroup(scope, "LambdaLogGroup", { + encryptionKey: cloudWatchLogsKmsKey, + logGroupName: `/aws/lambda/${props.stackName}-truststore-deployment`, + retention: props.logRetentionInDays, + removalPolicy: RemovalPolicy.DESTROY + }) + const trustStoreDeploymentPolicy = new ManagedPolicy(this, "TrustStoreDeploymentPolicy", { + statements: [ + new PolicyStatement({ + actions: [ + "s3:ListBucket" + ], + resources: [ + trustStoreBucket.bucketArn, + trustStoreDeploymentBucket.bucketArn + ] + }), + new PolicyStatement({ + actions: [ + "s3:GetObject" + ], + resources: [trustStoreBucket.arnForObjects(props.mutualTlsTrustStoreKey)] + }), + new PolicyStatement({ + actions: [ + "s3:DeleteObject", + "s3:PutObject" + ], + resources: [ + trustStoreDeploymentBucket.arnForObjects(trustStoreKeyPrefix + "/" + props.mutualTlsTrustStoreKey) + ] + }), + new PolicyStatement({ + actions: [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey" + ], + resources: [trustStoreBucketKmsKey.keyArn] + }), + new PolicyStatement({ + actions: [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + resources: [ + logGroup.logGroupArn, + `${logGroup.logGroupArn}:log-stream:*` + ] + }) + ] + }) + NagSuppressions.addResourceSuppressions(trustStoreDeploymentPolicy, [ + { + id: "AwsSolutions-IAM5", + // eslint-disable-next-line max-len + reason: "Suppress error for not having wildcards in permissions. This is a fine as we need to have permissions on all log streams under path" + } + ]) + const trustStoreDeploymentRole = new Role(this, "TrustStoreDeploymentRole", { + assumedBy: new ServicePrincipal("lambda.amazonaws.com"), + managedPolicies: [trustStoreDeploymentPolicy] + }).withoutPolicyUpdates() + const deployment = new BucketDeployment(this, "TrustStoreDeployment", { + sources: [Source.bucket(trustStoreBucket, props.mutualTlsTrustStoreKey)], + destinationBucket: trustStoreDeploymentBucket, + destinationKeyPrefix: trustStoreKeyPrefix, + extract: false, + retainOnDelete: false, + role: trustStoreDeploymentRole, + logGroup: logGroup + }) + mtlsConfig = { + bucket: deployment.deployedBucket, + key: trustStoreKeyPrefix + "/" + props.mutualTlsTrustStoreKey + } + } + + const apiGateway = new RestApi(this, "ApiGateway", { + restApiName: `${props.stackName}-apigw`, + domainName: { + domainName: serviceDomainName, + certificate: certificate, + securityPolicy: SecurityPolicy.TLS_1_2, + endpointType: EndpointType.REGIONAL, + mtls: mtlsConfig + }, + disableExecuteApiEndpoint: mtlsConfig ? true : false, + endpointConfiguration: { + types: [EndpointType.REGIONAL] + }, + deploy: true, + deployOptions: { + accessLogDestination: new LogGroupLogDestination(logGroup), + accessLogFormat: accessLogFormat(), + loggingLevel: MethodLoggingLevel.INFO, + metricsEnabled: true + } + }) + + const role = new Role(this, "ApiGatewayRole", { + assumedBy: new ServicePrincipal("apigateway.amazonaws.com"), + managedPolicies: props.executionPolicies + }).withoutPolicyUpdates() + + new ARecord(this, "ARecord", { + recordName: props.stackName, + target: RecordTarget.fromAlias(new ApiGatewayTarget(apiGateway)), + zone: hostedZone + }) + + const cfnStage = apiGateway.deploymentStage.node.defaultChild as CfnStage + cfnStage.cfnOptions.metadata = { + guard: { + SuppressedRules: [ + "API_GW_CACHE_ENABLED_AND_ENCRYPTED" + ] + } + } + + // Outputs + this.api = apiGateway + this.role = role + } +} diff --git a/packages/cdkConstructs/src/constructs/RestApiGateway/LambdaEndpoint.ts b/packages/cdkConstructs/src/constructs/RestApiGateway/LambdaEndpoint.ts new file mode 100644 index 00000000..b4b762ea --- /dev/null +++ b/packages/cdkConstructs/src/constructs/RestApiGateway/LambdaEndpoint.ts @@ -0,0 +1,32 @@ +import {IResource, LambdaIntegration} from "aws-cdk-lib/aws-apigateway" +import {IRole} from "aws-cdk-lib/aws-iam" +import {IFunction} from "aws-cdk-lib/aws-lambda" +import {HttpMethod} from "aws-cdk-lib/aws-lambda" +import {Construct} from "constructs" + +export interface LambdaFunctionHolder { + readonly function: IFunction +} + +export interface LambdaEndpointProps { + parentResource: IResource + readonly resourceName: string + readonly method: HttpMethod + restApiGatewayRole: IRole + lambdaFunction: LambdaFunctionHolder +} + +export class LambdaEndpoint extends Construct { + resource: IResource + + public constructor(scope: Construct, id: string, props: LambdaEndpointProps) { + super(scope, id) + + const resource = props.parentResource.addResource(props.resourceName) + resource.addMethod(props.method, new LambdaIntegration(props.lambdaFunction.function, { + credentialsRole: props.restApiGatewayRole + })) + + this.resource = resource + } +} diff --git a/packages/cdkConstructs/src/constructs/RestApiGateway/accessLogFormat.ts b/packages/cdkConstructs/src/constructs/RestApiGateway/accessLogFormat.ts new file mode 100644 index 00000000..1504faf7 --- /dev/null +++ b/packages/cdkConstructs/src/constructs/RestApiGateway/accessLogFormat.ts @@ -0,0 +1,38 @@ +import {AccessLogFormat} from "aws-cdk-lib/aws-apigateway" + +export const accessLogFormat = () => { + return AccessLogFormat.custom(JSON.stringify({ + requestId: "$context.requestId", + ip: "$context.identity.sourceIp", + caller: "$context.identity.caller", + user: "$context.identity.user", + requestTime: "$context.requestTime", + httpMethod: "$context.httpMethod", + resourcePath: "$context.resourcePath", + status: "$context.status", + protocol: "$context.protocol", + responseLength: "$context.responseLength", + accountId: "$context.accountId", + apiId: "$context.apiId", + stage: "$context.stage", + api_key: "$context.identity.apiKey", + identity: { + sourceIp: "$context.identity.sourceIp", + userAgent: "$context.identity.userAgent", + clientCert: { + subjectDN: "$context.identity.clientCert.subjectDN", + issuerDN: "$context.identity.clientCert.issuerDN", + serialNumber: "$context.identity.clientCert.serialNumber", + validityNotBefore: "$context.identity.clientCert.validity.notBefore", + validityNotAfter: "$context.identity.clientCert.validity.notAfter" + } + }, + integration:{ + error: "$context.integration.error", + integrationStatus: "$context.integration.integrationStatus", + latency: "$context.integration.latency", + requestId: "$context.integration.requestId", + status: "$context.integration.status" + } + })) +} diff --git a/packages/cdkConstructs/src/constructs/TypescriptLambdaFunction.ts b/packages/cdkConstructs/src/constructs/TypescriptLambdaFunction.ts index 3c180847..32e5467f 100644 --- a/packages/cdkConstructs/src/constructs/TypescriptLambdaFunction.ts +++ b/packages/cdkConstructs/src/constructs/TypescriptLambdaFunction.ts @@ -1,25 +1,24 @@ -import {Duration, Fn, RemovalPolicy} from "aws-cdk-lib" +import {Duration} from "aws-cdk-lib" import { IManagedPolicy, + IRole, ManagedPolicy, PolicyStatement, - Role, - ServicePrincipal + Role } from "aws-cdk-lib/aws-iam" -import {Stream} from "aws-cdk-lib/aws-kinesis" -import {Key} from "aws-cdk-lib/aws-kms" import { Architecture, CfnFunction, ILayerVersion, - LayerVersion, Runtime } from "aws-cdk-lib/aws-lambda" import {NodejsFunction, NodejsFunctionProps} from "aws-cdk-lib/aws-lambda-nodejs" -import {CfnLogGroup, CfnSubscriptionFilter, LogGroup} from "aws-cdk-lib/aws-logs" import {Construct} from "constructs" import {join} from "node:path" -import {NagSuppressions} from "cdk-nag" +import {createSharedLambdaResources} from "./lambdaSharedResources" +import {addSuppressions} from "../utils/helpers" +import {IKey} from "aws-cdk-lib/aws-kms" +import {CfnDeliveryStream} from "aws-cdk-lib/aws-kinesisfirehose" export interface TypescriptLambdaFunctionProps { /** @@ -83,21 +82,55 @@ export interface TypescriptLambdaFunctionProps { * @default Runtime.NODEJS_24_X */ readonly runtime?: Runtime + /** + * Optional architecture for the Lambda function. Defaults to x86_64. + * @default Architecture.X86_64 + */ + readonly architecture?: Architecture + /** + * Optional KMS key for encrypting CloudWatch Logs. + * If not provided, the value is imported from account resources export. + */ + readonly cloudWatchLogsKmsKey?: IKey + /** + * Optional IAM policy for allowing CloudWatch to use the KMS key for encrypting logs. + * If not provided, the value is imported from account resources export. + */ + readonly cloudwatchEncryptionKMSPolicy?: IManagedPolicy + /** + * Optional firehose delivery stream for forwarding logs to Splunk. + * If not provided, the value is imported from account resources export. + */ + readonly splunkDeliveryStream?: CfnDeliveryStream + /** + * Optional IAM role for the subscription filter that forwards logs to Splunk. + * If not provided, the value is imported from account resources export. + */ + readonly splunkSubscriptionFilterRole?: IRole + /** + * Optional IAM policy for allowing lambdas to use Lambda Insights log groups and streams. + * If not provided, the value is imported from account resources export. + */ + readonly lambdaInsightsLogGroupPolicy?: IManagedPolicy + /** + * Whether to create a subscription filter on the Lambda log group to forward logs to Splunk. Defaults to true. + */ + readonly addSplunkSubscriptionFilter?: boolean } -const insightsLayerArn = "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:60" const getDefaultLambdaOptions = ( packageBasePath: string, projectBaseDir: string, timeoutInSeconds: number, - runtime: Runtime + runtime: Runtime, + architecture: Architecture ): NodejsFunctionProps => { return { runtime: runtime, projectRoot: projectBaseDir, memorySize: 256, timeout: Duration.seconds(timeoutInSeconds), - architecture: Architecture.X86_64, + architecture: architecture, handler: "handler", bundling: { minify: true, @@ -143,6 +176,11 @@ export class TypescriptLambdaFunction extends Construct { */ public readonly function: NodejsFunction + /** + * The IAM role assumed by the Lambda function during execution. + */ + public readonly executionRole: Role + /** * Creates a new TypescriptLambdaFunction construct. * @@ -163,6 +201,7 @@ export class TypescriptLambdaFunction extends Construct { * ```typescript * const lambdaFunction = new TypescriptLambdaFunction(this, 'MyFunction', { * functionName: 'my-lambda', + * projectBaseDir: '/path/to/monorepo', * packageBasePath: 'packages/my-lambda', * entryPoint: 'src/handler.ts', * environmentVariables: { @@ -171,8 +210,7 @@ export class TypescriptLambdaFunction extends Construct { * logRetentionInDays: 30, * logLevel: 'INFO', * version: '1.0.0', - * commitId: 'abc123', - * baseDir: '/path/to/monorepo' + * commitId: 'abc123' * }); * @param scope - The scope in which to define this construct * @param id - The scoped construct ID. Must be unique amongst siblings in the same scope @@ -195,87 +233,31 @@ export class TypescriptLambdaFunction extends Construct { layers = [], // Default to empty array projectBaseDir, timeoutInSeconds = 50, - runtime = Runtime.NODEJS_24_X + runtime = Runtime.NODEJS_24_X, + architecture = Architecture.X86_64, + cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream, + splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy, + addSplunkSubscriptionFilter } = props - // Imports - const cloudWatchLogsKmsKey = Key.fromKeyArn( - this, "cloudWatchLogsKmsKey", Fn.importValue("account-resources:CloudwatchLogsKmsKeyArn")) - - const splunkDeliveryStream = Stream.fromStreamArn( - this, "SplunkDeliveryStream", Fn.importValue("lambda-resources:SplunkDeliveryStream")) - - const splunkSubscriptionFilterRole = Role.fromRoleArn( - this, "splunkSubscriptionFilterRole", Fn.importValue("lambda-resources:SplunkSubscriptionFilterRole")) - - const lambdaInsightsLogGroupPolicy = ManagedPolicy.fromManagedPolicyArn( - this, "lambdaInsightsLogGroupPolicy", Fn.importValue("lambda-resources:LambdaInsightsLogGroupPolicy")) - - const cloudwatchEncryptionKMSPolicy = ManagedPolicy.fromManagedPolicyArn( - this, "cloudwatchEncryptionKMSPolicyArn", Fn.importValue("account-resources:CloudwatchEncryptionKMSPolicyArn")) - - const insightsLambdaLayer = LayerVersion.fromLayerVersionArn( - this, "LayerFromArn", insightsLayerArn) - - // Resources - const logGroup = new LogGroup(this, "LambdaLogGroup", { - encryptionKey: cloudWatchLogsKmsKey, - logGroupName: `/aws/lambda/${functionName}`, - retention: logRetentionInDays, - removalPolicy: RemovalPolicy.DESTROY - }) - - const cfnlogGroup = logGroup.node.defaultChild as CfnLogGroup - cfnlogGroup.cfnOptions.metadata = { - guard: { - SuppressedRules: [ - "CW_LOGGROUP_RETENTION_PERIOD_CHECK" - ] - } - } - - new CfnSubscriptionFilter(this, "LambdaLogsSplunkSubscriptionFilter", { - destinationArn: splunkDeliveryStream.streamArn, - filterPattern: "", - logGroupName: logGroup.logGroupName, - roleArn: splunkSubscriptionFilterRole.roleArn - - }) - - const putLogsManagedPolicy = new ManagedPolicy(this, "LambdaPutLogsManagedPolicy", { - description: `write to ${functionName} logs`, - statements: [ - new PolicyStatement({ - actions: [ - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - resources: [ - logGroup.logGroupArn, - `${logGroup.logGroupArn}:log-stream:*` - ] - })] - }) - NagSuppressions.addResourceSuppressions(putLogsManagedPolicy, [ - { - id: "AwsSolutions-IAM5", - // eslint-disable-next-line max-len - reason: "Suppress error for not having wildcards in permissions. This is a fine as we need to have permissions on all log streams under path" - } - ]) - - const role = new Role(this, "LambdaRole", { - assumedBy: new ServicePrincipal("lambda.amazonaws.com"), - managedPolicies: [ - putLogsManagedPolicy, - lambdaInsightsLogGroupPolicy, - cloudwatchEncryptionKMSPolicy, - ...(additionalPolicies) - ] + const {logGroup, role, insightsLayer} = createSharedLambdaResources(this, { + functionName, + logRetentionInDays, + additionalPolicies, + architecture, + cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream, + splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy, + addSplunkSubscriptionFilter }) const lambdaFunction = new NodejsFunction(this, functionName, { - ...getDefaultLambdaOptions(packageBasePath, projectBaseDir, timeoutInSeconds, runtime), + ...getDefaultLambdaOptions(packageBasePath, projectBaseDir, timeoutInSeconds, runtime, architecture), functionName: `${functionName}`, entry: join(projectBaseDir, packageBasePath, entryPoint), role, @@ -288,21 +270,17 @@ export class TypescriptLambdaFunction extends Construct { }, logGroup, layers: [ - insightsLambdaLayer, + insightsLayer, ...layers ] }) const cfnLambda = lambdaFunction.node.defaultChild as CfnFunction - cfnLambda.cfnOptions.metadata = { - guard: { - SuppressedRules: [ - "LAMBDA_DLQ_CHECK", - "LAMBDA_INSIDE_VPC", - "LAMBDA_CONCURRENCY_CHECK" - ] - } - } + addSuppressions([cfnLambda], [ + "LAMBDA_DLQ_CHECK", + "LAMBDA_INSIDE_VPC", + "LAMBDA_CONCURRENCY_CHECK" + ]) const executionManagedPolicy = new ManagedPolicy(this, "ExecuteLambdaManagedPolicy", { description: `execute lambda ${functionName}`, @@ -316,5 +294,6 @@ export class TypescriptLambdaFunction extends Construct { // Outputs this.function = lambdaFunction this.executionPolicy = executionManagedPolicy + this.executionRole = role } } diff --git a/packages/cdkConstructs/src/constructs/lambdaSharedResources.ts b/packages/cdkConstructs/src/constructs/lambdaSharedResources.ts new file mode 100644 index 00000000..20769cf8 --- /dev/null +++ b/packages/cdkConstructs/src/constructs/lambdaSharedResources.ts @@ -0,0 +1,136 @@ +import {Construct} from "constructs" +import {Fn, RemovalPolicy} from "aws-cdk-lib" +import {Architecture, ILayerVersion, LayerVersion} from "aws-cdk-lib/aws-lambda" +import {IKey, Key} from "aws-cdk-lib/aws-kms" +import {CfnLogGroup, CfnSubscriptionFilter, LogGroup} from "aws-cdk-lib/aws-logs" +import { + IManagedPolicy, + IRole, + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal +} from "aws-cdk-lib/aws-iam" +import {NagSuppressions} from "cdk-nag" +import {LAMBDA_INSIGHTS_LAYER_ARNS} from "../config" +import {addSuppressions} from "../utils/helpers" +import {CfnDeliveryStream} from "aws-cdk-lib/aws-kinesisfirehose" +import {Stream} from "aws-cdk-lib/aws-kinesis" + +export interface SharedLambdaResourceProps { + readonly functionName: string + readonly logRetentionInDays: number + readonly additionalPolicies: Array + readonly architecture: Architecture + readonly cloudWatchLogsKmsKey?: IKey + readonly cloudwatchEncryptionKMSPolicy?: IManagedPolicy + readonly splunkDeliveryStream?: CfnDeliveryStream + readonly splunkSubscriptionFilterRole?: IRole + readonly lambdaInsightsLogGroupPolicy?: IManagedPolicy + readonly addSplunkSubscriptionFilter?: boolean +} + +export interface SharedLambdaResources { + readonly logGroup: LogGroup + readonly role: Role + readonly insightsLayer: ILayerVersion +} + +export const createSharedLambdaResources = ( + scope: Construct, + props: SharedLambdaResourceProps +): SharedLambdaResources => { + const { + functionName, + logRetentionInDays, + additionalPolicies, + architecture, + cloudWatchLogsKmsKey = Key.fromKeyArn( + scope, "cloudWatchLogsKmsKey", Fn.importValue("account-resources:CloudwatchLogsKmsKeyArn")), + cloudwatchEncryptionKMSPolicy = ManagedPolicy.fromManagedPolicyArn( + scope, "cloudwatchEncryptionKMSPolicyArn", Fn.importValue("account-resources:CloudwatchEncryptionKMSPolicyArn")), + splunkDeliveryStream, + splunkSubscriptionFilterRole = Role.fromRoleArn( + scope, "splunkSubscriptionFilterRole", Fn.importValue("lambda-resources:SplunkSubscriptionFilterRole")), + lambdaInsightsLogGroupPolicy = ManagedPolicy.fromManagedPolicyArn( + scope, "lambdaInsightsLogGroupPolicy", Fn.importValue("lambda-resources:LambdaInsightsLogGroupPolicy")), + addSplunkSubscriptionFilter = true + } = props + const insightsLambdaLayerArn = architecture === Architecture.ARM_64 + ? LAMBDA_INSIGHTS_LAYER_ARNS.arm64 + : LAMBDA_INSIGHTS_LAYER_ARNS.x64 + const insightsLambdaLayer = LayerVersion.fromLayerVersionArn( + scope, "LayerFromArn", insightsLambdaLayerArn) + + const logGroup = new LogGroup(scope, "LambdaLogGroup", { + encryptionKey: cloudWatchLogsKmsKey, + logGroupName: `/aws/lambda/${functionName}`, + retention: logRetentionInDays, + removalPolicy: RemovalPolicy.DESTROY + }) + + const cfnlogGroup = logGroup.node.defaultChild as CfnLogGroup + addSuppressions([cfnlogGroup], ["CW_LOGGROUP_RETENTION_PERIOD_CHECK"]) + + if (addSplunkSubscriptionFilter) { + // This is in an if statement to ensure correct value is used + // importing and coercing to cfnDeliveryStream causes issues + if (splunkDeliveryStream) { + new CfnSubscriptionFilter(scope, "LambdaLogsSplunkSubscriptionFilter", { + destinationArn: splunkDeliveryStream.attrArn, + filterPattern: "", + logGroupName: logGroup.logGroupName, + roleArn: splunkSubscriptionFilterRole.roleArn + }) + } else { + const splunkDeliveryStreamImport = Stream.fromStreamArn( + scope, "SplunkDeliveryStream", Fn.importValue("lambda-resources:SplunkDeliveryStream")) + new CfnSubscriptionFilter(scope, "LambdaLogsSplunkSubscriptionFilter", { + destinationArn: splunkDeliveryStreamImport.streamArn, + filterPattern: "", + logGroupName: logGroup.logGroupName, + roleArn: splunkSubscriptionFilterRole.roleArn + }) + } + } + + const putLogsManagedPolicy = new ManagedPolicy(scope, "LambdaPutLogsManagedPolicy", { + description: `write to ${functionName} logs`, + statements: [ + new PolicyStatement({ + actions: [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + resources: [ + logGroup.logGroupArn, + `${logGroup.logGroupArn}:log-stream:*` + ] + }) + ] + }) + + NagSuppressions.addResourceSuppressions(putLogsManagedPolicy, [ + { + id: "AwsSolutions-IAM5", + // eslint-disable-next-line max-len + reason: "Suppress error for not having wildcards in permissions. This is a fine as we need to have permissions on all log streams under path" + } + ]) + + const role = new Role(scope, "LambdaRole", { + assumedBy: new ServicePrincipal("lambda.amazonaws.com"), + managedPolicies: [ + putLogsManagedPolicy, + lambdaInsightsLogGroupPolicy, + cloudwatchEncryptionKMSPolicy, + ...additionalPolicies + ] + }) + + return { + logGroup, + role, + insightsLayer: insightsLambdaLayer + } +} diff --git a/packages/cdkConstructs/src/index.ts b/packages/cdkConstructs/src/index.ts index d5ab521c..a964a253 100644 --- a/packages/cdkConstructs/src/index.ts +++ b/packages/cdkConstructs/src/index.ts @@ -1,2 +1,12 @@ // Export all constructs export * from "./constructs/TypescriptLambdaFunction.js" +export * from "./constructs/RestApiGateway.js" +export * from "./constructs/RestApiGateway/accessLogFormat.js" +export * from "./constructs/RestApiGateway/LambdaEndpoint.js" +export * from "./constructs/PythonLambdaFunction.js" +export * from "./apps/createApp.js" +export * from "./config/index.js" +export * from "./utils/helpers.js" +export * from "./stacks/deleteUnusedStacks.js" +export * from "./nag/pack/epsNagPack.js" +export * from "./changesets/checkDestructiveChanges" diff --git a/packages/cdkConstructs/src/nag/pack/epsNagPack.ts b/packages/cdkConstructs/src/nag/pack/epsNagPack.ts new file mode 100644 index 00000000..e6f0661b --- /dev/null +++ b/packages/cdkConstructs/src/nag/pack/epsNagPack.ts @@ -0,0 +1,219 @@ +import {NagMessageLevel, NagPack, NagPackProps} from "cdk-nag" +import {IConstruct} from "constructs" +import {CfnResource} from "aws-cdk-lib" +import {ApiGWMutualTls, APIGWStructuredLogging} from "../rules" +import {LambdaFunctionPublicAccessProhibited} from "cdk-nag/lib/rules/lambda" +import {CloudWatchLogGroupEncrypted} from "cdk-nag/lib/rules/cloudwatch" +import {ALBHttpDropInvalidHeaderEnabled, ELBLoggingEnabled, ELBTlsHttpsListenersOnly} from "cdk-nag/lib/rules/elb" +import {APIGWAccessLogging} from "cdk-nag/lib/rules/apigw" +import { + IAMNoInlinePolicy, + IAMPolicyNoStatementsWithAdminAccess, + IAMPolicyNoStatementsWithFullAccess +} from "cdk-nag/lib/rules/iam" +import {S3BucketPublicReadProhibited, S3BucketPublicWriteProhibited, S3DefaultEncryptionKMS} from "cdk-nag/lib/rules/s3" +import {SecretsManagerUsingKMSKey} from "cdk-nag/lib/rules/secretsmanager" +import {SNSEncryptedKMS} from "cdk-nag/lib/rules/sns" +import {VPCDefaultSecurityGroupClosed, VPCFlowLogsEnabled} from "cdk-nag/lib/rules/vpc" +import {WAFv2LoggingEnabled} from "cdk-nag/lib/rules/waf" + +// Nag pack implementing EPS specific rules +// It implements API gateway must have mutual TLS enabled +// and rules from https://github.com/cdklabs/cdk-nag/blob/main/RULES.md that are not in aws-solutions pack + +export class EpsNagPack extends NagPack { + constructor(props?: NagPackProps) { + super(props) + this.packName = "EpsNagPack" + } + + public visit(node: IConstruct): void { + if (node instanceof CfnResource) { + this.applyRule({ + ruleSuffixOverride: "EPS1", + info: "API Gateway must does not use mutual TLS.", + explanation: "All non pull request deployments must enforce mutual TLS on api gateways.", + level: NagMessageLevel.ERROR, + rule: ApiGWMutualTls, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS2", + info: "The Lambda function permission grants public access.", + explanation: + "Public access allows anyone on the internet to perform unauthenticated actions on the function.", + level: NagMessageLevel.ERROR, + rule: LambdaFunctionPublicAccessProhibited, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS3", + info: "The CloudWatch Log Group is not encrypted with an AWS KMS key.", + explanation: + "To help protect sensitive data at rest, ensure encryption is enabled for your Amazon CloudWatch Log Groups.", + level: NagMessageLevel.ERROR, + rule: CloudWatchLogGroupEncrypted, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS4", + info: "The ALB does not have invalid HTTP header dropping enabled.", + explanation: + "Ensure that your Application Load Balancers (ALB) are configured to drop http headers.", + level: NagMessageLevel.ERROR, + rule: ALBHttpDropInvalidHeaderEnabled, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS5", + info: "The WAFv2 web ACL does not have logging enabled.", + explanation: + // eslint-disable-next-line max-len + "AWS WAF logging provides detailed information about the traffic that is analyzed by your web ACL. The logs record the time that AWS WAF received the request from your AWS resource, information about the request, and an action for the rule that each request matched.", + level: NagMessageLevel.ERROR, + rule: WAFv2LoggingEnabled, + node: node + }) + + this.applyRule({ + ruleSuffixOverride: "EPS6", + info: "The API does not have access logging enabled.", + explanation: + "Enabling access logs helps operators view who accessed an API and how the caller accessed the API.", + level: NagMessageLevel.ERROR, + rule: APIGWAccessLogging, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS7", + info: "The API Gateway logs are not configured for the JSON format.", + explanation: + "JSON Structured logging makes it easier to derive queries to answer questions about your application.", + level: NagMessageLevel.ERROR, + rule: APIGWStructuredLogging, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS8", + info: "The ELB does not have access logs enabled.", + explanation: + "Access logs allow operators to analyze traffic patterns and identify and troubleshoot security issues.", + level: NagMessageLevel.ERROR, + rule: ELBLoggingEnabled, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS9", + info: "The ELB listener is not configured for secure (HTTPs or SSL) protocols for client communication.", + explanation: + // eslint-disable-next-line max-len + "The SSL protocols enable secure communication by encrypting the communication between the client and the load balancer.", + level: NagMessageLevel.ERROR, + rule: ELBTlsHttpsListenersOnly, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS10", + info: "The IAM Group, User, or Role contains an inline policy.", + explanation: + // eslint-disable-next-line max-len + "AWS recommends to use managed policies instead of inline policies. The managed policies allow reusability, versioning and rolling back, and delegating permissions management.", + level: NagMessageLevel.ERROR, + rule: IAMNoInlinePolicy, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS11", + // eslint-disable-next-line max-len + info: "The IAM policy grants admin access, meaning the policy allows a principal to perform all actions on all resources.", + explanation: + // eslint-disable-next-line max-len + 'AWS Identity and Access Management (IAM) can help you incorporate the principles of least privilege and separation of duties with access permissions and authorizations, restricting policies from containing "Effect": "Allow" with "Action": "*" over "Resource": "*". Allowing users to have more privileges than needed to complete a task may violate the principle of least privilege and separation of duties.', + level: NagMessageLevel.ERROR, + rule: IAMPolicyNoStatementsWithAdminAccess, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS12", + // eslint-disable-next-line max-len + info: "The IAM policy grants full access, meaning the policy allows a principal to perform all actions on individual resources.", + explanation: + // eslint-disable-next-line max-len + "Ensure IAM Actions are restricted to only those actions that are needed. Allowing users to have more privileges than needed to complete a task may violate the principle of least privilege and separation of duties.", + level: NagMessageLevel.ERROR, + rule: IAMPolicyNoStatementsWithFullAccess, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS13", + // eslint-disable-next-line max-len + info: "The S3 Bucket does not prohibit public read access through its Block Public Access configurations and bucket ACLs.", + explanation: + "The management of access should be consistent with the classification of the data.", + level: NagMessageLevel.ERROR, + rule: S3BucketPublicReadProhibited, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS14", + // eslint-disable-next-line max-len + info: "The S3 Bucket does not prohibit public write access through its Block Public Access configurations and bucket ACLs.", + explanation: + "The management of access should be consistent with the classification of the data.", + level: NagMessageLevel.ERROR, + rule: S3BucketPublicWriteProhibited, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS15", + info: "The S3 Bucket is not encrypted with a KMS Key by default.", + explanation: + // eslint-disable-next-line max-len + "Ensure that encryption is enabled for your Amazon Simple Storage Service (Amazon S3) buckets. Because sensitive data can exist at rest in an Amazon S3 bucket, enable encryption at rest to help protect that data.", + level: NagMessageLevel.ERROR, + rule: S3DefaultEncryptionKMS, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS16", + info: "The secret is not encrypted with a KMS Customer managed key.", + explanation: + // eslint-disable-next-line max-len + "To help protect data at rest, ensure encryption with AWS Key Management Service (AWS KMS) is enabled for AWS Secrets Manager secrets. Because sensitive data can exist at rest in Secrets Manager secrets, enable encryption at rest to help protect that data.", + level: NagMessageLevel.ERROR, + rule: SecretsManagerUsingKMSKey, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS17", + info: "The SNS topic does not have KMS encryption enabled.", + explanation: + // eslint-disable-next-line max-len + "Because sensitive data can exist at rest in published messages, enable encryption at rest to help protect that data.", + level: NagMessageLevel.ERROR, + rule: SNSEncryptedKMS, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS18", + info: "The VPC's default security group allows inbound or outbound traffic.", + explanation: + // eslint-disable-next-line max-len + "When creating a VPC through CloudFormation, the default security group will always be open. Therefore it is important to always close the default security group after stack creation whenever a VPC is created. Restricting all the traffic on the default security group helps in restricting remote access to your AWS resources.", + level: NagMessageLevel.ERROR, + rule: VPCDefaultSecurityGroupClosed, + node: node + }) + this.applyRule({ + ruleSuffixOverride: "EPS19", + info: "The VPC does not have an associated Flow Log.", + explanation: + // eslint-disable-next-line max-len + "The VPC flow logs provide detailed records for information about the IP traffic going to and from network interfaces in your Amazon Virtual Private Cloud (Amazon VPC). By default, the flow log record includes values for the different components of the IP flow, including the source, destination, and protocol.", + level: NagMessageLevel.ERROR, + rule: VPCFlowLogsEnabled, + node: node + }) + } + } +} diff --git a/packages/cdkConstructs/src/nag/rules/APIGWStructuredLogging.ts b/packages/cdkConstructs/src/nag/rules/APIGWStructuredLogging.ts new file mode 100644 index 00000000..bec59a2a --- /dev/null +++ b/packages/cdkConstructs/src/nag/rules/APIGWStructuredLogging.ts @@ -0,0 +1,73 @@ + +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +Modifications copyright (c) 2026 NHS Digital – see THIRD_PARTY_NOTICES.md +*/ +import {parse} from "node:path" +import {CfnResource, Stack} from "aws-cdk-lib" +import {CfnDeployment, CfnStage} from "aws-cdk-lib/aws-apigateway" +import {CfnStage as CfnStageV2} from "aws-cdk-lib/aws-apigatewayv2" +import {CfnApi, CfnHttpApi} from "aws-cdk-lib/aws-sam" +import {NagRuleCompliance} from "cdk-nag" + +// Copied from https://github.com/cdklabs/cdk-nag/blob/main/src/rules/apigw/APIGWStructuredLogging.ts +// with minor adjustments to handle CfnDeployment access log settings possibly being undefined +// see https://github.com/cdklabs/cdk-nag/issues/2267 +// and https://github.com/cdklabs/cdk-nag/pull/2268 + +const isJSON = (str: string) => { + try { + JSON.parse(str) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return false + } + return true +} + +/** + * API Gateway logs are configured in JSON format. + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if ( + node instanceof CfnApi || + node instanceof CfnHttpApi || + node instanceof CfnStage + ) { + const accessLogSetting = Stack.of(node).resolve(node.accessLogSetting) + if (!accessLogSetting ) { + return NagRuleCompliance.NOT_APPLICABLE + } + if (isJSON(accessLogSetting.format)) { + return NagRuleCompliance.COMPLIANT + } + return NagRuleCompliance.NON_COMPLIANT + } else if (node instanceof CfnDeployment) { + const stageDescription = Stack.of(node).resolve(node.stageDescription) + const accessLogSetting = stageDescription?.accessLogSetting + if (!accessLogSetting) { + return NagRuleCompliance.NOT_APPLICABLE + } + if (isJSON(accessLogSetting.format)) { + return NagRuleCompliance.COMPLIANT + } + return NagRuleCompliance.NON_COMPLIANT + } else if (node instanceof CfnStageV2) { + const accessLogSetting = Stack.of(node).resolve(node.accessLogSettings) + if (!accessLogSetting) { + return NagRuleCompliance.NOT_APPLICABLE + } + if (isJSON(accessLogSetting.format)) { + return NagRuleCompliance.COMPLIANT + } + return NagRuleCompliance.NON_COMPLIANT + } + return NagRuleCompliance.NOT_APPLICABLE + + }, + "name", + {value: parse(__filename).name} +) diff --git a/packages/cdkConstructs/src/nag/rules/ApiGWMutualTls.ts b/packages/cdkConstructs/src/nag/rules/ApiGWMutualTls.ts new file mode 100644 index 00000000..d5a78169 --- /dev/null +++ b/packages/cdkConstructs/src/nag/rules/ApiGWMutualTls.ts @@ -0,0 +1,48 @@ +import {parse} from "node:path" +import {CfnResource, Stack} from "aws-cdk-lib" +import {CfnDomainName} from "aws-cdk-lib/aws-apigateway" +import {NagRules, NagRuleCompliance} from "cdk-nag" +/** + * APIs gateway have mutual TLS enabled unless this is a pull request + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnDomainName) { + const stack = Stack.of(node) + // Try getting isPullRequest context value + const isPullRequest = stack.node.tryGetContext("isPullRequest") === true + + if (isPullRequest) { + return NagRuleCompliance.COMPLIANT + } + + const mutualTls = node.mutualTlsAuthentication + if (mutualTls === undefined) { + return NagRuleCompliance.NON_COMPLIANT + } + + const trustStoreUri = NagRules.resolveIfPrimitive( + node, + (mutualTls as CfnDomainName.MutualTlsAuthenticationProperty) + .truststoreUri + ) + + const trustStoreVersion = NagRules.resolveIfPrimitive( + node, + (mutualTls as CfnDomainName.MutualTlsAuthenticationProperty) + .truststoreVersion + ) + + if (trustStoreUri === undefined || trustStoreVersion === undefined) { + return NagRuleCompliance.NON_COMPLIANT + } + + return NagRuleCompliance.COMPLIANT + } + + return NagRuleCompliance.NOT_APPLICABLE + }, + "name", + {value: parse(__filename).name} +) diff --git a/packages/cdkConstructs/src/nag/rules/index.ts b/packages/cdkConstructs/src/nag/rules/index.ts new file mode 100644 index 00000000..d4b8d670 --- /dev/null +++ b/packages/cdkConstructs/src/nag/rules/index.ts @@ -0,0 +1,2 @@ +export {default as ApiGWMutualTls} from "./ApiGWMutualTls" +export {default as APIGWStructuredLogging} from "./APIGWStructuredLogging" diff --git a/packages/cdkConstructs/src/stacks/deleteUnusedStacks.ts b/packages/cdkConstructs/src/stacks/deleteUnusedStacks.ts new file mode 100644 index 00000000..d20dea22 --- /dev/null +++ b/packages/cdkConstructs/src/stacks/deleteUnusedStacks.ts @@ -0,0 +1,316 @@ +import { + CloudFormationClient, + DeleteStackCommand, + ListStacksCommand, + StackSummary +} from "@aws-sdk/client-cloudformation" +import { + ChangeResourceRecordSetsCommand, + ListHostedZonesByNameCommand, + ListResourceRecordSetsCommand, + ResourceRecordSet, + Route53Client +} from "@aws-sdk/client-route-53" + +/** + * Deletes unused CloudFormation stacks and their associated Route 53 CNAME records. + * + * A stack is considered unused if it is a superseded version of the base stack + * (and is not within the 24‑hour embargo window). + * + * @param baseStackName - Base name/prefix of the CloudFormation stacks to evaluate. + * @param getActiveVersions - Function to get the currently active versions. + * @param hostedZoneName - Hosted zone name used to look up Route 53 records. + * (Only required if stacks have CNAME records that need cleaning up.) + * @returns A promise that resolves when all eligible stacks have been processed. + */ +export async function deleteUnusedMainStacks( + baseStackName: string, + getActiveVersions: () => Promise, + hostedZoneName?: string | undefined +): Promise { + const cloudFormationClient = new CloudFormationClient({}) + const route53Client = new Route53Client({}) + const {hostedZoneId, cnameRecords} = await getHostedZoneInfo(route53Client, hostedZoneName) + const activeVersions = await getActiveVersions() + console.log("checking cloudformation stacks") + + const allStacks = await listAllStacks(cloudFormationClient) + const activeVersionDeployed = allStacks.find(stack => { + const versionInfo = getVersion(stack.StackName!, baseStackName) + if (!versionInfo) { + return false + } + const {version, isSandbox} = versionInfo + return !isSandbox && version === activeVersions.baseEnvVersion?.replaceAll(".", "-") + })?.CreationTime + if (isEmbargoed(activeVersionDeployed)) { + console.log( + `Active version ${activeVersions.baseEnvVersion} deployed less than 24 hours ago,` + + "skipping deletion of superseded stacks") + return + } + + for (const stack of allStacks) { + if (stack.StackStatus === "DELETE_COMPLETE" || !stack.StackName) { + continue + } + + const stackName = stack.StackName + if (!isSupersededVersion(stack, baseStackName, activeVersions)) { + continue + } + + await deleteStack(cloudFormationClient, route53Client, hostedZoneId, cnameRecords, stackName) + } +} + +/** + * Deletes unused CloudFormation stacks and their associated Route 53 CNAME records. + * + * A stack is considered unused if it represents a pull request deployment whose PR has been closed. + * + * @param baseStackName - Base name/prefix of the CloudFormation stacks to evaluate. + * @param repoName - GitHub repository name used to look up pull request state. + * @param hostedZoneName - Hosted zone name used to look up Route 53 records. + * (Only required if stacks have CNAME records that need cleaning up.) + * @returns A promise that resolves when all eligible stacks have been processed. + */ +export async function deleteUnusedPrStacks( + baseStackName: string, + repoName: string, + hostedZoneName?: string | undefined): Promise { + const cloudFormationClient = new CloudFormationClient({}) + const route53Client = new Route53Client({}) + const {hostedZoneId, cnameRecords} = await getHostedZoneInfo(route53Client, hostedZoneName) + + console.log("checking cloudformation stacks") + + const allStacks = await listAllStacks(cloudFormationClient) + + for (const stack of allStacks) { + if (stack.StackStatus === "DELETE_COMPLETE" || !stack.StackName) { + continue + } + + const stackName = stack.StackName + if (!(await isClosedPullRequest(stackName, baseStackName, repoName))) { + continue + } + + await deleteStack(cloudFormationClient, route53Client, hostedZoneId, cnameRecords, stackName) + } +} + +async function deleteStack( + cloudFormationClient: CloudFormationClient, + route53Client: Route53Client, + hostedZoneId: string | undefined, + cnameRecords: Array, + stackName: string +): Promise { + await cloudFormationClient.send(new DeleteStackCommand({StackName: stackName})) + console.log("** Sleeping for 60 seconds to avoid 429 on delete stack **") + await new Promise((resolve) => setTimeout(resolve, 60_000)) + + console.log(`** going to delete CNAME records for stack ${stackName} **`) + const toDelete = cnameRecords.filter(r => r.Name?.includes(stackName)) + if (!hostedZoneId || toDelete.length === 0) { + console.log(`No CNAME records to delete for stack ${stackName}`) + return + } + await route53Client.send( + new ChangeResourceRecordSetsCommand({ + HostedZoneId: hostedZoneId, + ChangeBatch: { + Changes: toDelete.map(r => ({ + Action: "DELETE", + ResourceRecordSet: r + })) + } + }) + ) + + for (const record of toDelete) { + console.log(`Deleted CNAME record: ${record.Name}`) + } +} + +async function listAllStacks(cloudFormationClient: CloudFormationClient): Promise> { + const stacks: Array = [] + let nextToken: string | undefined + + do { + const response = await cloudFormationClient.send(new ListStacksCommand({NextToken: nextToken})) + + if (response.StackSummaries) { + stacks.push(...response.StackSummaries) + } + + nextToken = response.NextToken + } while (nextToken) + + return stacks +} + +async function getHostedZoneInfo( + route53Client: Route53Client, + hostedZoneName: string | undefined +): Promise<{ hostedZoneId: string | undefined, cnameRecords: Array }> { + if (!hostedZoneName) { + return {hostedZoneId: undefined, cnameRecords: []} + } + const response = await route53Client.send( + new ListHostedZonesByNameCommand({ + DNSName: hostedZoneName + }) + ) + + const hostedZoneId = response.HostedZones?.[0]?.Id + if (!hostedZoneId) { + console.log(`Hosted zone ${hostedZoneName} not found`) + return {hostedZoneId: undefined, cnameRecords: []} + } + + let cnameRecords: Array = [] + let nextRecordName: string | undefined + do { + const response = await route53Client.send( + new ListResourceRecordSetsCommand({ + HostedZoneId: hostedZoneId, + StartRecordName: nextRecordName + }) + ) + cnameRecords.push(...(response.ResourceRecordSets?.filter(record => record.Type === "CNAME") || [])) + nextRecordName = response.NextRecordName + } while (nextRecordName) + + return {hostedZoneId, cnameRecords} +} + +async function isClosedPullRequest(stackName: string, baseStackName: string, repoName: string): Promise { + const match = new RegExp(String.raw`^${baseStackName}-pr-(?\d+)(-sandbox)?$`).exec(stackName) + if (!match?.groups?.pullRequestId) { + return false + } + + const pullRequestId = match.groups.pullRequestId + console.log(`Checking pull request id ${pullRequestId}`) + const url = `https://api.github.com/repos/NHSDigital/${repoName}/pulls/${pullRequestId}` + + const headers: Record = { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${process.env.GITHUB_TOKEN}` + } + + const response = await fetch(url, {headers}) + if (!response.ok) { + console.log(`Failed to fetch PR ${pullRequestId}: ${response.status} ${await response.text()}`) + return false + } + + const data = (await response.json()) as {state?: string} + if (data.state !== "closed") { + console.log(`not going to delete stack ${stackName} as PR state is ${data.state}`) + return false + } + + console.log(`** going to delete stack ${stackName} as PR state is ${data.state} **`) + return true +} + +/** + * Represents the currently active API versions in the base environment + * and (optionally) the corresponding sandbox environment. + */ +export type ActiveVersions = { + /** Currently deployed version in the base APIGEE environment (e.g. "v1.2.3"). */ + baseEnvVersion: string + /** + * Currently deployed version in the sandbox APIGEE environment, or null when + * there is no sandbox deployment for the given base environment. + */ + sandboxEnvVersion: string | null +} + +/** + * Fetches the active API versions from the APIM status endpoint for the + * configured APIGEE environment, and where applicable its sandbox variant. + * + * The base environment is taken from `process.env.APIGEE_ENVIRONMENT`, and the + * sandbox environment is queried for `int` ("sandbox") and `internal-dev` + * ("internal-dev-sandbox"). Failures to resolve the sandbox version are + * logged and surfaced as `sandboxEnvVersion: null`. + * + * @param basePath - Base path of the API used to build the _status URL. + * @returns An object containing the active base and sandbox API versions. + */ +export async function getActiveApiVersions(basePath: string): Promise { + let apigeeEnv = process.env.APIGEE_ENVIRONMENT! + const baseEnvVersion = await getActiveApiVersion(apigeeEnv, basePath) + let sandboxEnvVersion: string | null = null + try { + if (apigeeEnv === "int") { + sandboxEnvVersion = await getActiveApiVersion("sandbox", basePath) + } else if (apigeeEnv === "internal-dev") { + sandboxEnvVersion = await getActiveApiVersion("internal-dev-sandbox", basePath) + } + } catch (error) { + console.log(`Failed to get active version for sandbox environment: ${(error as Error).message}`) + } + return {baseEnvVersion, sandboxEnvVersion} +} + +async function getActiveApiVersion(apigeeEnv: string, basePath: string): Promise { + const headers: Record = { + Accept: "application/json", + apikey: `${process.env.APIM_STATUS_API_KEY}` + } + let apimDomain = "api.service.nhs.uk" + if (apigeeEnv !== "prod") { + apimDomain = `${apigeeEnv}.api.service.nhs.uk` + } + const url = `https://${apimDomain}/${basePath}/_status` + console.log(`Checking live api status endpoint at ${url} for active version`) + const response = await fetch(url, {headers}) + if (!response.ok) { + throw new Error(`Failed to fetch active version from ${url}: ${response.status} ${await response.text()}`) + } + + const data = (await response.json()) as {checks: {healthcheck: {outcome: {versionNumber: string}}}} + return data.checks.healthcheck.outcome.versionNumber +} + +function getVersion(stackName: string, baseStackName: string): {version: string, isSandbox: boolean} | null { + const pattern = String.raw`^${baseStackName}(?-sandbox)?-(?[\da-z-]+)?$` + const match = new RegExp(pattern).exec(stackName) + if (!match?.groups?.version || match.groups.version.startsWith("pr-")) { + return null + } + return {version: match.groups.version, isSandbox: match.groups.sandbox === "-sandbox"} +} + +function isEmbargoed(deployDate: Date | undefined): boolean { + return !!deployDate && Date.now() - deployDate.getTime() < 24 * 60 * 60 * 1000 +} + +function isSupersededVersion( + stack: StackSummary, + baseStackName: string, + activeVersions: ActiveVersions +): boolean { + const versionInfo = getVersion(stack.StackName!, baseStackName) + if (!versionInfo) { + return false + } + if (isEmbargoed(stack.CreationTime)) { + console.log(`Stack ${stack.StackName} created less than 24 hours ago, keeping for potential rollback`) + return false + } + const {version, isSandbox} = versionInfo + const currentVersion = isSandbox ? activeVersions.sandboxEnvVersion : activeVersions.baseEnvVersion + if (!currentVersion) { + return false + } + return version !== currentVersion.replaceAll(".", "-") +} diff --git a/packages/cdkConstructs/src/utils/helpers.ts b/packages/cdkConstructs/src/utils/helpers.ts new file mode 100644 index 00000000..c2d0c321 --- /dev/null +++ b/packages/cdkConstructs/src/utils/helpers.ts @@ -0,0 +1,126 @@ +import {Stack, CfnResource} from "aws-cdk-lib" +import {NagPackSuppression, NagSuppressions} from "cdk-nag" +import {IConstruct} from "constructs" + +/** + * Locate CloudFormation resources by their synthesized `aws:cdk:path` metadata. + * + * Use this helper when logical IDs vary between synths but the fully-qualified + * construct path is stable (for example when targeting resources for nag + * suppressions). + * + * @param construct - Root construct that will be walked recursively. + * @param paths - One or more fully qualified `aws:cdk:path` strings to match. + * @returns Every resource whose metadata path equals one of the supplied paths. + */ +export function findCloudFormationResourcesByPath(construct: IConstruct, paths: Array): Array { + const matches: Array = [] + const targetPaths = new Set(paths) + const seen = new Set() + const search = (node: IConstruct): void => { + if (node instanceof CfnResource) { + const resourcePath = node.cfnOptions.metadata?.["aws:cdk:path"] + if (typeof resourcePath === "string" && targetPaths.has(resourcePath) && !seen.has(node.logicalId)) { + matches.push(node) + seen.add(node.logicalId) + } + } + for (const child of node.node.children) { + search(child) + } + } + search(construct) + return matches +} + +/** + * Locate CloudFormation resources by CloudFormation type. + * + * Recursively traverses the construct tree and returns all `CfnResource` + * instances that match the provided `AWS::::` type string. + * + * @param construct - Root construct to traverse. + * @param type - CloudFormation type identifier (for example `AWS::Lambda::Function`). + * @returns All resources whose `cfnResourceType` matches `type`. + */ +export function findCloudFormationResourcesByType(construct: IConstruct, type: string): Array { + const matches: Array = [] + const search = (node: IConstruct): void => { + if (node instanceof CfnResource && node.cfnResourceType === type) { + matches.push(node) + } + for (const child of node.node.children) { + search(child) + } + } + search(construct) + return matches +} +/** + * Merge cfn-guard rule suppressions onto the provided resources. + * + * Ensures the metadata structure exists, deduplicates rule IDs, and leaves any + * pre-existing suppressions intact. + * + * @param resources - CloudFormation resources that require suppressions. + * @param rules - One or more cfn-guard rule identifiers to suppress. + */ +export function addSuppressions(resources: Array, rules: Array): void { + resources.forEach(resource => { + resource.cfnOptions.metadata ??= {} + const existing = resource.cfnOptions.metadata.guard?.SuppressedRules || [] + const combined = Array.from(new Set([...existing, ...rules])) + resource.cfnOptions.metadata.guard = {SuppressedRules: combined} + }) +} + +/** + * Apply the default lambda-focused cfn-guard suppressions. + * + * Finds every `AWS::Lambda::Function` in the stack and suppresses the common + * Lambda guard rules related to DLQ usage, VPC placement, and reserved concurrency. + * + * @param stack - Stack containing the Lambda resources to update. + */ +export function addLambdaCfnGuardSuppressions(stack: Stack): void { + const allLambdas = findCloudFormationResourcesByType(stack, "AWS::Lambda::Function") + addSuppressions(allLambdas, ["LAMBDA_DLQ_CHECK", "LAMBDA_INSIDE_VPC", "LAMBDA_CONCURRENCY_CHECK"]) +} + +/** + * Attach identical nag suppressions to several construct paths. + * + * Invokes `safeAddNagSuppression` for each path, allowing missing paths to be + * skipped without failing the entire operation. + * + * @param stack - CDK stack that contains the constructs. + * @param paths - Paths to apply the suppression group to. + * @param suppressions - Suppression definitions shared by all targets. + * The suppressions must include id and reason and can optionally include appliesTo. + */ +export function safeAddNagSuppressionGroup( + stack: Stack, + paths: Array, + suppressions: Array +) { + paths.forEach(path => safeAddNagSuppression(stack, path, suppressions)) +} + +/** + * Attach nag suppressions to a single construct path. + * + * Wraps `NagSuppressions.addResourceSuppressionsByPath` and logs an info-level + * message when the path cannot be resolved, preventing build failures in + * partially synthesized stacks. + * + * @param stack - CDK stack that contains the construct. + * @param path - Fully qualified CDK path to the resource. + * @param suppressions - Suppression entries to apply. + */ +export function safeAddNagSuppression(stack: Stack, path: string, suppressions: Array) { + try { + NagSuppressions.addResourceSuppressionsByPath(stack, path, suppressions) + } catch (err) { + console.log(`Could not find path ${path}: ${err}`) + } +} diff --git a/packages/cdkConstructs/tests/apps/createApp.test.ts b/packages/cdkConstructs/tests/apps/createApp.test.ts new file mode 100644 index 00000000..b5383d1a --- /dev/null +++ b/packages/cdkConstructs/tests/apps/createApp.test.ts @@ -0,0 +1,200 @@ +/** + * Tests for createApp function + * + * This test suite covers the createApp function which creates a CDK App with + * standard configuration including: + * - Environment variable validation + * - App creation with proper aspects and tags + * - Stack properties configuration + * - Pull request detection and drift detection group modification + * - Regional configuration + * + * Note: The getBooleanConfigFromEnvVar function uses Boolean() which converts + * any non-empty string (including "false", "0", etc.) to true. Tests account + * for this behavior. + */ +import {App, Aspects, Tags} from "aws-cdk-lib" +import { + describe, + test, + beforeEach, + afterEach, + expect, + vi +} from "vitest" +import {createApp, type CreateAppParams} from "../../src/apps/createApp" +import {AwsSolutionsChecks} from "cdk-nag" + +describe("createApp", () => { + const originalEnv = process.env + const defaultParams: CreateAppParams = { + productName: "testProduct", + appName: "testApp", + repoName: "testRepo", + driftDetectionGroup: "test-drift-group" + } + const buildParams = (overrides: Partial = {}): CreateAppParams => ({ + ...defaultParams, + ...overrides + }) + + beforeEach(() => { + // Reset environment before each test + vi.resetModules() + process.env = {...originalEnv} + }) + + afterEach(() => { + // Restore environment after each test + process.env = originalEnv + }) + + describe("when all environment variables are set", () => { + beforeEach(() => { + process.env.CDK_CONFIG_versionNumber = "1.2.3" + process.env.CDK_CONFIG_commitId = "abc123def456" + process.env.CDK_CONFIG_isPullRequest = "false" + process.env.CDK_CONFIG_environment = "test-environment" + }) + + test("creates an App with correct configuration", () => { + const {app, props} = createApp(buildParams()) + + expect(app).toBeInstanceOf(App) + expect(props.version).toBe("1.2.3") + expect(props.commitId).toBe("abc123def456") + expect(props.isPullRequest).toBe(false) + expect(props.env?.region).toBe("eu-west-2") + }) + + test("uses custom region when provided", () => { + const {props} = createApp(buildParams({region: "us-east-1"})) + + expect(props.env?.region).toBe("us-east-1") + }) + + test("applies correct tags to the app", () => { + // Spy on Tags.of(app).add to verify tag calls + const addTagSpy = vi.fn() + const tagsOfSpy = vi.spyOn(Tags, "of").mockReturnValue({ + add: addTagSpy + } as unknown as Tags) + + const {app} = createApp(buildParams()) + + // Verify Tags.of was called with the app + expect(tagsOfSpy).toHaveBeenCalledWith(app) + + // Verify all expected tags were added with correct values + expect(addTagSpy).toHaveBeenCalledWith("TagVersion", "1") + expect(addTagSpy).toHaveBeenCalledWith("Programme", "EPS") + expect(addTagSpy).toHaveBeenCalledWith("Product", "testProduct") + expect(addTagSpy).toHaveBeenCalledWith("Owner", "england.epssupport@nhs.net") + expect(addTagSpy).toHaveBeenCalledWith("Product", "testProduct") + expect(addTagSpy).toHaveBeenCalledWith("CostCentre", "128997") + expect(addTagSpy).toHaveBeenCalledWith("Customer", "NHS England") + expect(addTagSpy).toHaveBeenCalledWith("data_classification", "5") + expect(addTagSpy).toHaveBeenCalledWith("DataType", "PII") + expect(addTagSpy).toHaveBeenCalledWith("Environment", "test-environment") + expect(addTagSpy).toHaveBeenCalledWith("ProjectType", "Production") + expect(addTagSpy).toHaveBeenCalledWith("PublicFacing", "Y") + expect(addTagSpy).toHaveBeenCalledWith("ServiceCategory", "Platinum") + expect(addTagSpy).toHaveBeenCalledWith("OnOffPattern", "AlwaysOn") + expect(addTagSpy).toHaveBeenCalledWith("DeploymentTool", "CDK") + expect(addTagSpy).toHaveBeenCalledWith("version", "1.2.3") + expect(addTagSpy).toHaveBeenCalledWith("commit", "abc123def456") + expect(addTagSpy).toHaveBeenCalledWith("cdkApp", "testApp") + expect(addTagSpy).toHaveBeenCalledWith("repo", "testRepo") + expect(addTagSpy).toHaveBeenCalledWith("cfnDriftDetectionGroup", "test-drift-group") + + // Verify exactly 19 tags were added + expect(addTagSpy).toHaveBeenCalledTimes(19) + + // Restore the spy + tagsOfSpy.mockRestore() + }) + + test("adds AwsSolutionsChecks aspect", () => { + const {app} = createApp(buildParams()) + + const aspects = Aspects.of(app).all + expect(aspects).toContainEqual(new AwsSolutionsChecks({verbose: true})) + }) + }) + + describe("when isPullRequest is true", () => { + beforeEach(() => { + process.env.CDK_CONFIG_versionNumber = "0.0.1-pr" + process.env.CDK_CONFIG_commitId = "pr123" + process.env.CDK_CONFIG_isPullRequest = "true" + process.env.CDK_CONFIG_environment = "test-environment" + }) + + test("correctly modifies props", () => { + const {props} = createApp(buildParams()) + + expect(props.version).toBe("0.0.1-pr") + expect(props.commitId).toBe("pr123") + expect(props.isPullRequest).toBe(true) + expect(props.environment).toBe("test-environment") + }) + + test("modifies drift detection group with -pull-request suffix", () => { + // Spy on Tags.of(app).add to verify tag calls + const addTagSpy = vi.fn() + const tagsOfSpy = vi.spyOn(Tags, "of").mockReturnValue({ + add: addTagSpy + } as unknown as Tags) + + const {app} = createApp(buildParams()) + + // Verify Tags.of was called with the app + expect(tagsOfSpy).toHaveBeenCalledWith(app) + + // Verify all expected tags were added with correct values + expect(addTagSpy).toHaveBeenCalledWith("cfnDriftDetectionGroup", "test-drift-group-pull-request") + }) + }) + + describe("when environment variables are missing", () => { + + test("throws error when versionNumber is not set", () => { + process.env.CDK_CONFIG_commitId = "abc123" + process.env.CDK_CONFIG_isPullRequest = "false" + process.env.CDK_CONFIG_environment = "test-environment" + + expect(() => { + createApp(buildParams()) + }).toThrow("Environment variable CDK_CONFIG_versionNumber is not set") + }) + + test("throws error when commitId is not set", () => { + process.env.CDK_CONFIG_versionNumber = "1.0.0" + process.env.CDK_CONFIG_isPullRequest = "false" + process.env.CDK_CONFIG_environment = "test-environment" + + expect(() => { + createApp(buildParams()) + }).toThrow("Environment variable CDK_CONFIG_commitId is not set") + }) + + test("throws error when isPullRequest is not set", () => { + process.env.CDK_CONFIG_versionNumber = "1.0.0" + process.env.CDK_CONFIG_commitId = "abc123" + process.env.CDK_CONFIG_environment = "test-environment" + expect(() => { + createApp(buildParams()) + }).toThrow("Environment variable CDK_CONFIG_isPullRequest is not set") + }) + + test("throws error when environment is not set", () => { + process.env.CDK_CONFIG_versionNumber = "1.0.0" + process.env.CDK_CONFIG_commitId = "abc123" + process.env.CDK_CONFIG_isPullRequest = "false" + expect(() => { + createApp(buildParams()) + }).toThrow("Environment variable CDK_CONFIG_environment is not set") + }) + + }) +}) diff --git a/packages/cdkConstructs/tests/changesets/checkDestructiveChanges.test.ts b/packages/cdkConstructs/tests/changesets/checkDestructiveChanges.test.ts new file mode 100644 index 00000000..dbd297f7 --- /dev/null +++ b/packages/cdkConstructs/tests/changesets/checkDestructiveChanges.test.ts @@ -0,0 +1,339 @@ +import {readFileSync} from "node:fs" +import {dirname, join} from "node:path" +import {fileURLToPath} from "node:url" +import { + beforeEach, + describe, + expect, + test, + vi, + afterEach +} from "vitest" +import { + checkDestructiveChanges, + checkDestructiveChangeSet, + AllowedDestructiveChange +} from "../../src/changesets/checkDestructiveChanges" + +const mockCloudFormationSend = vi.fn() + +vi.mock("@aws-sdk/client-cloudformation", () => { + class CloudFormationClient { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + config: any + constructor(config: { region: string }) { + this.config = config + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + send(command: any) { + return mockCloudFormationSend(command) + } + } + + class DescribeChangeSetCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + return {CloudFormationClient, DescribeChangeSetCommand} +}) + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) +const fixturesDir = join(__dirname, "examples") + +const loadChangeSet = (filePath: string) => JSON.parse(readFileSync(filePath, "utf-8")) + +const destructiveChangeSet = loadChangeSet(join(fixturesDir, "destructive_changeset.json")) +const safeChangeSet = loadChangeSet(join(fixturesDir, "safe_changeset.json")) + +beforeEach(() => { + mockCloudFormationSend.mockReset() +}) + +describe("checkDestructiveChanges", () => { + test("returns resources that require replacement", () => { + const replacements = checkDestructiveChanges(destructiveChangeSet) + + expect(replacements.length).toBeGreaterThan(0) + expect(replacements).toContainEqual({ + logicalId: "AlarmsAccountLambdaConcurrencyAlarm8AF49AD8", + physicalId: "monitoring-Account_Lambda_Concurrency", + resourceType: "AWS::CloudWatch::Alarm", + policyAction: "ReplaceAndDelete", + action: "Modify", + replacement: "True" + }) + }) + + test("returns an empty array when no replacements or removals exist", () => { + const replacements = checkDestructiveChanges(safeChangeSet) + + expect(replacements).toEqual([]) + }) + + test("includes resources marked for removal", () => { + const changeSet = { + Changes: [ + { + ResourceChange: { + PolicyAction: "Delete", + LogicalResourceId: "ResourceToRemove", + PhysicalResourceId: "physical-id", + ResourceType: "AWS::S3::Bucket", + Action: "Remove", + Replacement: "False" + } + } + ] + } + const replacements = checkDestructiveChanges(changeSet) + + expect(replacements).toEqual([ + { + logicalId: "ResourceToRemove", + physicalId: "physical-id", + resourceType: "AWS::S3::Bucket", + policyAction: "Delete", + action: "Remove", + replacement: "False" + } + ]) + }) + + test("marks changes with Delete policy action as destructive even without removal", () => { + const changeSet = { + Changes: [ + { + ResourceChange: { + PolicyAction: "Delete", + LogicalResourceId: "PolicyOnly", + PhysicalResourceId: "policy-only", + ResourceType: "Custom::Thing", + Action: "Modify", + Replacement: "False" + } + } + ] + } + + const replacements = checkDestructiveChanges(changeSet) + + expect(replacements).toEqual([ + { + logicalId: "PolicyOnly", + physicalId: "policy-only", + resourceType: "Custom::Thing", + policyAction: "Delete", + action: "Modify", + replacement: "False" + } + ]) + }) + + test("marks changes with ReplaceAndDelete policy action as destructive even when replacement is false", () => { + const changeSet = { + Changes: [ + { + ResourceChange: { + PolicyAction: "ReplaceAndDelete", + LogicalResourceId: "PolicyReplace", + PhysicalResourceId: "policy-replace", + ResourceType: "Custom::Thing", + Action: "Modify", + Replacement: "False" + } + } + ] + } + + const replacements = checkDestructiveChanges(changeSet) + + expect(replacements).toEqual([ + { + logicalId: "PolicyReplace", + physicalId: "policy-replace", + resourceType: "Custom::Thing", + policyAction: "ReplaceAndDelete", + action: "Modify", + replacement: "False" + } + ]) + }) + + test("does not mark conditional replacements as destructive when no other indicator is present", () => { + const changeSet = { + Changes: [ + { + ResourceChange: { + LogicalResourceId: "Conditional", + PhysicalResourceId: "conditional", + ResourceType: "Custom::Thing", + Action: "Modify", + Replacement: "Conditional" + } + } + ] + } + + const replacements = checkDestructiveChanges(changeSet) + + expect(replacements).toEqual([]) + }) +}) + +describe("checkDestructiveChangeSet", () => { + const logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined) + afterEach(() => { + logSpy.mockReset() + errorSpy.mockReset() + }) + + test("logs success when no destructive changes are present", async () => { + mockCloudFormationSend.mockResolvedValueOnce(safeChangeSet) + + await expect(checkDestructiveChangeSet("cs", "stack", "eu-west-2")).resolves.toBeUndefined() + + expect(mockCloudFormationSend).toHaveBeenCalledTimes(1) + const command = mockCloudFormationSend.mock.calls[0][0] as { input: { ChangeSetName: string; StackName: string } } + expect(command.input).toEqual({ChangeSetName: "cs", StackName: "stack"}) + expect(logSpy).toHaveBeenCalledWith("Change set cs for stack stack has no destructive changes that are not waived.") + expect(errorSpy).not.toHaveBeenCalled() + }) + + test("logs details and throws when destructive changes exist", async () => { + mockCloudFormationSend.mockResolvedValueOnce(destructiveChangeSet) + + await expect(checkDestructiveChangeSet("cs", "stack", "eu-west-2")) + .rejects.toThrow("Change set cs contains destructive changes") + + expect(mockCloudFormationSend).toHaveBeenCalledTimes(1) + expect(logSpy).not.toHaveBeenCalled() + expect(errorSpy).toHaveBeenCalledWith("Resources that require attention:") + }) + + test("allows matching destructive changes when waiver is active", async () => { + const changeSet = { + CreationTime: "2026-02-20T11:54:17.083Z", + StackName: "stack", + Changes: [ + { + ResourceChange: { + LogicalResourceId: "ResourceToRemove", + PhysicalResourceId: "physical-id", + ResourceType: "AWS::S3::Bucket", + PolicyAction: "Delete", + Action: "Remove" + } + } + ] + } + mockCloudFormationSend.mockResolvedValueOnce(changeSet) + + const allowedChanges: Array = [ + { + LogicalResourceId: "ResourceToRemove", + PhysicalResourceId: "physical-id", + ResourceType: "AWS::S3::Bucket", + PolicyAction: "Delete", + Action: "Remove", + Replacement: null, + ExpiryDate: "2026-03-01T00:00:00Z", + StackName: "stack", + AllowedReason: "Pending migration" + } + ] + + await expect(checkDestructiveChangeSet("cs", "stack", "eu-west-2", allowedChanges)) + .resolves.toBeUndefined() + + expect(mockCloudFormationSend).toHaveBeenCalledTimes(1) + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("Allowing destructive change ResourceToRemove")) + expect(logSpy).toHaveBeenCalledWith("Change set cs for stack stack has no destructive changes that are not waived.") + expect(errorSpy).not.toHaveBeenCalled() + }) + + test("throws when waiver expired before change set creation", async () => { + const changeSet = { + CreationTime: "2026-02-20T11:54:17.083Z", + StackName: "stack", + Changes: [ + { + ResourceChange: { + LogicalResourceId: "ResourceToRemove", + PhysicalResourceId: "physical-id", + ResourceType: "AWS::S3::Bucket", + PolicyAction: "Delete", + Action: "Remove" + } + } + ] + } + mockCloudFormationSend.mockResolvedValueOnce(changeSet) + + const allowedChanges: Array = [ + { + LogicalResourceId: "ResourceToRemove", + PhysicalResourceId: "physical-id", + ResourceType: "AWS::S3::Bucket", + PolicyAction: "Delete", + Action: "Remove", + Replacement: null, + ExpiryDate: "2026-02-01T00:00:00Z", + StackName: "stack", + AllowedReason: "Expired waiver" + } + ] + + await expect(checkDestructiveChangeSet("cs", "stack", "eu-west-2", allowedChanges)) + .rejects.toThrow("Change set cs contains destructive changes") + + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("Waiver for ResourceToRemove")) + expect(errorSpy).toHaveBeenCalledWith("Resources that require attention:") + expect(logSpy).not.toHaveBeenCalledWith("Change set cs for stack stack has no destructive changes.") + }) + + test("does not allow waivers that mismatch policy or action", async () => { + const changeSet = { + CreationTime: "2026-02-20T11:54:17.083Z", + StackName: "stack", + Changes: [ + { + ResourceChange: { + LogicalResourceId: "ResourceToRemove", + PhysicalResourceId: "physical-id", + ResourceType: "AWS::S3::Bucket", + PolicyAction: "Delete", + Action: "Remove" + } + } + ] + } + mockCloudFormationSend.mockResolvedValueOnce(changeSet) + + const allowedChanges: Array = [ + { + LogicalResourceId: "ResourceToRemove", + PhysicalResourceId: "physical-id", + ResourceType: "AWS::S3::Bucket", + PolicyAction: "ReplaceAndDelete", + Action: "Remove", + Replacement: null, + ExpiryDate: "2026-03-01T00:00:00Z", + StackName: "stack", + AllowedReason: "Incorrect policy" + } + ] + + await expect(checkDestructiveChangeSet("cs", "stack", "eu-west-2", allowedChanges)) + .rejects.toThrow("Change set cs contains destructive changes") + + expect(errorSpy).toHaveBeenCalledWith("Resources that require attention:") + }) +}) diff --git a/packages/cdkConstructs/tests/changesets/examples/destructive_changeset.json b/packages/cdkConstructs/tests/changesets/examples/destructive_changeset.json new file mode 100644 index 00000000..b01fc0fe --- /dev/null +++ b/packages/cdkConstructs/tests/changesets/examples/destructive_changeset.json @@ -0,0 +1,1498 @@ +{ + "Changes": [ + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "ReplaceAndDelete", + "Action": "Modify", + "LogicalResourceId": "AlarmsAccountLambdaConcurrencyAlarm8AF49AD8", + "PhysicalResourceId": "monitoring-Account_Lambda_Concurrency", + "ResourceType": "AWS::CloudWatch::Alarm", + "Replacement": "True", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Threshold", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "ActionsEnabled", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "AlarmName", + "RequiresRecreation": "Always" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "ReplaceAndDelete", + "Action": "Modify", + "LogicalResourceId": "AlarmsAccountLambdaConcurrencyWARNINGAlarm5C305BA1", + "PhysicalResourceId": "monitoring-Account_Lambda_Concurrency_WARNING", + "ResourceType": "AWS::CloudWatch::Alarm", + "Replacement": "True", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Threshold", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "ActionsEnabled", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "AlarmName", + "RequiresRecreation": "Always" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "ReplaceAndDelete", + "Action": "Modify", + "LogicalResourceId": "AlarmsAccountLambdaErrorsAlarmC3513C53", + "PhysicalResourceId": "monitoring-Account_Lambda_Errors", + "ResourceType": "AWS::CloudWatch::Alarm", + "Replacement": "True", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "ActionsEnabled", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "AlarmName", + "RequiresRecreation": "Always" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "ReplaceAndDelete", + "Action": "Modify", + "LogicalResourceId": "AlarmsAccountLambdaThrottlesAlarm12FF5DEF", + "PhysicalResourceId": "monitoring-Account_Lambda_Throttles", + "ResourceType": "AWS::CloudWatch::Alarm", + "Replacement": "True", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "ActionsEnabled", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "AlarmName", + "RequiresRecreation": "Always" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "ReplaceAndDelete", + "Action": "Modify", + "LogicalResourceId": "AlarmsAccountStepFunctionErrorsAlarm737F0047", + "PhysicalResourceId": "monitoring-Account_StepFunction_Errors", + "ResourceType": "AWS::CloudWatch::Alarm", + "Replacement": "True", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "ActionsEnabled", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "AlarmName", + "RequiresRecreation": "Always" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "ReplaceAndDelete", + "Action": "Modify", + "LogicalResourceId": "AlarmsAccountStepFunctionExecutionsAbortedAlarmADC4F811", + "PhysicalResourceId": "monitoring-Account_StepFunction_ExecutionsAborted", + "ResourceType": "AWS::CloudWatch::Alarm", + "Replacement": "True", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "ActionsEnabled", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "AlarmName", + "RequiresRecreation": "Always" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "ReplaceAndDelete", + "Action": "Modify", + "LogicalResourceId": "AlarmsAccountStepFunctionExecutionsThrottledAlarmAFBE1044", + "PhysicalResourceId": "monitoring-Account_StepFunction_ExecutionsThrottled", + "ResourceType": "AWS::CloudWatch::Alarm", + "Replacement": "True", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "ActionsEnabled", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "AlarmName", + "RequiresRecreation": "Always" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "ReplaceAndDelete", + "Action": "Modify", + "LogicalResourceId": "AlarmsAccountStepFunctionExecutionsTimedOutAlarm207042BA", + "PhysicalResourceId": "monitoring-Account_StepFunction_ExecutionsTimedOut", + "ResourceType": "AWS::CloudWatch::Alarm", + "Replacement": "True", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "ActionsEnabled", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "AlarmName", + "RequiresRecreation": "Always" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "AlarmsalertSuppressions80C5A1E6", + "PhysicalResourceId": "monitoring-alertSuppressions", + "ResourceType": "AWS::SSM::Parameter", + "Replacement": "False", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "CDKMetadata", + "PhysicalResourceId": "a388b600-9eaa-11f0-949f-0294dec6a941", + "ResourceType": "AWS::CDK::Metadata", + "Replacement": "Conditional", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Analytics", + "RequiresRecreation": "Conditionally" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "FunctionsReadAlertSuppressionsPolicy8AAF0540", + "PhysicalResourceId": "arn:aws:iam::591291862413:policy/monitoring-FunctionsReadAlertSuppressionsPolicy8AAF0540-PrwqQQw8uF1w", + "ResourceType": "AWS::IAM::ManagedPolicy", + "Replacement": "False", + "Scope": [ + "Metadata", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "FunctionsReportAlertSuppressionsLambdaExecuteLambdaManagedPolicyDC695E63", + "PhysicalResourceId": "arn:aws:iam::591291862413:policy/monitoring-FunctionsReportAlertSuppressionsLambdaExecuteLambdaManagedPolicyDC695E63-B3ADk82vDRAx", + "ResourceType": "AWS::IAM::ManagedPolicy", + "Replacement": "False", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "PolicyDocument", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "FunctionsReportAlertSuppressionsLambdamonitoringsuppressionreporter4CD52F81.Arn" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "FunctionsReportAlertSuppressionsLambdaLambdaLogGroup46AB865A", + "PhysicalResourceId": "/aws/lambda/monitoring-suppression-reporter", + "ResourceType": "AWS::Logs::LogGroup", + "Replacement": "False", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "FunctionsReportAlertSuppressionsLambdaLambdaLogsSplunkSubscriptionFilterE77EBC49", + "PhysicalResourceId": "monitoring-FunctionsReportAlertSuppressionsLambdaLambdaLogsSplunkSubscriptionFilterE77EBC49-0j4ME9nInfhl", + "ResourceType": "AWS::Logs::SubscriptionFilter", + "Replacement": "False", + "Scope": [ + "Metadata", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "FunctionsReportAlertSuppressionsLambdaLambdaPutLogsManagedPolicy04D0828F", + "PhysicalResourceId": "arn:aws:iam::591291862413:policy/monitoring-FunctionsReportAlertSuppressionsLambdaLambdaPutLogsManagedPolicy04D0828F-SElFtd0RvV64", + "ResourceType": "AWS::IAM::ManagedPolicy", + "Replacement": "False", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "PolicyDocument", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "FunctionsReportAlertSuppressionsLambdaLambdaLogGroup46AB865A.Arn" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "FunctionsReportAlertSuppressionsLambdaLambdaRoleD6821958", + "PhysicalResourceId": "monitoring-FunctionsReportAlertSuppressionsLambdaLa-DJLPbMaRc2H6", + "ResourceType": "AWS::IAM::Role", + "Replacement": "False", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "FunctionsReportAlertSuppressionsLambdamonitoringsuppressionreporter4CD52F81", + "PhysicalResourceId": "monitoring-suppression-reporter", + "ResourceType": "AWS::Lambda::Function", + "Replacement": "False", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Code", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Environment", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Layers", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Role", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "FunctionsReportAlertSuppressionsLambdaLambdaRoleD6821958.Arn" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202135515E3CA4405", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/5ee25a89e4426417", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE20213551679EE0A49", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/26e02d8ae24ed11a", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202135517B0EA5CEE", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/6ec959ea4e072a03", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE20213609057B3EB0F", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/433108e635ed5c29", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE20224288994453378", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/b2f88d44bc8d2f7b", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE2023240571EE2F86B", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/55a848a1bcb671b2", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202328465C5880459", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/327106688b971118", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202329764CE608FD", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/0bd529a435103ed4", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE2024452940F364610", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/78d391b96733b97f", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202451132A67BF53B", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/2d3bdada1cf70940", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202452007BA433847", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/c19b23172257cef2", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE2024558879CA46A81", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/88fe30aebb61eac4", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202521587A8EEC6CA", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/5a749058ef10666a", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202522871D27C72C0", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/229c3c0734e533d7", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE2025307494C95FAE8", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/9d7569bc780acd76", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202547907377693AF", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/d9ce69db8e33da00", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202550059DE603B11", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/40f4475b5a36e5fc", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202550106004945A2", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/f52c27b32003ff89", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202558187E821BDAF", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/78351e7452353fe8", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE2025581883F0401C5", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/56de086700002631", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE2025617237FCAF774", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/54b111738b23a35c", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE2025617250E71BB06", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/72ee9313afe4b41a", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE2025617293FC93AFD", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/d0f7179b23ad76aa", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202564756C11EFAD9", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/0b603116ab31c4f5", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202569420101BF792", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/cfc1086ce35bc50a", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE20259230E37B1FB8", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/f5b2682ad28ba254", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202623745A5B68F13", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/ae73a1280ca9a627", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202623950F9799C0B", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/bb51b98b7568d81d", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE20262404986DE9D80", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/0d9f7761cc79eece", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "InspectorFiltersSuppressCVE202624842FB264968", + "PhysicalResourceId": "arn:aws:inspector2:eu-west-2:591291862413:owner/591291862413/filter/093c5e7f8d05a8eb", + "ResourceType": "AWS::InspectorV2::Filter", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "ReportAlertSuppressionsScheduleRoleDefaultPolicy1DDE3593", + "PhysicalResourceId": "monit-Repor-J2fb2PaUKrut", + "ResourceType": "AWS::IAM::Policy", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ReportAlertSuppressionsScheduleRoleED08C397", + "PhysicalResourceId": "monitoring-ReportAlertSuppressionsScheduleRoleED08C-UZSXaZOPlZfK", + "ResourceType": "AWS::IAM::Role", + "Replacement": "False", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "WeeklyScheduleRule1CC9B0E6", + "PhysicalResourceId": "monitoring-WeeklyScheduleRule1CC9B0E6-qJw8GFGHLyPe", + "ResourceType": "AWS::Events::Rule", + "Replacement": "False", + "Scope": [ + "Metadata", + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "RoleArn", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "ReportAlertSuppressionsScheduleRoleED08C397.Arn" + }, + { + "Target": { + "Attribute": "Metadata", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Targets", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "FunctionsReportAlertSuppressionsLambdamonitoringsuppressionreporter4CD52F81.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Add", + "LogicalResourceId": "WeeklyScheduleRuleAllowEventRuleMonitoringFunctionsReportAlertSuppressionsLambdamonitoringsuppressionreporter1B7A78C5462BD8DF", + "ResourceType": "AWS::Lambda::Permission", + "Scope": [], + "Details": [] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "PolicyAction": "Delete", + "Action": "Remove", + "LogicalResourceId": "WeeklyScheduleRuleAllowEventRuleMonitoringStackFunctionsReportAlertSuppressionsLambdamonitoringsuppressionreporter4C6D6F0C6068F8CF", + "PhysicalResourceId": "monitoring-WeeklyScheduleRuleAllowEventRuleMonitoringStackFunctionsReportAlertSuppressi-ENFKKYUJ3Cfb", + "ResourceType": "AWS::Lambda::Permission", + "Scope": [], + "Details": [] + } + } + ], + "ChangeSetName": "DESTRUCTIVE-monitoring-changeset-pr-1988-1771588444", + "ChangeSetId": "arn:aws:cloudformation:eu-west-2:591291862413:changeSet/DESTRUCTIVE-monitoring-changeset-pr-1988-1771588444/3ef115a1-a7d6-4e0b-af8a-b97e2aaad7e8", + "StackId": "arn:aws:cloudformation:eu-west-2:591291862413:stack/monitoring/a388b600-9eaa-11f0-949f-0294dec6a941", + "StackName": "monitoring", + "Description": "CDK Changeset for execution a97b7d1e-e1c2-42f3-b04e-1f645853becc", + "Parameters": [ + { + "ParameterKey": "BootstrapVersion", + "ParameterValue": "/cdk-bootstrap/hnb659fds/version", + "ResolvedValue": "30" + } + ], + "CreationTime": "2026-02-20T11:54:17.083000+00:00", + "ExecutionStatus": "AVAILABLE", + "Status": "CREATE_COMPLETE", + "StatusReason": null, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "Tags": [ + { + "Key": "PublicFacing", + "Value": "Y" + }, + { + "Key": "Owner", + "Value": "england.epssupport@nhs.net" + }, + { + "Key": "ServiceCategory", + "Value": "Platinum" + }, + { + "Key": "Customer", + "Value": "NHS England" + }, + { + "Key": "repo", + "Value": "electronic-prescription-service-account-resources" + }, + { + "Key": "commit", + "Value": "6661fabc0f4b8c79cd7f5e197fe6e57e0756aef0" + }, + { + "Key": "stackName", + "Value": "monitoring" + }, + { + "Key": "DeploymentTool", + "Value": "CDK" + }, + { + "Key": "Product", + "Value": "Account Resources" + }, + { + "Key": "version", + "Value": "pr-1988" + }, + { + "Key": "OnOffPattern", + "Value": "AlwaysOn" + }, + { + "Key": "Programme", + "Value": "EPS" + }, + { + "Key": "data_classification", + "Value": "5" + }, + { + "Key": "TagVersion", + "Value": "1" + }, + { + "Key": "cdkApp", + "Value": "AccountResourcesApp" + }, + { + "Key": "ProjectType", + "Value": "Production" + }, + { + "Key": "DataType", + "Value": "PII" + }, + { + "Key": "Environment", + "Value": "dev-account" + }, + { + "Key": "cfnDriftDetectionGroup", + "Value": "account-resources" + }, + { + "Key": "CostCentre", + "Value": "128997" + } + ], + "ParentChangeSetId": null, + "IncludeNestedStacks": false, + "RootChangeSetId": null, + "OnStackFailure": null, + "ImportExistingResources": false, + "StackDriftStatus": null, + "DeploymentMode": null +} diff --git a/packages/cdkConstructs/tests/changesets/examples/safe_changeset.json b/packages/cdkConstructs/tests/changesets/examples/safe_changeset.json new file mode 100644 index 00000000..b127e076 --- /dev/null +++ b/packages/cdkConstructs/tests/changesets/examples/safe_changeset.json @@ -0,0 +1,5739 @@ +{ + "Changes": [ + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "CDKMetadata", + "PhysicalResourceId": "efbc79b0-cb89-11f0-a7f0-0aa6b3b66477", + "ResourceType": "AWS::CDK::Metadata", + "Replacement": "False", + "Scope": [ + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdeploymentutils229EBC53", + "PhysicalResourceId": "deployment-utils", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontaineraccountresources3E31EF23", + "PhysicalResourceId": "dev-container-account-resources", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontaineractioncfnlint61A98760", + "PhysicalResourceId": "dev-container-action-cfn-lint", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontaineractionsbom6DCEA35B", + "PhysicalResourceId": "dev-container-action-sbom", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerassistmeA5CA1127", + "PhysicalResourceId": "dev-container-assist-me", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerawsdashboardsF36F24FC", + "PhysicalResourceId": "dev-container-aws-dashboards", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainercdkutils53FAA358", + "PhysicalResourceId": "dev-container-cdk-utils", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainercommonworkflowsD8AA3F80", + "PhysicalResourceId": "dev-container-common-workflows", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerdependabot9A650B2F", + "PhysicalResourceId": "dev-container-dependabot", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerfhirfacadeB6DDC88C", + "PhysicalResourceId": "dev-container-fhir-facade", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerfhirmiddyerrorhandler49D06461", + "PhysicalResourceId": "dev-container-fhir-middy-error-handler", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerfhirvalidatordocker439B7452", + "PhysicalResourceId": "dev-container-fhir-validator-docker", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerfhirvalidatorlambdaBBE0557F", + "PhysicalResourceId": "dev-container-fhir-validator-lambda", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainergetsecretsD0B1D936", + "PhysicalResourceId": "dev-container-get-secrets", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerloadtest1C44594D", + "PhysicalResourceId": "dev-container-load-test", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerpfpE3F7865C", + "PhysicalResourceId": "dev-container-pfp", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerprescriptiontrackerapi4616E571", + "PhysicalResourceId": "dev-container-prescription-tracker-api", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerprescriptiontrackeruiB6BF29E9", + "PhysicalResourceId": "dev-container-prescription-tracker-ui", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerpsu671DA173", + "PhysicalResourceId": "dev-container-psu", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerregressiontestsC7BEA5B0", + "PhysicalResourceId": "dev-container-regression-tests", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerreleasenotesDE0ABDDB", + "PhysicalResourceId": "dev-container-release-notes", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainersemanticreleaseEBC1D25A", + "PhysicalResourceId": "dev-container-semantic-release", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerspineclient9EB7BAE1", + "PhysicalResourceId": "dev-container-spine-client", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainerstorageterraformD576531A", + "PhysicalResourceId": "dev-container-storage-terraform", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesdevcontainervpcresources8B3A962C", + "PhysicalResourceId": "dev-container-vpc-resources", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "ECRRepositoriesgitsecretsA75BCFDF", + "PhysicalResourceId": "git-secrets", + "ResourceType": "AWS::ECR::Repository", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTCPTFHIRCLIENTIDStaticSecretstaticSecret6B8027A7", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/CPT_FHIR_CLIENT_ID-ci4yJ0", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTCPTFHIRCLIENTSECRETStaticSecretstaticSecret8A1DA8BD", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/CPT_FHIR_CLIENT_SECRET-YujLtL", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSASSISTMEROLEARNStaticSecretstaticSecret4B2E411E", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_ASSIST_ME_ROLE_ARN-vWno4u", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRCLIENTIDStaticSecretstaticSecretB086791B", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_CLIENT_ID-Kjrs6u", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRCLIENTSECRETStaticSecretstaticSecret9D01B02F", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_CLIENT_SECRET-LYAtSv", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRDISPENSINGCLIENTIDStaticSecretstaticSecret81AF4099", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_DISPENSING_CLIENT_ID-4CdjHn", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRDISPENSINGCLIENTSECRETStaticSecretstaticSecret2A556163", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_DISPENSING_CLIENT_SECRET-DvrDVf", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRDISPENSINGJWTCLIENTIDStaticSecretstaticSecretC7F0AD68", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_DISPENSING_JWT_CLIENT_ID-GBIZ84", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRDISPENSINGJWTKIDStaticSecretstaticSecret09EEA236", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_DISPENSING_JWT_KID-4kfbGX", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRDISPENSINGJWTPRIVATEKEYStaticSecretstaticSecret53E44B48", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_DISPENSING_JWT_PRIVATE_KEY-1myaHr", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRPRESCRIBINGCLIENTIDStaticSecretstaticSecret9DE1FC7B", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_PRESCRIBING_CLIENT_ID-29OTTA", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRPRESCRIBINGCLIENTSECRETStaticSecretstaticSecret08EFD32B", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_PRESCRIBING_CLIENT_SECRET-lNoJIu", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRPRESCRIBINGSHA1CLIENTIDStaticSecretstaticSecret84A6ABD2", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_PRESCRIBING_SHA1_CLIENT_ID-qdMij6", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRPRESCRIBINGSHA1CLIENTSECRETStaticSecretstaticSecret9290C98C", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_PRESCRIBING_SHA1_CLIENT_SECRET-m5B4ay", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRSHA1CLIENTIDStaticSecretstaticSecretFB1D3CE7", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_SHA1_CLIENT_ID-DMkXRT", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTEPSFHIRSHA1CLIENTSECRETStaticSecretstaticSecret2EE6D950", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/EPS_FHIR_SHA1_CLIENT_SECRET-EvMo1A", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVCPTFHIRCLIENTIDStaticSecretstaticSecret62250EA9", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/CPT_FHIR_CLIENT_ID-3DBblD", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVCPTFHIRCLIENTSECRETStaticSecretstaticSecret50130BEC", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/CPT_FHIR_CLIENT_SECRET-7OvCKo", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSASSISTMEROLEARNStaticSecretstaticSecretF1994A5A", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_ASSIST_ME_ROLE_ARN-7dsxhW", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRCLIENTIDStaticSecretstaticSecret5E379877", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_CLIENT_ID-gnWCHw", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRCLIENTSECRETStaticSecretstaticSecret17AB9D81", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_CLIENT_SECRET-2wACKL", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRDISPENSINGCLIENTIDStaticSecretstaticSecretC8A1AD32", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_DISPENSING_CLIENT_ID-vWno4u", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRDISPENSINGCLIENTSECRETStaticSecretstaticSecret549F76DA", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_DISPENSING_CLIENT_SECRET-264IHn", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRDISPENSINGJWTCLIENTIDStaticSecretstaticSecret885A721D", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_DISPENSING_JWT_CLIENT_ID-8kgSJV", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRDISPENSINGJWTKIDStaticSecretstaticSecret5D306E2C", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_DISPENSING_JWT_KID-hLE6my", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRDISPENSINGJWTPRIVATEKEYStaticSecretstaticSecret514B5343", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_DISPENSING_JWT_PRIVATE_KEY-YGjUr8", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRPRESCRIBINGCLIENTIDStaticSecretstaticSecret6501DCD0", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_PRESCRIBING_CLIENT_ID-1Dgqgj", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRPRESCRIBINGCLIENTSECRETStaticSecretstaticSecret7045B96E", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_PRESCRIBING_CLIENT_SECRET-9mLaKX", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRPRESCRIBINGSHA1CLIENTIDStaticSecretstaticSecretBB93CDFD", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_PRESCRIBING_SHA1_CLIENT_ID-rG1BLj", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRPRESCRIBINGSHA1CLIENTSECRETStaticSecretstaticSecret1233935B", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_PRESCRIBING_SHA1_CLIENT_SECRET-1xn3lL", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRSHA1CLIENTIDStaticSecretstaticSecret35430B5E", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_SHA1_CLIENT_ID-TfkoNr", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVEPSFHIRSHA1CLIENTSECRETStaticSecretstaticSecret0A5BAE16", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/EPS_FHIR_SHA1_CLIENT_SECRET-lsaczK", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVJWTKIDStaticSecretstaticSecret741ED06F", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/JWT_KID-KIsAwP", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVJWTPRIVATEKEYStaticSecretstaticSecret9C9FD653", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/JWT_PRIVATE_KEY-abxev6", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVPFPCLIENTIDStaticSecretstaticSecretAFE22955", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/PFP_CLIENT_ID-wSRMfh", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVPFPCLIENTSECRETStaticSecretstaticSecret0D0161F6", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/PFP_CLIENT_SECRET-s4a5JP", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVPSUCLIENTIDStaticSecretstaticSecret923A2ED7", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/PSU_CLIENT_ID-2CtZEj", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVPSUCLIENTSECRETStaticSecretstaticSecretFF813F5F", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV/PSU_CLIENT_SECRET-BK2q7o", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXCPTFHIRCLIENTIDStaticSecretstaticSecretA92A6E9A", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/CPT_FHIR_CLIENT_ID-JemuKW", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXCPTFHIRCLIENTSECRETStaticSecretstaticSecret4FADA644", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/CPT_FHIR_CLIENT_SECRET-B1XM32", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSASSISTMEROLEARNStaticSecretstaticSecretC8CF2F6B", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_ASSIST_ME_ROLE_ARN-Is4GGK", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRCLIENTIDStaticSecretstaticSecretC36E41EA", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_CLIENT_ID-AjJNXt", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRCLIENTSECRETStaticSecretstaticSecret2EB6BBF7", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_CLIENT_SECRET-XpeNae", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRDISPENSINGCLIENTIDStaticSecretstaticSecret209C3E96", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_DISPENSING_CLIENT_ID-zUNSqW", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRDISPENSINGCLIENTSECRETStaticSecretstaticSecretDF01EB38", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_DISPENSING_CLIENT_SECRET-aEZLSQ", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRDISPENSINGJWTCLIENTIDStaticSecretstaticSecret80DBA3BC", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_DISPENSING_JWT_CLIENT_ID-BjRiVW", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRDISPENSINGJWTKIDStaticSecretstaticSecret2B7E225E", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_DISPENSING_JWT_KID-Sqcl2v", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRDISPENSINGJWTPRIVATEKEYStaticSecretstaticSecretB4C09439", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_DISPENSING_JWT_PRIVATE_KEY-zNkvJb", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRPRESCRIBINGCLIENTIDStaticSecretstaticSecret61D075D3", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_PRESCRIBING_CLIENT_ID-t2nD7Q", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRPRESCRIBINGCLIENTSECRETStaticSecretstaticSecret8C6C5F61", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_PRESCRIBING_CLIENT_SECRET-I4fXPy", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRPRESCRIBINGSHA1CLIENTIDStaticSecretstaticSecretF287FFDD", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_PRESCRIBING_SHA1_CLIENT_ID-H7UBCq", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRPRESCRIBINGSHA1CLIENTSECRETStaticSecretstaticSecret3062CE5F", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_PRESCRIBING_SHA1_CLIENT_SECRET-wVzXtu", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRSHA1CLIENTIDStaticSecretstaticSecret9AFB2482", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_SHA1_CLIENT_ID-V6S9HW", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXEPSFHIRSHA1CLIENTSECRETStaticSecretstaticSecret3F167829", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/EPS_FHIR_SHA1_CLIENT_SECRET-VC7ra7", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXJWTKIDStaticSecretstaticSecret9FCADE0F", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/JWT_KID-SSwFXf", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXJWTPRIVATEKEYStaticSecretstaticSecret14CAB9CE", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/JWT_PRIVATE_KEY-nQLGyq", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXPFPCLIENTIDStaticSecretstaticSecretAA206AA3", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/PFP_CLIENT_ID-HOI4vI", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXPFPCLIENTSECRETStaticSecretstaticSecret076AE3AD", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/PFP_CLIENT_SECRET-LlocIH", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXPSUCLIENTIDStaticSecretstaticSecret92546DFB", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/PSU_CLIENT_ID-U6ln5S", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALDEVSANDBOXPSUCLIENTSECRETStaticSecretstaticSecretCDDE2426", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_DEV_SANDBOX/PSU_CLIENT_SECRET-2oZ4D1", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQACPTFHIRCLIENTIDStaticSecretstaticSecretB0D13C54", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/CPT_FHIR_CLIENT_ID-wWI9aM", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQACPTFHIRCLIENTSECRETStaticSecretstaticSecretE4A5B97C", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/CPT_FHIR_CLIENT_SECRET-ZftnTD", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSASSISTMEROLEARNStaticSecretstaticSecret148AF2AF", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_ASSIST_ME_ROLE_ARN-8IRNeT", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRCLIENTIDStaticSecretstaticSecretF79B2A73", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_CLIENT_ID-ghV05E", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRCLIENTSECRETStaticSecretstaticSecretF7754294", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_CLIENT_SECRET-vj97VG", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRDISPENSINGCLIENTIDStaticSecretstaticSecret68744F25", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_DISPENSING_CLIENT_ID-rMBYcq", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRDISPENSINGCLIENTSECRETStaticSecretstaticSecret72B03821", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_DISPENSING_CLIENT_SECRET-V4zYuS", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRDISPENSINGJWTCLIENTIDStaticSecretstaticSecretED2CEF27", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_DISPENSING_JWT_CLIENT_ID-fgTf2U", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRDISPENSINGJWTKIDStaticSecretstaticSecret8AA0D335", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_DISPENSING_JWT_KID-wPfOs1", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRDISPENSINGJWTPRIVATEKEYStaticSecretstaticSecretA2D21F98", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_DISPENSING_JWT_PRIVATE_KEY-YoZAvi", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRPRESCRIBINGCLIENTIDStaticSecretstaticSecretB064955A", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_PRESCRIBING_CLIENT_ID-7rWsid", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRPRESCRIBINGCLIENTSECRETStaticSecretstaticSecretD76CAED0", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_PRESCRIBING_CLIENT_SECRET-nlAZZt", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRPRESCRIBINGSHA1CLIENTIDStaticSecretstaticSecret2F35D0FA", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_PRESCRIBING_SHA1_CLIENT_ID-omvKCH", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRPRESCRIBINGSHA1CLIENTSECRETStaticSecretstaticSecret94608FE6", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_PRESCRIBING_SHA1_CLIENT_SECRET-ZgCzAX", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRSHA1CLIENTIDStaticSecretstaticSecret42C8D879", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_SHA1_CLIENT_ID-4xBWgg", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAEPSFHIRSHA1CLIENTSECRETStaticSecretstaticSecret24B81DD6", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/EPS_FHIR_SHA1_CLIENT_SECRET-eDyrUD", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAJWTKIDStaticSecretstaticSecret5040B744", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/JWT_KID-TYjOu0", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAJWTPRIVATEKEYStaticSecretstaticSecretFA0E148A", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/JWT_PRIVATE_KEY-CbHwS7", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAPFPCLIENTIDStaticSecretstaticSecret53867241", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/PFP_CLIENT_ID-5PwheW", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAPFPCLIENTSECRETStaticSecretstaticSecret39587179", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/PFP_CLIENT_SECRET-Ut96W4", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAPSUCLIENTIDStaticSecretstaticSecret4E1FBDA3", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/PSU_CLIENT_ID-8p83Jd", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTERNALQAPSUCLIENTSECRETStaticSecretstaticSecret9EFFBCE0", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INTERNAL_QA/PSU_CLIENT_SECRET-CmcicK", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTJWTKIDStaticSecretstaticSecretBF78999F", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/JWT_KID-IWEpJO", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTJWTPRIVATEKEYStaticSecretstaticSecretCF6BFD72", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/JWT_PRIVATE_KEY-eGiB6Q", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTPFPCLIENTIDStaticSecretstaticSecret391BE698", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/PFP_CLIENT_ID-Rc0R3V", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTPFPCLIENTSECRETStaticSecretstaticSecret0CF50E4B", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/PFP_CLIENT_SECRET-wLQdDp", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTPSUCLIENTIDStaticSecretstaticSecret761DF456", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/PSU_CLIENT_ID-Q3I5eZ", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsINTPSUCLIENTSECRETStaticSecretstaticSecret1E3145A5", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/INT/PSU_CLIENT_SECRET-B010qP", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFCPTFHIRCLIENTIDStaticSecretstaticSecretB2C7FEEB", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/CPT_FHIR_CLIENT_ID-NBo6yC", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFCPTFHIRCLIENTSECRETStaticSecretstaticSecret0E8884FC", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/CPT_FHIR_CLIENT_SECRET-7l35oF", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSASSISTMEROLEARNStaticSecretstaticSecretC0FD645B", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_ASSIST_ME_ROLE_ARN-XhDf3t", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRCLIENTIDStaticSecretstaticSecretE87FD761", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_CLIENT_ID-tIA71h", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRCLIENTSECRETStaticSecretstaticSecret0A4699C2", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_CLIENT_SECRET-vSCdQh", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRDISPENSINGCLIENTIDStaticSecretstaticSecretA6F0E975", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_DISPENSING_CLIENT_ID-x9qFC5", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRDISPENSINGCLIENTSECRETStaticSecretstaticSecretF4E636F4", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_DISPENSING_CLIENT_SECRET-ATZrPX", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRDISPENSINGJWTCLIENTIDStaticSecretstaticSecret8029B4AC", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_DISPENSING_JWT_CLIENT_ID-RkNcvU", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRDISPENSINGJWTKIDStaticSecretstaticSecretFE4C5373", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_DISPENSING_JWT_KID-7L8cma", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRDISPENSINGJWTPRIVATEKEYStaticSecretstaticSecretD9D842EE", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_DISPENSING_JWT_PRIVATE_KEY-Lkj5Uq", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRPRESCRIBINGCLIENTIDStaticSecretstaticSecret99D60D4F", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_PRESCRIBING_CLIENT_ID-EvMo1A", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRPRESCRIBINGCLIENTSECRETStaticSecretstaticSecret040B7487", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_PRESCRIBING_CLIENT_SECRET-sr7M6M", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRPRESCRIBINGSHA1CLIENTIDStaticSecretstaticSecret0A941120", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_PRESCRIBING_SHA1_CLIENT_ID-0Eqiq9", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRPRESCRIBINGSHA1CLIENTSECRETStaticSecretstaticSecret53679649", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_PRESCRIBING_SHA1_CLIENT_SECRET-P24wPQ", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRSHA1CLIENTIDStaticSecretstaticSecret924922A9", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_SHA1_CLIENT_ID-PoH3YH", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFEPSFHIRSHA1CLIENTSECRETStaticSecretstaticSecret821C6957", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/EPS_FHIR_SHA1_CLIENT_SECRET-jniv8K", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFJWTKIDStaticSecretstaticSecret9CB2E625", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/JWT_KID-OKmrMH", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFJWTPRIVATEKEYStaticSecretstaticSecretE3D04F7A", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/JWT_PRIVATE_KEY-cnmWEH", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFPFPCLIENTIDStaticSecretstaticSecret1748DBCE", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/PFP_CLIENT_ID-P24wPQ", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFPFPCLIENTSECRETStaticSecretstaticSecretA043E20B", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/PFP_CLIENT_SECRET-N7Nnuz", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFPSUCLIENTIDStaticSecretstaticSecretAD54E0C7", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/PSU_CLIENT_ID-ZSSfrr", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsREFPSUCLIENTSECRETStaticSecretstaticSecret788758B6", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/REF/PSU_CLIENT_SECRET-EZzgCg", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA", + "PhysicalResourceId": "f0367481-71dc-437b-9a77-31854c7e04cb", + "ResourceType": "AWS::KMS::Key", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsRegressionTestSecretsKMSKeyAlias3E5FEEF9", + "PhysicalResourceId": "alias/account-resources-cdk-uk-RegressionTestSecretsKMSKey", + "ResourceType": "AWS::KMS::Alias", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "TargetKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsptlPrescriptionSigningPrivateKeyStaticSecretstaticSecretAE253E21", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/ptl-Prescription-SigningPrivateKey-6ATs0O", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + }, + { + "Type": "Resource", + "ResourceChange": { + "Action": "Modify", + "LogicalResourceId": "RegressionTestSecretsptlPrescriptionSigningPublicKeyStaticSecretstaticSecretAC0D3FA6", + "PhysicalResourceId": "arn:aws:secretsmanager:eu-west-2:591291862413:secret:/regression-tests/ptl-Prescription-SigningPublicKey-bN6iNd", + "ResourceType": "AWS::SecretsManager::Secret", + "Replacement": "False", + "Scope": [ + "Properties", + "Tags" + ], + "Details": [ + { + "Target": { + "Attribute": "Properties", + "Name": "KmsKeyId", + "RequiresRecreation": "Never" + }, + "Evaluation": "Dynamic", + "ChangeSource": "ResourceAttribute", + "CausingEntity": "RegressionTestSecretsRegressionTestSecretsKMSKey9D658CAA.Arn" + }, + { + "Target": { + "Attribute": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static" + }, + { + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + }, + "Evaluation": "Static", + "ChangeSource": "DirectModification" + } + ] + } + } + ], + "ChangeSetName": "DESTRUCTIVE-account-resources-cdk-uk-changeset-pr-1988-1771588452", + "ChangeSetId": "arn:aws:cloudformation:eu-west-2:591291862413:changeSet/DESTRUCTIVE-account-resources-cdk-uk-changeset-pr-1988-1771588452/c7997f4c-e7a6-4a4a-b4e8-ef6f07db0854", + "StackId": "arn:aws:cloudformation:eu-west-2:591291862413:stack/account-resources-cdk-uk/efbc79b0-cb89-11f0-a7f0-0aa6b3b66477", + "StackName": "account-resources-cdk-uk", + "Description": "CDK Changeset for execution c489aff8-aa64-4b5b-93eb-5a2b79d93d98", + "Parameters": [ + { + "ParameterKey": "BootstrapVersion", + "ParameterValue": "/cdk-bootstrap/hnb659fds/version", + "ResolvedValue": "30" + } + ], + "CreationTime": "2026-02-20T11:54:20.206000+00:00", + "ExecutionStatus": "AVAILABLE", + "Status": "CREATE_COMPLETE", + "StatusReason": null, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "Tags": [ + { + "Key": "PublicFacing", + "Value": "Y" + }, + { + "Key": "Owner", + "Value": "england.epssupport@nhs.net" + }, + { + "Key": "ServiceCategory", + "Value": "Platinum" + }, + { + "Key": "Customer", + "Value": "NHS England" + }, + { + "Key": "repo", + "Value": "electronic-prescription-service-account-resources" + }, + { + "Key": "commit", + "Value": "6661fabc0f4b8c79cd7f5e197fe6e57e0756aef0" + }, + { + "Key": "stackName", + "Value": "account-resources-cdk-uk" + }, + { + "Key": "DeploymentTool", + "Value": "CDK" + }, + { + "Key": "Product", + "Value": "Account Resources" + }, + { + "Key": "version", + "Value": "pr-1988" + }, + { + "Key": "OnOffPattern", + "Value": "AlwaysOn" + }, + { + "Key": "Programme", + "Value": "EPS" + }, + { + "Key": "data_classification", + "Value": "5" + }, + { + "Key": "TagVersion", + "Value": "1" + }, + { + "Key": "cdkApp", + "Value": "AccountResourcesApp" + }, + { + "Key": "ProjectType", + "Value": "Production" + }, + { + "Key": "DataType", + "Value": "PII" + }, + { + "Key": "Environment", + "Value": "dev-account" + }, + { + "Key": "cfnDriftDetectionGroup", + "Value": "account-resources" + }, + { + "Key": "CostCentre", + "Value": "128997" + } + ], + "ParentChangeSetId": null, + "IncludeNestedStacks": false, + "RootChangeSetId": null, + "OnStackFailure": null, + "ImportExistingResources": false, + "StackDriftStatus": null, + "DeploymentMode": null +} diff --git a/packages/cdkConstructs/tests/config/index.test.ts b/packages/cdkConstructs/tests/config/index.test.ts new file mode 100644 index 00000000..ec6be439 --- /dev/null +++ b/packages/cdkConstructs/tests/config/index.test.ts @@ -0,0 +1,144 @@ +import { + describe, + test, + beforeEach, + afterAll, + expect, + vi +} from "vitest" +import { + getConfigFromEnvVar, + getBooleanConfigFromEnvVar, + getNumberConfigFromEnvVar, + getTrustStoreVersion +} from "../../src/config/index" + +const mockCloudFormationSend = vi.fn() +const mockS3Send = vi.fn() +const createdCfnClients: Array<{region?: string}> = [] +const createdS3Clients: Array<{region?: string}> = [] + +vi.mock("@aws-sdk/client-cloudformation", () => { + class CloudFormationClient { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + config: any + constructor(config: {region: string}) { + this.config = config + createdCfnClients.push({region: config.region}) + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + send(command: any) { + return mockCloudFormationSend(command) + } + } + + class DescribeStacksCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + return {CloudFormationClient, DescribeStacksCommand} +}) + +vi.mock("@aws-sdk/client-s3", () => { + class S3Client { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + config: any + constructor(config: {region: string}) { + this.config = config + createdS3Clients.push({region: config.region}) + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + send(command: any) { + return mockS3Send(command) + } + } + + class HeadObjectCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + return {S3Client, HeadObjectCommand} +}) + +const ORIGINAL_ENV = process.env + +describe("config helpers", () => { + beforeEach(() => { + process.env = {...ORIGINAL_ENV} + mockCloudFormationSend.mockReset() + mockS3Send.mockReset() + createdCfnClients.length = 0 + createdS3Clients.length = 0 + }) + + afterAll(() => { + process.env = ORIGINAL_ENV + }) + + test("getConfigFromEnvVar returns the configured value", () => { + process.env.CDK_CONFIG_STACK_NAME = "primary" + + expect(getConfigFromEnvVar("STACK_NAME")).toBe("primary") + }) + + test("getConfigFromEnvVar throws when value is missing", () => { + delete process.env.CDK_CONFIG_MISSING + + expect(() => getConfigFromEnvVar("MISSING")) + .toThrow("Environment variable CDK_CONFIG_MISSING is not set") + }) + + test("getConfigFromEnvVar supports alternate prefixes", () => { + process.env.APP_CUSTOM_VALUE = "alt" + + expect(getConfigFromEnvVar("CUSTOM_VALUE", "APP_")).toBe("alt") + }) + + test("getBooleanConfigFromEnvVar maps string booleans", () => { + process.env.CDK_CONFIG_FEATURE_FLAG = "true " + process.env.CDK_CONFIG_OTHER_FLAG = " false" + + expect(getBooleanConfigFromEnvVar("FEATURE_FLAG")).toBe(true) + expect(getBooleanConfigFromEnvVar("OTHER_FLAG")).toBe(false) + }) + + test("getNumberConfigFromEnvVar parses numeric strings", () => { + process.env.CDK_CONFIG_TIMEOUT = "45" + + expect(getNumberConfigFromEnvVar("TIMEOUT")).toBe(45) + }) + + test("getTrustStoreVersion returns the version ID from S3", async () => { + mockCloudFormationSend.mockResolvedValueOnce({ + Stacks: [{ + Outputs: [{OutputKey: "TrustStoreBucket", OutputValue: "arn:aws:s3:::nhs-trust"}] + }] + }) + mockS3Send.mockResolvedValueOnce({VersionId: "abc123"}) + + const version = await getTrustStoreVersion("truststore.pem", "eu-central-1") + + expect(version).toBe("abc123") + + expect(createdCfnClients.at(-1)?.region).toBe("eu-central-1") + expect(createdS3Clients.at(-1)?.region).toBe("eu-central-1") + + const describeCommand = mockCloudFormationSend.mock.calls[0][0] as {input: {StackName: string}} + expect(describeCommand.input.StackName).toBe("account-resources") + + const headCommand = mockS3Send.mock.calls[0][0] as {input: {Bucket: string, Key: string}} + expect(headCommand.input).toEqual({Bucket: "nhs-trust", Key: "truststore.pem"}) + }) +}) diff --git a/packages/cdkConstructs/tests/constructs/RestApiGateway.test.ts b/packages/cdkConstructs/tests/constructs/RestApiGateway.test.ts new file mode 100644 index 00000000..b6bd576e --- /dev/null +++ b/packages/cdkConstructs/tests/constructs/RestApiGateway.test.ts @@ -0,0 +1,329 @@ +import {App, Stack} from "aws-cdk-lib" +import {Template, Match} from "aws-cdk-lib/assertions" +import {ManagedPolicy, PolicyStatement} from "aws-cdk-lib/aws-iam" +import { + describe, + test, + beforeAll, + expect +} from "vitest" + +import {RestApiGateway} from "../../src/constructs/RestApiGateway.js" + +describe("RestApiGateway without mTLS", () => { + let stack: Stack + let app: App + let template: Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "RestApiGatewayStack") + + const testPolicy = new ManagedPolicy(stack, "TestPolicy", { + description: "test execution policy", + statements: [ + new PolicyStatement({ + actions: ["lambda:InvokeFunction"], + resources: ["arn:aws:lambda:eu-west-2:123456789012:function:test-function"] + }) + ] + }) + + const apiGateway = new RestApiGateway(stack, "TestApiGateway", { + stackName: "test-stack", + logRetentionInDays: 30, + mutualTlsTrustStoreKey: undefined, + forwardCsocLogs: false, + csocApiGatewayDestination: "", + executionPolicies: [testPolicy] + }) + + // Add a dummy method to satisfy API Gateway validation + apiGateway.api.root.addMethod("GET") + + template = Template.fromStack(stack) + }) + + test("creates CloudWatch log group with correct properties", () => { + template.hasResourceProperties("AWS::Logs::LogGroup", { + LogGroupName: "/aws/apigateway/test-stack-apigw", + KmsKeyId: {"Fn::ImportValue": "account-resources:CloudwatchLogsKmsKeyArn"}, + RetentionInDays: 30 + }) + }) + + test("creates Splunk subscription filter", () => { + template.hasResourceProperties("AWS::Logs::SubscriptionFilter", { + FilterPattern: "", + RoleArn: {"Fn::ImportValue": "lambda-resources:SplunkSubscriptionFilterRole"}, + DestinationArn: {"Fn::ImportValue": "lambda-resources:SplunkDeliveryStream"} + }) + }) + + test("does not create CSOC subscription filter", () => { + const filters = template.findResources("AWS::Logs::SubscriptionFilter") + const filterCount = Object.keys(filters).length + expect(filterCount).toBe(1) + }) + + test("creates ACM certificate", () => { + template.hasResourceProperties("AWS::CertificateManager::Certificate", { + DomainName: { + "Fn::Join": ["", [ + "test-stack.", + {"Fn::ImportValue": "eps-route53-resources:EPS-domain"} + ]] + }, + DomainValidationOptions: [{ + DomainName: { + "Fn::Join": ["", [ + "test-stack.", + {"Fn::ImportValue": "eps-route53-resources:EPS-domain"} + ]] + }, + HostedZoneId: {"Fn::ImportValue": "eps-route53-resources:EPS-ZoneID"} + }], + ValidationMethod: "DNS" + }) + }) + + test("creates REST API Gateway with correct configuration", () => { + template.hasResourceProperties("AWS::ApiGateway::RestApi", { + Name: "test-stack-apigw", + EndpointConfiguration: { + Types: ["REGIONAL"] + }, + DisableExecuteApiEndpoint: false + }) + }) + + test("creates API Gateway domain name with TLS 1.2", () => { + template.hasResourceProperties("AWS::ApiGateway::DomainName", { + DomainName: { + "Fn::Join": ["", [ + "test-stack.", + {"Fn::ImportValue": "eps-route53-resources:EPS-domain"} + ]] + }, + EndpointConfiguration: { + Types: ["REGIONAL"] + }, + SecurityPolicy: "TLS_1_2" + }) + }) + + test("creates deployment with logging and metrics enabled", () => { + template.hasResourceProperties("AWS::ApiGateway::Stage", { + MethodSettings: [{ + LoggingLevel: "INFO", + MetricsEnabled: true, + DataTraceEnabled: false, + HttpMethod: "*", + ResourcePath: "/*" + }], + AccessLogSetting: Match.objectLike({ + Format: Match.stringLikeRegexp("requestId") + }) + }) + }) + + test("creates IAM role for API Gateway execution", () => { + template.hasResourceProperties("AWS::IAM::Role", { + AssumeRolePolicyDocument: { + Statement: [{ + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + Service: "apigateway.amazonaws.com" + } + }], + Version: "2012-10-17" + } + }) + }) + + test("creates Route53 A record", () => { + template.hasResourceProperties("AWS::Route53::RecordSet", { + Name: { + "Fn::Join": ["", [ + "test-stack.", + {"Fn::ImportValue": "eps-route53-resources:EPS-domain"}, + "." + ]] + }, + Type: "A" + }) + }) + + test("sets guard metadata on stage", () => { + const stages = template.findResources("AWS::ApiGateway::Stage") + const stageKeys = Object.keys(stages) + expect(stageKeys.length).toBeGreaterThan(0) + + const stage = stages[stageKeys[0]] + expect(stage.Metadata).toBeDefined() + expect(stage.Metadata.guard).toBeDefined() + expect(stage.Metadata.guard.SuppressedRules).toContain("API_GW_CACHE_ENABLED_AND_ENCRYPTED") + }) +}) + +describe("RestApiGateway with CSOC logs", () => { + let stack: Stack + let app: App + let template: Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "RestApiGatewayStack") + + const testPolicy = new ManagedPolicy(stack, "TestPolicy", { + description: "test execution policy", + statements: [ + new PolicyStatement({ + actions: ["lambda:InvokeFunction"], + resources: ["arn:aws:lambda:eu-west-2:123456789012:function:test-function"] + }) + ] + }) + + const apiGateway = new RestApiGateway(stack, "TestApiGateway", { + stackName: "test-stack", + logRetentionInDays: 30, + mutualTlsTrustStoreKey: undefined, + forwardCsocLogs: true, + csocApiGatewayDestination: "arn:aws:logs:eu-west-2:123456789012:destination:csoc-destination", + executionPolicies: [testPolicy] + }) + + // Add a dummy method to satisfy API Gateway validation + apiGateway.api.root.addMethod("GET") + + template = Template.fromStack(stack) + }) + + test("creates both Splunk and CSOC subscription filters", () => { + const filters = template.findResources("AWS::Logs::SubscriptionFilter") + const filterCount = Object.keys(filters).length + expect(filterCount).toBe(2) + }) + + test("creates CSOC subscription filter with correct destination", () => { + template.hasResourceProperties("AWS::Logs::SubscriptionFilter", { + FilterPattern: "", + DestinationArn: "arn:aws:logs:eu-west-2:123456789012:destination:csoc-destination" + }) + }) +}) + +describe("RestApiGateway with mTLS", () => { + let stack: Stack + let app: App + let template: Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "RestApiGatewayStack") + + const testPolicy = new ManagedPolicy(stack, "TestPolicy", { + description: "test execution policy", + statements: [ + new PolicyStatement({ + actions: ["lambda:InvokeFunction"], + resources: ["arn:aws:lambda:eu-west-2:123456789012:function:test-function"] + }) + ] + }) + + const apiGateway = new RestApiGateway(stack, "TestApiGateway", { + stackName: "test-stack", + logRetentionInDays: 30, + mutualTlsTrustStoreKey: "truststore.pem", + forwardCsocLogs: false, + csocApiGatewayDestination: "", + executionPolicies: [testPolicy] + }) + + // Add a dummy method to satisfy API Gateway validation + apiGateway.api.root.addMethod("GET") + + template = Template.fromStack(stack) + }) + + test("creates trust store deployment log group", () => { + template.hasResourceProperties("AWS::Logs::LogGroup", { + LogGroupName: "/aws/lambda/test-stack-truststore-deployment", + KmsKeyId: {"Fn::ImportValue": "account-resources:CloudwatchLogsKmsKeyArn"}, + RetentionInDays: 30 + }) + }) + + test("creates trust store deployment policy with S3 permissions", () => { + interface PolicyResource { + Properties?: { + PolicyDocument?: { + Statement?: Array<{Action?: Array}> + } + } + } + interface Statement { + Action?: Array + } + + const policies = template.findResources("AWS::IAM::ManagedPolicy") + const trustStorePolicy = Object.values(policies).find((p: PolicyResource) => + p.Properties?.PolicyDocument?.Statement?.some((s: Statement) => + s.Action?.includes("s3:ListBucket") + ) + ) as PolicyResource + expect(trustStorePolicy).toBeDefined() + const statements = trustStorePolicy.Properties?.PolicyDocument?.Statement ?? [] + expect(statements.some((s: Statement) => s.Action?.includes("s3:ListBucket"))).toBe(true) + expect(statements.some((s: Statement) => s.Action?.includes("s3:GetObject"))).toBe(true) + expect(statements.some((s: Statement) => s.Action?.includes("kms:Decrypt"))).toBe(true) + expect(statements.some((s: Statement) => s.Action?.includes("logs:CreateLogStream"))).toBe(true) + }) + + test("creates trust store deployment role", () => { + template.hasResourceProperties("AWS::IAM::Role", { + AssumeRolePolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + Service: "lambda.amazonaws.com" + } + }) + ]), + Version: "2012-10-17" + } + }) + }) + + test("creates bucket deployment custom resource", () => { + const customResources = template.findResources("Custom::CDKBucketDeployment") + expect(Object.keys(customResources).length).toBeGreaterThan(0) + }) + + test("disables execute-api endpoint when mTLS is enabled", () => { + template.hasResourceProperties("AWS::ApiGateway::RestApi", { + Name: "test-stack-apigw", + DisableExecuteApiEndpoint: true + }) + }) + + test("configures mTLS on domain name", () => { + interface DomainNameResource { + Properties: { + MutualTlsAuthentication: { + TruststoreUri: unknown + } + } + } + + const domainNames = template.findResources("AWS::ApiGateway::DomainName") + const domainName = Object.values(domainNames)[0] as DomainNameResource + expect(domainName.Properties.MutualTlsAuthentication).toBeDefined() + expect(domainName.Properties.MutualTlsAuthentication.TruststoreUri).toBeDefined() + }) +}) diff --git a/packages/cdkConstructs/tests/constructs/RestApiGateway/LambdaEndpoint.test.ts b/packages/cdkConstructs/tests/constructs/RestApiGateway/LambdaEndpoint.test.ts new file mode 100644 index 00000000..2a88c922 --- /dev/null +++ b/packages/cdkConstructs/tests/constructs/RestApiGateway/LambdaEndpoint.test.ts @@ -0,0 +1,89 @@ +import {App, Stack} from "aws-cdk-lib" +import {RestApi} from "aws-cdk-lib/aws-apigateway" +import {Role, ServicePrincipal} from "aws-cdk-lib/aws-iam" +import {Template, Match} from "aws-cdk-lib/assertions" +import {Architecture, Function as LambdaFunction, Runtime} from "aws-cdk-lib/aws-lambda" +import { + describe, + test, + beforeAll, + expect +} from "vitest" +import {HttpMethod} from "aws-cdk-lib/aws-lambda" + +import {LambdaEndpoint} from "../../../src/constructs/RestApiGateway/LambdaEndpoint.js" + +describe("LambdaEndpoint construct", () => { + let stack: Stack + let template: Template + let construct: LambdaEndpoint + + beforeAll(() => { + const app = new App() + stack = new Stack(app, "LambdaEndpointStack") + + const api = new RestApi(stack, "TestApi") + + const credentialsRole = new Role(stack, "ApiGwRole", { + assumedBy: new ServicePrincipal("apigateway.amazonaws.com") + }) + + // Minimal lambda function stub that satisfies LambdaFunctionHolder interface + const lambdaFn = new LambdaFunction(stack, "DummyFn", { + runtime: Runtime.NODEJS_22_X, + handler: "index.handler", + code: { + bind: () => ({ + s3Location: {bucketName: "dummy", objectKey: "dummy.zip"} + }), + bindToResource: () => undefined, + isInline: false + } as unknown as never, + architecture: Architecture.X86_64 + }) + + construct = new LambdaEndpoint(stack, "TestLambdaEndpoint", { + parentResource: api.root, + resourceName: "test-resource", + method: HttpMethod.GET, + restApiGatewayRole: credentialsRole, + lambdaFunction: {function: lambdaFn} + }) + + template = Template.fromStack(stack) + }) + + test("creates an API Gateway resource with the correct path part", () => { + template.hasResourceProperties("AWS::ApiGateway::Resource", { + PathPart: "test-resource" + }) + }) + + test("creates a GET method on the resource", () => { + template.hasResourceProperties("AWS::ApiGateway::Method", { + HttpMethod: "GET" + }) + }) + + test("exposes the resource as a public property", () => { + expect(construct.resource).toBeDefined() + }) + + test("uses credentials role on the Lambda integration", () => { + template.hasResourceProperties("AWS::ApiGateway::Method", { + HttpMethod: "GET", + Integration: Match.objectLike({ + Type: "AWS_PROXY" + }) + }) + }) +}) + +describe("LambdaEndpoint accepts TypescriptLambdaFunction via structural typing", () => { + test("LambdaFunctionHolder interface is satisfied by any object with a function property", () => { + // This is a compile-time check verified by the build step. Here we just + // assert the interface shape is correct at runtime. + const holder = {function: {} as unknown as never} + expect(holder.function).toBeDefined() + }) +}) diff --git a/packages/cdkConstructs/tests/constructs/pythonLambdaFunctionConstruct.test.ts b/packages/cdkConstructs/tests/constructs/pythonLambdaFunctionConstruct.test.ts new file mode 100644 index 00000000..7f618713 --- /dev/null +++ b/packages/cdkConstructs/tests/constructs/pythonLambdaFunctionConstruct.test.ts @@ -0,0 +1,530 @@ +import {App, assertions, Stack} from "aws-cdk-lib" +import {Template, Match} from "aws-cdk-lib/assertions" +import { + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal +} from "aws-cdk-lib/aws-iam" +import {LogGroup} from "aws-cdk-lib/aws-logs" +import { + Architecture, + Function as LambdaFunction, + LayerVersion, + Runtime +} from "aws-cdk-lib/aws-lambda" +import {resolve} from "node:path" +import { + beforeAll, + describe, + expect, + test +} from "vitest" + +import {PythonLambdaFunction} from "../../src/constructs/PythonLambdaFunction" +import {CfnDeliveryStream} from "aws-cdk-lib/aws-kinesisfirehose" +import {Key} from "aws-cdk-lib/aws-kms" + +describe("pythonFunctionConstruct works correctly", () => { + let stack: Stack + let app: App + let template: assertions.Template + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaLogGroupResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaRoleResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaResource: any + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + const functionConstruct = new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO" + }) + template = Template.fromStack(stack) + const lambdaLogGroup = functionConstruct.node.tryFindChild("LambdaLogGroup") as LogGroup + const lambdaRole = functionConstruct.node.tryFindChild("LambdaRole") as Role + const cfnLambda = functionConstruct.node.tryFindChild("testPythonLambda") as LambdaFunction + lambdaRoleResource = stack.resolve(lambdaRole.roleName) + lambdaLogGroupResource = stack.resolve(lambdaLogGroup.logGroupName) + lambdaResource = stack.resolve(cfnLambda.functionName) + }) + + test("We have found log group, role and lambda", () => { + expect(lambdaRoleResource).not.toBe(undefined) + expect(lambdaLogGroupResource).not.toBe(undefined) + expect(lambdaResource).not.toBe(undefined) + }) + + test("it has the correct log group", () => { + template.hasResourceProperties("AWS::Logs::LogGroup", { + LogGroupName: "/aws/lambda/testPythonLambda", + KmsKeyId: {"Fn::ImportValue": "account-resources:CloudwatchLogsKmsKeyArn"}, + RetentionInDays: 30 + }) + }) + + test("it has the correct policy for writing logs", () => { + template.hasResourceProperties("AWS::IAM::ManagedPolicy", { + Description: "write to testPythonLambda logs", + PolicyDocument: { + Version: "2012-10-17", + Statement: [{ + Action: ["logs:CreateLogStream", "logs:PutLogEvents"], + Effect: "Allow", + Resource: [ + {"Fn::GetAtt": [lambdaLogGroupResource.Ref, "Arn"]}, + {"Fn::Join": ["", [{"Fn::GetAtt": [lambdaLogGroupResource.Ref, "Arn"]}, ":log-stream:*"]]} + ] + }] + } + }) + }) + + test("it has the correct subscription filter", () => { + template.hasResourceProperties("AWS::Logs::SubscriptionFilter", { + LogGroupName: {"Ref": lambdaLogGroupResource.Ref}, + FilterPattern: "", + RoleArn: {"Fn::ImportValue": "lambda-resources:SplunkSubscriptionFilterRole"}, + DestinationArn: {"Fn::ImportValue": "lambda-resources:SplunkDeliveryStream"} + }) + }) + + test("it has the correct role", () => { + template.hasResourceProperties("AWS::IAM::Role", { + AssumeRolePolicyDocument: { + Version: "2012-10-17", + Statement: [{ + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: {Service: "lambda.amazonaws.com"} + }] + }, + ManagedPolicyArns: Match.arrayWith([ + {"Fn::ImportValue": "lambda-resources:LambdaInsightsLogGroupPolicy"}, + {"Fn::ImportValue": "account-resources:CloudwatchEncryptionKMSPolicyArn"} + ]) + }) + }) + + test("it has the correct lambda", () => { + template.hasResourceProperties("AWS::Lambda::Function", { + Handler: "index.handler", + Runtime: "python3.14", + FunctionName: "testPythonLambda", + MemorySize: 256, + Architectures: ["x86_64"], + Timeout: 50, + LoggingConfig: { + LogGroup: lambdaLogGroupResource + }, + Environment: { + Variables: { + POWERTOOLS_LOG_LEVEL: "INFO" + } + }, + Layers: ["arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:64"], + Role: {"Fn::GetAtt": [lambdaRoleResource.Ref, "Arn"]} + }) + }) + + test("it has the correct policy for executing the lambda", () => { + template.hasResourceProperties("AWS::IAM::ManagedPolicy", { + Description: "execute lambda testPythonLambda", + PolicyDocument: { + Version: "2012-10-17", + Statement: [{ + Action: "lambda:InvokeFunction", + Effect: "Allow", + Resource: {"Fn::GetAtt": [lambdaResource.Ref, "Arn"]} + }] + } + }) + }) +}) + +describe("pythonFunctionConstruct works correctly with environment variables", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {foo: "bar"}, + logRetentionInDays: 30, + logLevel: "DEBUG" + }) + template = Template.fromStack(stack) + }) + + test("environment variables are added correctly", () => { + template.hasResourceProperties("AWS::Lambda::Function", { + Runtime: "python3.14", + FunctionName: "testPythonLambda", + Environment: {Variables: {foo: "bar", POWERTOOLS_LOG_LEVEL: "DEBUG"}} + }) + }) +}) + +describe("pythonFunctionConstruct works correctly with additional policies", () => { + let stack: Stack + let app: App + let template: assertions.Template + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let testPolicyResource: any + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + const testPolicy = new ManagedPolicy(stack, "testPolicy", { + description: "test policy", + statements: [ + new PolicyStatement({ + actions: ["logs:CreateLogStream"], + resources: ["*"] + }) + ] + }) + new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {}, + additionalPolicies: [testPolicy], + logRetentionInDays: 30, + logLevel: "INFO" + }) + template = Template.fromStack(stack) + testPolicyResource = stack.resolve(testPolicy.managedPolicyArn) + }) + + test("it has the correct policies in the role", () => { + template.hasResourceProperties("AWS::IAM::Role", { + ManagedPolicyArns: Match.arrayWith([ + {"Fn::ImportValue": "lambda-resources:LambdaInsightsLogGroupPolicy"}, + {"Fn::ImportValue": "account-resources:CloudwatchEncryptionKMSPolicyArn"}, + {Ref: testPolicyResource.Ref} + ]) + }) + }) +}) + +describe("pythonFunctionConstruct works correctly with additional layers", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + const parameterAndSecretsLayerArn = + "arn:aws:lambda:eu-west-2:133256977650:layer:AWS-Parameters-and-Secrets-Lambda-Extension:20" + const parameterAndSecretsLayer = LayerVersion.fromLayerVersionArn( + stack, "AdditionalLayerFromArn", parameterAndSecretsLayerArn) + new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + layers: [parameterAndSecretsLayer] + }) + template = Template.fromStack(stack) + }) + + test("it has the correct layers added", () => { + template.hasResourceProperties("AWS::Lambda::Function", { + Layers: [ + "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:64", + "arn:aws:lambda:eu-west-2:133256977650:layer:AWS-Parameters-and-Secrets-Lambda-Extension:20" + ] + }) + }) +}) + +describe("pythonFunctionConstruct works correctly with dependency layer", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + dependencyLocation: "packages/cdkConstructs/tests/src", + handler: "index.handler", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO" + }) + template = Template.fromStack(stack) + }) + + test("it creates a lambda layer", () => { + template.hasResourceProperties("AWS::Lambda::LayerVersion", { + CompatibleArchitectures: ["x86_64"] + }) + }) + + test("it adds both insights and dependency layers", () => { + template.hasResourceProperties("AWS::Lambda::Function", { + Layers: Match.arrayWith([ + "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:64", + Match.objectLike({ + Ref: Match.stringLikeRegexp("DependencyLayer") + }) + ]) + }) + }) +}) + +describe("pythonFunctionConstruct works correctly with custom timeout", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + timeoutInSeconds: 120 + }) + template = Template.fromStack(stack) + }) + + test("it has the correct timeout", () => { + template.hasResourceProperties("AWS::Lambda::Function", { + Timeout: 120 + }) + }) +}) + +describe("pythonFunctionConstruct works correctly with different runtime", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + runtime: Runtime.PYTHON_3_12 + }) + template = Template.fromStack(stack) + }) + + test("it has correct runtime", () => { + template.hasResourceProperties("AWS::Lambda::Function", { + Runtime: "python3.12" + }) + }) +}) + +describe("pythonFunctionConstruct works correctly with different architecture", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + architecture: Architecture.ARM_64 + }) + template = Template.fromStack(stack) + }) + + test("it has correct architecture and layer", () => { + template.hasResourceProperties("AWS::Lambda::Function", { + Architectures: ["arm64"], + Layers: ["arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:31"] + }) + }) +}) + +describe("pythonFunctionConstruct works correctly with addSplunkSubscriptionFilter set to false", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {foo: "bar"}, + logRetentionInDays: 30, + logLevel: "DEBUG", + addSplunkSubscriptionFilter: false + }) + template = Template.fromStack(stack) + }) + + test("it does not have a subscription filter", () => { + template.resourceCountIs("AWS::Logs::SubscriptionFilter", 0) + }) +}) + +describe("pythonFunctionConstruct works correctly when not using imports", () => { + let stack: Stack + let app: App + let template: assertions.Template + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaLogGroupResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let cloudWatchLogsKmsKeyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaInsightsLogGroupPolicyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let cloudwatchEncryptionKMSPolicyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let splunkSubscriptionFilterRoleResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let splunkDeliveryStreamResource: any + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + const cloudWatchLogsKmsKey = new Key(stack, "cloudWatchLogsKmsKey") + const cloudwatchEncryptionKMSPolicy = new ManagedPolicy(stack, "cloudwatchEncryptionKMSPolicy", { + description: "cloudwatch encryption KMS policy", + statements: [ + new PolicyStatement({ + actions: [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + resources: ["*"] + })] + }) + const splunkDeliveryStream = new CfnDeliveryStream(stack, "SplunkDeliveryStream", { + deliveryStreamName: "SplunkDeliveryStream", + s3DestinationConfiguration: { + bucketArn: "arn:aws:s3:::my-bucket", + roleArn: "arn:aws:iam::123456789012:role/my-role" + } + }) + const splunkSubscriptionFilterRole = new Role(stack, "SplunkSubscriptionFilterRole", { + assumedBy: new ServicePrincipal("logs.amazonaws.com") + }) + const lambdaInsightsLogGroupPolicy = new ManagedPolicy(stack, "LambdaInsightsLogGroupPolicy", { + description: "permissions to create log group and set retention policy for Lambda Insights", + statements: [ + new PolicyStatement({ + actions: [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + resources: [ + "*" + ] + }) + ] + }) + + const functionConstruct = new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + cloudWatchLogsKmsKey: cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy: cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream: splunkDeliveryStream, + splunkSubscriptionFilterRole: splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy: lambdaInsightsLogGroupPolicy + }) + template = Template.fromStack(stack) + const lambdaLogGroup = functionConstruct.node.tryFindChild("LambdaLogGroup") as LogGroup + lambdaLogGroupResource = stack.resolve(lambdaLogGroup.logGroupName) + cloudWatchLogsKmsKeyResource = stack.resolve(cloudWatchLogsKmsKey.keyId) + lambdaInsightsLogGroupPolicyResource = stack.resolve(lambdaInsightsLogGroupPolicy.managedPolicyArn) + cloudwatchEncryptionKMSPolicyResource = stack.resolve(cloudwatchEncryptionKMSPolicy.managedPolicyArn) + splunkSubscriptionFilterRoleResource = stack.resolve(splunkSubscriptionFilterRole.roleName) + splunkDeliveryStreamResource = stack.resolve(splunkDeliveryStream.ref) + }) + + test("it has the correct cloudWatchLogsKmsKey", () => { + template.hasResourceProperties("AWS::Logs::LogGroup", { + LogGroupName: "/aws/lambda/testPythonLambda", + KmsKeyId: {"Fn::GetAtt": [cloudWatchLogsKmsKeyResource.Ref, "Arn"]}, + RetentionInDays: 30 + }) + }) + + test("it has the correct cloudwatchEncryptionKMSPolicy and lambdaInsightsLogGroupPolicy", () => { + template.hasResourceProperties("AWS::IAM::Role", { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": Match.arrayWith([ + {"Ref": lambdaInsightsLogGroupPolicyResource.Ref}, + {"Ref": cloudwatchEncryptionKMSPolicyResource.Ref} + ]) + }) + }) + test("it has the correct subscription filter", () => { + template.hasResourceProperties("AWS::Logs::SubscriptionFilter", { + LogGroupName: {"Ref": lambdaLogGroupResource.Ref}, + FilterPattern: "", + RoleArn: {"Fn::GetAtt": [splunkSubscriptionFilterRoleResource.Ref, "Arn"]}, + DestinationArn: {"Fn::GetAtt": [splunkDeliveryStreamResource.Ref, "Arn"]} + }) + }) +}) diff --git a/packages/cdkConstructs/tests/functionConstruct.test.ts b/packages/cdkConstructs/tests/constructs/typescriptFunctionConstruct.test.ts similarity index 56% rename from packages/cdkConstructs/tests/functionConstruct.test.ts rename to packages/cdkConstructs/tests/constructs/typescriptFunctionConstruct.test.ts index 36dfe650..b640048c 100644 --- a/packages/cdkConstructs/tests/functionConstruct.test.ts +++ b/packages/cdkConstructs/tests/constructs/typescriptFunctionConstruct.test.ts @@ -1,7 +1,17 @@ import {App, assertions, Stack} from "aws-cdk-lib" -import {ManagedPolicy, PolicyStatement, Role} from "aws-cdk-lib/aws-iam" +import { + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal +} from "aws-cdk-lib/aws-iam" import {LogGroup} from "aws-cdk-lib/aws-logs" -import {Function, LayerVersion, Runtime} from "aws-cdk-lib/aws-lambda" +import { + Architecture, + Function, + LayerVersion, + Runtime +} from "aws-cdk-lib/aws-lambda" import {Template, Match} from "aws-cdk-lib/assertions" import { describe, @@ -10,10 +20,12 @@ import { expect } from "vitest" -import {TypescriptLambdaFunction} from "../src/constructs/TypescriptLambdaFunction" +import {TypescriptLambdaFunction} from "../../src/constructs/TypescriptLambdaFunction" import {resolve} from "node:path" +import {Key} from "aws-cdk-lib/aws-kms" +import {CfnDeliveryStream} from "aws-cdk-lib/aws-kinesisfirehose" -describe("functionConstruct works correctly", () => { +describe("TypescriptLambdaFunctionConstruct works correctly", () => { let stack: Stack let app: App let template: assertions.Template @@ -39,7 +51,7 @@ describe("functionConstruct works correctly", () => { logLevel: "DEBUG", version: "1.0.0", commitId: "abcd1234", - projectBaseDir: resolve(__dirname, "../../..") + projectBaseDir: resolve(__dirname, "../../../..") }) template = Template.fromStack(stack) const lambdaLogGroup = functionConstruct.node.tryFindChild("LambdaLogGroup") as LogGroup @@ -122,7 +134,7 @@ describe("functionConstruct works correctly", () => { LoggingConfig: { "LogGroup": lambdaLogGroupResource }, - Layers: ["arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:60"], + Layers: ["arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:64"], Role: {"Fn::GetAtt": [lambdaRoleResource.Ref, "Arn"]} }) }) @@ -159,7 +171,7 @@ describe("functionConstruct works correctly with environment variables", () => { logLevel: "DEBUG", version: "1.0.0", commitId: "abcd1234", - projectBaseDir: resolve(__dirname, "../../..") + projectBaseDir: resolve(__dirname, "../../../..") }) template = Template.fromStack(stack) }) @@ -202,7 +214,7 @@ describe("functionConstruct works correctly with additional policies", () => { logLevel: "DEBUG", version: "1.0.0", commitId: "abcd1234", - projectBaseDir: resolve(__dirname, "../../..") + projectBaseDir: resolve(__dirname, "../../../..") }) template = Template.fromStack(stack) testPolicyResource = stack.resolve(testPolicy.managedPolicyArn) @@ -241,7 +253,7 @@ describe("functionConstruct works correctly with additional layers", () => { version: "1.0.0", layers: [parameterAndSecretsLayer], commitId: "abcd1234", - projectBaseDir: resolve(__dirname, "../../..") + projectBaseDir: resolve(__dirname, "../../../..") }) template = Template.fromStack(stack) }) @@ -255,7 +267,7 @@ describe("functionConstruct works correctly with additional layers", () => { Architectures: ["x86_64"], Timeout: 50, Layers: [ - "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:60", + "arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension:64", "arn:aws:lambda:eu-west-2:133256977650:layer:AWS-Parameters-and-Secrets-Lambda-Extension:20" ] }) @@ -280,7 +292,7 @@ describe("functionConstruct works correctly with custom timeout", () => { version: "1.0.0", layers: [], commitId: "abcd1234", - projectBaseDir: resolve(__dirname, "../../.."), + projectBaseDir: resolve(__dirname, "../../../.."), timeoutInSeconds: 120 }) template = Template.fromStack(stack) @@ -315,7 +327,7 @@ describe("functionConstruct works correctly with different runtime", () => { logLevel: "DEBUG", version: "1.0.0", commitId: "abcd1234", - projectBaseDir: resolve(__dirname, "../../.."), + projectBaseDir: resolve(__dirname, "../../../.."), runtime: Runtime.NODEJS_22_X }) template = Template.fromStack(stack) @@ -328,3 +340,187 @@ describe("functionConstruct works correctly with different runtime", () => { }) }) }) + +describe("TypescriptLambdaFunctionConstruct works correctly with different architecture", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "typescriptLambdaConstructStack") + new TypescriptLambdaFunction(stack, "dummyTypescriptFunction", { + functionName: "testTypescriptLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + entryPoint: "tests/src/dummyLambda.ts", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + architecture: Architecture.ARM_64, + version: "1.0.0", + commitId: "abcd1234" + }) + template = Template.fromStack(stack) + }) + + test("it has correct architecture and layer", () => { + template.hasResourceProperties("AWS::Lambda::Function", { + Architectures: ["arm64"], + Layers: ["arn:aws:lambda:eu-west-2:580247275435:layer:LambdaInsightsExtension-Arm64:31"] + }) + }) +}) + +describe("TypescriptLambdaFunctionConstruct works correctly with addSplunkSubscriptionFilter set to false", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "typescriptLambdaConstructStack") + new TypescriptLambdaFunction(stack, "dummyTypescriptFunction", { + functionName: "testTypescriptLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + entryPoint: "tests/src/dummyLambda.ts", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + architecture: Architecture.X86_64, + version: "1.0.0", + commitId: "abcd1234", + addSplunkSubscriptionFilter: false + }) + template = Template.fromStack(stack) + }) + + test("it does not have a subscription filter", () => { + template.resourceCountIs("AWS::Logs::SubscriptionFilter", 0) + }) +}) + +describe("TypescriptLambdaFunctionConstruct works correctly when not using imports", () => { + let stack: Stack + let app: App + let template: assertions.Template + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaLogGroupResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let cloudWatchLogsKmsKeyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaInsightsLogGroupPolicyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let cloudwatchEncryptionKMSPolicyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let splunkSubscriptionFilterRoleResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let splunkDeliveryStreamResource: any + + beforeAll(() => { + app = new App() + stack = new Stack(app, "typescriptLambdaConstructStack") + const cloudWatchLogsKmsKey = new Key(stack, "cloudWatchLogsKmsKey") + const cloudwatchEncryptionKMSPolicy = new ManagedPolicy(stack, "cloudwatchEncryptionKMSPolicy", { + description: "cloudwatch encryption KMS policy", + statements: [ + new PolicyStatement({ + actions: [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + resources: ["*"] + })] + }) + const splunkDeliveryStream = new CfnDeliveryStream(stack, "SplunkDeliveryStream", { + deliveryStreamName: "SplunkDeliveryStream", + s3DestinationConfiguration: { + bucketArn: "arn:aws:s3:::my-bucket", + roleArn: "arn:aws:iam::123456789012:role/my-role" + } + }) + const splunkSubscriptionFilterRole = new Role(stack, "SplunkSubscriptionFilterRole", { + assumedBy: new ServicePrincipal("logs.amazonaws.com") + }) + const lambdaInsightsLogGroupPolicy = new ManagedPolicy(stack, "LambdaInsightsLogGroupPolicy", { + description: "permissions to create log group and set retention policy for Lambda Insights", + statements: [ + new PolicyStatement({ + actions: [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + resources: [ + "*" + ] + }) + ] + }) + + const functionConstruct = new TypescriptLambdaFunction(stack, "dummyTypescriptFunction", { + functionName: "testTypescriptLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + entryPoint: "tests/src/dummyLambda.ts", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + architecture: Architecture.X86_64, + version: "1.0.0", + commitId: "abcd1234", + cloudWatchLogsKmsKey: cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy: cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream: splunkDeliveryStream, + splunkSubscriptionFilterRole: splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy: lambdaInsightsLogGroupPolicy + }) + template = Template.fromStack(stack) + const lambdaLogGroup = functionConstruct.node.tryFindChild("LambdaLogGroup") as LogGroup + lambdaLogGroupResource = stack.resolve(lambdaLogGroup.logGroupName) + cloudWatchLogsKmsKeyResource = stack.resolve(cloudWatchLogsKmsKey.keyId) + lambdaInsightsLogGroupPolicyResource = stack.resolve(lambdaInsightsLogGroupPolicy.managedPolicyArn) + cloudwatchEncryptionKMSPolicyResource = stack.resolve(cloudwatchEncryptionKMSPolicy.managedPolicyArn) + splunkSubscriptionFilterRoleResource = stack.resolve(splunkSubscriptionFilterRole.roleName) + splunkDeliveryStreamResource = stack.resolve(splunkDeliveryStream.ref) + }) + + test("it has the correct cloudWatchLogsKmsKey", () => { + template.hasResourceProperties("AWS::Logs::LogGroup", { + LogGroupName: "/aws/lambda/testTypescriptLambda", + KmsKeyId: {"Fn::GetAtt": [cloudWatchLogsKmsKeyResource.Ref, "Arn"]}, + RetentionInDays: 30 + }) + }) + + test("it has the correct cloudwatchEncryptionKMSPolicy and lambdaInsightsLogGroupPolicy", () => { + template.hasResourceProperties("AWS::IAM::Role", { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": Match.arrayWith([ + {"Ref": lambdaInsightsLogGroupPolicyResource.Ref}, + {"Ref": cloudwatchEncryptionKMSPolicyResource.Ref} + ]) + }) + }) + test("it has the correct subscription filter", () => { + template.hasResourceProperties("AWS::Logs::SubscriptionFilter", { + LogGroupName: {"Ref": lambdaLogGroupResource.Ref}, + FilterPattern: "", + RoleArn: {"Fn::GetAtt": [splunkSubscriptionFilterRoleResource.Ref, "Arn"]}, + DestinationArn: {"Fn::GetAtt": [splunkDeliveryStreamResource.Ref, "Arn"]} + }) + }) +}) diff --git a/packages/cdkConstructs/tests/nag/ApiGWStructuredLogging.test.ts b/packages/cdkConstructs/tests/nag/ApiGWStructuredLogging.test.ts new file mode 100644 index 00000000..cbf1a63d --- /dev/null +++ b/packages/cdkConstructs/tests/nag/ApiGWStructuredLogging.test.ts @@ -0,0 +1,163 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +Modifications copyright (c) 2026 NHS Digital – see THIRD_PARTY_NOTICES.md +*/ +import {Aspects, Stack} from "aws-cdk-lib" +import {beforeEach, test} from "vitest" +import {TestPack, TestType, validateStack} from "./utils" +import {APIGWStructuredLogging} from "../../src/nag/rules" +import {describe} from "node:test" +import {CfnDeployment, CfnStage} from "aws-cdk-lib/aws-apigateway" +import {CfnApi, CfnHttpApi} from "aws-cdk-lib/aws-sam" +import {CfnStage as CfnV2Stage} from "aws-cdk-lib/aws-apigatewayv2" + +// Copied from https://github.com/cdklabs/cdk-nag/blob/main/test/rules/APIGW.test.ts +// with minor adjustments to handle CfnDeployment access log settings possibly being undefined +// only copied relevant tests for structured logging +// see https://github.com/cdklabs/cdk-nag/issues/2267 +// and https://github.com/cdklabs/cdk-nag/pull/2268 + +const testPack = new TestPack([ + APIGWStructuredLogging +]) +let stack: Stack +beforeEach(() => { + stack = new Stack() + Aspects.of(stack).add(testPack) +}) + +describe("APIGWStructuredLogging: API Gateway stages use JSON-formatted structured logging", () => { + const ruleId = "APIGWStructuredLogging" + + test("Noncompliance 1: Non-JSON format (CfnStage)", () => { + new CfnStage(stack, "RestApiStageNonJsonFormat", { + restApiId: "foo", + stageName: "prod", + accessLogSetting: { + destinationArn: + "arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod", + format: + // eslint-disable-next-line max-len + '$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] "$context.httpMethod $context.resourcePath $context.protocol" $context.status $context.responseLength $context.requestId' + } + }) + validateStack(stack, ruleId, TestType.NON_COMPLIANCE) + }) + + test("Noncompliance 2: No access log settings (CfnDeployment)", () => { + new CfnDeployment(stack, "RestApiDeploymentNoLogs", { + restApiId: "foo", + stageDescription: { + accessLogSetting: { + destinationArn: + "arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod" + } + } + }) + validateStack(stack, ruleId, TestType.NON_COMPLIANCE) + }) + + test("Noncompliance 3: No access log settings (CfnApi)", () => { + new CfnApi(stack, "SamApiNoLogs", { + stageName: "MyApi", + accessLogSetting: { + destinationArn: + "arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod" + } + }) + validateStack(stack, ruleId, TestType.NON_COMPLIANCE) + }) + + test("Noncompliance 4: No access log settings (CfnHttpApi)", () => { + new CfnHttpApi(stack, "SamHttpApiNoLogs", { + stageName: "MyApi", + accessLogSetting: { + destinationArn: + "arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod" + } + }) + validateStack(stack, ruleId, TestType.NON_COMPLIANCE) + }) + + test("Compliance 1: JSON-formatted log (CfnStage)", () => { + new CfnStage(stack, "RestApiStageJsonFormat", { + restApiId: "foo", + stageName: "prod", + accessLogSetting: { + destinationArn: + "arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod", + format: + // eslint-disable-next-line max-len + '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "caller":"$context.identity.caller", "user":"$context.identity.user","requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength"}' + } + }) + validateStack(stack, ruleId, TestType.COMPLIANCE) + }) + + test("Compliance 2: HTTP API with JSON-formatted log (CfnStageV2)", () => { + new CfnV2Stage(stack, "HttpApiStageJsonFormat", { + apiId: "bar", + stageName: "prod", + accessLogSettings: { + destinationArn: + "arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod", + format: + // eslint-disable-next-line max-len + '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength"}' + } + }) + validateStack(stack, ruleId, TestType.COMPLIANCE) + }) + + test("Compliance 3: JSON-formatted log (CfnDeployment)", () => { + new CfnDeployment(stack, "RestApiDeploymentJsonFormat", { + restApiId: "foo", + stageDescription: { + accessLogSetting: { + destinationArn: + "arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod", + format: + // eslint-disable-next-line max-len + '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "caller":"$context.identity.caller", "user":"$context.identity.user","requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength"}' + } + } + }) + validateStack(stack, ruleId, TestType.COMPLIANCE) + }) + + test("Compliance 4: JSON-formatted log (CfnApi)", () => { + new CfnApi(stack, "SamApiJsonFormat", { + stageName: "MyApi", + accessLogSetting: { + destinationArn: + "arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod", + format: + // eslint-disable-next-line max-len + '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "caller":"$context.identity.caller", "user":"$context.identity.user","requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength"}' + } + }) + validateStack(stack, ruleId, TestType.COMPLIANCE) + }) + + test("Compliance 5: JSON-formatted log (CfnHttpApi)", () => { + new CfnHttpApi(stack, "SamHttpApiJsonFormat", { + stageName: "MyApi", + accessLogSetting: { + destinationArn: + "arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod", + format: + // eslint-disable-next-line max-len + '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength"}' + } + }) + validateStack(stack, ruleId, TestType.COMPLIANCE) + }) + + test("Compliance 6: No stageDescription (CfnDeployment)", () => { + new CfnDeployment(stack, "RestApiDeploymentNoStageDescription", { + restApiId: "foo" + }) + validateStack(stack, ruleId, TestType.COMPLIANCE) + }) +}) diff --git a/packages/cdkConstructs/tests/nag/ApiGatewayMutualTls.test.ts b/packages/cdkConstructs/tests/nag/ApiGatewayMutualTls.test.ts new file mode 100644 index 00000000..2a70a2b5 --- /dev/null +++ b/packages/cdkConstructs/tests/nag/ApiGatewayMutualTls.test.ts @@ -0,0 +1,70 @@ +import {Aspects, Stack} from "aws-cdk-lib" +import {beforeEach, test} from "vitest" +import {TestPack, TestType, validateStack} from "./utils" +import {ApiGWMutualTls} from "../../src/nag/rules" +import {describe} from "node:test" +import {CfnDomainName} from "aws-cdk-lib/aws-apigateway" + +const testPack = new TestPack([ + ApiGWMutualTls +]) +let stack: Stack +beforeEach(() => { + stack = new Stack() + Aspects.of(stack).add(testPack) +}) + +describe("ApiGWMutualTls", () => { + const ruleId = "ApiGWMutualTls" + test("Non-compliant when mutual TLS is not enabled", () => { + new CfnDomainName(stack, "TestDomain", { + domainName: "test.example.com" + }) + + // Validate + validateStack(stack, ruleId, TestType.NON_COMPLIANCE) + }) + test("Compliant when mutual TLS is enabled", () => { + new CfnDomainName(stack, "TestDomain", { + domainName: "test.example.com", + mutualTlsAuthentication: { + truststoreUri: "truststoreUri", + truststoreVersion: "truststoreVersion" + } + }) + + // Validate + validateStack(stack, ruleId, TestType.COMPLIANCE) + }) + + test("Non-compliant when mutual TLS is missing trustStoreVersion", () => { + new CfnDomainName(stack, "TestDomain", { + domainName: "test.example.com", + mutualTlsAuthentication: { + truststoreUri: "truststoreUri" + } + }) + + // Validate + validateStack(stack, ruleId, TestType.NON_COMPLIANCE) + }) + test("Compliant when mutual TLS is not enabled in a pull request", () => { + stack.node.setContext("isPullRequest", true) + new CfnDomainName(stack, "TestDomain", { + domainName: "test.example.com" + }) + + // Validate + validateStack(stack, ruleId, TestType.COMPLIANCE) + }) + test("Compliant when mutual TLS is not enabled in not a pull request", () => { + stack.node.setContext("isPullRequest", false) + new CfnDomainName(stack, "TestDomain", { + domainName: "test.example.com" + }) + + // Validate + validateStack(stack, ruleId, TestType.NON_COMPLIANCE) + }) + +}) diff --git a/packages/cdkConstructs/tests/nag/epsNagPack.test.ts b/packages/cdkConstructs/tests/nag/epsNagPack.test.ts new file mode 100644 index 00000000..28b716cd --- /dev/null +++ b/packages/cdkConstructs/tests/nag/epsNagPack.test.ts @@ -0,0 +1,63 @@ +import { + App, + Aspects, + CfnResource, + Stack +} from "aws-cdk-lib" +import {EpsNagPack} from "../../src/" +import {test, describe, expect} from "vitest" +import {IApplyRule, NagMessageLevel} from "cdk-nag" + +describe("Check NagPack Details", () => { + describe("EPSNagPack", () => { + class EpsNagPackExtended extends EpsNagPack { + actualWarnings = new Array() + actualErrors = new Array() + applyRule(params: IApplyRule): void { + const ruleSuffix = params.ruleSuffixOverride + ? params.ruleSuffixOverride + : params.rule.name + const ruleId = `${pack.readPackName}-${ruleSuffix}` + if (params.level === NagMessageLevel.WARN) { + this.actualWarnings.push(ruleId) + } else { + this.actualErrors.push(ruleId) + } + } + } + const pack = new EpsNagPackExtended() + test("Pack Name is correct", () => { + expect(pack.readPackName).toStrictEqual("EpsNagPack") + }) + test("Pack contains expected warning and error rules", () => { + const expectedWarnings = [] as Array + const expectedErrors = [ + "EpsNagPack-EPS1", + "EpsNagPack-EPS2", + "EpsNagPack-EPS3", + "EpsNagPack-EPS4", + "EpsNagPack-EPS5", + "EpsNagPack-EPS6", + "EpsNagPack-EPS7", + "EpsNagPack-EPS8", + "EpsNagPack-EPS9", + "EpsNagPack-EPS10", + "EpsNagPack-EPS11", + "EpsNagPack-EPS12", + "EpsNagPack-EPS13", + "EpsNagPack-EPS14", + "EpsNagPack-EPS15", + "EpsNagPack-EPS16", + "EpsNagPack-EPS17", + "EpsNagPack-EPS18", + "EpsNagPack-EPS19" + ] + const stack = new Stack() + Aspects.of(stack).add(pack) + new CfnResource(stack, "rTestResource", {type: "foo"}) + App.of(stack)?.synth() + expect(pack.actualWarnings.sort()).toEqual(expectedWarnings.sort()) + expect(pack.actualErrors.sort()).toEqual(expectedErrors.sort()) + }) + }) +}) diff --git a/packages/cdkConstructs/tests/nag/utils.ts b/packages/cdkConstructs/tests/nag/utils.ts new file mode 100644 index 00000000..c44d565b --- /dev/null +++ b/packages/cdkConstructs/tests/nag/utils.ts @@ -0,0 +1,98 @@ + +import {App, CfnResource, Stack} from "aws-cdk-lib" +import {IConstruct} from "constructs" +import { + NagPack, + NagPackProps, + INagSuppressionIgnore, + NagMessageLevel, + NagRuleResult +} from "cdk-nag" +import {expect} from "vitest" + +export enum TestType { + NON_COMPLIANCE, + COMPLIANCE, + VALIDATION_FAILURE, +} +export function validateStack(stack: Stack, ruleId: string, type: TestType) { + expect(ruleId).not.toEqual("") + //const messages = SynthUtils.synthesize(stack).messages + const synthedApp = App.of(stack)?.synth() + const messages = synthedApp?.stacks[0].messages || [] + switch (type) { + case TestType.COMPLIANCE: + expect(messages).not.toContainEqual( + expect.objectContaining({ + entry: expect.objectContaining({ + data: expect.stringMatching(`.*${ruleId}(\\[.*\\])?:`) + }) + }) + ) + noValidationFailure() + break + case TestType.NON_COMPLIANCE: + expect(messages).toContainEqual( + expect.objectContaining({ + entry: expect.objectContaining({ + data: expect.stringContaining(`${ruleId}:`) + }) + }) + ) + noValidationFailure() + break + case TestType.VALIDATION_FAILURE: + expect(messages).toContainEqual( + expect.objectContaining({ + entry: expect.objectContaining({ + data: expect.stringMatching(`.*CdkNagValidationFailure.*${ruleId}`) + }) + }) + ) + break + } + + function noValidationFailure() { + expect(messages).not.toContainEqual( + expect.objectContaining({ + entry: expect.objectContaining({ + data: expect.stringMatching(`.*CdkNagValidationFailure.*${ruleId}`) + }) + }) + ) + } +} + +export class TestPack extends NagPack { + readonly rules: Array<(node: CfnResource) => NagRuleResult> + readonly ruleSuffixOverride?: string + readonly level?: NagMessageLevel + constructor( + rules: Array<(node: CfnResource) => NagRuleResult>, + ignoreSuppressionCondition?: INagSuppressionIgnore, + ruleSuffixOverride?: string, + level?: NagMessageLevel, + props?: NagPackProps + ) { + super(props) + this.packName = "Test" + this.rules = rules + this.packGlobalSuppressionIgnore = ignoreSuppressionCondition + this.ruleSuffixOverride = ruleSuffixOverride + this.level = level + } + public visit(node: IConstruct): void { + if (node instanceof CfnResource) { + this.rules.forEach((rule) => { + this.applyRule({ + ruleSuffixOverride: this.ruleSuffixOverride, + info: "foo.", + explanation: "bar.", + level: this.level ?? NagMessageLevel.ERROR, + rule: rule, + node: node + }) + }) + } + } +} diff --git a/packages/cdkConstructs/tests/stacks/deleteUnusedStacks.test.ts b/packages/cdkConstructs/tests/stacks/deleteUnusedStacks.test.ts new file mode 100644 index 00000000..ecb88239 --- /dev/null +++ b/packages/cdkConstructs/tests/stacks/deleteUnusedStacks.test.ts @@ -0,0 +1,1023 @@ +import { + describe, + test, + beforeEach, + afterEach, + expect, + vi +} from "vitest" + +import {deleteUnusedMainStacks, deleteUnusedPrStacks, getActiveApiVersions} from "../../src/stacks/deleteUnusedStacks" + +const mockListStacksSend = vi.fn() +const mockDeleteStackSend = vi.fn() +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const mockListHostedZonesByNameSend = vi.fn((_) => ({HostedZones: [{Id: "Z123"}]})) +const mockListResourceRecordSetsSend = vi.fn() +const mockChangeResourceRecordSetsSend = vi.fn() + +vi.mock("@aws-sdk/client-cloudformation", () => { + class CloudFormationClient { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(public config: any = {}) {} + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + send(command: any) { + if (command instanceof ListStacksCommand) { + return mockListStacksSend(command.input) + } else if (command instanceof DeleteStackCommand) { + return mockDeleteStackSend(command.input) + } else { + throw new TypeError("Unknown command") + } + } + } + + class ListStacksCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + class DeleteStackCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + return {CloudFormationClient, ListStacksCommand, DeleteStackCommand} +}) + +vi.mock("@aws-sdk/client-route-53", () => { + class Route53Client { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(public config: any = {}) {} + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + send(command: any) { + if (command instanceof ListHostedZonesByNameCommand) { + return mockListHostedZonesByNameSend(command.input) + } else if (command instanceof ListResourceRecordSetsCommand) { + return mockListResourceRecordSetsSend(command.input) + } else if (command instanceof ChangeResourceRecordSetsCommand) { + return mockChangeResourceRecordSetsSend(command.input) + } else { + throw new TypeError("Unknown command") + } + } + } + + class ListHostedZonesByNameCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + class ListResourceRecordSetsCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + class ChangeResourceRecordSetsCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + return {Route53Client, ListHostedZonesByNameCommand, ListResourceRecordSetsCommand, ChangeResourceRecordSetsCommand} +}) + +const originalEnv = process.env +const originalFetch = globalThis.fetch + +const mockActiveVersion = "v1.2.3" +const mockGetPRState = vi.fn<(url: string) => string>((url: string) => { + throw new Error(`Unexpected URL: ${url}`) +}) + +describe("stack deletion", () => { + const baseStackName = "eps-api" + const repoName = "eps-cdk-utils" + const basePath = "status-path" + const hostedZoneName = "dev.eps.national.nhs.uk." + + beforeEach(() => { + process.env = { + ...originalEnv, + APIGEE_ENVIRONMENT: "prod", + APIM_STATUS_API_KEY: "test-api-key", + GITHUB_TOKEN: "test-github-token" + } + + mockListStacksSend.mockReset() + mockDeleteStackSend.mockReset() + mockListHostedZonesByNameSend.mockReset() + mockListResourceRecordSetsSend.mockReset() + mockChangeResourceRecordSetsSend.mockReset() + mockGetPRState.mockReset() + + // By default, no CNAME records are present; individual tests + // can override this where specific records are required. + mockListResourceRecordSetsSend.mockReturnValue({ResourceRecordSets: []}) + + vi.useFakeTimers() + vi.setSystemTime(new Date("2024-01-03T00:00:00.000Z")) + }) + + afterEach(() => { + process.env = originalEnv + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = originalFetch + vi.useRealTimers() + }) + + describe("deleteUnusedMainStacks", () => { + test("deletes superseded stacks when embargo has passed", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-abcd123`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: null}), + hostedZoneName + ) + await vi.runAllTimersAsync() + await promise + + // Superseded version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(2) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-v1-2-2`}) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-abcd123`}) + }) + + test("does not delete embargoed versions even if active version is outside embargo period", async () => { + const now = new Date() + const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000) + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-v1-2-4`, + StackStatus: "CREATE_COMPLETE", + CreationTime: oneHourAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: null}), + hostedZoneName + ) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + }) + + test("does not delete superseded stack when active version is within embargo period", async () => { + const now = new Date() + const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000) + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: oneHourAgo + }, + { + StackName: `${baseStackName}-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: null}), + hostedZoneName + ) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + }) + + test("deletes superseded sandbox stacks when embargo has passed", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + process.env.APIGEE_ENVIRONMENT = "int" + + mockListResourceRecordSetsSend.mockReturnValue({ + ResourceRecordSets: [ + { + Name: `${baseStackName}-sandbox-v1-2-3.dev.eps.national.nhs.uk.`, + Type: "CNAME" + }, + { + Name: `${baseStackName}-sandbox-v1-2-2.dev.eps.national.nhs.uk.`, + Type: "CNAME" + } + ] + }) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: mockActiveVersion}), + hostedZoneName + ) + await vi.runAllTimersAsync() + await promise + + // Superseded sandbox version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-sandbox-v1-2-2`}) + + // CNAME deletion for the superseded sandbox stack + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledTimes(1) + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledWith({ + HostedZoneId: "Z123", + ChangeBatch: { + Changes: [{ + Action: "DELETE", + ResourceRecordSet: { + Name: `${baseStackName}-sandbox-v1-2-2.dev.eps.national.nhs.uk.`, + Type: "CNAME" + } + }] + } + }) + }) + + test("does not delete CNAME records if no hosted zone name is provided", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + process.env.APIGEE_ENVIRONMENT = "int" + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: null}), + undefined + ) + await vi.runAllTimersAsync() + await promise + + // Superseded sandbox version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-v1-2-2`}) + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) + + test("does not delete CNAME records if hosted zone cannot be found", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + process.env.APIGEE_ENVIRONMENT = "int" + + mockListHostedZonesByNameSend.mockReturnValueOnce({HostedZones: []}) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: null}), + hostedZoneName + ) + await vi.runAllTimersAsync() + await promise + + // Superseded sandbox version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-v1-2-2`}) + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) + + test("does not delete CNAME records if no CNAME records are found", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + process.env.APIGEE_ENVIRONMENT = "int" + mockListResourceRecordSetsSend.mockReturnValueOnce({}) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: null}), + hostedZoneName + ) + await vi.runAllTimersAsync() + await promise + + // Superseded sandbox version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-v1-2-2`}) + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) + + test("deletes superseded internal-dev sandbox stacks when embargo has passed", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + process.env.APIGEE_ENVIRONMENT = "internal-dev" + + mockListResourceRecordSetsSend.mockReturnValue({ + ResourceRecordSets: [ + { + Name: `${baseStackName}-sandbox-v1-2-3.dev.eps.national.nhs.uk.`, + Type: "CNAME" + }, + { + Name: `${baseStackName}-sandbox-v1-2-2.dev.eps.national.nhs.uk.`, + Type: "CNAME" + } + ] + }) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: mockActiveVersion}), + hostedZoneName + ) + await vi.runAllTimersAsync() + await promise + + // Superseded sandbox version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-sandbox-v1-2-2`}) + + // CNAME deletion for the superseded sandbox stack + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledTimes(1) + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledWith({ + HostedZoneId: "Z123", + ChangeBatch: { + Changes: [{ + Action: "DELETE", + ResourceRecordSet: { + Name: `${baseStackName}-sandbox-v1-2-2.dev.eps.national.nhs.uk.`, + Type: "CNAME" + } + }] + } + }) + }) + + test("still deletes non sandbox superseded stacks when sandbox state is unavailable", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + process.env.APIGEE_ENVIRONMENT = "int" + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-sandbox-v1-2-2`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: null}), + hostedZoneName + ) + await vi.runAllTimersAsync() + await promise + + // Superseded version should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-v1-2-2`}) + }) + + test("ignores PR stacks", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-pr-123`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: null}), + hostedZoneName + ) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + }) + + test("skips stacks with DELETE_COMPLETE status", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-2`, + StackStatus: "DELETE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedMainStacks( + baseStackName, + () => Promise.resolve({baseEnvVersion: mockActiveVersion, sandboxEnvVersion: null}), + hostedZoneName + ) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + }) + }) + + describe("getActiveApiVersions", () => { + test("fetches active version for prod environment", async () => { + process.env.APIGEE_ENVIRONMENT = "prod" + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = vi.fn((url: string) => { + expect(url).toBe(`https://api.service.nhs.uk/${basePath}/_status`) + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: "v2.0.0"}}}}) + }) + }) + + const result = await getActiveApiVersions(basePath) + + expect(result).toEqual({ + baseEnvVersion: "v2.0.0", + sandboxEnvVersion: null + }) + }) + + test("fetches active version for int environment with sandbox", async () => { + process.env.APIGEE_ENVIRONMENT = "int" + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = vi.fn((url: string) => { + if (url === `https://int.api.service.nhs.uk/${basePath}/_status`) { + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: "v1.5.0"}}}}) + }) + } else if (url === `https://sandbox.api.service.nhs.uk/${basePath}/_status`) { + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: "v1.5.1"}}}}) + }) + } + throw new Error(`Unexpected URL: ${url}`) + }) + + const result = await getActiveApiVersions(basePath) + + expect(result).toEqual({ + baseEnvVersion: "v1.5.0", + sandboxEnvVersion: "v1.5.1" + }) + }) + + test("fetches active version for internal-dev environment with sandbox", async () => { + process.env.APIGEE_ENVIRONMENT = "internal-dev" + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = vi.fn((url: string) => { + if (url === `https://internal-dev.api.service.nhs.uk/${basePath}/_status`) { + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: "v1.3.0"}}}}) + }) + } else if (url === `https://internal-dev-sandbox.api.service.nhs.uk/${basePath}/_status`) { + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: "v1.3.2"}}}}) + }) + } + throw new Error(`Unexpected URL: ${url}`) + }) + + const result = await getActiveApiVersions(basePath) + + expect(result).toEqual({ + baseEnvVersion: "v1.3.0", + sandboxEnvVersion: "v1.3.2" + }) + }) + + test("handles sandbox environment fetch failure gracefully", async () => { + process.env.APIGEE_ENVIRONMENT = "int" + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = vi.fn((url: string) => { + if (url === `https://int.api.service.nhs.uk/${basePath}/_status`) { + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: "v1.7.0"}}}}) + }) + } else if (url === `https://sandbox.api.service.nhs.uk/${basePath}/_status`) { + return Promise.resolve({ + ok: false, + status: 503, + text: async () => "Service Unavailable" + }) + } + throw new Error(`Unexpected URL: ${url}`) + }) + + const result = await getActiveApiVersions(basePath) + + expect(result).toEqual({ + baseEnvVersion: "v1.7.0", + sandboxEnvVersion: null + }) + }) + + test("throws error when base environment fetch fails", async () => { + process.env.APIGEE_ENVIRONMENT = "prod" + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = vi.fn(() => { + return Promise.resolve({ + ok: false, + status: 404, + text: async () => "Not Found" + }) + }) + + await expect(getActiveApiVersions(basePath)).rejects.toThrow( + `Failed to fetch active version from https://api.service.nhs.uk/${basePath}/_status: 404 Not Found` + ) + }) + + test("does not fetch sandbox for non-int/internal-dev environments", async () => { + process.env.APIGEE_ENVIRONMENT = "ref" + + const mockFetch = vi.fn((url: string) => { + if (url === `https://ref.api.service.nhs.uk/${basePath}/_status`) { + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: "v1.9.0"}}}}) + }) + } + throw new Error(`Unexpected URL: ${url}`) + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = mockFetch + + const result = await getActiveApiVersions(basePath) + + expect(result).toEqual({ + baseEnvVersion: "v1.9.0", + sandboxEnvVersion: null + }) + + // Should only have called fetch once (for base environment) + expect(mockFetch).toHaveBeenCalledTimes(1) + }) + + test("includes authorization header in requests", async () => { + process.env.APIGEE_ENVIRONMENT = "prod" + const testApiKey = "test-api-key-value" + process.env.APIM_STATUS_API_KEY = testApiKey + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const mockFetch = vi.fn((_url: string, options: any) => { + expect(options.headers.apikey).toBe(testApiKey) + expect(options.headers.Accept).toBe("application/json") + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: "v2.1.0"}}}}) + }) + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = mockFetch + + const result = await getActiveApiVersions(basePath) + + expect(result).toEqual({ + baseEnvVersion: "v2.1.0", + sandboxEnvVersion: null + }) + expect(mockFetch).toHaveBeenCalled() + }) + }) + + describe("deleteUnusedPrStacks", () => { + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = (url: string) => { + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({state: mockGetPRState(url)}) + }) + } + }) + + test("deletes closed PR stacks and CNAME records", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + }, + { + StackName: `${baseStackName}-pr-123`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + mockListResourceRecordSetsSend.mockReturnValue({ + ResourceRecordSets: [ + { + Name: `${baseStackName}-pr-123.dev.eps.national.nhs.uk.`, + Type: "CNAME" + } + ] + }) + + mockGetPRState.mockImplementation((url: string) => { + if (url.endsWith("/repos/NHSDigital/eps-cdk-utils/pulls/123")) { + return "closed" + } + throw new Error(`Unexpected URL: ${url}`) + }) + + const promise = deleteUnusedPrStacks(baseStackName, repoName, hostedZoneName) + await vi.runAllTimersAsync() + await promise + + // One delete stack call for the PR stack + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-pr-123`}) + + // CNAME deletion for the PR stack + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledTimes(1) + expect(mockChangeResourceRecordSetsSend).toHaveBeenCalledWith({ + HostedZoneId: "Z123", + ChangeBatch: { + Changes: [{ + Action: "DELETE", + ResourceRecordSet: { + Name: `${baseStackName}-pr-123.dev.eps.national.nhs.uk.`, + Type: "CNAME" + } + }] + } + }) + }) + + test("does not delete open PR stacks", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-pr-456`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + mockGetPRState.mockImplementation((url: string) => { + if (url.endsWith("/repos/NHSDigital/eps-cdk-utils/pulls/456")) { + return "open" + } + throw new Error(`Unexpected URL: ${url}`) + }) + + const promise = deleteUnusedPrStacks(baseStackName, repoName, hostedZoneName) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) + + test("handles multiple pages of CloudFormation stacks", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockImplementation(({NextToken}) => { + if (!NextToken) { + return { + StackSummaries: [ + { + StackName: `${baseStackName}-v1-2-3`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ], + NextToken: "token-1" + } + } + + return { + StackSummaries: [ + { + StackName: `${baseStackName}-pr-789`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + } + }) + + mockGetPRState.mockImplementation((url: string) => { + if (url.endsWith("/repos/NHSDigital/eps-cdk-utils/pulls/789")) { + return "closed" + } + throw new Error(`Unexpected URL: ${url}`) + }) + + const promise = deleteUnusedPrStacks(baseStackName, repoName, hostedZoneName) + await vi.runAllTimersAsync() + await promise + + // Both pages of stacks should have been requested + expect(mockListStacksSend).toHaveBeenCalledTimes(2) + + // PR stack from the second page should be deleted + expect(mockDeleteStackSend).toHaveBeenCalledTimes(1) + expect(mockDeleteStackSend).toHaveBeenCalledWith({StackName: `${baseStackName}-pr-789`}) + }) + + test("skips stacks with DELETE_COMPLETE status", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-pr-101`, + StackStatus: "DELETE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + const promise = deleteUnusedPrStacks(baseStackName, repoName, hostedZoneName) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) + + test("skips PR stacks when fetching PR state fails", async () => { + const now = new Date() + const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) + + mockListStacksSend.mockReturnValue({ + StackSummaries: [ + { + StackName: `${baseStackName}-pr-202`, + StackStatus: "CREATE_COMPLETE", + CreationTime: twoDaysAgo + } + ] + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = (url: string) => { + if (url.includes("api.github.com")) { + return Promise.resolve({ + ok: false, + status: 500, + text: async () => "Error fetching PR" + }) + } + // Default mock for other fetch calls + return Promise.resolve({ + ok: true, + status: 200, + text: async () => "", + json: async () => ({checks: {healthcheck: {outcome: {versionNumber: mockActiveVersion}}}}) + }) + } + + const promise = deleteUnusedPrStacks(baseStackName, repoName, hostedZoneName) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) + + test("handles no stacks returned", async () => { + mockListStacksSend.mockReturnValue({}) + + const promise = deleteUnusedPrStacks(baseStackName, repoName, hostedZoneName) + await vi.runAllTimersAsync() + await promise + + // No delete stack call should have been made + expect(mockDeleteStackSend).not.toHaveBeenCalled() + + // No CNAME deletion should have been made + expect(mockChangeResourceRecordSetsSend).not.toHaveBeenCalled() + }) + }) +}) diff --git a/packages/cdkConstructs/tests/utils/helpers.test.ts b/packages/cdkConstructs/tests/utils/helpers.test.ts new file mode 100644 index 00000000..62673434 --- /dev/null +++ b/packages/cdkConstructs/tests/utils/helpers.test.ts @@ -0,0 +1,131 @@ +import { + afterEach, + describe, + expect, + test, + vi +} from "vitest" +import {Stack, CfnResource} from "aws-cdk-lib" +import {Code, Function as LambdaFunction, Runtime} from "aws-cdk-lib/aws-lambda" +import {NagPackSuppression, NagSuppressions} from "cdk-nag" + +import * as helpers from "../../src/utils/helpers" + +const defaultSuppressionRules = ["LAMBDA_DLQ_CHECK", "LAMBDA_INSIDE_VPC", "LAMBDA_CONCURRENCY_CHECK"] + +const createResource = (stack: Stack, id: string, type = "Custom::Test", path?: string): CfnResource => { + const resource = new CfnResource(stack, id, {type, properties: {}}) + resource.cfnOptions.metadata = { + ...(resource.cfnOptions.metadata), + "aws:cdk:path": path ?? `${stack.stackName}/${id}` + } + return resource +} + +afterEach(() => { + vi.restoreAllMocks() +}) + +describe("findCloudFormationResourcesByPath", () => { + test("returns unique matches for the provided metadata paths", () => { + const stack = new Stack(undefined, "HelpersTestStack") + const first = createResource(stack, "First", "Custom::Foo", "match/one") + const second = createResource(stack, "Second", "Custom::Foo", "match/two") + createResource(stack, "Third", "Custom::Foo", "nope") + + const matches = helpers.findCloudFormationResourcesByPath(stack, ["match/one", "match/one", "match/two"]) + + expect(matches).toEqual([first, second]) + }) +}) + +describe("findCloudFormationResourcesByType", () => { + test("returns every resource whose CloudFormation type matches", () => { + const stack = new Stack(undefined, "HelpersTestStack") + const fooOne = createResource(stack, "FooOne", "Custom::Foo") + const fooTwo = createResource(stack, "FooTwo", "Custom::Foo") + createResource(stack, "Bar", "Custom::Bar") + + const matches = helpers.findCloudFormationResourcesByType(stack, "Custom::Foo") + + expect(matches).toEqual([fooOne, fooTwo]) + }) +}) + +describe("addSuppressions", () => { + test("merges new rules, deduplicates them, and creates metadata when missing", () => { + const stack = new Stack(undefined, "HelpersTestStack") + const existing = createResource(stack, "Existing") + existing.cfnOptions.metadata = { + ...existing.cfnOptions.metadata, + guard: {SuppressedRules: ["EXISTING", "SHARED"]} + } + const empty = createResource(stack, "Empty") + + helpers.addSuppressions([existing, empty], ["SHARED", "NEW"]) + + expect(existing.cfnOptions.metadata?.guard?.SuppressedRules).toEqual(["EXISTING", "SHARED", "NEW"]) + expect(empty.cfnOptions.metadata?.guard?.SuppressedRules).toEqual(["SHARED", "NEW"]) + }) +}) + +describe("addLambdaCfnGuardSuppressions", () => { + test("applies the default lambda suppressions to every lambda in the stack", () => { + const stack = new Stack(undefined, "HelpersTestStack") + const lambdaOne = new LambdaFunction(stack, "LambdaOne", { + runtime: Runtime.NODEJS_18_X, + handler: "index.handler", + code: Code.fromInline("exports.handler = async () => {};") + }) + const lambdaTwo = new LambdaFunction(stack, "LambdaTwo", { + runtime: Runtime.NODEJS_18_X, + handler: "index.handler", + code: Code.fromInline("exports.handler = async () => {};") + }) + + helpers.addLambdaCfnGuardSuppressions(stack) + + const firstCfn = lambdaOne.node.defaultChild as CfnResource + const secondCfn = lambdaTwo.node.defaultChild as CfnResource + expect(firstCfn.cfnOptions.metadata?.guard?.SuppressedRules).toEqual(defaultSuppressionRules) + expect(secondCfn.cfnOptions.metadata?.guard?.SuppressedRules).toEqual(defaultSuppressionRules) + }) +}) + +describe("safeAddNagSuppressionGroup", () => { + test("invokes cdk-nag for every provided path", () => { + const stack = new Stack(undefined, "HelpersTestStack") + const suppressions: Array = [{id: "RULE", reason: "already covered"}] + const spy = vi.spyOn(NagSuppressions, "addResourceSuppressionsByPath").mockImplementation(() => {}) + + helpers.safeAddNagSuppressionGroup(stack, ["one", "two"], suppressions) + + expect(spy).toHaveBeenCalledTimes(2) + expect(spy).toHaveBeenNthCalledWith(1, stack, "one", suppressions) + expect(spy).toHaveBeenNthCalledWith(2, stack, "two", suppressions) + }) +}) + +describe("safeAddNagSuppression", () => { + const sampleSuppressions: Array = [{id: "RULE", reason: "covered elsewhere"}] + + test("routes suppressions to cdk-nag", () => { + const stack = new Stack(undefined, "HelpersTestStack") + const spy = vi.spyOn(NagSuppressions, "addResourceSuppressionsByPath").mockImplementation(() => {}) + + helpers.safeAddNagSuppression(stack, "path/to/resource", sampleSuppressions) + + expect(spy).toHaveBeenCalledWith(stack, "path/to/resource", sampleSuppressions) + }) + + test("logs and swallows errors when the target path cannot be resolved", () => { + const stack = new Stack(undefined, "HelpersTestStack") + vi.spyOn(NagSuppressions, "addResourceSuppressionsByPath").mockImplementation(() => { + throw new Error("missing") + }) + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}) + + expect(() => helpers.safeAddNagSuppression(stack, "missing/path", sampleSuppressions)).not.toThrow() + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("missing/path")) + }) +}) diff --git a/packages/cdkConstructs/tsconfig.json b/packages/cdkConstructs/tsconfig.json index aabe8c5c..992d769b 100644 --- a/packages/cdkConstructs/tsconfig.json +++ b/packages/cdkConstructs/tsconfig.json @@ -12,7 +12,7 @@ "outDir": "lib", "strict": true, "lib": [ - "es2020" + "es2022" ], "noImplicitAny": true, "strictNullChecks": true, @@ -32,6 +32,12 @@ "../../node_modules/@types" ] }, - "include": ["src/**/*"], - "exclude": ["node_modules", "cdk.out", "tests/**/*"] + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "cdk.out", + "tests/**/*" + ] } diff --git a/packages/deploymentUtils/package.json b/packages/deploymentUtils/package.json new file mode 100644 index 00000000..91413caf --- /dev/null +++ b/packages/deploymentUtils/package.json @@ -0,0 +1,36 @@ +{ + "name": "@nhsdigital/eps-deployment-utils", + "version": "1.0.0", + "description": "Shared deployment utilities for EPS projects (spec publishing, config helpers, etc)", + "scripts": { + "build": "tsc", + "clean": "rm -rf lib", + "prepublishOnly": "npm run clean && npm run build", + "lint": "eslint --max-warnings 0 --fix --config ../../eslint.config.mjs .", + "test": "vitest run --coverage", + "check-licenses": "license-checker --failOn GPL --failOn LGPL" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/NHSDigital/eps-cdk-utils.git" + }, + "keywords": [], + "author": "NHSDigital", + "license": "MIT", + "private": false, + "type": "module", + "dependencies": { + "@aws-sdk/client-cloudformation": "^3.1008.0", + "@aws-sdk/client-lambda": "^3.1008.0", + "json-schema-to-ts": "^3.1.1" + }, + "bugs": { + "url": "https://github.com/NHSDigital/eps-cdk-utils/issues" + }, + "homepage": "https://github.com/NHSDigital/eps-cdk-utils#readme", + "files": [ + "lib" + ], + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts" +} diff --git a/packages/deploymentUtils/src/config/index.ts b/packages/deploymentUtils/src/config/index.ts new file mode 100644 index 00000000..ff03769f --- /dev/null +++ b/packages/deploymentUtils/src/config/index.ts @@ -0,0 +1,57 @@ +import {CloudFormationClient, ListExportsCommand} from "@aws-sdk/client-cloudformation" + +export function getConfigFromEnvVar(varName: string): string { + const value = process.env[varName] + if (!value) { + throw new Error(`Environment variable ${varName} is not set`) + } + return value +} + +export function getBooleanConfigFromEnvVar(varName: string): boolean { + const value = getConfigFromEnvVar(varName) + return value.toLowerCase() === "true" +} + +export function getNumberConfigFromEnvVar(varName: string): number { + const value = getConfigFromEnvVar(varName) + return Number(value) +} + +export async function getCloudFormationExports(region: string = "eu-west-2"): Promise> { + const cfnClient = new CloudFormationClient({region}) + const listExportsCommand = new ListExportsCommand({}) + const exports: Record = {} + // eslint-disable-next-line no-useless-assignment + let nextToken: string | undefined = undefined + + do { + const response = await cfnClient.send(listExportsCommand) + response.Exports?.forEach((exp) => { + if (exp.Name && exp.Value) { + exports[exp.Name] = exp.Value + } + }) + nextToken = response.NextToken + listExportsCommand.input.NextToken = nextToken + } while (nextToken) + + return exports +} + +export function getCFConfigValue(exports: Record, exportName: string): string { + const value = exports[exportName] + if (!value) { + throw new Error(`CloudFormation export ${exportName} not found`) + } + return value +} + +export function getBooleanCFConfigValue(exports: Record, exportName: string): boolean { + const value = getCFConfigValue(exports, exportName) + return value.toLowerCase() === "true" +} + +export function calculateVersionedStackName(baseStackName: string, version: string): string { + return `${baseStackName}-${version.replaceAll(".", "-")}` +} diff --git a/packages/deploymentUtils/src/index.ts b/packages/deploymentUtils/src/index.ts new file mode 100644 index 00000000..b6b8443f --- /dev/null +++ b/packages/deploymentUtils/src/index.ts @@ -0,0 +1,4 @@ +export * from "./specifications/deployApi" +export * from "./specifications/writeSchemas" +export * from "./specifications/deleteProxygenDeployments" +export * from "./config/index" diff --git a/packages/deploymentUtils/src/specifications/deleteProxygenDeployments.ts b/packages/deploymentUtils/src/specifications/deleteProxygenDeployments.ts new file mode 100644 index 00000000..53dc69b0 --- /dev/null +++ b/packages/deploymentUtils/src/specifications/deleteProxygenDeployments.ts @@ -0,0 +1,120 @@ +import {LambdaClient} from "@aws-sdk/client-lambda" +import {getCFConfigValue, getCloudFormationExports} from "../config" +import {invokeLambda} from "./invokeLambda" + +interface ProxygenInstance { + name: string +} + +async function isClosedPullRequest(instanceName: string, apigeeApi: string, repoName: string): Promise { + const match = new RegExp(String.raw`^${apigeeApi}-pr-(?\d+)$`).exec(instanceName) + if (!match?.groups?.pullRequestId) { + return false + } + + const pullRequestId = match.groups.pullRequestId + console.log(`Checking pull request id ${pullRequestId}`) + const url = `https://api.github.com/repos/NHSDigital/${repoName}/pulls/${pullRequestId}` + + const headers: Record = { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${process.env.GITHUB_TOKEN}` + } + + const response = await fetch(url, {headers}) + if (!response.ok) { + console.log(`Failed to fetch PR ${pullRequestId}: ${response.status} ${await response.text()}`) + return false + } + + const data = (await response.json()) as {state?: string} + if (data.state !== "closed") { + console.log(`not going to delete instance ${instanceName} as PR state is ${data.state}`) + return false + } + + console.log(`** going to delete instance ${instanceName} as PR state is ${data.state} **`) + return true +} + +async function deleteEnvProxygenDeployments( + apigeeEnvironment: string, + apigeeApi: string, + repoName: string, + proxygenPrivateKeyName: string, + proxygenKid: string +): Promise { + const lambda = new LambdaClient({}) + + const exports = await getCloudFormationExports() + const proxygenPrivateKeyArn = getCFConfigValue(exports, `account-resources:${proxygenPrivateKeyName}`) + + console.log(`Checking Apigee deployments of ${apigeeApi} on ${apigeeEnvironment}`) + const instances = JSON.parse(await invokeLambda( + lambda, + false, + "lambda-resources-ProxygenPTLInstanceGet", + { + apiName: apigeeApi, + environment: apigeeEnvironment, + kid: proxygenKid, + proxygenSecretName: proxygenPrivateKeyArn + } + )) as Array + + for (const instance of instances) { + const name = instance.name + + if (!(await isClosedPullRequest(name, apigeeApi, repoName))) { + continue + } + + await invokeLambda( + lambda, + false, + "lambda-resources-ProxygenPTLInstanceDelete", + { + apiName: apigeeApi, + environment: apigeeEnvironment, + instance: name, + kid: proxygenKid, + proxygenSecretName: proxygenPrivateKeyArn + } + ) + } +} + +/** + * Deletes Proxygen PTL deployments for closed pull requests across internal-dev and internal-dev-sandbox. + * + * For each supported Apigee environment, this function queries existing Proxygen instances + * for the given API and deletes those whose instance name corresponds to a closed GitHub PR + * in the specified repository. + * + * @param apigeeApi - The Apigee API name whose Proxygen deployments should be cleaned up. + * @param repoName - The GitHub repository name used to look up pull request state. + * @param proxygenPrivateKeyName - The CloudFormation export key for the Proxygen private key secret. + * @param proxygenKid - The key ID (kid) used when invoking the Proxygen Lambda functions. + * @returns A promise that resolves when all eligible deployments have been processed. + */ +export async function deleteProxygenDeployments( + apigeeApi: string, + repoName: string, + proxygenPrivateKeyName: string, + proxygenKid: string +): Promise { + await deleteEnvProxygenDeployments( + "internal-dev", + apigeeApi, + repoName, + proxygenPrivateKeyName, + proxygenKid + ) + await deleteEnvProxygenDeployments( + "internal-dev-sandbox", + apigeeApi, + repoName, + proxygenPrivateKeyName, + proxygenKid + ) +} diff --git a/packages/deploymentUtils/src/specifications/deployApi.ts b/packages/deploymentUtils/src/specifications/deployApi.ts new file mode 100644 index 00000000..f36393bb --- /dev/null +++ b/packages/deploymentUtils/src/specifications/deployApi.ts @@ -0,0 +1,128 @@ +import {LambdaClient} from "@aws-sdk/client-lambda" +import {getCFConfigValue, getCloudFormationExports} from "../config/index" +import {fixSpec} from "./fixSpec" +import {invokeLambda} from "./invokeLambda" + +export type ApiConfig = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + spec: any + apiName: string + version: string + apigeeEnvironment: string + isPullRequest: boolean + awsEnvironment: string + stackName: string + mtlsSecretName: string + clientCert: string + clientPrivateKey: string + proxygenPrivateKeyExportName: string + proxygenKid: string + hiddenPaths: Array +} + +export async function deployApi( + { + spec, + apiName, + version, + apigeeEnvironment, + isPullRequest, + awsEnvironment, + stackName, + mtlsSecretName, + clientCert, + clientPrivateKey, + proxygenPrivateKeyExportName, + proxygenKid, + hiddenPaths + }: ApiConfig, + blueGreen: boolean, + dryRun: boolean +): Promise { + const lambda = new LambdaClient({}) + const instance = fixSpec({ + spec, + apiName, + version, + apigeeEnvironment, + isPullRequest, + awsEnvironment, + stackName, + mtlsSecretName, + blueGreen + }) + + const exports = await getCloudFormationExports() + const proxygenPrivateKeyArn = getCFConfigValue(exports, `account-resources:${proxygenPrivateKeyExportName}`) + + let put_secret_lambda = "lambda-resources-ProxygenPTLMTLSSecretPut" + let instance_put_lambda = "lambda-resources-ProxygenPTLInstancePut" + let spec_publish_lambda = "lambda-resources-ProxygenPTLSpecPublish" + if (/^(int|sandbox|prod)$/.test(apigeeEnvironment)) { + put_secret_lambda = "lambda-resources-ProxygenProdMTLSSecretPut" + instance_put_lambda = "lambda-resources-ProxygenProdInstancePut" + spec_publish_lambda = "lambda-resources-ProxygenProdSpecPublish" + } + + if (!isPullRequest) { + console.log("Store the secret used for mutual TLS to AWS using Proxygen proxy lambda") + await invokeLambda( + lambda, + dryRun, + put_secret_lambda, + { + apiName, + environment: apigeeEnvironment, + secretName: mtlsSecretName, + secretKey: clientPrivateKey, + secretCert: clientCert, + kid: proxygenKid, + proxygenSecretName: proxygenPrivateKeyArn + } + ) + } + + console.log("Deploy the API instance using Proxygen proxy lambda") + await invokeLambda( + lambda, + dryRun, + instance_put_lambda, + { + apiName, + environment: apigeeEnvironment, + specDefinition: spec, + instance, + kid: proxygenKid, + proxygenSecretName: proxygenPrivateKeyArn + } + ) + + let spec_publish_env + if (apigeeEnvironment === "int") { + console.log("Deploy the API spec to prod catalogue as it is int environment") + spec.servers = [ {url: `https://sandbox.api.service.nhs.uk/${instance}`} ] + spec_publish_env = "prod" + } else if (apigeeEnvironment === "internal-dev" && !isPullRequest) { + console.log("Deploy the API spec to uat catalogue as it is internal-dev environment") + spec.servers = [ {url: `https://internal-dev-sandbox.api.service.nhs.uk/${instance}`} ] + spec_publish_env = "uat" + } + if (spec_publish_env) { + for (const path of hiddenPaths) { + delete spec.paths[path] + } + await invokeLambda( + lambda, + dryRun, + spec_publish_lambda, + { + apiName, + environment: spec_publish_env, + specDefinition: spec, + instance, + kid: proxygenKid, + proxygenSecretName: proxygenPrivateKeyArn + } + ) + } +} diff --git a/packages/deploymentUtils/src/specifications/fixSpec.ts b/packages/deploymentUtils/src/specifications/fixSpec.ts new file mode 100644 index 00000000..be28f4e1 --- /dev/null +++ b/packages/deploymentUtils/src/specifications/fixSpec.ts @@ -0,0 +1,66 @@ +import {calculateVersionedStackName} from "../config/index" + +type SpecConfig = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + spec: any + apiName: string + version: string + apigeeEnvironment: string + isPullRequest: boolean + awsEnvironment: string + stackName: string + mtlsSecretName: string + blueGreen: boolean +} + +function replaceSchemeRefs( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + spec: any, + domain: string +) { + const schemes = ["nhs-cis2-aal3", "nhs-login-p9", "app-level3", "app-level0"] + for (const scheme of schemes) { + if (spec.components.securitySchemes[scheme]) { + spec.components.securitySchemes[scheme] = { + "$ref": `https://${domain}/components/securitySchemes/${scheme}` + } + } + } +} + +export function fixSpec({ + spec, + apiName, + version, + apigeeEnvironment, + isPullRequest, + awsEnvironment, + stackName, + mtlsSecretName, + blueGreen +}: SpecConfig): string { + let instance = apiName + let stack = stackName + if (isPullRequest) { + const pr_id = stackName.split("-").pop() + instance = `${apiName}-pr-${pr_id}` + spec.info.title = `[PR-${pr_id}] ${spec.info.title}` + spec["x-nhsd-apim"].monitoring = false + } else if (blueGreen) { + stack = calculateVersionedStackName(stackName, version) + } + spec.info.version = version + spec["x-nhsd-apim"].target.url = `https://${stack}.${awsEnvironment}.eps.national.nhs.uk` + spec["x-nhsd-apim"].target.security.secret = mtlsSecretName + if (apigeeEnvironment === "prod") { + spec.servers = [ {url: `https://api.service.nhs.uk/${instance}`} ] + replaceSchemeRefs(spec, "proxygen.prod.api.platform.nhs.uk") + } else { + spec.servers = [ {url: `https://${apigeeEnvironment}.api.service.nhs.uk/${instance}`} ] + replaceSchemeRefs(spec, "proxygen.ptl.api.platform.nhs.uk") + } + if (apigeeEnvironment.includes("sandbox")) { + delete spec["x-nhsd-apim"]["target-attributes"] // Resolve issue with sandbox trying to look up app name + } + return instance +} diff --git a/packages/deploymentUtils/src/specifications/invokeLambda.ts b/packages/deploymentUtils/src/specifications/invokeLambda.ts new file mode 100644 index 00000000..8a3f343e --- /dev/null +++ b/packages/deploymentUtils/src/specifications/invokeLambda.ts @@ -0,0 +1,23 @@ +import {InvokeCommand, LambdaClient} from "@aws-sdk/client-lambda" + +export async function invokeLambda( + lambda: LambdaClient, + dryRun: boolean, + functionName: string, + payload: unknown +): Promise { + if (dryRun) { + console.log(`Would invoke lambda ${functionName}`) + return "null" + } + const invokeResult = await lambda.send(new InvokeCommand({ + FunctionName: functionName, + Payload: Buffer.from(JSON.stringify(payload)) + })) + const responsePayload = Buffer.from(invokeResult.Payload!).toString() + if (invokeResult.FunctionError) { + throw new Error(`Error calling lambda ${functionName}: ${responsePayload}`) + } + console.log(`Lambda ${functionName} invoked successfully. Response:`, responsePayload) + return responsePayload +} diff --git a/packages/deploymentUtils/src/specifications/writeSchemas.ts b/packages/deploymentUtils/src/specifications/writeSchemas.ts new file mode 100644 index 00000000..6c262971 --- /dev/null +++ b/packages/deploymentUtils/src/specifications/writeSchemas.ts @@ -0,0 +1,64 @@ +import fs from "node:fs" +import path from "node:path" +import {JSONSchema} from "json-schema-to-ts" + +function isNotJSONSchemaArray(schema: JSONSchema | ReadonlyArray): schema is JSONSchema { + return !Array.isArray(schema) +} + +function collapseExamples(schema: JSONSchema): JSONSchema { + if (typeof schema !== "object" || schema === null) { + return schema + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const result: any = {...schema} + + if (Array.isArray(schema.examples) && schema.examples.length > 0) { + result.example = schema.examples[0] + delete result.examples + } + + if (schema.items) { + if (isNotJSONSchemaArray(schema.items)) { + result.items = collapseExamples(schema.items) + } else { + result.items = schema.items.map(collapseExamples) + } + } + + if (schema.properties) { + const properties: Record = {} + for (const key in schema.properties) { + if (Object.hasOwn(schema.properties, key)) { + properties[key] = collapseExamples(schema.properties[key]) + } + } + result.properties = properties + } + + return result +} + +export function writeSchemas( + schemas: Record, + outputDir: string +): void { + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, {recursive: true}) + } + for (const name in schemas) { + if (Object.hasOwn(schemas, name)) { + const schema = schemas[name] + const fileName = `${name}.json` + const filePath = path.join(outputDir, fileName) + + try { + fs.writeFileSync(filePath, JSON.stringify(collapseExamples(schema), null, 2)) + console.log(`Schema ${fileName} written successfully.`) + } catch (error) { + console.error(`Error writing schema ${fileName}:`, error) + } + } + } +} diff --git a/packages/deploymentUtils/tests/config/index.test.ts b/packages/deploymentUtils/tests/config/index.test.ts new file mode 100644 index 00000000..8fc099b3 --- /dev/null +++ b/packages/deploymentUtils/tests/config/index.test.ts @@ -0,0 +1,151 @@ +import { + describe, + test, + beforeEach, + afterAll, + expect, + vi +} from "vitest" +import { + getConfigFromEnvVar, + getBooleanConfigFromEnvVar, + getNumberConfigFromEnvVar, + getCloudFormationExports, + getCFConfigValue, + getBooleanCFConfigValue +} from "../../src/config/index" + +const mockCloudFormationSend = vi.fn() +const mockS3Send = vi.fn() +const createdCfnClients: Array<{region?: string}> = [] +const createdS3Clients: Array<{region?: string}> = [] + +vi.mock("@aws-sdk/client-cloudformation", () => { + class CloudFormationClient { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + config: any + constructor(config: {region: string}) { + this.config = config + createdCfnClients.push({region: config.region}) + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + send(command: any) { + return mockCloudFormationSend(command) + } + } + + class ListExportsCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = {...input} + } + } + + return {CloudFormationClient, ListExportsCommand} +}) + +vi.mock("@aws-sdk/client-s3", () => { + class S3Client { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + config: any + constructor(config: {region: string}) { + this.config = config + createdS3Clients.push({region: config.region}) + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + send(command: any) { + return mockS3Send(command) + } + } + + class HeadObjectCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + return {S3Client, HeadObjectCommand} +}) + +const ORIGINAL_ENV = process.env + +describe("config helpers", () => { + beforeEach(() => { + process.env = {...ORIGINAL_ENV} + mockCloudFormationSend.mockReset() + mockS3Send.mockReset() + createdCfnClients.length = 0 + createdS3Clients.length = 0 + }) + + afterAll(() => { + process.env = ORIGINAL_ENV + }) + + test("getConfigFromEnvVar returns the configured value", () => { + process.env.STACK_NAME = "primary" + + expect(getConfigFromEnvVar("STACK_NAME")).toBe("primary") + }) + + test("getConfigFromEnvVar throws when value is missing", () => { + delete process.env.MISSING + + expect(() => getConfigFromEnvVar("MISSING")) + .toThrow("Environment variable MISSING is not set") + }) + + test("getBooleanConfigFromEnvVar maps string booleans", () => { + process.env.FEATURE_FLAG = "true" + process.env.OTHER_FLAG = "false" + + expect(getBooleanConfigFromEnvVar("FEATURE_FLAG")).toBe(true) + expect(getBooleanConfigFromEnvVar("OTHER_FLAG")).toBe(false) + }) + + test("getNumberConfigFromEnvVar parses numeric strings", () => { + process.env.TIMEOUT = "45" + + expect(getNumberConfigFromEnvVar("TIMEOUT")).toBe(45) + }) + + test("getCloudFormationExports aggregates paginated results", async () => { + mockCloudFormationSend + .mockResolvedValueOnce({ + Exports: [{Name: "exportA", Value: "valueA"}], + NextToken: "next" + }) + .mockResolvedValueOnce({ + Exports: [ + {Name: "exportB", Value: "valueB"}, + {Name: "missingValue", Value: undefined} + ] + }) + + const exports = await getCloudFormationExports("eu-west-1") + + expect(mockCloudFormationSend).toHaveBeenCalledTimes(2) + expect(exports).toEqual({exportA: "valueA", exportB: "valueB"}) + }) + + test("getCFConfigValue returns values and throws when missing", () => { + const exports = {foo: "bar"} + + expect(getCFConfigValue(exports, "foo")).toBe("bar") + expect(() => getCFConfigValue(exports, "baz")).toThrow("CloudFormation export baz not found") + }) + + test("getBooleanCFConfigValue interprets true/false strings", () => { + const exports = {flagTrue: "TRUE", flagFalse: "false"} + + expect(getBooleanCFConfigValue(exports, "flagTrue")).toBe(true) + expect(getBooleanCFConfigValue(exports, "flagFalse")).toBe(false) + }) +}) diff --git a/packages/deploymentUtils/tests/specifications/deleteProxygenDeployments.test.ts b/packages/deploymentUtils/tests/specifications/deleteProxygenDeployments.test.ts new file mode 100644 index 00000000..02b981ac --- /dev/null +++ b/packages/deploymentUtils/tests/specifications/deleteProxygenDeployments.test.ts @@ -0,0 +1,132 @@ +import { + beforeEach, + afterEach, + describe, + expect, + test, + vi +} from "vitest" +import {deleteProxygenDeployments} from "../../src/specifications/deleteProxygenDeployments" + +const getCloudFormationExportsMock = vi.hoisted(() => vi.fn()) +const invokeLambdaMock = vi.hoisted(() => vi.fn()) + +vi.mock("../../src/config/index", async (importOriginal) => { + const originalModule = await importOriginal() + return { + ...originalModule, + getCloudFormationExports: getCloudFormationExportsMock + } +}) + +vi.mock("../../src/specifications/invokeLambda", async () => { + return { + invokeLambda: invokeLambdaMock + } +}) + +const originalFetch = globalThis.fetch + +function createFetchResponse(state: string, ok = true, status = 200, textBody = "") { + return Promise.resolve({ + ok, + status, + text: async () => textBody, + json: async () => ({state}) + }) as unknown as Promise +} + +describe("deleteProxygenDeployments", () => { + beforeEach(() => { + getCloudFormationExportsMock.mockReset().mockResolvedValue({ + "account-resources:proxygenKey": "arn:proxygen-key" + }) + invokeLambdaMock.mockReset() + + // default fetch mock; tests can override behaviour + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = vi.fn((url: string) => { + if (url.includes("/pulls/456")) { + return createFetchResponse("open") + } + return createFetchResponse("closed") + }) + }) + + afterEach(() => { + // restore original fetch between tests + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = originalFetch + }) + + test("deletes instances whose pull requests are closed in both environments", async () => { + const deletePayloads: Array<{environment: string, instance: string}> = [] + invokeLambdaMock.mockImplementation(async (_lambda, _dryRun, functionName: string, payload: unknown) => { + if (functionName === "lambda-resources-ProxygenPTLInstanceGet") { + const {apiName} = payload as {apiName: string} + return JSON.stringify([{name: `${apiName}-pr-123`}]) + } + if (functionName === "lambda-resources-ProxygenPTLInstanceDelete") { + deletePayloads.push(payload as {environment: string, instance: string}) + return "\"deleted\"" + } + return "\"ok\"" + }) + + await deleteProxygenDeployments("eps", "eps-repo", "proxygenKey", "kid-123") + + expect(deletePayloads).toEqual(expect.arrayContaining([ + expect.objectContaining({environment: "internal-dev", instance: "eps-pr-123"}), + expect.objectContaining({environment: "internal-dev-sandbox", instance: "eps-pr-123"}) + ])) + }) + + test("does not delete instances for open pull requests or non-PR names", async () => { + let deleteCalls = 0 + invokeLambdaMock.mockImplementation(async (_lambda, _dryRun, functionName: string) => { + if (functionName === "lambda-resources-ProxygenPTLInstanceGet") { + return JSON.stringify([ + {name: "eps-pr-456"}, + {name: "eps"} + ]) + } + if (functionName === "lambda-resources-ProxygenPTLInstanceDelete") { + deleteCalls++ + return "\"deleted\"" + } + return "\"ok\"" + }) + + await deleteProxygenDeployments("eps", "eps-repo", "proxygenKey", "kid-123") + + expect(deleteCalls).toBe(0) + }) + + test("does not delete instances when GitHub API call fails", async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(globalThis as any).fetch = vi.fn(() => { + return Promise.resolve({ + ok: false, + status: 500, + text: async () => "server error", + json: async () => ({state: "unknown"}) + }) as unknown as Promise + }) + + let deleteCalls = 0 + invokeLambdaMock.mockImplementation(async (_lambda, _dryRun, functionName: string) => { + if (functionName === "lambda-resources-ProxygenPTLInstanceGet") { + return JSON.stringify([{name: "eps-pr-999"}]) + } + if (functionName === "lambda-resources-ProxygenPTLInstanceDelete") { + deleteCalls++ + return "\"deleted\"" + } + return "\"ok\"" + }) + + await deleteProxygenDeployments("eps", "eps-repo", "proxygenKey", "kid-123") + + expect(deleteCalls).toBe(0) + }) +}) diff --git a/packages/deploymentUtils/tests/specifications/deployApi.test.ts b/packages/deploymentUtils/tests/specifications/deployApi.test.ts new file mode 100644 index 00000000..5d981396 --- /dev/null +++ b/packages/deploymentUtils/tests/specifications/deployApi.test.ts @@ -0,0 +1,258 @@ +import { + beforeEach, + describe, + expect, + test, + vi +} from "vitest" +import {deployApi} from "../../src/specifications/deployApi" +import type {ApiConfig} from "../../src/specifications/deployApi" + +const lambdaSendMock = vi.fn() + +vi.mock("@aws-sdk/client-lambda", () => { + class InvokeCommand { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(input: any) { + this.input = input + } + } + + class LambdaClient { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + send(command: any) { + return lambdaSendMock(command) + } + } + + return {LambdaClient, InvokeCommand} +}) + +const getCloudFormationExportsMock = vi.hoisted(() => vi.fn()) + +vi.mock("../../src/config/index", async (importOriginal) => { + const originalModule = await importOriginal() + return { + ...originalModule, + getCloudFormationExports: getCloudFormationExportsMock + } +}) + +type SpecOverrides = { + securitySchemes?: Record, + paths?: Record +} + +function createSpec(overrides: SpecOverrides = {}) { + return { + info: {title: "EPS API", version: "0.0.1"}, + "x-nhsd-apim": { + monitoring: true, + target: { + url: "", + security: {secret: "initial"} + }, + "target-attributes": {app: "eps"} + }, + components: { + securitySchemes: overrides.securitySchemes || {"nhs-cis2-aal3": {}} + }, + paths: overrides.paths || {}, + servers: [] + } +} + +const defaultExportsMap = { + "account-resources:proxygenKey": "arn:proxygen-key" +} + +function buildConfig(overrides: Partial = {}): ApiConfig { + return { + spec: createSpec(), + apiName: "eps", + version: "1.0.0", + apigeeEnvironment: "internal-dev", + isPullRequest: false, + awsEnvironment: "nonprod", + stackName: "eps-stack-001", + mtlsSecretName: "mtls/secret", + clientCert: "clientCert", + clientPrivateKey: "clientKey", + proxygenPrivateKeyExportName: "proxygenKey", + proxygenKid: "kid-123", + hiddenPaths: [], + ...overrides + } +} + +function payloadFromCall(callIndex: number) { + const command = lambdaSendMock.mock.calls[callIndex][0] as {input: {Payload: Buffer}} + return JSON.parse(command.input.Payload.toString()) +} + +function functionNameFromCall(callIndex: number) { + const command = lambdaSendMock.mock.calls[callIndex][0] as {input: {FunctionName: string}} + return command.input.FunctionName +} + +describe("deployApi", () => { + beforeEach(() => { + lambdaSendMock.mockReset().mockResolvedValue({Payload: Buffer.from('"ok"')}) + getCloudFormationExportsMock.mockReset().mockResolvedValue(defaultExportsMap) + }) + + test("stores secrets, deploys instance and publishes spec for internal-dev", async () => { + await deployApi( + buildConfig({ + version: "2.0.0", + apigeeEnvironment: "internal-dev", + stackName: "eps-stack" + }), + true, + false + ) + + expect(getCloudFormationExportsMock).toHaveBeenCalledTimes(1) + expect(lambdaSendMock).toHaveBeenCalledTimes(3) + + expect(functionNameFromCall(0)).toBe("lambda-resources-ProxygenPTLMTLSSecretPut") + const secretPayload = payloadFromCall(0) + expect(secretPayload).toMatchObject({ + apiName: "eps", + environment: "internal-dev", + secretName: "mtls/secret", + secretKey: "clientKey", + secretCert: "clientCert", + kid: "kid-123", + proxygenSecretName: "arn:proxygen-key" + }) + + expect(functionNameFromCall(1)).toBe("lambda-resources-ProxygenPTLInstancePut") + const instancePayload = payloadFromCall(1) + expect(instancePayload.instance).toBe("eps") + + expect(functionNameFromCall(2)).toBe("lambda-resources-ProxygenPTLSpecPublish") + const publishPayload = payloadFromCall(2) + expect(publishPayload.environment).toBe("uat") + expect(publishPayload.specDefinition.servers[0].url) + .toBe("https://internal-dev-sandbox.api.service.nhs.uk/eps") + }) + + test("handles pull requests in sandbox without storing secrets", async () => { + await deployApi( + buildConfig({ + version: "3.1.4", + apigeeEnvironment: "sandbox", + isPullRequest: true, + stackName: "eps-pr-stack-456", + proxygenKid: "kid-789" + }), + true, + false + ) + + expect(lambdaSendMock).toHaveBeenCalledTimes(1) + expect(functionNameFromCall(0)).toBe("lambda-resources-ProxygenProdInstancePut") + + const instancePayload = payloadFromCall(0) + expect(instancePayload.instance).toBe("eps-pr-456") + }) + + test("uses prod lambdas for prod environment", async () => { + await deployApi( + buildConfig({ + version: "4.0.0", + apigeeEnvironment: "prod", + awsEnvironment: "prod", + stackName: "eps-prod-stack", + proxygenKid: "kid-prod" + }), + true, + false + ) + + expect(lambdaSendMock).toHaveBeenCalledTimes(2) + expect(functionNameFromCall(0)).toBe("lambda-resources-ProxygenProdMTLSSecretPut") + expect(functionNameFromCall(1)).toBe("lambda-resources-ProxygenProdInstancePut") + }) + + test("publishes spec to prod catalogue for int environment", async () => { + await deployApi( + buildConfig({ + version: "5.0.0", + apigeeEnvironment: "int", + stackName: "eps-int-stack", + proxygenKid: "kid-int" + }), + true, + false + ) + + expect(lambdaSendMock).toHaveBeenCalledTimes(3) + expect(functionNameFromCall(2)).toBe("lambda-resources-ProxygenProdSpecPublish") + const publishPayload = payloadFromCall(2) + expect(publishPayload.environment).toBe("prod") + expect(publishPayload.specDefinition.servers[0].url) + .toBe("https://sandbox.api.service.nhs.uk/eps") + }) + + test("removes hidden paths from published spec", async () => { + const spec = createSpec({ + paths: { + "/visible": {get: {}}, + "/hidden": {post: {}} + } + }) + await deployApi( + buildConfig({ + spec, + apigeeEnvironment: "int", + stackName: "eps-int-stack", + proxygenKid: "kid-int", + hiddenPaths: ["/hidden"] + }), + true, + false + ) + const publishPayload = payloadFromCall(2) + expect(publishPayload.specDefinition.paths["/hidden"]).toBeUndefined() + expect(publishPayload.specDefinition.paths["/visible"]).toBeDefined() + }) + + test("dry run only logs intended invocations", async () => { + const logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined) + + await deployApi( + buildConfig({ + apigeeEnvironment: "int", + stackName: "eps-int-stack", + proxygenKid: "kid-int" + }), + true, + true + ) + + expect(lambdaSendMock).not.toHaveBeenCalled() + expect(logSpy.mock.calls.some(([message]) => + typeof message === "string" && message.includes("Would invoke lambda lambda-resources-ProxygenProdMTLSSecretPut") + )).toBe(true) + logSpy.mockRestore() + }) + + test("throws when lambda invocation returns a FunctionError", async () => { + lambdaSendMock + .mockResolvedValueOnce({FunctionError: "Handled", Payload: Buffer.from('"bad"')}) + + await expect(deployApi( + buildConfig({ + version: "1.2.3", + apigeeEnvironment: "int", + stackName: "eps-stack" + }), + true, + false + )).rejects.toThrow("Error calling lambda lambda-resources-ProxygenProdMTLSSecretPut: \"bad\"") + }) +}) diff --git a/packages/deploymentUtils/tests/specifications/fixSpec.test.ts b/packages/deploymentUtils/tests/specifications/fixSpec.test.ts new file mode 100644 index 00000000..e60c7eb6 --- /dev/null +++ b/packages/deploymentUtils/tests/specifications/fixSpec.test.ts @@ -0,0 +1,135 @@ +import {describe, expect, test} from "vitest" +import {fixSpec} from "../../src/specifications/fixSpec" + +type SpecOverrides = { + securitySchemes?: Record, + paths?: Record +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function createSpec(overrides: SpecOverrides = {}): any { + return { + info: {title: "EPS API", version: "0.0.1"}, + "x-nhsd-apim": { + monitoring: true, + target: { + url: "", + security: {secret: "initial"} + }, + "target-attributes": {app: "eps"} + }, + components: { + securitySchemes: overrides.securitySchemes || {"nhs-cis2-aal3": {}} + }, + paths: overrides.paths || {}, + servers: [] + } +} + +describe("fixSpec", () => { + test("sets version, mtls secret, target url and PTL refs for internal-dev", () => { + const spec = createSpec() + + const instance = fixSpec({ + spec, + apiName: "eps", + version: "2.0.0", + apigeeEnvironment: "internal-dev", + isPullRequest: false, + awsEnvironment: "dev", + stackName: "eps-stack", + mtlsSecretName: "mtls/secret", + blueGreen: true + }) + + expect(instance).toBe("eps") + expect(spec.info.version).toBe("2.0.0") + expect(spec["x-nhsd-apim"].target.security.secret).toBe("mtls/secret") + expect(spec["x-nhsd-apim"].target.url) + .toBe("https://eps-stack-2-0-0.dev.eps.national.nhs.uk") + expect(spec.components.securitySchemes["nhs-cis2-aal3"].$ref) + .toBe("https://proxygen.ptl.api.platform.nhs.uk/components/securitySchemes/nhs-cis2-aal3") + expect(spec.servers[0].url) + .toBe("https://internal-dev.api.service.nhs.uk/eps") + }) + + test("handles pull request sandbox specs and removes sandbox-only fields", () => { + const spec = createSpec() + + const instance = fixSpec({ + spec, + apiName: "eps", + version: "3.1.4", + apigeeEnvironment: "sandbox", + isPullRequest: true, + awsEnvironment: "int", + stackName: "eps-pr-stack-456", + mtlsSecretName: "mtls/secret", + blueGreen: true + }) + + expect(instance).toBe("eps-pr-456") + expect(spec.info.title).toBe("[PR-456] EPS API") + expect(spec["x-nhsd-apim"].monitoring).toBe(false) + expect(spec["x-nhsd-apim"]["target-attributes"]).toBeUndefined() + expect(spec.servers[0].url) + .toBe("https://sandbox.api.service.nhs.uk/eps-pr-456") + }) + + test("replaces all supported security scheme refs and sets prod server url for prod", () => { + const spec = createSpec({ + securitySchemes: { + "nhs-cis2-aal3": {}, + "nhs-login-p9": {}, + "app-level3": {}, + "app-level0": {} + } + }) + + const instance = fixSpec({ + spec, + apiName: "eps", + version: "4.0.0", + apigeeEnvironment: "prod", + isPullRequest: false, + awsEnvironment: "prod", + stackName: "eps-prod-stack", + mtlsSecretName: "mtls/secret", + blueGreen: true + }) + + expect(instance).toBe("eps") + expect(spec.servers[0].url).toBe("https://api.service.nhs.uk/eps") + + const schemes = [ + "nhs-cis2-aal3", + "nhs-login-p9", + "app-level3", + "app-level0" + ] + for (const scheme of schemes) { + expect(spec.components.securitySchemes[scheme].$ref) + .toBe(`https://proxygen.prod.api.platform.nhs.uk/components/securitySchemes/${scheme}`) + } + }) + + test("does not version stack name when blueGreen is false", () => { + const spec = createSpec() + + const instance = fixSpec({ + spec, + apiName: "eps", + version: "1.2.3", + apigeeEnvironment: "internal-dev", + isPullRequest: false, + awsEnvironment: "dev", + stackName: "eps-stack", + mtlsSecretName: "mtls/secret", + blueGreen: false + }) + + expect(instance).toBe("eps") + expect(spec["x-nhsd-apim"].target.url) + .toBe("https://eps-stack.dev.eps.national.nhs.uk") + }) +}) diff --git a/packages/deploymentUtils/tests/specifications/writeSchemas.test.ts b/packages/deploymentUtils/tests/specifications/writeSchemas.test.ts new file mode 100644 index 00000000..4a54c4ad --- /dev/null +++ b/packages/deploymentUtils/tests/specifications/writeSchemas.test.ts @@ -0,0 +1,158 @@ +import fs from "node:fs" +import path from "node:path" +import { + describe, + test, + beforeEach, + afterEach, + expect, + vi +} from "vitest" +import {writeSchemas} from "../../src/specifications/writeSchemas" + +describe("writeSchemas", () => { + beforeEach(() => { + vi.restoreAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + test("creates output directory and writes schemas with collapsed examples", () => { + const schemas = { + patient: { + type: "object", + examples: [{foo: "bar"}, {foo: "baz"}], + properties: { + id: { + type: "string", + examples: ["123", "456"] + }, + nested: { + type: "object", + properties: { + items: { + type: "array", + examples: [["item-1"], ["item-2"]], + items: { + type: "string", + examples: ["deep-value"] + } + } + } + } + } + } + } as const + + const outputDir = "schemas" + const writes: Record = {} + + const existsSpy = vi.spyOn(fs, "existsSync").mockReturnValue(false) + const mkdirSpy = vi.spyOn(fs, "mkdirSync").mockImplementation(() => undefined) + vi.spyOn(fs, "writeFileSync").mockImplementation((filePath, data) => { + writes[filePath.toString()] = data.toString() + }) + + writeSchemas(schemas, outputDir) + + expect(existsSpy).toHaveBeenCalledWith(outputDir) + expect(mkdirSpy).toHaveBeenCalledWith(outputDir, {recursive: true}) + + const writtenSchema = JSON.parse( + writes[path.join(outputDir, "patient.json")] + ) + + expect(writtenSchema.example).toEqual({foo: "bar"}) + expect(writtenSchema.examples).toBeUndefined() + expect(writtenSchema.properties.id.example).toBe("123") + expect(writtenSchema.properties.id.examples).toBeUndefined() + expect(writtenSchema.properties.nested.properties.items.example).toEqual(["item-1"]) + expect(writtenSchema.properties.nested.properties.items.examples).toBeUndefined() + expect(writtenSchema.properties.nested.properties.items.items.example).toBe("deep-value") + }) + + test("collapses examples within array based items and nested properties", () => { + const schemas = { + collection: { + type: "array", + items: [ + { + type: "string", + examples: ["first"] + }, + { + type: "object", + properties: { + flag: { + type: "boolean", + examples: [true, false] + } + } + } + ] + } + } as const + + const outputDir = "arrays" + const writes: Record = {} + + vi.spyOn(fs, "existsSync").mockReturnValue(true) + vi.spyOn(fs, "writeFileSync").mockImplementation((filePath, data) => { + writes[filePath.toString()] = data.toString() + }) + + writeSchemas(schemas, outputDir) + + const writtenSchema = JSON.parse( + writes[path.join(outputDir, "collection.json")] + ) + + expect(Array.isArray(writtenSchema.items)).toBe(true) + expect(writtenSchema.items[0].example).toBe("first") + expect(writtenSchema.items[0].examples).toBeUndefined() + expect(writtenSchema.items[1].properties.flag.example).toBe(true) + expect(writtenSchema.items[1].properties.flag.examples).toBeUndefined() + }) + + test("logs an error when writing a schema fails", () => { + const schemas = { + failing: {type: "string"} + } as const + + const outputDir = "failing" + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined) + vi.spyOn(fs, "existsSync").mockReturnValue(true) + vi.spyOn(fs, "writeFileSync").mockImplementation(() => { + throw new Error("disk full") + }) + + writeSchemas(schemas, outputDir) + + expect(errorSpy).toHaveBeenCalledTimes(1) + const [message, err] = errorSpy.mock.calls[0] + expect(message).toContain("failing.json") + expect(err).toBeInstanceOf(Error) + }) + + test("does not modify non-object schemas (collapseExamples type check)", () => { + const schemas = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + primitive: true as any + } + + const outputDir = "primitive-schemas" + const writes: Record = {} + + vi.spyOn(fs, "existsSync").mockReturnValue(true) + vi.spyOn(fs, "writeFileSync").mockImplementation((filePath, data) => { + writes[filePath.toString()] = data.toString() + }) + + writeSchemas(schemas, outputDir) + + const writtenSchema = writes[path.join(outputDir, "primitive.json")] + expect(writtenSchema).toBe("true") + }) +}) diff --git a/packages/deploymentUtils/tsconfig.json b/packages/deploymentUtils/tsconfig.json new file mode 100644 index 00000000..a75dbaaf --- /dev/null +++ b/packages/deploymentUtils/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "module": "es2020", + "target": "es2020", + "moduleResolution": "bundler", + "esModuleInterop": true, + "allowJs": true, + "declaration": true, + "sourceMap": true, + "composite": true, + "rootDir": ".", + "outDir": "lib", + "strict": true, + "lib": [ + "es2022" + ], + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "forceConsistentCasingInFileNames": true, + "preserveConstEnums": true, + "resolveJsonModule": true, + "typeRoots": [ + "../../node_modules/@types" + ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "tests/**/*" + ] +} diff --git a/packages/deploymentUtils/vitest.config.ts b/packages/deploymentUtils/vitest.config.ts new file mode 100644 index 00000000..dd7e1aa0 --- /dev/null +++ b/packages/deploymentUtils/vitest.config.ts @@ -0,0 +1,11 @@ +import {defineConfig} from "vitest/config" + +export default defineConfig({ + test: { + coverage: { + provider: "v8", + reporter: ["text", "json", "html", "lcov"], + reportsDirectory: "./coverage" + } + } +}) diff --git a/poetry.lock b/poetry.lock index 2af55a75..7e8b78f4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,52 +1,51 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "cfgv" -version = "3.4.0" +version = "3.5.0" description = "Validate configuration and produce human readable error messages." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main", "dev"] files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, + {file = "cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0"}, + {file = "cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132"}, ] [[package]] name = "distlib" -version = "0.3.9" +version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] [[package]] name = "filelock" -version = "3.16.1" +version = "3.20.3" description = "A platform independent file lock." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main", "dev"] files = [ - {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, - {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, + {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, + {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2)"] - [[package]] name = "identify" -version = "2.6.1" +version = "2.6.15" description = "File identification library for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, - {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, + {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, + {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, ] [package.extras] @@ -54,58 +53,43 @@ license = ["ukkonen"] [[package]] name = "nodeenv" -version = "1.9.1" +version = "1.10.0" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, + {file = "nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827"}, + {file = "nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb"}, ] -[[package]] -name = "pip-licenses" -version = "5.0.0" -description = "Dump the software license list of Python packages installed with pip." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pip_licenses-5.0.0-py3-none-any.whl", hash = "sha256:82c83666753efb86d1af1c405c8ab273413eb10d6689c218df2f09acf40e477d"}, - {file = "pip_licenses-5.0.0.tar.gz", hash = "sha256:0633a1f9aab58e5a6216931b0e1d5cdded8bcc2709ff563674eb0e2ff9e77e8e"}, -] - -[package.dependencies] -prettytable = ">=2.3.0" -tomli = ">=2" - -[package.extras] -dev = ["autopep8", "black", "docutils", "isort", "mypy", "pip-tools", "pypandoc", "pytest-cov", "pytest-pycodestyle", "pytest-runner", "tomli-w", "twine", "wheel"] - [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.5.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main", "dev"] files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31"}, + {file = "platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] +type = ["mypy (>=1.18.2)"] [[package]] name = "pre-commit" -version = "4.1.0" +version = "4.5.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +groups = ["main", "dev"] files = [ - {file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"}, - {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"}, + {file = "pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77"}, + {file = "pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61"}, ] [package.dependencies] @@ -115,128 +99,111 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" -[[package]] -name = "prettytable" -version = "3.11.0" -description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" -optional = false -python-versions = ">=3.8" -files = [ - {file = "prettytable-3.11.0-py3-none-any.whl", hash = "sha256:aa17083feb6c71da11a68b2c213b04675c4af4ce9c541762632ca3f2cb3546dd"}, - {file = "prettytable-3.11.0.tar.gz", hash = "sha256:7e23ca1e68bbfd06ba8de98bf553bf3493264c96d5e8a615c0471025deeba722"}, -] - -[package.dependencies] -wcwidth = "*" - -[package.extras] -tests = ["pytest", "pytest-cov", "pytest-lazy-fixtures"] - [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "tomli" -version = "2.0.2" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] [[package]] name = "virtualenv" -version = "20.26.6" +version = "20.36.1" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, - {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, + {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, + {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, ] [package.dependencies] distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" +filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""} platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[[package]] -name = "wcwidth" -version = "0.2.13" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, -] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [metadata] -lock-version = "2.0" -python-versions = "^3.12" -content-hash = "b7c2f147f9193849ae14a2f3715d168d586b2846d76df73aba40fa26ef519060" +lock-version = "2.1" +python-versions = "^3.14" +content-hash = "54f4b99d9f2caaf2556872d9c3452b44a8599eaba4b646774f581bd0fe8db921" diff --git a/pyproject.toml b/pyproject.toml index a847a18e..00ae3444 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,6 @@ [project] -python = "^3.12" +python = "^3.14" +name = "eps-cdk-uitl" [tool.poetry] name = "eps-cdk-uitl" @@ -14,14 +15,10 @@ repository = "https://github.com/NHSDigital/eps-cdk-util" package-mode = false [tool.poetry.dependencies] -python = "^3.12" +python = "^3.14" pre-commit = "^4.1.0" -[tool.poetry.dev-dependencies] -pip-licenses = "^5.0.0" - [tool.poetry.scripts] -[build-system] -requires = ["poetry>=1.8"] -build-backend = "poetry.masonry.api" +[tool.poetry.group.dev.dependencies] +pre-commit = "^4.1.0" diff --git a/scripts/check_python_licenses.sh b/scripts/check_python_licenses.sh deleted file mode 100755 index 1a8148fe..00000000 --- a/scripts/check_python_licenses.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -euo pipefail - -LICENSES=$(poetry run pip-licenses) -INCOMPATIBLE_LIBS=$(echo "$LICENSES" | grep 'GPL' || true) - -if [[ -z $INCOMPATIBLE_LIBS ]]; then - exit 0 -else - echo "The following libraries were found which are not compatible with this project's license:" - echo "$INCOMPATIBLE_LIBS" - exit 1 -fi diff --git a/sonar-project.properties b/sonar-project.properties index 893f04b0..d631f11b 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,6 +2,10 @@ sonar.organization=nhsdigital sonar.projectKey=NHSDigital_eps-cdk-utils sonar.host.url=https://sonarcloud.io +sonar.exclusions=\ + packages/serviceSearchClient/vitest.config.ts,\ + packages/enrichPrescriptions/vitest.config.ts + sonar.coverage.exclusions=\ **/*.test.*,\ **/jest.config.ts,scripts/*,\ @@ -15,9 +19,14 @@ sonar.cpd.exclusions=\ # Define the modules sonar.modules=\ - cdkConstructs + cdkConstructs, \ + deploymentUtils # Modules cdkConstructs.sonar.projectBaseDir=packages/cdkConstructs cdkConstructs.sonar.sources=. cdkConstructs.sonar.javascript.lcov.reportPaths=coverage/lcov.info + +deploymentUtils.sonar.projectBaseDir=packages/deploymentUtils +deploymentUtils.sonar.sources=. +deploymentUtils.sonar.javascript.lcov.reportPaths=coverage/lcov.info diff --git a/trivy.yaml b/trivy.yaml new file mode 100644 index 00000000..eb243375 --- /dev/null +++ b/trivy.yaml @@ -0,0 +1 @@ +ignorefile: ".trivyignore.yaml"