From 7a52947d28cdbe581d8cebe86f782db1e6794dd3 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 11:40:11 +0000 Subject: [PATCH 01/31] Attempt to deploy an empty stack --- .../actions/install_dependencies/action.yml | 28 + .github/workflows/cdk_package_code.yml | 70 + .github/workflows/cdk_release_code.yml | 96 + .github/workflows/ci.yml | 47 +- .github/workflows/pull_request.yml | 44 +- .github/workflows/release.yml | 61 +- .gitignore | 2 + .pre-commit-config.yaml | 14 +- ...scription-status-update-api.code-workspace | 14 +- Makefile | 35 + package-lock.json | 2159 +++++++++++++---- package.json | 1 + packages/cdk/bin/PsuStatelessApp.ts | 21 + packages/cdk/nagSuppressions.ts | 6 + packages/cdk/package.json | 20 + packages/cdk/stacks/PsuStatelessStack.ts | 17 + packages/cdk/tsconfig.json | 37 + 17 files changed, 2117 insertions(+), 555 deletions(-) create mode 100644 .github/actions/install_dependencies/action.yml create mode 100644 .github/workflows/cdk_package_code.yml create mode 100644 .github/workflows/cdk_release_code.yml create mode 100644 packages/cdk/bin/PsuStatelessApp.ts create mode 100644 packages/cdk/nagSuppressions.ts create mode 100644 packages/cdk/package.json create mode 100644 packages/cdk/stacks/PsuStatelessStack.ts create mode 100644 packages/cdk/tsconfig.json diff --git a/.github/actions/install_dependencies/action.yml b/.github/actions/install_dependencies/action.yml new file mode 100644 index 0000000000..b6889575f0 --- /dev/null +++ b/.github/actions/install_dependencies/action.yml @@ -0,0 +1,28 @@ +name: "Install dependencies" +description: "Install dependencies defined in .tool-versions using asdf and npm packages" + +inputs: + npm-required: + description: "Set to true if npm dependencies are already installed" + required: false + default: "true" + GITHUB_TOKEN: + description: "GitHub token to access private npm packages" + required: true + +runs: + using: "composite" + steps: + - name: Setting up .npmrc + shell: bash + env: + NODE_AUTH_TOKEN: ${{ inputs.GITHUB_TOKEN }} + run: | + echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc + echo "@nhsdigital:registry=https://npm.pkg.github.com" >> ~/.npmrc + + - name: Run make install + if: inputs.npm-required == 'true' + shell: bash + run: | + make install-node diff --git a/.github/workflows/cdk_package_code.yml b/.github/workflows/cdk_package_code.yml new file mode 100644 index 0000000000..fda9c3f69d --- /dev/null +++ b/.github/workflows/cdk_package_code.yml @@ -0,0 +1,70 @@ +name: cdk package code + +on: + workflow_call: + inputs: + BRANCH_NAME: + required: true + type: string + VERSION_NUMBER: + required: true + type: string + COMMIT_ID: + required: true + type: string + pinned_image: + required: true + type: string + +permissions: {} + +jobs: + package_code: + 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ inputs.BRANCH_NAME }} + persist-credentials: false + + - name: install dependencies + uses: ./.github/actions/install_dependencies + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: make compile + run: make compile + + - name: download the get secrets lambda layer + run: | + make download-get-secrets-layer + + - name: "Tar files" + run: | + tar -rf artifact.tar \ + .github \ + packages \ + node_modules \ + package.json \ + package-lock.json \ + tsconfig.defaults.json + + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f + name: upload build artifact + with: + name: build_artifact + path: artifact.tar diff --git a/.github/workflows/cdk_release_code.yml b/.github/workflows/cdk_release_code.yml new file mode 100644 index 0000000000..9361be0a60 --- /dev/null +++ b/.github/workflows/cdk_release_code.yml @@ -0,0 +1,96 @@ +name: cdk release code + +on: + workflow_call: + inputs: + BRANCH_NAME: + required: true + type: string + STACK_NAME: + required: true + type: string + AWS_ENVIRONMENT: + required: true + type: string + VERSION_NUMBER: + required: true + type: string + COMMIT_ID: + required: true + type: string + CDK_APP_NAME: + required: true + type: string + LOG_RETENTION_IN_DAYS: + required: true + type: string + LOG_LEVEL: + type: string + IS_PULL_REQUEST: + type: boolean + required: true + pinned_image: + required: true + type: string + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: + required: true +permissions: {} + +jobs: + release_code: + runs-on: ubuntu-22.04 + environment: ${{ inputs.AWS_ENVIRONMENT }} + container: + image: ${{ inputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + name: deploy cdk app ${{ inputs.CDK_APP_NAME }} + permissions: + id-token: write + contents: read + + steps: + - name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" + + - name: build_artifact download + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: build_artifact + + - name: extract build_artifact + run: tar -xf artifact.tar + + - name: install dependencies + uses: ./.github/actions/install_dependencies + with: + npm-required: false + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure AWS Credentials + id: connect-aws-deploy + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 + with: + aws-region: eu-west-2 + role-to-assume: ${{ secrets.CLOUD_FORMATION_DEPLOY_ROLE }} + role-session-name: psu-deployment + + - name: Deploy AWS infrastructure and code + run: npm run cdk-deploy --workspace packages/cdk + shell: bash + env: + CDK_APP_NAME: "${{ inputs.CDK_APP_NAME }}" + CDK_CONFIG_stackName: "${{ inputs.STACK_NAME }}" + CDK_CONFIG_versionNumber: "${{ inputs.VERSION_NUMBER }}" + CDK_CONFIG_commitId: "${{ inputs.COMMIT_ID }}" + CDK_CONFIG_isPullRequest: "${{ inputs.IS_PULL_REQUEST }}" + CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}" + CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" + CDK_CONFIG_logLevel: "${{ inputs.LOG_LEVEL }}" + REQUIRE_APPROVAL: "never" + +# later, there will be API deployment steps c.f. https://github.com/NHSDigital/electronic-prescription-service-clinical-prescription-tracker/blob/main/.github/workflows/cdk_release_code.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 527d56b536..145606984d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,40 @@ jobs: branch_name: main tag_format: ${{ needs.get_config_values.outputs.tag_format }} - package_code: + cdk_package_code: + needs: [get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_package_code.yml + permissions: + contents: read + packages: read + id-token: write + with: + BRANCH_NAME: main + 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 }} + + cdk_release_dev: + needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: main + STACK_NAME: psu-cdk + AWS_ENVIRONMENT: dev + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + CDK_APP_NAME: PsuStatelessApp + LOG_RETENTION_IN_DAYS: "30" + LOG_LEVEL: DEBUG + IS_PULL_REQUEST: false + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + + sam_package_code: needs: [tag_release, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml permissions: @@ -62,7 +95,7 @@ jobs: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} release_dev: - needs: [tag_release, package_code, get_commit_id, get_config_values] + needs: [tag_release, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -111,7 +144,7 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_sandbox_dev: - needs: [tag_release, package_code, get_commit_id, get_config_values] + needs: [tag_release, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -155,7 +188,13 @@ jobs: release_qa: needs: - [tag_release, release_dev, package_code, get_commit_id, get_config_values] + [ + tag_release, + release_dev, + sam_package_code, + get_commit_id, + get_config_values, + ] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 5dc6e7f020..cbaa104966 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -93,7 +93,41 @@ jobs: run: | echo "commit_id=${{ github.sha }}" >> "$GITHUB_OUTPUT" - package_code: + cdk_package_code: + needs: [get_issue_number, get_commit_id, get_config_values] + uses: ./.github/workflows/cdk_package_code.yml + permissions: + contents: read + packages: read + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + + cdk_release_code: + needs: + [get_issue_number, cdk_package_code, get_commit_id, get_config_values] + uses: ./.github/workflows/cdk_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + STACK_NAME: psu-cdk-pr-${{needs.get_issue_number.outputs.issue_number}} + AWS_ENVIRONMENT: dev + VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + CDK_APP_NAME: PsuStatelessApp + LOG_RETENTION_IN_DAYS: "30" + LOG_LEVEL: DEBUG + IS_PULL_REQUEST: true + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + + sam_package_code: needs: [get_issue_number, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml permissions: @@ -103,8 +137,9 @@ jobs: with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - release_code: - needs: [get_issue_number, package_code, get_commit_id, get_config_values] + sam_release_code: + needs: + [get_issue_number, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -153,7 +188,8 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_sandbox_code: - needs: [get_issue_number, package_code, get_commit_id, get_config_values] + needs: + [get_issue_number, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fb268dc86a..95bca18572 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,40 @@ jobs: secrets: EXECUTE_JIRA_LAMBDA_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_EXECUTE_LAMBDA_ROLE }} - package_code: + cdk_package_code: + needs: [get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_package_code.yml + permissions: + contents: read + packages: read + id-token: write + with: + BRANCH_NAME: main + 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 }} + + cdk_release_dev: + needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: main + STACK_NAME: psu-cdk + AWS_ENVIRONMENT: dev + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + CDK_APP_NAME: PsuStatelessApp + LOG_RETENTION_IN_DAYS: "30" + LOG_LEVEL: DEBUG + IS_PULL_REQUEST: false + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + + sam_package_code: needs: [tag_release, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml permissions: @@ -64,7 +97,7 @@ jobs: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} release_dev: - needs: [tag_release, package_code, get_commit_id, get_config_values] + needs: [tag_release, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -117,7 +150,7 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_dev_sandbox: - needs: [tag_release, package_code, get_commit_id, get_config_values] + needs: [tag_release, sam_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -168,7 +201,7 @@ jobs: tag_release, release_dev, release_dev_sandbox, - package_code, + sam_package_code, get_commit_id, get_config_values, ] @@ -224,7 +257,7 @@ jobs: tag_release, release_dev, release_dev_sandbox, - package_code, + sam_package_code, get_commit_id, get_config_values, ] @@ -276,7 +309,13 @@ jobs: release_int: needs: - [tag_release, release_qa, package_code, get_commit_id, get_config_values] + [ + tag_release, + release_qa, + sam_package_code, + get_commit_id, + get_config_values, + ] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -329,7 +368,13 @@ jobs: release_int_sandbox: needs: - [tag_release, release_qa, package_code, get_commit_id, get_config_values] + [ + tag_release, + release_qa, + sam_package_code, + get_commit_id, + get_config_values, + ] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -380,7 +425,7 @@ jobs: tag_release, release_int, release_int_sandbox, - package_code, + sam_package_code, get_commit_id, get_config_values, ] diff --git a/.gitignore b/.gitignore index fde104b2ec..67cb59f7ff 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ release_notes /ecs-*.yml .venv .aws-sam +cdk.out lib/ *.tsbuildinfo _site/ @@ -29,3 +30,4 @@ vendor .cfn_guard_out/ .trivy_out/ .sbom/ +.npmrc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 847e8dc9ce..26b304ca75 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,13 +23,13 @@ repos: - repo: local hooks: - - id: grype-scan-local - name: Grype scan local changes - entry: make - args: ["grype-scan-local"] - language: system - pass_filenames: false - always_run: true + # - id: grype-scan-local + # name: Grype scan local changes + # entry: make + # args: ["grype-scan-local"] + # language: system + # pass_filenames: false + # always_run: true - id: check-commit-signing name: Check commit signing diff --git a/.vscode/eps-prescription-status-update-api.code-workspace b/.vscode/eps-prescription-status-update-api.code-workspace index 06664a3e91..e1135d6827 100644 --- a/.vscode/eps-prescription-status-update-api.code-workspace +++ b/.vscode/eps-prescription-status-update-api.code-workspace @@ -4,6 +4,10 @@ "name": "eps-prescription-status-update-monorepo", "path": ".." }, + { + "name": "packages/cdk", + "path": "../packages/cdk" + }, { "name": "packages/gsul", "path": "../packages/gsul" @@ -156,8 +160,14 @@ "versionable", "whens" ], - "cSpell.dictionaries": ["en-GB"], - "cSpell.ignorePaths": ["package-lock.json", "node_modules", ".vscode"], + "cSpell.dictionaries": [ + "en-GB" + ], + "cSpell.ignorePaths": [ + "package-lock.json", + "node_modules", + ".vscode" + ], "jest.jestCommandLine": "NODE_OPTIONS=--experimental-vm-modules node_modules/.bin/jest --no-cache", "jest.nodeEnv": { "POWERTOOLS_DEV": true diff --git a/Makefile b/Makefile index 251be16be8..5cf013b19b 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,20 @@ +SHELL = /bin/bash +.SHELLFLAGS = -o pipefail -c +export CDK_APP_NAME=PsuStatelessApp +export CDK_CONFIG_stackName=${stack_name} +export CDK_CONFIG_versionNumber=undefined +export CDK_CONFIG_commitId=undefined +export CDK_CONFIG_isPullRequest=true +export CDK_CONFIG_environment=dev +export CDK_CONFIG_logRetentionInDays=30 +export CDK_CONFIG_logLevel=DEBUG + +guard-%: + @ if [ "${${*}}" = "" ]; then \ + echo "Environment variable $* not set"; \ + exit 1; \ + fi + .PHONY: install build test publish release clean lint compile install: install-node install-python install-hooks @@ -156,6 +173,7 @@ compile-specification: compile: compile-node compile-specification lint-node: compile-node + npm run lint --workspace packages/cdk npm run lint --workspace packages/updatePrescriptionStatus npm run lint --workspace packages/gsul npm run lint --workspace packages/nhsd-psu-sandbox @@ -217,12 +235,29 @@ clean: rm -rf packages/common/testing/lib rm -rf packages/common/middyErrorHandler/lib rm -rf packages/common/commonTypes/lib + rm -rf packages/cdk/lib rm -rf .aws-sam + rm -rf cdk.out deep-clean: clean rm -rf venv find . -name 'node_modules' -type d -prune -exec rm -rf '{}' + poetry env remove --all +cdk-deploy: + REQUIRE_APPROVAL="$${REQUIRE_APPROVAL:-any-change}" && \ + npm run cdk-deploy --workspace packages/cdk + +cdk-synth: + CDK_CONFIG_stackName=psu-api \ + npm run cdk-synth --workspace packages/cdk + +cdk-diff: + npm run cdk-diff --workspace packages/cdk + +cdk-watch: + REQUIRE_APPROVAL="$${REQUIRE_APPROVAL:-any-change}" && \ + npm run cdk-watch --workspace packages/cdk + %: @$(MAKE) -f /usr/local/share/eps/Mk/common.mk $@ diff --git a/package-lock.json b/package-lock.json index ed950f9e7e..ba69f5bb37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "workspaces": [ + "packages/cdk", "packages/gsul", "packages/nhsd-psu-sandbox", "packages/specification", @@ -54,6 +55,132 @@ "typescript-eslint": "^8.57.2" } }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.273", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.273.tgz", + "integrity": "sha512-X57HYUtHt9BQrlrzUNcMyRsDUCoakYNnY6qh5lNwRCHPtQoTfXmuISkfLk0AjLkcbS5lw1LLTQFiQhTDXfiTvg==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "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": "53.14.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-53.14.0.tgz", + "integrity": "sha512-prx2sbFfKrVf3NNXMOmWq6lsIBeWQDIqMTILLAddGiidn9j7OnLUubknWpJlLozMveTvQELdI++nUwq6jwCm9g==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.4" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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/@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/@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": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "license": "Apache-2.0", @@ -277,6 +404,57 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-cloudformation": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.1027.0.tgz", + "integrity": "sha512-Ml96/FeA5bon0QY2l5KGS2/2XPHbEB4RVJQIn6O7VfYI21RruIUU5W1zgH1Aq9C+W+rOSQ5tsT1ZfExN/5EJEg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@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.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-dynamodb": { "version": "3.1024.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.1024.0.tgz", @@ -330,11 +508,130 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-route-53": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-route-53/-/client-route-53-3.1027.0.tgz", + "integrity": "sha512-vS+s4cteBkYMp55goEYgDDkY06pS6vAj5NAHtvhIHPXsVb42o+kFwaiaqGqaTwAeRLV6pt6H9veTSLtAM7EaWQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-sdk-route53": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@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.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1027.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1027.0.tgz", + "integrity": "sha512-g6kaFE/pW0Tsoq/BYg8PfXa1hIZQBmyoKtmJTgcbdyzYWiOOu8vj4PZUE2kS8myita6avaY8Ama5IodHJ39lPA==", + "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.27", + "@aws-sdk/credential-provider-node": "^3.972.30", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.9", + "@aws-sdk/middleware-expect-continue": "^3.972.9", + "@aws-sdk/middleware-flexible-checksums": "^3.974.7", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-location-constraint": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-sdk-s3": "^3.972.28", + "@aws-sdk/middleware-ssec": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/signature-v4-multi-region": "^3.996.16", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/eventstream-serde-browser": "^4.2.13", + "@smithy/eventstream-serde-config-resolver": "^4.3.13", + "@smithy/eventstream-serde-node": "^4.2.13", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-blob-browser": "^4.2.14", + "@smithy/hash-node": "^4.2.13", + "@smithy/hash-stream-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/md5-js": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@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.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.15", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-secrets-manager": { "version": "3.1019.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.1019.0.tgz", "integrity": "sha512-pocE77Q7wmnt8grxi0qNKUIq05GW1USIqZ6jwr/pC9zd5lwp9BHIFWxg/pkJ4ffNPbX9LekimpoL8IYl3ScWbQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -484,22 +781,22 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.973.26", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.26.tgz", - "integrity": "sha512-A/E6n2W42ruU+sfWk+mMUOyVXbsSgGrY3MJ9/0Az5qUdG67y8I6HYzzoAa+e/lzxxl1uCYmEL6BTMi9ZiZnplQ==", + "version": "3.973.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.27.tgz", + "integrity": "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@aws-sdk/xml-builder": "^3.972.16", - "@smithy/core": "^3.23.13", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/signature-v4": "^5.3.12", - "@smithy/smithy-client": "^4.12.8", - "@smithy/types": "^4.13.1", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/xml-builder": "^3.972.17", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", - "@smithy/util-middleware": "^4.2.12", + "@smithy/util-middleware": "^4.2.13", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -507,16 +804,29 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.6.tgz", + "integrity": "sha512-NMbiqKdruhwwgI6nzBVe2jWMkXjaoQz2YOs3rFX+2F3gGyrJDkDPwMpV/RsTFeq2vAQ055wZNtOXFK4NYSkM8g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.24.tgz", - "integrity": "sha512-FWg8uFmT6vQM7VuzELzwVo5bzExGaKHdubn0StjgrcU5FvuLExUe+k06kn/40uKv59rYzhez8eFNM4yYE/Yb/w==", + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.25.tgz", + "integrity": "sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -524,20 +834,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.26", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.26.tgz", - "integrity": "sha512-CY4ppZ+qHYqcXqBVi//sdHST1QK3KzOEiLtpLsc9W2k2vfZPKExGaQIsOwcyvjpjUEolotitmd3mUNY56IwDEA==", + "version": "3.972.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.27.tgz", + "integrity": "sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/types": "^3.973.6", - "@smithy/fetch-http-handler": "^5.3.15", - "@smithy/node-http-handler": "^4.5.1", - "@smithy/property-provider": "^4.2.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/smithy-client": "^4.12.8", - "@smithy/types": "^4.13.1", - "@smithy/util-stream": "^4.5.21", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", "tslib": "^2.6.2" }, "engines": { @@ -545,24 +855,24 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.28.tgz", - "integrity": "sha512-wXYvq3+uQcZV7k+bE4yDXCTBdzWTU9x/nMiKBfzInmv6yYK1veMK0AKvRfRBd72nGWYKcL6AxwiPg9z/pYlgpw==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.29.tgz", + "integrity": "sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/credential-provider-env": "^3.972.24", - "@aws-sdk/credential-provider-http": "^3.972.26", - "@aws-sdk/credential-provider-login": "^3.972.28", - "@aws-sdk/credential-provider-process": "^3.972.24", - "@aws-sdk/credential-provider-sso": "^3.972.28", - "@aws-sdk/credential-provider-web-identity": "^3.972.28", - "@aws-sdk/nested-clients": "^3.996.18", - "@aws-sdk/types": "^3.973.6", - "@smithy/credential-provider-imds": "^4.2.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-login": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -570,18 +880,18 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.28.tgz", - "integrity": "sha512-ZSTfO6jqUTCysbdBPtEX5OUR//3rbD0lN7jO3sQeS2Gjr/Y+DT6SbIJ0oT2cemNw3UzKu97sNONd1CwNMthuZQ==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.29.tgz", + "integrity": "sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/nested-clients": "^3.996.18", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -589,22 +899,22 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.29.tgz", - "integrity": "sha512-clSzDcvndpFJAggLDnDb36sPdlZYyEs5Zm6zgZjjUhwsJgSWiWKwFIXUVBcbruidNyBdbpOv2tNDL9sX8y3/0g==", + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.30.tgz", + "integrity": "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.24", - "@aws-sdk/credential-provider-http": "^3.972.26", - "@aws-sdk/credential-provider-ini": "^3.972.28", - "@aws-sdk/credential-provider-process": "^3.972.24", - "@aws-sdk/credential-provider-sso": "^3.972.28", - "@aws-sdk/credential-provider-web-identity": "^3.972.28", - "@aws-sdk/types": "^3.973.6", - "@smithy/credential-provider-imds": "^4.2.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/credential-provider-env": "^3.972.25", + "@aws-sdk/credential-provider-http": "^3.972.27", + "@aws-sdk/credential-provider-ini": "^3.972.29", + "@aws-sdk/credential-provider-process": "^3.972.25", + "@aws-sdk/credential-provider-sso": "^3.972.29", + "@aws-sdk/credential-provider-web-identity": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -612,16 +922,16 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.24.tgz", - "integrity": "sha512-Q2k/XLrFXhEztPHqj4SLCNID3hEPdlhh1CDLBpNnM+1L8fq7P+yON9/9M1IGN/dA5W45v44ylERfXtDAlmMNmw==", + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.25.tgz", + "integrity": "sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -629,18 +939,18 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.28.tgz", - "integrity": "sha512-IoUlmKMLEITFn1SiCTjPfR6KrE799FBo5baWyk/5Ppar2yXZoUdaRqZzJzK6TcJxx450M8m8DbpddRVYlp5R/A==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.29.tgz", + "integrity": "sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/nested-clients": "^3.996.18", - "@aws-sdk/token-providers": "3.1021.0", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/token-providers": "3.1026.0", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -648,17 +958,17 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.28.tgz", - "integrity": "sha512-d+6h0SD8GGERzKe27v5rOzNGKOl0D+l0bWJdqrxH8WSQzHzjsQFIAPgIeOTUwBHVsKKwtSxc91K/SWax6XgswQ==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.29.tgz", + "integrity": "sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/nested-clients": "^3.996.18", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -715,6 +1025,24 @@ "@aws-sdk/client-dynamodb": "^3.1019.0" } }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.9.tgz", + "integrity": "sha512-COToYKgquDyligbcAep7ygs48RK+mwe/IYprq4+TSrVFzNOYmzWvHf6werpnKV5VYpRiwdn+Wa5ZXkPqLVwcTg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-endpoint-discovery": { "version": "3.972.9", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.9.tgz", @@ -732,15 +1060,69 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.9.tgz", + "integrity": "sha512-V/FNCjFxnh4VGu+HdSiW4Yg5GELihA1MIDSAdsEPvuayXBVmr0Jaa6jdLAZLH38KYXl/vVjri9DQJWnTAujHEA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.974.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.7.tgz", + "integrity": "sha512-uU4/ch2CLHB8Phu1oTKnnQ4e8Ujqi49zEnQYBhWYT53zfFvtJCdGsaOoypBr8Fm/pmCBssRmGoIQ4sixgdLP9w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/crc64-nvme": "^3.972.6", + "@aws-sdk/types": "^3.973.7", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", + "@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.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz", - "integrity": "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.9.tgz", + "integrity": "sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.9.tgz", + "integrity": "sha512-TyfOi2XNdOZpNKeTJwRUsVAGa+14nkyMb2VVGG+eDgcWG/ed6+NUo72N3hT6QJioxym80NSinErD+LBRF0Ir1w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -748,13 +1130,13 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz", - "integrity": "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.9.tgz", + "integrity": "sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/types": "^4.13.1", + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -762,99 +1144,152 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.9.tgz", - "integrity": "sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.10.tgz", + "integrity": "sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", + "@aws-sdk/types": "^3.973.7", "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-sqs": { - "version": "3.972.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.17.tgz", - "integrity": "sha512-LnzPRRoDXGtlFV2G1p2rsY6fRKrbf6Pvvc21KliSLw3+NmQca2+Aa1QIMRbpQvZYedsSqkGYwxe+qvXwQ2uxDw==", + "node_modules/@aws-sdk/middleware-sdk-route53": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-route53/-/middleware-sdk-route53-3.972.11.tgz", + "integrity": "sha512-5nvSVRgcbxR677ON2+AAWPOJkyqyyjx9Pi9EBSNAkU/G7W4/tGPs9qxjBnuJx5YEGJX+XoTGz5qq0CWmXGJHkA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/smithy-client": "^4.12.7", - "@smithy/types": "^4.13.1", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/middleware-sdk-s3": { "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.28.tgz", - "integrity": "sha512-cfWZFlVh7Va9lRay4PN2A9ARFzaBYcA097InT5M2CdRS05ECF5yaz86jET8Wsl2WcyKYEvVr/QNmKtYtafUHtQ==", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.28.tgz", + "integrity": "sha512-qJHcJQH9UNPUrnPlRtCozKjtqAaypQ5IgQxTNoPsVYIQeuwNIA8Rwt3NvGij1vCDYDfCmZaPLpnJEHlZXeFqmg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/types": "^3.973.6", - "@aws-sdk/util-endpoints": "^3.996.5", - "@smithy/core": "^3.23.13", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "@smithy/util-retry": "^4.2.13", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.996.18", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.18.tgz", - "integrity": "sha512-c7ZSIXrESxHKx2Mcopgd8AlzZgoXMr20fkx5ViPWPOLBvmyhw9VwJx/Govg8Ef/IhEon5R9l53Z8fdYSEmp6VA==", + "node_modules/@aws-sdk/middleware-sdk-sqs": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.17.tgz", + "integrity": "sha512-LnzPRRoDXGtlFV2G1p2rsY6fRKrbf6Pvvc21KliSLw3+NmQca2+Aa1QIMRbpQvZYedsSqkGYwxe+qvXwQ2uxDw==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/middleware-host-header": "^3.972.8", - "@aws-sdk/middleware-logger": "^3.972.8", - "@aws-sdk/middleware-recursion-detection": "^3.972.9", - "@aws-sdk/middleware-user-agent": "^3.972.28", - "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/types": "^3.973.6", - "@aws-sdk/util-endpoints": "^3.996.5", - "@aws-sdk/util-user-agent-browser": "^3.972.8", - "@aws-sdk/util-user-agent-node": "^3.973.14", - "@smithy/config-resolver": "^4.4.13", - "@smithy/core": "^3.23.13", - "@smithy/fetch-http-handler": "^5.3.15", - "@smithy/hash-node": "^4.2.12", - "@smithy/invalid-dependency": "^4.2.12", - "@smithy/middleware-content-length": "^4.2.12", - "@smithy/middleware-endpoint": "^4.4.28", - "@smithy/middleware-retry": "^4.4.46", - "@smithy/middleware-serde": "^4.2.16", - "@smithy/middleware-stack": "^4.2.12", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/node-http-handler": "^4.5.1", - "@smithy/protocol-http": "^5.3.12", - "@smithy/smithy-client": "^4.12.8", + "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.9.tgz", + "integrity": "sha512-wSA2BR7L0CyBNDJeSrleIIzC+DzL93YNTdfU0KPGLiocK6YsRv1nPAzPF+BFSdcs0Qa5ku5Kcf4KvQcWwKGenQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.29.tgz", + "integrity": "sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-retry": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.19.tgz", + "integrity": "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/middleware-host-header": "^3.972.9", + "@aws-sdk/middleware-logger": "^3.972.9", + "@aws-sdk/middleware-recursion-detection": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/region-config-resolver": "^3.972.11", + "@aws-sdk/types": "^3.973.7", + "@aws-sdk/util-endpoints": "^3.996.6", + "@aws-sdk/util-user-agent-browser": "^3.972.9", + "@aws-sdk/util-user-agent-node": "^3.973.15", + "@smithy/config-resolver": "^4.4.14", + "@smithy/core": "^3.23.14", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/hash-node": "^4.2.13", + "@smithy/invalid-dependency": "^4.2.13", + "@smithy/middleware-content-length": "^4.2.13", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-retry": "^4.5.0", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/protocol-http": "^5.3.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", "@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.44", - "@smithy/util-defaults-mode-node": "^4.2.48", - "@smithy/util-endpoints": "^3.3.3", - "@smithy/util-middleware": "^4.2.12", - "@smithy/util-retry": "^4.2.13", + "@smithy/util-defaults-mode-browser": "^4.3.45", + "@smithy/util-defaults-mode-node": "^4.2.49", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -863,15 +1298,32 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.10.tgz", - "integrity": "sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ==", + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.11.tgz", + "integrity": "sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/config-resolver": "^4.4.13", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", + "@aws-sdk/types": "^3.973.7", + "@smithy/config-resolver": "^4.4.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.16.tgz", + "integrity": "sha512-EMdXYB4r/k5RWq86fugjRhid5JA+Z6MpS7n4sij4u5/C+STrkvuf9aFu41rJA9MjUzxCLzv8U2XL8cH2GSRYpQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "^3.972.28", + "@aws-sdk/types": "^3.973.7", + "@smithy/protocol-http": "^5.3.13", + "@smithy/signature-v4": "^5.3.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -879,17 +1331,17 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1021.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1021.0.tgz", - "integrity": "sha512-TKY6h9spUk3OLs5v1oAgW9mAeBE3LAGNBwJokLy96wwmd4W2v/tYlXseProyed9ValDj2u1jK/4Rg1T+1NXyJA==", + "version": "3.1026.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1026.0.tgz", + "integrity": "sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.26", - "@aws-sdk/nested-clients": "^3.996.18", - "@aws-sdk/types": "^3.973.6", - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@aws-sdk/core": "^3.973.27", + "@aws-sdk/nested-clients": "^3.996.19", + "@aws-sdk/types": "^3.973.7", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -897,12 +1349,24 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", - "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.7.tgz", + "integrity": "sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "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": { - "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -925,15 +1389,15 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", - "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", + "version": "3.996.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.6.tgz", + "integrity": "sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "@smithy/util-endpoints": "^3.3.3", + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-endpoints": "^3.3.4", "tslib": "^2.6.2" }, "engines": { @@ -951,27 +1415,27 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz", - "integrity": "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.9.tgz", + "integrity": "sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.6", - "@smithy/types": "^4.13.1", + "@aws-sdk/types": "^3.973.7", + "@smithy/types": "^4.14.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.14.tgz", - "integrity": "sha512-vNSB/DYaPOyujVZBg/zUznH9QC142MaTHVmaFlF7uzzfg3CgT9f/l4C0Yi+vU/tbBhxVcXVB90Oohk5+o+ZbWw==", + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.15.tgz", + "integrity": "sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.28", - "@aws-sdk/types": "^3.973.6", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", + "@aws-sdk/middleware-user-agent": "^3.972.29", + "@aws-sdk/types": "^3.973.7", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, @@ -988,12 +1452,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.16.tgz", - "integrity": "sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A==", + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.17.tgz", + "integrity": "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" }, @@ -1035,6 +1499,7 @@ "version": "7.28.4", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2765,6 +3230,7 @@ "node_modules/@middy/validator/node_modules/ajv": { "version": "8.17.1", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2838,6 +3304,21 @@ "uninstall": "^0.0.0" } }, + "node_modules/@nhsdigital/eps-cdk-constructs": { + "version": "1.6.1", + "resolved": "https://npm.pkg.github.com/download/@nhsdigital/eps-cdk-constructs/1.6.1/c2758a114d62470b00072c95d246f5c22de70e7a", + "integrity": "sha512-UbYkbO7GYGtDkFfSCzQmseVp8H+EiPtM59EN3eHDTVghX9BcaxXfJf8kKhNUHeImG6V+hQITj+ofNczF/zkxJw==", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-cloudformation": "^3.1018.0", + "@aws-sdk/client-route-53": "^3.1018.0", + "@aws-sdk/client-s3": "^3.1018.0", + "aws-cdk": "^2.1114.1", + "aws-cdk-lib": "^2.244.0", + "cdk-nag": "^2.37.52", + "constructs": "^10.6.0" + } + }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -2877,6 +3358,7 @@ "version": "1.9.0", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -3683,17 +4165,42 @@ "dev": true, "license": "(Unlicense OR Apache-2.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.13", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.13.tgz", - "integrity": "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==", + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.14.tgz", + "integrity": "sha512-N55f8mPEccpzKetUagdvmAy8oohf0J5cuj9jLI1TaSceRlq0pJsIZepY3kmAXAhyxqXPV6hDerDQhqQPKWgAoQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.3.3", - "@smithy/util-middleware": "^4.2.12", + "@smithy/util-endpoints": "^3.3.4", + "@smithy/util-middleware": "^4.2.13", "tslib": "^2.6.2" }, "engines": { @@ -3701,18 +4208,18 @@ } }, "node_modules/@smithy/core": { - "version": "3.23.13", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.13.tgz", - "integrity": "sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q==", + "version": "3.23.14", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.14.tgz", + "integrity": "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.12", - "@smithy/util-stream": "^4.5.21", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-stream": "^4.5.22", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" @@ -3722,15 +4229,85 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", - "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.13.tgz", + "integrity": "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.13.tgz", + "integrity": "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.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.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.13.tgz", + "integrity": "sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.13.tgz", + "integrity": "sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.13.tgz", + "integrity": "sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.13", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.13.tgz", + "integrity": "sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3738,14 +4315,14 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.15", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz", - "integrity": "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz", + "integrity": "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/querystring-builder": "^4.2.12", - "@smithy/types": "^4.13.1", + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" }, @@ -3753,13 +4330,28 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.14.tgz", + "integrity": "sha512-rtQ5es8r/5v4rav7q5QTsfx9CtCyzrz/g7ZZZBH2xtMmd6G/KQrLOWfSHTvFOUPlVy59RQvxeBYJaLRoybMEyA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.2", + "@smithy/chunked-blob-reader-native": "^4.2.3", + "@smithy/types": "^4.14.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/hash-node": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz", - "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.13.tgz", + "integrity": "sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -3768,13 +4360,27 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.13.tgz", + "integrity": "sha512-WdQ7HwUjINXETeh6dqUeob1UHIYx8kAn9PSp1HhM2WWegiZBYVy2WXIs1lB07SZLan/udys9SBnQGt9MQbDpdg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.0", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", - "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.13.tgz", + "integrity": "sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3794,12 +4400,12 @@ } }, "node_modules/@smithy/md5-js": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.12.tgz", - "integrity": "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.13.tgz", + "integrity": "sha512-cNm7I9NXolFxtS20ojROddOEpSAeI1Obq6pd1Kj5HtHws3s9Fkk8DdHDfQSs5KuxCewZuVK6UqrJnfJmiMzDuQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -3808,13 +4414,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", - "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.13.tgz", + "integrity": "sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3822,18 +4428,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.28", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.28.tgz", - "integrity": "sha512-p1gfYpi91CHcs5cBq982UlGlDrxoYUX6XdHSo91cQ2KFuz6QloHosO7Jc60pJiVmkWrKOV8kFYlGFFbQ2WUKKQ==", + "version": "4.4.29", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.29.tgz", + "integrity": "sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.13", - "@smithy/middleware-serde": "^4.2.16", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "@smithy/util-middleware": "^4.2.12", + "@smithy/core": "^3.23.14", + "@smithy/middleware-serde": "^4.2.17", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", + "@smithy/url-parser": "^4.2.13", + "@smithy/util-middleware": "^4.2.13", "tslib": "^2.6.2" }, "engines": { @@ -3841,18 +4447,19 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.46", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.46.tgz", - "integrity": "sha512-SpvWNNOPOrKQGUqZbEPO+es+FRXMWvIyzUKUOYdDgdlA6BdZj/R58p4umoQ76c2oJC44PiM7mKizyyex1IJzow==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.0.tgz", + "integrity": "sha512-/NzISn4grj/BRFVua/xnQwF+7fakYZgimpw2dfmlPgcqecBMKxpB9g5mLYRrmBD5OrPoODokw4Vi1hrSR4zRyw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/service-error-classification": "^4.2.12", - "@smithy/smithy-client": "^4.12.8", - "@smithy/types": "^4.13.1", - "@smithy/util-middleware": "^4.2.12", - "@smithy/util-retry": "^4.2.13", + "@smithy/core": "^3.23.14", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/service-error-classification": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", + "@smithy/util-middleware": "^4.2.13", + "@smithy/util-retry": "^4.3.0", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" }, @@ -3861,14 +4468,14 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.16", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.16.tgz", - "integrity": "sha512-beqfV+RZ9RSv+sQqor3xroUUYgRFCGRw6niGstPG8zO9LgTl0B0MCucxjmrH/2WwksQN7UUgI7KNANoZv+KALA==", + "version": "4.2.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.17.tgz", + "integrity": "sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.13", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", + "@smithy/core": "^3.23.14", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3876,12 +4483,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", - "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.13.tgz", + "integrity": "sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3889,14 +4496,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", - "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.13.tgz", + "integrity": "sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", + "@smithy/property-provider": "^4.2.13", + "@smithy/shared-ini-file-loader": "^4.4.8", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3904,14 +4511,14 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", - "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz", + "integrity": "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.12", - "@smithy/querystring-builder": "^4.2.12", - "@smithy/types": "^4.13.1", + "@smithy/protocol-http": "^5.3.13", + "@smithy/querystring-builder": "^4.2.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3919,12 +4526,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", - "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.13.tgz", + "integrity": "sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3932,12 +4539,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.12", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", - "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.13.tgz", + "integrity": "sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3945,12 +4552,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", - "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.13.tgz", + "integrity": "sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, @@ -3959,12 +4566,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", - "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.13.tgz", + "integrity": "sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3972,24 +4579,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", - "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.13.tgz", + "integrity": "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1" + "@smithy/types": "^4.14.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", - "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.8.tgz", + "integrity": "sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -3997,16 +4604,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.12", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", - "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==", + "version": "5.3.13", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.13.tgz", + "integrity": "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.12", + "@smithy/util-middleware": "^4.2.13", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -4016,17 +4623,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.12.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.8.tgz", - "integrity": "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA==", + "version": "4.12.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.9.tgz", + "integrity": "sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.13", - "@smithy/middleware-endpoint": "^4.4.28", - "@smithy/middleware-stack": "^4.2.12", - "@smithy/protocol-http": "^5.3.12", - "@smithy/types": "^4.13.1", - "@smithy/util-stream": "^4.5.21", + "@smithy/core": "^3.23.14", + "@smithy/middleware-endpoint": "^4.4.29", + "@smithy/middleware-stack": "^4.2.13", + "@smithy/protocol-http": "^5.3.13", + "@smithy/types": "^4.14.0", + "@smithy/util-stream": "^4.5.22", "tslib": "^2.6.2" }, "engines": { @@ -4034,9 +4641,9 @@ } }, "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==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.0.tgz", + "integrity": "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4046,13 +4653,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", - "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.13.tgz", + "integrity": "sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.12", - "@smithy/types": "^4.13.1", + "@smithy/querystring-parser": "^4.2.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4123,14 +4730,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.44", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.44.tgz", - "integrity": "sha512-eZg6XzaCbVr2S5cAErU5eGBDaOVTuTo1I65i4tQcHENRcZ8rMWhQy1DaIYUSLyZjsfXvmCqZrstSMYyGFocvHA==", + "version": "4.3.45", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.45.tgz", + "integrity": "sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.12", - "@smithy/smithy-client": "^4.12.8", - "@smithy/types": "^4.13.1", + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4138,17 +4745,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.48", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.48.tgz", - "integrity": "sha512-FqOKTlqSaoV3nzO55pMs5NBnZX8EhoI0DGmn9kbYeXWppgHD6dchyuj2HLqp4INJDJbSrj6OFYJkAh/WhSzZPg==", + "version": "4.2.49", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.49.tgz", + "integrity": "sha512-jlN6vHwE8gY5AfiFBavtD3QtCX2f7lM3BKkz7nFKSNfFR5nXLXLg6sqXTJEEyDwtxbztIDBQCfjsGVXlIru2lQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.13", - "@smithy/credential-provider-imds": "^4.2.12", - "@smithy/node-config-provider": "^4.3.12", - "@smithy/property-provider": "^4.2.12", - "@smithy/smithy-client": "^4.12.8", - "@smithy/types": "^4.13.1", + "@smithy/config-resolver": "^4.4.14", + "@smithy/credential-provider-imds": "^4.2.13", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/property-provider": "^4.2.13", + "@smithy/smithy-client": "^4.12.9", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4156,13 +4763,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", - "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.4.tgz", + "integrity": "sha512-BKoR/ubPp9KNKFxPpg1J28N1+bgu8NGAtJblBP7yHy8yQPBWhIAv9+l92SlQLpolGm71CVO+btB60gTgzT0wog==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", + "@smithy/node-config-provider": "^4.3.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4182,12 +4789,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", - "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.13.tgz", + "integrity": "sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4195,13 +4802,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.13.tgz", - "integrity": "sha512-qQQsIvL0MGIbUjeSrg0/VlQ3jGNKyM3/2iU3FPNgy01z+Sp4OvcaxbgIoFOTvB61ZoohtutuOvOcgmhbD0katQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.0.tgz", + "integrity": "sha512-tSOPQNT/4KfbvqeMovWC3g23KSYy8czHd3tlN+tOYVNIDLSfxIsrPJihYi5TpNcoV789KWtgChUVedh2y6dDPg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.12", - "@smithy/types": "^4.13.1", + "@smithy/service-error-classification": "^4.2.13", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4209,14 +4816,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.21", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.21.tgz", - "integrity": "sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q==", + "version": "4.5.22", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.22.tgz", + "integrity": "sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.15", - "@smithy/node-http-handler": "^4.5.1", - "@smithy/types": "^4.13.1", + "@smithy/fetch-http-handler": "^5.3.16", + "@smithy/node-http-handler": "^4.5.2", + "@smithy/types": "^4.14.0", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", @@ -4253,12 +4860,12 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.14.tgz", - "integrity": "sha512-2zqq5o/oizvMaFUlNiTyZ7dbgYv1a893aGut2uaxtbzTx/VYYnRxWzDHuD/ftgcw94ffenua+ZNLrbqwUYE+Bg==", + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.15.tgz", + "integrity": "sha512-oUt9o7n8hBv3BL56sLSneL0XeigZSuem0Hr78JaoK33D9oKieyCvVP8eTSe3j7g2mm/S1DvzxKieG7JEWNJUNg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", + "@smithy/types": "^4.14.0", "tslib": "^2.6.2" }, "engines": { @@ -4420,6 +5027,7 @@ "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -4503,6 +5111,7 @@ "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", @@ -5031,216 +5640,623 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats-draft2019": { + "version": "1.6.1", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1", + "schemes": "^1.4.0", + "smtp-address-parser": "^1.0.3", + "uri-js": "^4.4.1" + }, + "peerDependencies": { + "ajv": "*" + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/ajv-ftl-i18n": { + "version": "0.1.1", + "license": "MIT", + "dependencies": { + "commander": "10.0.0", + "fluent-transpiler": "0.2.1" + }, + "bin": { + "ajv-ftl": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" + } + }, + "node_modules/ajv-ftl-i18n/node_modules/commander": { + "version": "10.0.0", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/apim-spec": { + "resolved": "packages/specification", + "link": true + }, + "node_modules/arg": { + "version": "4.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-cdk": { + "version": "2.1118.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1118.0.tgz", + "integrity": "sha512-Tfd865GRewDTXIbTVtix/l+v8t3rZENvdHcQQZS2wXYVXfHzljULFXe9JKkgZUNDPB1zo9tSBUu8jjiHRm7nWg==", + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.248.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.248.0.tgz", + "integrity": "sha512-PGQycx/OdyX+t0o6QUFI1KJAOLoyIVj2WwrN0syrwCi8lYxW2KzldZsW0X+/UN/ALNQwcjSr927ImTpuDOh+bg==", + "bundleDependencies": [ + "@balena/dockerignore", + "@aws-cdk/cloud-assembly-api", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "license": "Apache-2.0", + "dependencies": { + "@aws-cdk/asset-awscli-v1": "2.2.273", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.1", + "@aws-cdk/cloud-assembly-api": "^2.2.0", + "@aws-cdk/cloud-assembly-schema": "^53.0.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.3.3", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", + "minimatch": "^10.2.3", + "punycode": "^2.3.1", + "semver": "^7.7.4", + "table": "^6.9.0", + "yaml": "1.10.3" + }, + "engines": { + "node": ">= 20.0.0" + }, + "peerDependencies": { + "constructs": "^10.5.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": { + "version": "2.2.0", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.4" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "@aws-cdk/cloud-assembly-schema": ">=53.0.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.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.18.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "4.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "5.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.1.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=0.4.0" + "node": ">=14.14" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, "engines": { - "node": ">=0.4.0" + "node": ">= 4" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": ">=8" } }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.2.0", + "inBundle": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "universalify": "^2.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "inBundle": true, "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": "*" } }, - "node_modules/ajv-formats-draft2019": { - "version": "1.6.1", + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, "license": "MIT", - "dependencies": { - "punycode": "^2.1.1", - "schemes": "^1.4.0", - "smtp-address-parser": "^1.0.3", - "uri-js": "^4.4.1" - }, - "peerDependencies": { - "ajv": "*" + "engines": { + "node": ">= 0.6" } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "mime-db": "1.52.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">= 0.6" } }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/ajv-ftl-i18n": { - "version": "0.1.1", - "license": "MIT", + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "10.2.5", + "inBundle": true, + "license": "BlueOak-1.0.0", "dependencies": { - "commander": "10.0.0", - "fluent-transpiler": "0.2.1" + "brace-expansion": "^5.0.5" }, - "bin": { - "ajv-ftl": "cli.js" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/willfarrell" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ajv-ftl-i18n/node_modules/commander": { - "version": "10.0.0", + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=6" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, "engines": { - "node": ">=8" + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=10" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "dev": true, - "license": "ISC", + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/apim-spec": { - "resolved": "packages/specification", - "link": true - }, - "node_modules/arg": { - "version": "4.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "license": "MIT", + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "inBundle": true, + "license": "BSD-3-Clause", "dependencies": { - "sprintf-js": "~1.0.2" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.3", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" } }, "node_modules/aws-lambda": { @@ -5291,6 +6307,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", @@ -5495,6 +6512,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -5656,6 +6674,20 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/cdk": { + "resolved": "packages/cdk", + "link": true + }, + "node_modules/cdk-nag": { + "version": "2.37.55", + "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.37.55.tgz", + "integrity": "sha512-xcAkygwbph3pp7N0UEzJBmXUH/MIsluV7DYJSeZ/V3yCr0Y0QaRGO298WyD6mi4K+Rmnpl+EJoWUxcOblOqLKA==", + "license": "Apache-2.0", + "peerDependencies": { + "aws-cdk-lib": "^2.176.0", + "constructs": "^10.0.5" + } + }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -5872,6 +6904,13 @@ "upper-case": "^2.0.2" } }, + "node_modules/constructs": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.6.0.tgz", + "integrity": "sha512-TxHOnBO5zMo/G76ykzGF/wMpEHu257TbWiIxP9K0Yv/+t70UzgBQiTqjkAsWOPC6jW91DzJI0+ehQV6xDRNBuQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/convert-source-map": { "version": "2.0.0", "dev": true, @@ -6219,6 +7258,7 @@ "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -6273,6 +7313,7 @@ "version": "10.1.8", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -6809,7 +7850,6 @@ "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, @@ -6897,6 +7937,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -7395,6 +8447,7 @@ "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.3.0", "@jest/types": "30.3.0", @@ -8402,6 +9455,7 @@ "version": "6.15.0", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -9323,6 +10377,7 @@ "version": "19.2.1", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -9331,6 +10386,7 @@ "version": "19.2.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -9505,6 +10561,15 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/ret": { "version": "0.1.15", "license": "MIT", @@ -10041,6 +11106,7 @@ "integrity": "sha512-J72R4ltw0UBVUlEjTzI0gg2STOqlI9JBhQOL4Dxt7aJOnnSesy0qJDn4PYfMCafk9cWOaVg129Pesl5o+DIh0Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.4.0", "@emotion/unitless": "0.10.0", @@ -10222,6 +11288,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10327,6 +11394,7 @@ "version": "10.9.2", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -10369,6 +11437,25 @@ "version": "2.8.1", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -10405,6 +11492,7 @@ "version": "5.9.3", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10992,6 +12080,17 @@ "@psu-common/testing": "^1.0.0" } }, + "packages/cdk": { + "version": "0.1.0", + "dependencies": { + "@nhsdigital/eps-cdk-constructs": "^1.6.1", + "aws-cdk": "^2.1114.1", + "aws-cdk-lib": "^2.245.0", + "cdk-nag": "^2.37.55", + "constructs": "^10.6.0", + "tsx": "^4.21.0" + } + }, "packages/checkPrescriptionStatusUpdates": { "version": "1.0.0", "license": "MIT", diff --git a/package.json b/package.json index 1136800632..4b3dc48153 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "author": "NHS Digital", "license": "MIT", "workspaces": [ + "packages/cdk", "packages/gsul", "packages/nhsd-psu-sandbox", "packages/specification", diff --git a/packages/cdk/bin/PsuStatelessApp.ts b/packages/cdk/bin/PsuStatelessApp.ts new file mode 100644 index 0000000000..28208bad81 --- /dev/null +++ b/packages/cdk/bin/PsuStatelessApp.ts @@ -0,0 +1,21 @@ +import {calculateVersionedStackName, createApp, getConfigFromEnvVar} from "@nhsdigital/eps-cdk-constructs" +import {PsuStatelessStack} from "../stacks/PsuStatelessStack" + +async function main() { + const {app, props} = createApp({ + productName: "Prescription Status Update API", + appName: "PsuStatelessApp", + repoName: "eps-prescription-status-update-api", + driftDetectionGroup: "psu-api" + }) + + new PsuStatelessStack(app, "PsuStatelessStack", { + ...props, + stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props) + }) +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/packages/cdk/nagSuppressions.ts b/packages/cdk/nagSuppressions.ts new file mode 100644 index 0000000000..d53c150124 --- /dev/null +++ b/packages/cdk/nagSuppressions.ts @@ -0,0 +1,6 @@ +import {Stack} from "aws-cdk-lib" + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const nagSuppressions = (_stack: Stack) => { + // Nag suppressions will be added here as resources are migrated +} diff --git a/packages/cdk/package.json b/packages/cdk/package.json new file mode 100644 index 0000000000..c6988a51c4 --- /dev/null +++ b/packages/cdk/package.json @@ -0,0 +1,20 @@ +{ + "name": "cdk", + "version": "0.1.0", + "scripts": { + "cdk-synth": "cdk synth --output ../../cdk.out --quiet --app \"npm run tsx -- bin/${CDK_APP_NAME}.ts\"", + "cdk-diff": "cdk diff --app \"npm run tsx -- bin/${CDK_APP_NAME}.ts\"", + "cdk-deploy": "cdk deploy --app \"npm run tsx -- bin/${CDK_APP_NAME}.ts\" --all --ci true --require-approval ${REQUIRE_APPROVAL}", + "cdk-watch": "cdk deploy --app \"npm run tsx -- bin/${CDK_APP_NAME}.ts\" --watch --all --ci true --require-approval ${REQUIRE_APPROVAL}", + "lint": "eslint --max-warnings 0 --fix --config ../../eslint.config.mjs .", + "tsx": "tsx" + }, + "dependencies": { + "@nhsdigital/eps-cdk-constructs": "^1.6.1", + "aws-cdk": "^2.1114.1", + "aws-cdk-lib": "^2.245.0", + "cdk-nag": "^2.37.55", + "constructs": "^10.6.0", + "tsx": "^4.21.0" + } +} diff --git a/packages/cdk/stacks/PsuStatelessStack.ts b/packages/cdk/stacks/PsuStatelessStack.ts new file mode 100644 index 0000000000..c492c1469c --- /dev/null +++ b/packages/cdk/stacks/PsuStatelessStack.ts @@ -0,0 +1,17 @@ +import {App, Stack} from "aws-cdk-lib" +import {nagSuppressions} from "../nagSuppressions" +import {StandardStackProps} from "@nhsdigital/eps-cdk-constructs" + +export interface PsuStatelessStackProps extends StandardStackProps { + readonly stackName: string +} + +export class PsuStatelessStack extends Stack { + public constructor(scope: App, id: string, props: PsuStatelessStackProps) { + super(scope, id, props) + + // Resources will be added here as they are migrated from SAM templates + + nagSuppressions(this) + } +} diff --git a/packages/cdk/tsconfig.json b/packages/cdk/tsconfig.json new file mode 100644 index 0000000000..c7ce43bf53 --- /dev/null +++ b/packages/cdk/tsconfig.json @@ -0,0 +1,37 @@ +{ + "extends": "../../tsconfig.defaults.json", + "compilerOptions": { + "module": "commonjs", + "rootDir": ".", + "outDir": "lib", + "allowImportingTsExtensions": true, + "noEmit": true, + "strict": false, + "lib": [ + "es2020" + ], + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "../../node_modules/@types" + ] + }, + "include": [ + "stacks/**/*", + "bin/**/*", + "nagSuppressions.ts" + ], + "exclude": [ + "node_modules", + "cdk.out" + ] +} From b0b7aa634d0bdcb45770655136f3df47c9091aaa Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 11:47:10 +0000 Subject: [PATCH 02/31] Zizimor ignore --- zizmor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zizmor.yml b/zizmor.yml index 6659946d6b..2776fe96ab 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -5,6 +5,8 @@ rules: - run_release_code_and_api.yml:146:7 - run_regression_tests.yml:26:7 - run_package_code_and_api.yml:16:7 + - cdk_package_code.yml:25:7 + - cdk_release_code.yml:45:7 secrets-outside-env: # these are ignored because they are using known secrets ignore: From 4e8db26434c5998e8729b8faaf4c22d795f9ce61 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 12:25:47 +0000 Subject: [PATCH 03/31] Reenable the grype scan --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 26b304ca75..847e8dc9ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,13 +23,13 @@ repos: - repo: local hooks: - # - id: grype-scan-local - # name: Grype scan local changes - # entry: make - # args: ["grype-scan-local"] - # language: system - # pass_filenames: false - # always_run: true + - id: grype-scan-local + name: Grype scan local changes + entry: make + args: ["grype-scan-local"] + language: system + pass_filenames: false + always_run: true - id: check-commit-signing name: Check commit signing From 1a42b7fea8472fdeddc82f37be30d8152ef03c63 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 12:29:19 +0000 Subject: [PATCH 04/31] dont download get secrets layer yet --- .github/workflows/cdk_package_code.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/cdk_package_code.yml b/.github/workflows/cdk_package_code.yml index fda9c3f69d..0e41ae4f33 100644 --- a/.github/workflows/cdk_package_code.yml +++ b/.github/workflows/cdk_package_code.yml @@ -49,10 +49,6 @@ jobs: - name: make compile run: make compile - - name: download the get secrets lambda layer - run: | - make download-get-secrets-layer - - name: "Tar files" run: | tar -rf artifact.tar \ From 0728630964ca8b87390203f8b403547124ad2929 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 14:49:09 +0000 Subject: [PATCH 05/31] move API gateway to stateless stack --- .github/workflows/cdk_release_code.yml | 16 +++ .github/workflows/ci.yml | 4 + .github/workflows/pull_request.yml | 4 + .github/workflows/release.yml | 4 + Makefile | 4 + packages/cdk/bin/PsuStatelessApp.ts | 16 ++- packages/cdk/resources/Apis.ts | 151 +++++++++++++++++++++++ packages/cdk/stacks/PsuStatelessStack.ts | 22 +++- packages/cdk/tsconfig.json | 1 + 9 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 packages/cdk/resources/Apis.ts diff --git a/.github/workflows/cdk_release_code.yml b/.github/workflows/cdk_release_code.yml index 9361be0a60..ac579a968b 100644 --- a/.github/workflows/cdk_release_code.yml +++ b/.github/workflows/cdk_release_code.yml @@ -29,6 +29,18 @@ on: IS_PULL_REQUEST: type: boolean required: true + TRUSTSTORE_FILE: + type: string + required: true + FORWARD_CSOC_LOGS: + type: boolean + required: true + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: + type: boolean + required: true + EXPOSE_GET_STATUS_UPDATES: + type: boolean + required: true pinned_image: required: true type: string @@ -91,6 +103,10 @@ jobs: CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}" CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" CDK_CONFIG_logLevel: "${{ inputs.LOG_LEVEL }}" + CDK_CONFIG_trustStoreFile: "${{ inputs.TRUSTSTORE_FILE }}" + CDK_CONFIG_forwardCsocLogs: "${{ inputs.FORWARD_CSOC_LOGS }}" + CDK_CONFIG_deployCheckPrescriptionStatusUpdate: "${{ inputs.DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE }}" + CDK_CONFIG_exposeGetStatusUpdates: "${{ inputs.EXPOSE_GET_STATUS_UPDATES }}" REQUIRE_APPROVAL: "never" # later, there will be API deployment steps c.f. https://github.com/NHSDigital/electronic-prescription-service-clinical-prescription-tracker/blob/main/.github/workflows/cdk_release_code.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 145606984d..2ba72eb7a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,6 +81,10 @@ jobs: LOG_RETENTION_IN_DAYS: "30" LOG_LEVEL: DEBUG IS_PULL_REQUEST: false + TRUSTSTORE_FILE: psu-truststore.pem + FORWARD_CSOC_LOGS: false + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true + EXPOSE_GET_STATUS_UPDATES: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index cbaa104966..87d6618a44 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -124,6 +124,10 @@ jobs: LOG_RETENTION_IN_DAYS: "30" LOG_LEVEL: DEBUG IS_PULL_REQUEST: true + TRUSTSTORE_FILE: psu-truststore.pem + FORWARD_CSOC_LOGS: false + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true + EXPOSE_GET_STATUS_UPDATES: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 95bca18572..21f703f4f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,6 +83,10 @@ jobs: LOG_RETENTION_IN_DAYS: "30" LOG_LEVEL: DEBUG IS_PULL_REQUEST: false + TRUSTSTORE_FILE: psu-truststore.pem + FORWARD_CSOC_LOGS: false + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true + EXPOSE_GET_STATUS_UPDATES: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/Makefile b/Makefile index 5cf013b19b..e145acc941 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,10 @@ export CDK_CONFIG_isPullRequest=true export CDK_CONFIG_environment=dev export CDK_CONFIG_logRetentionInDays=30 export CDK_CONFIG_logLevel=DEBUG +export CDK_CONFIG_trustStoreFile=psu-truststore.pem +export CDK_CONFIG_forwardCsocLogs=false +export CDK_CONFIG_deployCheckPrescriptionStatusUpdate=true +export CDK_CONFIG_exposeGetStatusUpdates=false guard-%: @ if [ "${${*}}" = "" ]; then \ diff --git a/packages/cdk/bin/PsuStatelessApp.ts b/packages/cdk/bin/PsuStatelessApp.ts index 28208bad81..cebc23ca91 100644 --- a/packages/cdk/bin/PsuStatelessApp.ts +++ b/packages/cdk/bin/PsuStatelessApp.ts @@ -1,4 +1,10 @@ -import {calculateVersionedStackName, createApp, getConfigFromEnvVar} from "@nhsdigital/eps-cdk-constructs" +import { + calculateVersionedStackName, + createApp, + getBooleanConfigFromEnvVar, + getConfigFromEnvVar, + getNumberConfigFromEnvVar +} from "@nhsdigital/eps-cdk-constructs" import {PsuStatelessStack} from "../stacks/PsuStatelessStack" async function main() { @@ -11,7 +17,13 @@ async function main() { new PsuStatelessStack(app, "PsuStatelessStack", { ...props, - stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props) + stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props), + logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), + mutualTlsTrustStoreKey: props.isPullRequest ? undefined : getConfigFromEnvVar("trustStoreFile"), + csocApiGatewayDestination: "arn:aws:logs:eu-west-2:693466633220:destination:api_gateway_log_destination", + forwardCsocLogs: getBooleanConfigFromEnvVar("forwardCsocLogs"), + deployCheckPrescriptionStatusUpdate: getBooleanConfigFromEnvVar("deployCheckPrescriptionStatusUpdate"), + exposeGetStatusUpdates: getBooleanConfigFromEnvVar("exposeGetStatusUpdates") }) } diff --git a/packages/cdk/resources/Apis.ts b/packages/cdk/resources/Apis.ts new file mode 100644 index 0000000000..a7ddc3ad1f --- /dev/null +++ b/packages/cdk/resources/Apis.ts @@ -0,0 +1,151 @@ +import {LambdaIntegration, PassthroughBehavior, StepFunctionsIntegration} from "aws-cdk-lib/aws-apigateway" +import {IManagedPolicy} from "aws-cdk-lib/aws-iam" +import {HttpMethod} from "aws-cdk-lib/aws-lambda" +import {Construct} from "constructs" +import { + ExpressStateMachine, + LambdaEndpoint, + RestApiGateway, + StateMachineEndpoint, + TypescriptLambdaFunction +} from "@nhsdigital/eps-cdk-constructs" + +export interface ApisProps { + readonly stackName: string + readonly logRetentionInDays: number + readonly mutualTlsTrustStoreKey: string | undefined + readonly forwardCsocLogs: boolean + readonly csocApiGatewayDestination: string + readonly deployCheckPrescriptionStatusUpdate: boolean + readonly exposeGetStatusUpdates: boolean + functions: {[key: string]: TypescriptLambdaFunction} + stateMachines: {[key: string]: ExpressStateMachine} +} + +export class Apis extends Construct { + public constructor(scope: Construct, id: string, props: ApisProps) { + super(scope, id) + + // Collect execution policies for all resources that need API Gateway access + const executionPolicies: Array = [ + props.stateMachines.updatePrescriptionStatus.executionPolicy, + props.stateMachines.format1UpdatePrescriptionsStatus.executionPolicy, + props.functions.status.executionPolicy, + props.functions.capabilityStatement.executionPolicy, + props.functions.nhsNotifyUpdateCallback.executionPolicy + ] + + if (props.exposeGetStatusUpdates) { + executionPolicies.push(props.functions.getStatusUpdates.executionPolicy) + } + + if (props.deployCheckPrescriptionStatusUpdate) { + executionPolicies.push(props.functions.checkPrescriptionStatusUpdates.executionPolicy) + } + + const apiGateway = new RestApiGateway(this, "RestApiGateway", { + stackName: props.stackName, + logRetentionInDays: props.logRetentionInDays, + mutualTlsTrustStoreKey: props.mutualTlsTrustStoreKey, + forwardCsocLogs: props.forwardCsocLogs, + csocApiGatewayDestination: props.csocApiGatewayDestination, + executionPolicies + }) + + const rootResource = apiGateway.api.root + + // POST / — UpdatePrescriptionStatus state machine integration (root resource) + rootResource.addMethod( + HttpMethod.POST, + StepFunctionsIntegration.startExecution( + props.stateMachines.updatePrescriptionStatus.stateMachine, + { + credentialsRole: apiGateway.role, + passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH + } + ), + { + methodResponses: [ + {statusCode: "200"}, + {statusCode: "400"}, + {statusCode: "500"} + ] + } + ) + + // POST /format-1 — Format1 state machine integration + new StateMachineEndpoint(this, "Format1UpdatePrescriptionStatusEndpoint", { + parentResource: rootResource, + resourceName: "format-1", + method: HttpMethod.POST, + restApiGatewayRole: apiGateway.role, + stateMachine: props.stateMachines.format1UpdatePrescriptionsStatus + }) + + // POST /notification-delivery-status-callback — Lambda proxy integration + new LambdaEndpoint(this, "NotificationDeliveryStatusCallbackEndpoint", { + parentResource: rootResource, + resourceName: "notification-delivery-status-callback", + method: HttpMethod.POST, + restApiGatewayRole: apiGateway.role, + lambdaFunction: props.functions.nhsNotifyUpdateCallback + }) + + // GET /_status — Lambda proxy integration + new LambdaEndpoint(this, "StatusEndpoint", { + parentResource: rootResource, + resourceName: "_status", + method: HttpMethod.GET, + restApiGatewayRole: apiGateway.role, + lambdaFunction: props.functions.status + }) + + // GET /metadata — Lambda proxy integration + new LambdaEndpoint(this, "CapabilityStatementEndpoint", { + parentResource: rootResource, + resourceName: "metadata", + method: HttpMethod.GET, + restApiGatewayRole: apiGateway.role, + lambdaFunction: props.functions.capabilityStatement + }) + + // GET /checkprescriptionstatusupdates — conditional Lambda proxy integration + if (props.deployCheckPrescriptionStatusUpdate) { + new LambdaEndpoint(this, "CheckPrescriptionStatusUpdatesEndpoint", { + parentResource: rootResource, + resourceName: "checkprescriptionstatusupdates", + method: HttpMethod.GET, + restApiGatewayRole: apiGateway.role, + lambdaFunction: props.functions.checkPrescriptionStatusUpdates + }) + } + + // POST /get-status-updates — conditional Lambda integration (non-proxy) + if (props.exposeGetStatusUpdates) { + const getStatusUpdatesResource = rootResource.addResource("get-status-updates") + getStatusUpdatesResource.addMethod( + HttpMethod.POST, + new LambdaIntegration(props.functions.getStatusUpdates.function, { + credentialsRole: apiGateway.role, + proxy: false, + requestTemplates: { + "application/json": "$input.json('$')" + }, + integrationResponses: [ + { + statusCode: "200", + responseTemplates: { + "application/json": "$input.body" + } + } + ] + }), + { + methodResponses: [ + {statusCode: "200"} + ] + } + ) + } + } +} diff --git a/packages/cdk/stacks/PsuStatelessStack.ts b/packages/cdk/stacks/PsuStatelessStack.ts index c492c1469c..30d2368327 100644 --- a/packages/cdk/stacks/PsuStatelessStack.ts +++ b/packages/cdk/stacks/PsuStatelessStack.ts @@ -4,13 +4,33 @@ import {StandardStackProps} from "@nhsdigital/eps-cdk-constructs" export interface PsuStatelessStackProps extends StandardStackProps { readonly stackName: string + readonly logRetentionInDays: number + readonly mutualTlsTrustStoreKey: string | undefined + readonly forwardCsocLogs: boolean + readonly csocApiGatewayDestination: string + readonly deployCheckPrescriptionStatusUpdate: boolean + readonly exposeGetStatusUpdates: boolean } export class PsuStatelessStack extends Stack { public constructor(scope: App, id: string, props: PsuStatelessStackProps) { super(scope, id, props) - // Resources will be added here as they are migrated from SAM templates + // Apis construct will be instantiated here once Functions and StateMachines are migrated: + // + // const functions = new Functions(this, "Functions", { ... }) + // const stateMachines = new StateMachines(this, "StateMachines", { ... }) + // new Apis(this, "Apis", { + // stackName: props.stackName, + // logRetentionInDays: props.logRetentionInDays, + // mutualTlsTrustStoreKey: props.mutualTlsTrustStoreKey, + // forwardCsocLogs: props.forwardCsocLogs, + // csocApiGatewayDestination: props.csocApiGatewayDestination, + // deployCheckPrescriptionStatusUpdate: props.deployCheckPrescriptionStatusUpdate, + // exposeGetStatusUpdates: props.exposeGetStatusUpdates, + // functions: functions.functions, + // stateMachines: stateMachines.stateMachines, + // }) nagSuppressions(this) } diff --git a/packages/cdk/tsconfig.json b/packages/cdk/tsconfig.json index c7ce43bf53..280f62c4ca 100644 --- a/packages/cdk/tsconfig.json +++ b/packages/cdk/tsconfig.json @@ -28,6 +28,7 @@ "include": [ "stacks/**/*", "bin/**/*", + "resources/**/*", "nagSuppressions.ts" ], "exclude": [ From 8d158279b9c3c5c214b8e19097369ff12cdcb549 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 15:17:09 +0000 Subject: [PATCH 06/31] Migrate lambda functions. There is a required reference to the SAM stack so that it can access the stateful resources, e.g. tables, SQS, params. --- .github/workflows/cdk_release_code.yml | 16 + .github/workflows/ci.yml | 4 + .github/workflows/pull_request.yml | 4 + .github/workflows/release.yml | 4 + Makefile | 4 + packages/cdk/bin/PsuStatelessApp.ts | 8 +- packages/cdk/resources/Functions.ts | 488 +++++++++++++++++++++++ packages/cdk/stacks/PsuStatelessStack.ts | 26 +- zizmor.yml | 2 + 9 files changed, 553 insertions(+), 3 deletions(-) create mode 100644 packages/cdk/resources/Functions.ts diff --git a/.github/workflows/cdk_release_code.yml b/.github/workflows/cdk_release_code.yml index ac579a968b..ccd24991db 100644 --- a/.github/workflows/cdk_release_code.yml +++ b/.github/workflows/cdk_release_code.yml @@ -41,6 +41,18 @@ on: EXPOSE_GET_STATUS_UPDATES: type: boolean required: true + SAM_STACK_NAME: + type: string + required: true + ENABLE_POST_DATED_NOTIFICATIONS: + type: boolean + required: true + REQUIRE_APPLICATION_NAME: + type: boolean + required: true + ENABLE_BACKUP: + type: boolean + required: true pinned_image: required: true type: string @@ -107,6 +119,10 @@ jobs: CDK_CONFIG_forwardCsocLogs: "${{ inputs.FORWARD_CSOC_LOGS }}" CDK_CONFIG_deployCheckPrescriptionStatusUpdate: "${{ inputs.DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE }}" CDK_CONFIG_exposeGetStatusUpdates: "${{ inputs.EXPOSE_GET_STATUS_UPDATES }}" + CDK_CONFIG_samStackName: "${{ inputs.SAM_STACK_NAME }}" + CDK_CONFIG_enablePostDatedNotifications: "${{ inputs.ENABLE_POST_DATED_NOTIFICATIONS }}" + CDK_CONFIG_requireApplicationName: "${{ inputs.REQUIRE_APPLICATION_NAME }}" + CDK_CONFIG_enableBackup: "${{ inputs.ENABLE_BACKUP }}" REQUIRE_APPROVAL: "never" # later, there will be API deployment steps c.f. https://github.com/NHSDigital/electronic-prescription-service-clinical-prescription-tracker/blob/main/.github/workflows/cdk_release_code.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ba72eb7a4..7528f64153 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,10 @@ jobs: FORWARD_CSOC_LOGS: false DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true EXPOSE_GET_STATUS_UPDATES: false + SAM_STACK_NAME: psu + ENABLE_POST_DATED_NOTIFICATIONS: true + REQUIRE_APPLICATION_NAME: false + ENABLE_BACKUP: true secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 87d6618a44..7fa9f9976c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -128,6 +128,10 @@ jobs: FORWARD_CSOC_LOGS: false DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true EXPOSE_GET_STATUS_UPDATES: false + SAM_STACK_NAME: psu-pr-${{needs.get_issue_number.outputs.issue_number}} + ENABLE_POST_DATED_NOTIFICATIONS: true + REQUIRE_APPLICATION_NAME: false + ENABLE_BACKUP: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21f703f4f5..11da192a67 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,6 +87,10 @@ jobs: FORWARD_CSOC_LOGS: false DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true EXPOSE_GET_STATUS_UPDATES: false + SAM_STACK_NAME: psu + ENABLE_POST_DATED_NOTIFICATIONS: true + REQUIRE_APPLICATION_NAME: false + ENABLE_BACKUP: true secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/Makefile b/Makefile index e145acc941..e6ac7a7f6f 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ SHELL = /bin/bash .SHELLFLAGS = -o pipefail -c export CDK_APP_NAME=PsuStatelessApp export CDK_CONFIG_stackName=${stack_name} +export CDK_CONFIG_samStackName=${stack_name} export CDK_CONFIG_versionNumber=undefined export CDK_CONFIG_commitId=undefined export CDK_CONFIG_isPullRequest=true @@ -12,6 +13,9 @@ export CDK_CONFIG_trustStoreFile=psu-truststore.pem export CDK_CONFIG_forwardCsocLogs=false export CDK_CONFIG_deployCheckPrescriptionStatusUpdate=true export CDK_CONFIG_exposeGetStatusUpdates=false +export CDK_CONFIG_enablePostDatedNotifications=false +export CDK_CONFIG_requireApplicationName=false +export CDK_CONFIG_enableBackup=false guard-%: @ if [ "${${*}}" = "" ]; then \ diff --git a/packages/cdk/bin/PsuStatelessApp.ts b/packages/cdk/bin/PsuStatelessApp.ts index cebc23ca91..7116cc73c0 100644 --- a/packages/cdk/bin/PsuStatelessApp.ts +++ b/packages/cdk/bin/PsuStatelessApp.ts @@ -18,12 +18,18 @@ async function main() { new PsuStatelessStack(app, "PsuStatelessStack", { ...props, stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props), + samStackName: getConfigFromEnvVar("samStackName"), // TODO: REMOVE THE NEED FOR THIS logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), + logLevel: getConfigFromEnvVar("logLevel"), + environment: getConfigFromEnvVar("environment"), mutualTlsTrustStoreKey: props.isPullRequest ? undefined : getConfigFromEnvVar("trustStoreFile"), csocApiGatewayDestination: "arn:aws:logs:eu-west-2:693466633220:destination:api_gateway_log_destination", forwardCsocLogs: getBooleanConfigFromEnvVar("forwardCsocLogs"), deployCheckPrescriptionStatusUpdate: getBooleanConfigFromEnvVar("deployCheckPrescriptionStatusUpdate"), - exposeGetStatusUpdates: getBooleanConfigFromEnvVar("exposeGetStatusUpdates") + exposeGetStatusUpdates: getBooleanConfigFromEnvVar("exposeGetStatusUpdates"), + enablePostDatedNotifications: getConfigFromEnvVar("enablePostDatedNotifications", undefined, "false"), + requireApplicationName: getConfigFromEnvVar("requireApplicationName", undefined, "false"), + enableBackup: getBooleanConfigFromEnvVar("enableBackup", undefined, "false") }) } diff --git a/packages/cdk/resources/Functions.ts b/packages/cdk/resources/Functions.ts new file mode 100644 index 0000000000..1f08cae758 --- /dev/null +++ b/packages/cdk/resources/Functions.ts @@ -0,0 +1,488 @@ +import {Fn} from "aws-cdk-lib" +import {CfnSchedule, CfnScheduleGroup} from "aws-cdk-lib/aws-scheduler" +import { + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal +} from "aws-cdk-lib/aws-iam" +import {Rule} from "aws-cdk-lib/aws-events" +import {LambdaFunction as LambdaFunctionTarget} from "aws-cdk-lib/aws-events-targets" +import {IFunction} from "aws-cdk-lib/aws-lambda" +import {Construct} from "constructs" +import {TypescriptLambdaFunction} from "@nhsdigital/eps-cdk-constructs" +import {NagSuppressions} from "cdk-nag" +import {resolve} from "node:path" + +export interface FunctionsProps { + readonly stackName: string + readonly samStackName: string + readonly version: string + readonly commitId: string + readonly logRetentionInDays: number + readonly logLevel: string + readonly environment: string + readonly enablePostDatedNotifications: string + readonly requireApplicationName: string + readonly deployCheckPrescriptionStatusUpdate: boolean + readonly enableBackup: boolean +} + +const baseDir = resolve(__dirname, "../../..") + +export class Functions extends Construct { + functions: {[key: string]: TypescriptLambdaFunction} + + public constructor(scope: Construct, id: string, props: FunctionsProps) { + super(scope, id) + + const sam = props.samStackName + + // Table names derived from SAM stack naming convention (not exported) + const prescriptionStatusUpdatesTableName = `${sam}-PrescriptionStatusUpdates` + const prescriptionNotificationStatesTableName = `${sam}-PrescriptionNotificationStatesV1` + + // Cross-stack imports from SAM stateful stack — tables + const tableWritePolicy = ManagedPolicy.fromManagedPolicyArn( + this, "TableWritePolicy", + Fn.importValue(`${sam}:tables:${prescriptionStatusUpdatesTableName}:TableWritePolicyArn`) + ) + const tableReadPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "TableReadPolicy", + Fn.importValue(`${sam}:tables:${prescriptionStatusUpdatesTableName}:TableReadPolicyArn`) + ) + const tableKmsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "TableKmsPolicy", + Fn.importValue(`${sam}:tables:UsePrescriptionStatusUpdatesKMSKeyPolicyArn`) + ) + const notificationStatesReadPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "NotificationStatesTableReadPolicy", + Fn.importValue( + `${sam}:tables:${prescriptionNotificationStatesTableName}:TableReadPolicyArn` + ) + ) + const notificationStatesWritePolicy = ManagedPolicy.fromManagedPolicyArn( + this, "NotificationStatesTableWritePolicy", + Fn.importValue( + `${sam}:tables:${prescriptionNotificationStatesTableName}:TableWritePolicyArn` + ) + ) + const notificationStatesKmsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "NotificationStatesKmsPolicy", + Fn.importValue(`${sam}:tables:UsePrescriptionNotificationStatesKMSKeyPolicyArn`) + ) + + // Cross-stack imports from SAM stateful stack — messaging + const notificationSqsKmsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "NotificationSqsKmsPolicy", + Fn.importValue(`${sam}-UseNotificationSQSQueueKMSKeyPolicyArn`) + ) + const writeNotifySqsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "WriteNotifySqsPolicy", + Fn.importValue(`${sam}-WriteNHSNotifyPrescriptionsSQSQueuePolicyArn`) + ) + const readNotifySqsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "ReadNotifySqsPolicy", + Fn.importValue(`${sam}-ReadNHSNotifyPrescriptionsSQSQueuePolicyArn`) + ) + const writePostDatedSqsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "WritePostDatedSqsPolicy", + Fn.importValue(`${sam}-WritePostDatedNotificationsSQSQueuePolicyArn`) + ) + const readPostDatedSqsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "ReadPostDatedSqsPolicy", + Fn.importValue(`${sam}-ReadPostDatedNotificationsSQSQueuePolicyArn`) + ) + const nhsNotifyPrescriptionsSqsQueueUrl = Fn.importValue(`${sam}-NHSNotifyPrescriptionsSQSQueueUrl`) + const postDatedNotificationsSqsQueueUrl = Fn.importValue(`${sam}-PostDatedNotificationsSQSQueueUrl`) + + // Cross-stack imports from SAM stateful stack — secrets + const sqsSaltSecret = Fn.importValue(`${sam}-SQSSaltSecret`) + const getSqsSaltSecretPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "GetSqsSaltSecretPolicy", + Fn.importValue(`${sam}-GetSQSSaltSecretPolicy`) + ) + const notifyApiKeySecret = Fn.importValue(`${sam}-PSU-Notify-API-Key`) + const notifyPrivateKeySecret = Fn.importValue(`${sam}-PSU-Notify-PrivateKey`) + const notifyKidSecret = Fn.importValue(`${sam}-PSU-Notify-KID`) + const notifyAppIdSecret = Fn.importValue(`${sam}-PSU-Notify-Application-ID`) + const getPsuSecretPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "GetPsuSecretPolicy", + Fn.importValue(`${sam}-GetPSUSecretPolicy`) + ) + const usePsuSecretsKmsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "UsePsuSecretsKmsPolicy", + Fn.importValue(`${sam}-UsePSUSecretsKMSKeyPolicyArn`) + ) + + // Cross-stack imports from SAM stateful stack — parameters + const getNotificationsParamPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "GetNotificationsParamPolicy", + Fn.importValue(`${sam}-GetNotificationsParameterPolicy`) + ) + const enabledSiteOdsCodesParam = Fn.importValue(`${sam}-PSUNotifyEnabledSiteODSCodesParam`) + const enabledSystemsParam = Fn.importValue(`${sam}-PSUNotifyEnabledSystemsParam`) + const enabledSupplierAppIdsParam = Fn.importValue(`${sam}-PSUNotifyEnabledSupplierApplicationIDsParam`) + const blockedSiteOdsCodesParam = Fn.importValue(`${sam}-PSUNotifyBlockedSiteODSCodesParam`) + const notifyRoutingPlanIdParam = Fn.importValue(`${sam}-PSUNotifyRoutingPlanIDParam`) + const notifyApiBaseUrlParam = Fn.importValue(`${sam}-PSUNotifyApiBaseUrlParam`) + const enableNotificationsExternalParam = Fn.importValue(`${sam}-EnableNotificationsExternalName`) + const enableNotificationsInternalParam = Fn.importValue(`${sam}-EnableNotificationsInternalName`) + const testPrescriptionsParamName1 = Fn.importValue(`${sam}-TestPrescriptionsParameterName1`) + const testPrescriptionsParamName2 = Fn.importValue(`${sam}-TestPrescriptionsParameterName2`) + const testPrescriptionsParamName3 = Fn.importValue(`${sam}-TestPrescriptionsParameterName3`) + const testPrescriptionsParamName4 = Fn.importValue(`${sam}-TestPrescriptionsParameterName4`) + + // Account-level imports + const lambdaAccessSecretsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "LambdaAccessSecretsPolicy", + Fn.importValue("account-resources:LambdaAccessSecretsPolicy") + ) + + // Lambda Functions + + const updatePrescriptionStatus = new TypescriptLambdaFunction( + this, "UpdatePrescriptionStatus", { + functionName: `${props.stackName}-UpdatePrescriptionStatus`, + projectBaseDir: baseDir, + packageBasePath: "packages/updatePrescriptionStatus", + entryPoint: "src/updatePrescriptionStatus.ts", + environmentVariables: { + TABLE_NAME: prescriptionStatusUpdatesTableName, + NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: nhsNotifyPrescriptionsSqsQueueUrl, + POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL: postDatedNotificationsSqsQueueUrl, + ENABLE_POST_DATED_NOTIFICATIONS: props.enablePostDatedNotifications, + SQS_SALT: sqsSaltSecret, + USE_APP_ID_FOR_NOTIFICATIONS_FILTERING: "false", + ENABLED_SYSTEMS_PARAM: enabledSystemsParam, + ENABLED_APPLICATION_IDS_PARAM: enabledSupplierAppIdsParam, + ENABLED_SITE_ODS_CODES_PARAM: enabledSiteOdsCodesParam, + BLOCKED_SITE_ODS_CODES_PARAM: blockedSiteOdsCodesParam, + ENABLE_NOTIFICATIONS_PARAM: enableNotificationsInternalParam, + ENVIRONMENT: props.environment, + REQUIRE_APPLICATION_NAME: props.requireApplicationName, + TEST_PRESCRIPTIONS_PARAM_NAME_1: testPrescriptionsParamName1, + TEST_PRESCRIPTIONS_PARAM_NAME_2: testPrescriptionsParamName2, + TEST_PRESCRIPTIONS_PARAM_NAME_3: testPrescriptionsParamName3, + TEST_PRESCRIPTIONS_PARAM_NAME_4: testPrescriptionsParamName4 + }, + additionalPolicies: [ + tableWritePolicy, + tableReadPolicy, + tableKmsPolicy, + notificationSqsKmsPolicy, + writeNotifySqsPolicy, + writePostDatedSqsPolicy, + getSqsSaltSecretPolicy, + getNotificationsParamPolicy + ], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const convertRequestToFhirFormat = new TypescriptLambdaFunction( + this, "ConvertRequestToFhirFormat", { + functionName: `${props.stackName}-ConvertRequestToFhirFormat`, + projectBaseDir: baseDir, + packageBasePath: "packages/cpsuLambda", + entryPoint: "src/cpsu.ts", + environmentVariables: {}, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const getStatusUpdates = new TypescriptLambdaFunction(this, "GetStatusUpdates", { + functionName: `${props.stackName}-GetStatusUpdates`, + projectBaseDir: baseDir, + packageBasePath: "packages/gsul", + entryPoint: "src/getStatusUpdates.ts", + environmentVariables: { + TABLE_NAME: prescriptionStatusUpdatesTableName + }, + additionalPolicies: [tableReadPolicy, tableKmsPolicy], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const status = new TypescriptLambdaFunction(this, "Status", { + functionName: `${props.stackName}-status`, + projectBaseDir: baseDir, + packageBasePath: "packages/statusLambda", + entryPoint: "src/statusLambda.ts", + environmentVariables: {}, + additionalPolicies: [lambdaAccessSecretsPolicy], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const capabilityStatement = new TypescriptLambdaFunction(this, "CapabilityStatement", { + functionName: `${props.stackName}-CapabilityStatement`, + projectBaseDir: baseDir, + packageBasePath: "packages/capabilityStatement", + entryPoint: "src/capabilityStatement.ts", + environmentVariables: {}, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const nhsNotifyUpdateCallback = new TypescriptLambdaFunction( + this, "NHSNotifyUpdateCallback", { + functionName: `${props.stackName}-NHSNotifyUpdateCallback`, + projectBaseDir: baseDir, + packageBasePath: "packages/nhsNotifyUpdateCallback", + entryPoint: "src/lambdaHandler.ts", + environmentVariables: { + TABLE_NAME: prescriptionNotificationStatesTableName, + APP_ID_SECRET: notifyAppIdSecret, + API_KEY_SECRET: notifyApiKeySecret + }, + additionalPolicies: [ + notificationStatesReadPolicy, + notificationStatesWritePolicy, + notificationStatesKmsPolicy, + getPsuSecretPolicy, + usePsuSecretsKmsPolicy + ], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const notifyProcessor = new TypescriptLambdaFunction(this, "NotifyProcessor", { + functionName: `${props.stackName}-NotifyProcessor`, + projectBaseDir: baseDir, + packageBasePath: "packages/nhsNotifyLambda", + entryPoint: "src/nhsNotifyLambda.ts", + environmentVariables: { + NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: nhsNotifyPrescriptionsSqsQueueUrl, + TABLE_NAME: prescriptionNotificationStatesTableName, + API_KEY_SECRET: notifyApiKeySecret, + PRIVATE_KEY_SECRET: notifyPrivateKeySecret, + KID_SECRET: notifyKidSecret, + NHS_NOTIFY_ROUTING_ID_PARAM: notifyRoutingPlanIdParam, + NOTIFY_API_BASE_URL_PARAM: notifyApiBaseUrlParam, + MAKE_REAL_NOTIFY_REQUESTS_PARAM: enableNotificationsExternalParam + }, + additionalPolicies: [ + writeNotifySqsPolicy, + readNotifySqsPolicy, + notificationSqsKmsPolicy, + notificationStatesReadPolicy, + notificationStatesWritePolicy, + notificationStatesKmsPolicy, + getNotificationsParamPolicy, + getPsuSecretPolicy, + usePsuSecretsKmsPolicy + ], + timeoutInSeconds: 900, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + this.createSchedule( + "NotifyProcessorSchedule", + `${props.stackName}-NotifySchedule`, + "rate(1 minute)", + notifyProcessor.function, + props.stackName + ) + + const postDatedNotifyLambda = new TypescriptLambdaFunction( + this, "PostDatedNotifyLambda", { + functionName: `${props.stackName}-postDatedNotifyLambda`, + projectBaseDir: baseDir, + packageBasePath: "packages/postDatedLambda", + entryPoint: "src/main.ts", + environmentVariables: { + NHS_NOTIFY_PRESCRIPTIONS_SQS_QUEUE_URL: nhsNotifyPrescriptionsSqsQueueUrl, + POST_DATED_PRESCRIPTIONS_SQS_QUEUE_URL: postDatedNotificationsSqsQueueUrl, + TABLE_NAME: prescriptionStatusUpdatesTableName + }, + additionalPolicies: [ + writeNotifySqsPolicy, + readNotifySqsPolicy, + notificationSqsKmsPolicy, + writePostDatedSqsPolicy, + readPostDatedSqsPolicy, + tableWritePolicy, + tableReadPolicy, + tableKmsPolicy + ], + timeoutInSeconds: 900, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + this.createSchedule( + "PostDatedNotifySchedule", + `${props.stackName}-PostDatedNotifySchedule`, + "rate(15 minutes)", + postDatedNotifyLambda.function, + props.stackName + ) + + // Conditional: CheckPrescriptionStatusUpdates + let checkPrescriptionStatusUpdates: TypescriptLambdaFunction | undefined + if (props.deployCheckPrescriptionStatusUpdate) { + checkPrescriptionStatusUpdates = new TypescriptLambdaFunction( + this, "CheckPrescriptionStatusUpdates", { + functionName: `${props.stackName}-CheckPrescriptionStatusUpdates`, + projectBaseDir: baseDir, + packageBasePath: "packages/checkPrescriptionStatusUpdates", + entryPoint: "src/checkPrescriptionStatusUpdates.ts", + environmentVariables: { + TABLE_NAME: prescriptionStatusUpdatesTableName, + MIN_RESULTS_RETURNED: "5", + MAX_RESULTS_RETURNED: "15" + }, + additionalPolicies: [tableReadPolicy, tableKmsPolicy], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + } + ) + } + + // Conditional: PsuRestoreValidation + if (props.enableBackup) { + const psuRestoreValidationPolicy = new ManagedPolicy(this, "PsuRestoreValidationPolicy", { + managedPolicyName: `${props.stackName}-PsuRestoreValidationPolicy`, + statements: [ + new PolicyStatement({ + actions: ["backup:PutRestoreValidationResult"], + resources: ["*"] + }), + new PolicyStatement({ + actions: [ + "dynamodb:GetItem", + "dynamodb:BatchGetItem", + "dynamodb:Scan", + "dynamodb:Query", + "dynamodb:ConditionCheckItem", + "dynamodb:DescribeTable" + ], + resources: [ + Fn.sub( + "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/awsbackup-restore-test-*" + ) + ] + }) + ] + }) + + NagSuppressions.addResourceSuppressions(psuRestoreValidationPolicy, [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard needed for backup:PutRestoreValidationResult on any restore job" + } + ]) + + const psuRestoreValidation = new TypescriptLambdaFunction(this, "PsuRestoreValidation", { + functionName: `${props.stackName}-PsuRestoreValidation`, + projectBaseDir: baseDir, + packageBasePath: "packages/psuRestoreValidationLambda", + entryPoint: "src/handler.ts", + environmentVariables: {}, + additionalPolicies: [ + psuRestoreValidationPolicy, + tableReadPolicy, + tableKmsPolicy + ], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const restoreRule = new Rule(this, "PsuBackupRestoreJobStateChangeRule", { + eventPattern: { + source: ["aws.backup"], + detailType: ["Restore Job State Change"], + detail: { + resourceType: ["DynamoDB.FullyManaged"], + status: ["COMPLETED"], + sourceResourceArn: [ + Fn.sub( + + `arn:aws:dynamodb:\${AWS::Region}:\${AWS::AccountId}:table/${prescriptionStatusUpdatesTableName}` + ) + ] + } + } + }) + restoreRule.addTarget(new LambdaFunctionTarget(psuRestoreValidation.function)) + } + + this.functions = { + updatePrescriptionStatus, + convertRequestToFhirFormat, + getStatusUpdates, + status, + capabilityStatement, + nhsNotifyUpdateCallback, + notifyProcessor, + postDatedNotifyLambda, + ...(checkPrescriptionStatusUpdates ? {checkPrescriptionStatusUpdates} : {}) + } + } + + private createSchedule( + id: string, + scheduleName: string, + scheduleExpression: string, + lambdaFunction: IFunction, + stackName: string + ): void { + const scheduleRole = new Role(this, `${id}Role`, { + assumedBy: new ServicePrincipal("scheduler.amazonaws.com") + }) + + const schedulePolicy = new ManagedPolicy(this, `${id}Policy`, { + statements: [ + new PolicyStatement({ + actions: ["lambda:InvokeFunction"], + resources: [lambdaFunction.functionArn] + }) + ] + }) + scheduleRole.addManagedPolicy(schedulePolicy) + + NagSuppressions.addResourceSuppressions(scheduleRole, [ + { + id: "AwsSolutions-IAM5", + reason: "Role only grants InvokeFunction on a specific Lambda" + } + ]) + + const scheduleGroup = new CfnScheduleGroup(this, `${id}Group`, { + name: `${stackName}-${id}Group` + }) + + new CfnSchedule(this, id, { + name: scheduleName, + groupName: scheduleGroup.name, + scheduleExpression: scheduleExpression, + flexibleTimeWindow: {mode: "OFF"}, + target: { + arn: lambdaFunction.functionArn, + roleArn: scheduleRole.roleArn + } + }) + } +} diff --git a/packages/cdk/stacks/PsuStatelessStack.ts b/packages/cdk/stacks/PsuStatelessStack.ts index 30d2368327..d58988c7a2 100644 --- a/packages/cdk/stacks/PsuStatelessStack.ts +++ b/packages/cdk/stacks/PsuStatelessStack.ts @@ -1,24 +1,46 @@ import {App, Stack} from "aws-cdk-lib" import {nagSuppressions} from "../nagSuppressions" import {StandardStackProps} from "@nhsdigital/eps-cdk-constructs" +import {Functions} from "../resources/Functions" +// Apis will be used once StateMachines are also migrated +// import {Apis} from "../resources/Apis" export interface PsuStatelessStackProps extends StandardStackProps { readonly stackName: string + readonly samStackName: string readonly logRetentionInDays: number + readonly logLevel: string + readonly environment: string readonly mutualTlsTrustStoreKey: string | undefined readonly forwardCsocLogs: boolean readonly csocApiGatewayDestination: string readonly deployCheckPrescriptionStatusUpdate: boolean readonly exposeGetStatusUpdates: boolean + readonly enablePostDatedNotifications: string + readonly requireApplicationName: string + readonly enableBackup: boolean } export class PsuStatelessStack extends Stack { public constructor(scope: App, id: string, props: PsuStatelessStackProps) { super(scope, id, props) - // Apis construct will be instantiated here once Functions and StateMachines are migrated: + new Functions(this, "Functions", { + stackName: props.stackName, + samStackName: props.samStackName, + version: props.version, + commitId: props.commitId, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + environment: props.environment, + enablePostDatedNotifications: props.enablePostDatedNotifications, + requireApplicationName: props.requireApplicationName, + deployCheckPrescriptionStatusUpdate: props.deployCheckPrescriptionStatusUpdate, + enableBackup: props.enableBackup + }) + + // Apis construct will be fully wired once StateMachines are also migrated: // - // const functions = new Functions(this, "Functions", { ... }) // const stateMachines = new StateMachines(this, "StateMachines", { ... }) // new Apis(this, "Apis", { // stackName: props.stackName, diff --git a/zizmor.yml b/zizmor.yml index 2776fe96ab..728f741a11 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -7,6 +7,8 @@ rules: - run_package_code_and_api.yml:16:7 - cdk_package_code.yml:25:7 - cdk_release_code.yml:45:7 + - cdk_release_code.yml:57:7 + - cdk_release_code.yml:69:7 secrets-outside-env: # these are ignored because they are using known secrets ignore: From 9ef599475d9f082d05e17a4b0e4b4266b42d8312 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Thu, 9 Apr 2026 16:03:37 +0000 Subject: [PATCH 07/31] Migrate over state machine --- Makefile | 12 +- packages/cdk/nagSuppressions.ts | 80 ++++++++++- .../Format1UpdatePrescriptionsStatus.ts | 127 ++++++++++++++++++ .../UpdatePrescriptionStatus.ts | 84 ++++++++++++ packages/cdk/resources/StateMachines.ts | 92 +++++++++++++ packages/cdk/stacks/PsuStatelessStack.ts | 37 ++--- 6 files changed, 411 insertions(+), 21 deletions(-) create mode 100644 packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts create mode 100644 packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts create mode 100644 packages/cdk/resources/StateMachines.ts diff --git a/Makefile b/Makefile index e6ac7a7f6f..f06b8951e5 100644 --- a/Makefile +++ b/Makefile @@ -257,7 +257,17 @@ cdk-deploy: npm run cdk-deploy --workspace packages/cdk cdk-synth: - CDK_CONFIG_stackName=psu-api \ + CDK_CONFIG_stackName=psu-cdk \ + CDK_CONFIG_samStackName=psu \ + CDK_CONFIG_logRetentionInDays=30 \ + CDK_CONFIG_logLevel=DEBUG \ + CDK_CONFIG_environment=dev \ + CDK_CONFIG_forwardCsocLogs=false \ + CDK_CONFIG_deployCheckPrescriptionStatusUpdate=true \ + CDK_CONFIG_exposeGetStatusUpdates=false \ + CDK_CONFIG_enablePostDatedNotifications=false \ + CDK_CONFIG_requireApplicationName=false \ + CDK_CONFIG_enableBackup=false \ npm run cdk-synth --workspace packages/cdk cdk-diff: diff --git a/packages/cdk/nagSuppressions.ts b/packages/cdk/nagSuppressions.ts index d53c150124..d03ec56507 100644 --- a/packages/cdk/nagSuppressions.ts +++ b/packages/cdk/nagSuppressions.ts @@ -1,6 +1,80 @@ +/* eslint-disable max-len */ import {Stack} from "aws-cdk-lib" +import {safeAddNagSuppression, safeAddNagSuppressionGroup} from "@nhsdigital/eps-cdk-constructs" -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const nagSuppressions = (_stack: Stack) => { - // Nag suppressions will be added here as resources are migrated +export const nagSuppressions = (stack: Stack) => { + // State machine log policies require wildcard for log streams and log delivery actions + safeAddNagSuppressionGroup( + stack, + [ + "/PsuStatelessStack/StateMachines/UpdatePrescriptionStatusStateMachine/StateMachinePutLogsManagedPolicy/Resource", + "/PsuStatelessStack/StateMachines/Format1UpdatePrescriptionsStatusStateMachine/StateMachinePutLogsManagedPolicy/Resource" + ], + [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard on log-stream is required to write to any log stream under the log group. Wildcard on Resource::* is required for log delivery management actions (DescribeLogGroups, ListLogDeliveries, etc.) which do not support resource-level permissions." + } + ] + ) + + // API Gateway does not use request validation — validation is handled by service logic + safeAddNagSuppression( + stack, + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Resource", + [ + { + id: "AwsSolutions-APIG2", + reason: "Request validation is handled by the backend service logic and FHIR validation state machine, not at the API Gateway level." + } + ] + ) + + // API Gateway CloudWatch role uses AWS managed policy + safeAddNagSuppression( + stack, + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/CloudWatchRole/Resource", + [ + { + id: "AwsSolutions-IAM4", + reason: "AWS managed policy AmazonAPIGatewayPushToCloudWatchLogs is the standard approach for API Gateway logging and is maintained by AWS.", + appliesTo: ["Policy::arn::iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"] + } + ] + ) + + // API Gateway stage is not associated with WAFv2 — WAF is managed externally via Apigee + safeAddNagSuppression( + stack, + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/DeploymentStage.prod/Resource", + [ + { + id: "AwsSolutions-APIG3", + reason: "WAF is managed externally via the Apigee proxy layer, not at the API Gateway level." + } + ] + ) + + // API methods do not use authorization — mTLS and Apigee handle auth externally + safeAddNagSuppressionGroup( + stack, + [ + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/POST/Resource", + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/format-1/POST/Resource", + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/notification-delivery-status-callback/POST/Resource", + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/_status/GET/Resource", + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/metadata/GET/Resource", + "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/checkprescriptionstatusupdates/GET/Resource" + ], + [ + { + id: "AwsSolutions-APIG4", + reason: "Authorization is handled externally via mutual TLS and the Apigee API gateway proxy. API Gateway methods do not require an additional authorizer." + }, + { + id: "AwsSolutions-COG4", + reason: "This API does not use Cognito for authentication. Auth is handled via mutual TLS and the Apigee API gateway proxy." + } + ] + ) } diff --git a/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts b/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts new file mode 100644 index 0000000000..7e3192549e --- /dev/null +++ b/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts @@ -0,0 +1,127 @@ +import {IFunction} from "aws-cdk-lib/aws-lambda" +import {LambdaInvoke} from "aws-cdk-lib/aws-stepfunctions-tasks" +import {Construct} from "constructs" +import { + Chain, + Choice, + Condition, + IChainable, + Pass, + TaskInput +} from "aws-cdk-lib/aws-stepfunctions" + +export interface Format1UpdatePrescriptionsStatusDefinitionProps { + readonly convertRequestToFhirFormatFunction: IFunction + readonly updatePrescriptionStatusFunction: IFunction +} + +export class Format1UpdatePrescriptionsStatusDefinition extends Construct { + public readonly definition: IChainable + + public constructor( + scope: Construct, id: string, props: Format1UpdatePrescriptionsStatusDefinitionProps + ) { + super(scope, id) + + const catchAllError = new Pass(this, "CatchAllError", { + result: { + value: { + Payload: { + statusCode: 500, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: JSON.stringify({ + resourceType: "OperationOutcome", + issue: [{severity: "error", code: "processing", diagnostics: "System error"}] + }) + } + } + } + }) + + const callConvertRequestToFhirFormat = new LambdaInvoke( + this, "Call Convert Request To Fhir Format", { + lambdaFunction: props.convertRequestToFhirFormatFunction, + assign: { + convertStatusCode: "{% $states.result.Payload.statusCode %}", + convertHeaders: "{% $states.result.Payload.headers %}", + convertBody: "{% $parse($states.result.Payload.body) %}" + } + } + ) + callConvertRequestToFhirFormat.addCatch(catchAllError) + + const failedConvertRequestToFhir = new Pass(this, "Failed Convert Request to FHIR", { + outputs: { + Payload: { + statusCode: "{% $convertStatusCode %}", + headers: "{% $convertHeaders %}", + body: "{% $string($convertBody) %}" + } + } + }) + + const callUpdatePrescriptionStatus = new LambdaInvoke( + this, "Call Update Prescription Status", { + lambdaFunction: props.updatePrescriptionStatusFunction, + payload: TaskInput.fromObject({ + body: "{% $string($convertBody) %}", + headers: "{% $convertHeaders %}" + }), + assign: { + updateStatusCode: "{% $states.result.Payload.statusCode %}", + updatePayload: "{% $states.result.Payload %}" + } + } + ) + callUpdatePrescriptionStatus.addCatch(catchAllError) + + const translate409To202 = new Pass(this, "Translate 409 to 202", { + result: { + value: { + Payload: { + statusCode: 202, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: JSON.stringify({ + resourceType: "OperationOutcome", + issue: [{ + severity: "information", + code: "informational", + diagnostics: + "Duplicate update detected. The message was valid but did not result in an update." + }] + }) + } + } + } + }) + + const endState = new Pass(this, "End State") + + const checkConvertResult = new Choice(this, "Convert Request to FHIR result") + const convertNotOk = Condition.jsonata("{% $convertStatusCode != 200 %}") + + const checkUpdateResult = new Choice(this, "Check Update Prescription Status Result") + const updateIs409 = Condition.jsonata("{% $updateStatusCode = 409 %}") + + this.definition = Chain + .start(callConvertRequestToFhirFormat) + .next( + checkConvertResult + .when(convertNotOk, failedConvertRequestToFhir) + .otherwise( + callUpdatePrescriptionStatus + .next( + checkUpdateResult + .when(updateIs409, translate409To202) + .otherwise(endState) + ) + ) + ) + } +} diff --git a/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts b/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts new file mode 100644 index 0000000000..11847d5284 --- /dev/null +++ b/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts @@ -0,0 +1,84 @@ +import {IFunction} from "aws-cdk-lib/aws-lambda" +import {LambdaInvoke} from "aws-cdk-lib/aws-stepfunctions-tasks" +import {Construct} from "constructs" +import { + Chain, + Choice, + Condition, + IChainable, + Pass +} from "aws-cdk-lib/aws-stepfunctions" + +export interface UpdatePrescriptionStatusDefinitionProps { + readonly fhirValidationFunction: IFunction + readonly updatePrescriptionStatusFunction: IFunction +} + +export class UpdatePrescriptionStatusDefinition extends Construct { + public readonly definition: IChainable + + public constructor( + scope: Construct, id: string, props: UpdatePrescriptionStatusDefinitionProps + ) { + super(scope, id) + + const catchAllError = new Pass(this, "CatchAllError", { + result: { + value: { + Payload: { + statusCode: 500, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: JSON.stringify({ + resourceType: "OperationOutcome", + issue: [{severity: "error", code: "processing", diagnostics: "System error"}] + }) + } + } + } + }) + + const callFhirValidation = new LambdaInvoke(this, "Call FHIR Validation", { + lambdaFunction: props.fhirValidationFunction, + assign: { + fhirValidationResponse: "{% $states.result.Payload %}", + fhirValidationErrorCount: + "{% $count($states.result.Payload.issue[severity = 'error']) %}" + } + }) + callFhirValidation.addCatch(catchAllError) + + const returnFailedFhirValidationErrors = new Pass(this, "Return Failed FHIR Validation Errors", { + outputs: { + Payload: { + statusCode: 400, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: "{% $string($fhirValidationResponse) %}" + } + } + }) + + const callUpdatePrescriptionStatus = new LambdaInvoke( + this, "Call Update Prescription Status", { + lambdaFunction: props.updatePrescriptionStatusFunction + } + ) + callUpdatePrescriptionStatus.addCatch(catchAllError) + + const doFhirValidationErrorsExist = new Choice(this, "Do FHIR Validation Errors Exist") + const hasErrors = Condition.jsonata("{% $fhirValidationErrorCount > 0 %}") + + this.definition = Chain + .start(callFhirValidation) + .next( + doFhirValidationErrorsExist + .when(hasErrors, returnFailedFhirValidationErrors) + .otherwise(callUpdatePrescriptionStatus) + ) + } +} diff --git a/packages/cdk/resources/StateMachines.ts b/packages/cdk/resources/StateMachines.ts new file mode 100644 index 0000000000..4fabb92215 --- /dev/null +++ b/packages/cdk/resources/StateMachines.ts @@ -0,0 +1,92 @@ +import {Fn} from "aws-cdk-lib" +import {Function} from "aws-cdk-lib/aws-lambda" +import {ManagedPolicy, PolicyStatement} from "aws-cdk-lib/aws-iam" +import {Construct} from "constructs" +import {ExpressStateMachine, TypescriptLambdaFunction} from "@nhsdigital/eps-cdk-constructs" +import {UpdatePrescriptionStatusDefinition} from "./StateMachineDefinitions/UpdatePrescriptionStatus" +import {Format1UpdatePrescriptionsStatusDefinition} from "./StateMachineDefinitions/Format1UpdatePrescriptionsStatus" + +export interface StateMachinesProps { + readonly stackName: string + readonly logRetentionInDays: number + readonly functions: {[key: string]: TypescriptLambdaFunction} +} + +export class StateMachines extends Construct { + stateMachines: {[key: string]: ExpressStateMachine} + + public constructor(scope: Construct, id: string, props: StateMachinesProps) { + super(scope, id) + + // Import the FHIR Validator function from the fhir-validator stack + const fhirValidatorFunctionArn = `${Fn.importValue("fhir-validator:FHIRValidatorUKCoreLambdaArn")}:$LATEST` + const fhirValidatorFunction = Function.fromFunctionArn( + this, "FhirValidatorFunction", fhirValidatorFunctionArn + ) + const fhirValidatorExecutePolicy = ManagedPolicy.fromManagedPolicyArn( + this, "FhirValidatorExecutePolicy", + Fn.importValue("fhir-validator:FHIRValidatorUKCoreExecuteLambdaPolicyArn") + ) + + // Policy to invoke the FHIR validator function (needed because the import + // uses a qualified ARN with :$LATEST suffix which may not be covered by + // the execute policy from the fhir-validator stack) + const callFhirValidatorPolicy = new ManagedPolicy(this, "CallFhirValidatorPolicy", { + description: "Invoke FHIR validator lambda from state machine", + statements: [ + new PolicyStatement({ + actions: ["lambda:InvokeFunction"], + resources: [fhirValidatorFunctionArn] + }) + ] + }) + + // UpdatePrescriptionStatus state machine definition + const updatePrescriptionStatusDefinition = new UpdatePrescriptionStatusDefinition( + this, "UpdatePrescriptionStatusDefinition", { + fhirValidationFunction: fhirValidatorFunction, + updatePrescriptionStatusFunction: props.functions.updatePrescriptionStatus.function + } + ) + + const updatePrescriptionStatusStateMachine = new ExpressStateMachine( + this, "UpdatePrescriptionStatusStateMachine", { + stackName: props.stackName, + stateMachineName: `${props.stackName}-UpdatePrescriptionStatus`, + definition: updatePrescriptionStatusDefinition.definition, + logRetentionInDays: props.logRetentionInDays, + additionalPolicies: [ + props.functions.updatePrescriptionStatus.executionPolicy, + fhirValidatorExecutePolicy, + callFhirValidatorPolicy + ] + } + ) + + // Format1UpdatePrescriptionsStatus state machine definition + const format1Definition = new Format1UpdatePrescriptionsStatusDefinition( + this, "Format1UpdatePrescriptionsStatusDefinition", { + convertRequestToFhirFormatFunction: props.functions.convertRequestToFhirFormat.function, + updatePrescriptionStatusFunction: props.functions.updatePrescriptionStatus.function + } + ) + + const format1UpdatePrescriptionsStatusStateMachine = new ExpressStateMachine( + this, "Format1UpdatePrescriptionsStatusStateMachine", { + stackName: props.stackName, + stateMachineName: `${props.stackName}-Format1UpdatePrescriptionsStatus`, + definition: format1Definition.definition, + logRetentionInDays: props.logRetentionInDays, + additionalPolicies: [ + props.functions.convertRequestToFhirFormat.executionPolicy, + props.functions.updatePrescriptionStatus.executionPolicy + ] + } + ) + + this.stateMachines = { + updatePrescriptionStatus: updatePrescriptionStatusStateMachine, + format1UpdatePrescriptionsStatus: format1UpdatePrescriptionsStatusStateMachine + } + } +} diff --git a/packages/cdk/stacks/PsuStatelessStack.ts b/packages/cdk/stacks/PsuStatelessStack.ts index d58988c7a2..c1a681484a 100644 --- a/packages/cdk/stacks/PsuStatelessStack.ts +++ b/packages/cdk/stacks/PsuStatelessStack.ts @@ -2,8 +2,8 @@ import {App, Stack} from "aws-cdk-lib" import {nagSuppressions} from "../nagSuppressions" import {StandardStackProps} from "@nhsdigital/eps-cdk-constructs" import {Functions} from "../resources/Functions" -// Apis will be used once StateMachines are also migrated -// import {Apis} from "../resources/Apis" +import {StateMachines} from "../resources/StateMachines" +import {Apis} from "../resources/Apis" export interface PsuStatelessStackProps extends StandardStackProps { readonly stackName: string @@ -25,7 +25,7 @@ export class PsuStatelessStack extends Stack { public constructor(scope: App, id: string, props: PsuStatelessStackProps) { super(scope, id, props) - new Functions(this, "Functions", { + const functions = new Functions(this, "Functions", { stackName: props.stackName, samStackName: props.samStackName, version: props.version, @@ -39,20 +39,23 @@ export class PsuStatelessStack extends Stack { enableBackup: props.enableBackup }) - // Apis construct will be fully wired once StateMachines are also migrated: - // - // const stateMachines = new StateMachines(this, "StateMachines", { ... }) - // new Apis(this, "Apis", { - // stackName: props.stackName, - // logRetentionInDays: props.logRetentionInDays, - // mutualTlsTrustStoreKey: props.mutualTlsTrustStoreKey, - // forwardCsocLogs: props.forwardCsocLogs, - // csocApiGatewayDestination: props.csocApiGatewayDestination, - // deployCheckPrescriptionStatusUpdate: props.deployCheckPrescriptionStatusUpdate, - // exposeGetStatusUpdates: props.exposeGetStatusUpdates, - // functions: functions.functions, - // stateMachines: stateMachines.stateMachines, - // }) + const stateMachines = new StateMachines(this, "StateMachines", { + stackName: props.stackName, + logRetentionInDays: props.logRetentionInDays, + functions: functions.functions + }) + + new Apis(this, "Apis", { + stackName: props.stackName, + logRetentionInDays: props.logRetentionInDays, + mutualTlsTrustStoreKey: props.mutualTlsTrustStoreKey, + forwardCsocLogs: props.forwardCsocLogs, + csocApiGatewayDestination: props.csocApiGatewayDestination, + deployCheckPrescriptionStatusUpdate: props.deployCheckPrescriptionStatusUpdate, + exposeGetStatusUpdates: props.exposeGetStatusUpdates, + functions: functions.functions, + stateMachines: stateMachines.stateMachines + }) nagSuppressions(this) } From f568a10968d99a9fe49b19c6260541a463bf3155 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 10 Apr 2026 09:06:11 +0000 Subject: [PATCH 08/31] Fix permission issue --- .grype.yaml | 8 ++++++++ packages/cdk/resources/Functions.ts | 16 ++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.grype.yaml b/.grype.yaml index d07cef7c42..fef22bd293 100644 --- a/.grype.yaml +++ b/.grype.yaml @@ -1,3 +1,11 @@ ignore: # path-to-regexp - dependency of aws-sdk-client-mock - vulnerability: GHSA-j3q9-mxjg-w52f + # This is a ReDoS vulnerability that we're not exposed to, since we don't take user regex input + - vulnerability: GHSA-2g4f-4pwh-qvx6 + reason: "We don't take user regex input, so we're not vulnerable to this issue." + # Axios vulnerability in checking NO_PROXY rules, which we don't use + - vulnerability: GHSA-3p68-rc4w-qgx5 + reason: "We don't use NO_PROXY rules, so we're not vulnerable to this issue." + - vulnerability: GHSA-j965-2qgj-vjmq + reason: "We use v3 packages already, so we're not vulnerable to this issue." diff --git a/packages/cdk/resources/Functions.ts b/packages/cdk/resources/Functions.ts index 1f08cae758..1c73242a6b 100644 --- a/packages/cdk/resources/Functions.ts +++ b/packages/cdk/resources/Functions.ts @@ -1,5 +1,5 @@ import {Fn} from "aws-cdk-lib" -import {CfnSchedule, CfnScheduleGroup} from "aws-cdk-lib/aws-scheduler" +import {CfnSchedule} from "aws-cdk-lib/aws-scheduler" import { ManagedPolicy, PolicyStatement, @@ -296,8 +296,7 @@ export class Functions extends Construct { "NotifyProcessorSchedule", `${props.stackName}-NotifySchedule`, "rate(1 minute)", - notifyProcessor.function, - props.stackName + notifyProcessor.function ) const postDatedNotifyLambda = new TypescriptLambdaFunction( @@ -332,8 +331,7 @@ export class Functions extends Construct { "PostDatedNotifySchedule", `${props.stackName}-PostDatedNotifySchedule`, "rate(15 minutes)", - postDatedNotifyLambda.function, - props.stackName + postDatedNotifyLambda.function ) // Conditional: CheckPrescriptionStatusUpdates @@ -446,8 +444,7 @@ export class Functions extends Construct { id: string, scheduleName: string, scheduleExpression: string, - lambdaFunction: IFunction, - stackName: string + lambdaFunction: IFunction ): void { const scheduleRole = new Role(this, `${id}Role`, { assumedBy: new ServicePrincipal("scheduler.amazonaws.com") @@ -470,13 +467,8 @@ export class Functions extends Construct { } ]) - const scheduleGroup = new CfnScheduleGroup(this, `${id}Group`, { - name: `${stackName}-${id}Group` - }) - new CfnSchedule(this, id, { name: scheduleName, - groupName: scheduleGroup.name, scheduleExpression: scheduleExpression, flexibleTimeWindow: {mode: "OFF"}, target: { From 983cd09bef0a91dfce140aacbdb279106cbe8cb4 Mon Sep 17 00:00:00 2001 From: Jim Wild Date: Fri, 10 Apr 2026 09:40:47 +0000 Subject: [PATCH 09/31] Commit todo file for posterity --- TODO.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000..34f3bd7335 --- /dev/null +++ b/TODO.md @@ -0,0 +1,95 @@ +# FIXME: Delete this file + + +# CDK Migration TODO — Stateless Stack + +This file tracks the migration of stateless components from SAM templates to CDK. +Stateful resources (DynamoDB tables, SSM parameters, Secrets Manager secrets, SQS queues) remain in SAM. + +## Lambda Functions (`SAMtemplates/functions/main.yaml`) + +Each Lambda includes the function itself, a nested `lambda_resources.yaml` stack (IAM role, log group, Splunk subscription filter), and associated IAM policies. + +- [x] **UpdatePrescriptionStatus** — Core function that writes prescription status updates to DynamoDB and sends SQS messages for notifications. Has extensive IAM policies for DynamoDB, SQS, Secrets Manager, and SSM parameters. *(uses `TypescriptLambdaFunction` construct)* +- [x] **ConvertRequestToFhirFormat (CPSU)** — Converts custom format requests to FHIR format. No additional IAM policies beyond basic Lambda execution. *(uses `TypescriptLambdaFunction` construct)* +- [x] **GetStatusUpdates** — Reads prescription status updates from DynamoDB. Needs DynamoDB read and KMS policies. *(uses `TypescriptLambdaFunction` construct)* +- [x] **Status** — Returns `_status` health check info (version, commit ID). Needs Secrets Manager access. *(uses `TypescriptLambdaFunction` construct)* +- [x] **CapabilityStatement** — Returns FHIR CapabilityStatement. No additional IAM policies. *(uses `TypescriptLambdaFunction` construct)* +- [x] **CheckPrescriptionStatusUpdates** — Conditionally deployed (`DeployCheckPrescriptionStatusUpdate`). Reads from DynamoDB. Needs DynamoDB read and KMS policies. *(conditional `TypescriptLambdaFunction` construct)* +- [x] **NotifyProcessor** — Processes NHS Notify messages from SQS on a 1-minute schedule (EventBridge Scheduler). Needs SQS, DynamoDB, Secrets Manager, SSM, and KMS policies. Has a dedicated scheduler IAM role. *(uses `TypescriptLambdaFunction` + `CfnSchedule`)* +- [x] **PostDatedNotifyLambda** — Processes post-dated notifications on a 15-minute schedule (EventBridge Scheduler). Needs SQS and DynamoDB policies. Has a dedicated scheduler IAM role. *(uses `TypescriptLambdaFunction` + `CfnSchedule`)* +- [x] **NHSNotifyUpdateCallback** — Handles NHS Notify delivery status callbacks. Needs DynamoDB and Secrets Manager policies. *(uses `TypescriptLambdaFunction` construct)* +- [x] **PsuRestoreValidation** — Conditionally deployed (`EnableBackup`). Validates DynamoDB backup restores. Has a custom IAM policy for `backup:PutRestoreValidationResult` and an EventBridge rule for restore job state changes. *(conditional `TypescriptLambdaFunction` + `Rule`)* + +### Per-Lambda shared resources (`lambda_resources.yaml`) +Each Lambda above has a nested stack that creates: +- [x] IAM execution role with CloudWatch Logs permissions *(handled by `TypescriptLambdaFunction` construct)* +- [x] CloudWatch Log Group (with configurable retention, optional KMS encryption) *(handled by `TypescriptLambdaFunction` construct)* +- [x] Splunk subscription filter (conditional on `EnableSplunk`) *(handled by `TypescriptLambdaFunction` construct — always enabled)* +- [x] Execute Lambda managed policy (exported for cross-stack references) *(handled by `TypescriptLambdaFunction` construct)* + +## State Machines (`SAMtemplates/state_machines/main.yaml`) + +Each state machine includes the state machine itself and a nested `state_machine_resources.yaml` stack (IAM role, log group, Splunk subscription filter). + +- [x] **UpdatePrescriptionStatusStateMachine** — EXPRESS type. Invokes FHIR Validator (cross-stack import from `fhir-validator` stack) and UpdatePrescriptionStatus Lambda. Uses ASL definition file `UpdatePrescriptionStatusStateMachine.asl.json`. *(uses `ExpressStateMachine` construct + CDK step function definition)* +- [x] **Format1UpdatePrescriptionsStatusStateMachine** — EXPRESS type. Invokes ConvertRequestToFhirFormat and UpdatePrescriptionStatus Lambdas. Uses ASL definition file `Format1UpdatePrescriptionsStatusStateMachine.asl.json`. *(uses `ExpressStateMachine` construct + CDK step function definition)* + +### Per-state-machine shared resources (`state_machine_resources.yaml`) +Each state machine has a nested stack that creates: +- [x] IAM execution role with state machine and Lambda invoke permissions *(handled by `ExpressStateMachine` construct)* +- [x] CloudWatch Log Group (with configurable retention, optional KMS encryption) *(handled by `ExpressStateMachine` construct)* +- [x] Splunk subscription filter (conditional on `EnableSplunk`) *(handled by `ExpressStateMachine` construct — always enabled)* +- [x] Execute state machine managed policy (exported for cross-stack references) *(handled by `ExpressStateMachine` construct)* + +## API Gateway (`SAMtemplates/apis/main.yaml`) + +- [x] **ACM Certificate** (`GenerateCertificate`) — DNS-validated certificate for the custom domain. *(handled by `RestApiGateway` construct)* +- [x] **REST API** (`RestApiGateway`) — Regional REST API with optional execute-api endpoint disabling (when mTLS is enabled). *(handled by `RestApiGateway` construct)* +- [x] **Custom Domain** (`RestApiDomain`) — Regional domain with TLS 1.2, optional mTLS truststore from S3. *(handled by `RestApiGateway` construct)* +- [x] **Route 53 Record** (`RestApiRecordSet`) — Alias A record pointing to the API Gateway domain. *(handled by `RestApiGateway` construct)* +- [x] **API Deployment and Stage** (`RestApiGatewayDeploymentV2h`, `RestApiGatewayStage`) — Deployment with X-Ray tracing and structured access logging. *(handled by `RestApiGateway` construct via `deploy: true`)* +- [x] **Domain Mapping** (`RestApiDomainMapping`) — Base path mapping to the `prod` stage. *(handled by `RestApiGateway` construct)* + +### API Endpoints +- [x] **POST /** — `UpdatePrescriptionStatusMethod` — Step Functions integration (StartSyncExecution) to UpdatePrescriptionStatus state machine. *(root resource method in `Apis` construct)* +- [x] **POST /format-1** — `Format1UpdatePrescriptionStatusMethod` — Step Functions integration to Format1 state machine. *(uses `StateMachineEndpoint` construct)* +- [x] **POST /notification-delivery-status-callback** — `NotificationDeliveryStatusCallbackMethod` — Lambda proxy integration to NHSNotifyUpdateCallback. *(uses `LambdaEndpoint` construct)* +- [x] **GET /_status** — `StatusLambdaMethod` — Lambda proxy integration to Status function. *(uses `LambdaEndpoint` construct)* +- [x] **GET /metadata** — `CapabilityStatementMethod` — Lambda proxy integration to CapabilityStatement function. *(uses `LambdaEndpoint` construct)* +- [x] **GET /checkprescriptionstatusupdates** — `CheckPrescriptionStatusUpdatesMethod` — Conditional. Lambda proxy integration. *(conditional `LambdaEndpoint` in `Apis` construct)* +- [x] **POST /get-status-updates** — `GetStatusUpdatesMethod` — Conditional. Lambda integration (non-proxy) with request/response templates. *(conditional non-proxy `LambdaIntegration` in `Apis` construct)* + +### API shared resources (`api_resources.yaml`) +- [x] API Gateway IAM role with policies to invoke Lambdas and state machines *(handled by `RestApiGateway` construct)* +- [x] API Gateway access log group (with configurable retention, optional KMS encryption) *(handled by `RestApiGateway` construct)* +- [x] Splunk subscription filter (conditional on `EnableSplunk`) *(handled by `RestApiGateway` construct — always enabled)* +- [x] CSOC log forwarding (conditional on `ForwardCsocLogs`) *(handled by `RestApiGateway` construct)* + +## CloudWatch Alarms (`SAMtemplates/alarms/main.yaml`) + +### Log Metric Filters +- [ ] `GetStatusUpdatesErrorsLogsMetricFilter` — Filters ERROR level logs from GetStatusUpdates +- [ ] `PrescriptionStatusUpdateErrorsLogsMetricFilter` — Filters 500 status responses from UpdatePrescriptionStatus +- [ ] `PrescriptionStatusUpdateForcedErrorsLogsMetricFilter` — Filters forced INT test errors +- [ ] `CPSUFhirConverterErrorLogsMetricFilter` — Filters 500 status responses from CPSU converter +- [ ] `NotifyProcessorTimeoutsMetricFilter` — Filters timeout messages from NotifyProcessor + +### Alarms +- [ ] `GetStatusUpdatesErrorsAlarm` — Alerts on GetStatusUpdates errors +- [ ] `GetStatusUpdatesUnhandledErrorsAlarm` — Alerts on GetStatusUpdates unhandled Lambda errors +- [ ] `PrescriptionStatusUpdateErrorsAlarm` — Alerts on true 500 errors (excludes forced INT test errors via math expression) +- [ ] `PrescriptionStatusUpdateUnhandledErrorsAlarm` — Alerts on unhandled Lambda errors +- [ ] `DynamoDBSystemErrorsAlarm` — Alerts on DynamoDB system errors +- [ ] `DynamoDBWriteConsumptionAlarm` — Alerts when DynamoDB write usage nears provisioned capacity threshold +- [ ] `CPSUFhirConverterErrorsAlarm` — Alerts on CPSU FHIR converter errors +- [ ] `NotifyProcessorTimeoutsAlarm` — Alerts on NotifyProcessor timeouts +- [ ] `NHSNotifyPrescriptionsDeadLetterQueueMessagesAlarm` — Alerts on DLQ messages + +## Cross-cutting concerns + +- [ ] **Splunk log forwarding** — Conditionally enabled per log group via subscription filters. Requires `SplunkDeliveryStreamArn` and `SplunkSubscriptionFilterRole` from `lambda-resources` stack. +- [ ] **CSOC log forwarding** — Conditional forwarding of API Gateway access logs to CSOC destination. +- [ ] **KMS encryption** — CloudWatch log groups optionally encrypted with `account-resources:CloudwatchLogsKmsKeyArn`. +- [ ] **Cross-stack imports** — Many resources import values from `account-resources`, `lambda-resources`, `eps-route53-resources`, and `fhir-validator` stacks. These will become `Fn.importValue()` calls in CDK. +- [ ] **Conditional deployments** — `CheckPrescriptionStatusUpdates` and `GetStatusUpdates` endpoints are conditionally deployed. `PsuRestoreValidation` depends on `EnableBackup`. These will need CDK conditions or conditional construct instantiation. From 04fd85f49ed523e0848c49c9b5b2ff53f0e92c10 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:21:06 +0000 Subject: [PATCH 10/31] fix: remove dupe method responses & SQ unused vars --- packages/cdk/resources/Apis.ts | 35 +++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/cdk/resources/Apis.ts b/packages/cdk/resources/Apis.ts index a7ddc3ad1f..288ae9d0ab 100644 --- a/packages/cdk/resources/Apis.ts +++ b/packages/cdk/resources/Apis.ts @@ -23,6 +23,9 @@ export interface ApisProps { } export class Apis extends Construct { + apis: {[key: string]: RestApiGateway} + endpoints: {[key: string]: Construct} + public constructor(scope: Construct, id: string, props: ApisProps) { super(scope, id) @@ -63,18 +66,11 @@ export class Apis extends Construct { credentialsRole: apiGateway.role, passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH } - ), - { - methodResponses: [ - {statusCode: "200"}, - {statusCode: "400"}, - {statusCode: "500"} - ] - } + ) ) // POST /format-1 — Format1 state machine integration - new StateMachineEndpoint(this, "Format1UpdatePrescriptionStatusEndpoint", { + const format1PsuEndpoint = new StateMachineEndpoint(this, "Format1UpdatePrescriptionStatusEndpoint", { parentResource: rootResource, resourceName: "format-1", method: HttpMethod.POST, @@ -83,7 +79,7 @@ export class Apis extends Construct { }) // POST /notification-delivery-status-callback — Lambda proxy integration - new LambdaEndpoint(this, "NotificationDeliveryStatusCallbackEndpoint", { + const notificationDeliveryStatusCallbackEndpoint = new LambdaEndpoint(this, "NotificationDeliveryStatusCallbackEndpoint", { parentResource: rootResource, resourceName: "notification-delivery-status-callback", method: HttpMethod.POST, @@ -92,7 +88,7 @@ export class Apis extends Construct { }) // GET /_status — Lambda proxy integration - new LambdaEndpoint(this, "StatusEndpoint", { + const statusEndpoint = new LambdaEndpoint(this, "StatusEndpoint", { parentResource: rootResource, resourceName: "_status", method: HttpMethod.GET, @@ -101,7 +97,7 @@ export class Apis extends Construct { }) // GET /metadata — Lambda proxy integration - new LambdaEndpoint(this, "CapabilityStatementEndpoint", { + const capabilityStatementEndpoint = new LambdaEndpoint(this, "CapabilityStatementEndpoint", { parentResource: rootResource, resourceName: "metadata", method: HttpMethod.GET, @@ -109,15 +105,24 @@ export class Apis extends Construct { lambdaFunction: props.functions.capabilityStatement }) + this.endpoints = { + rootResource, + format1UpdatePrescriptionStatusEndpoint: format1PsuEndpoint, + status: statusEndpoint, + capabilityStatement: capabilityStatementEndpoint, + notificationDeliveryStatusCallback: notificationDeliveryStatusCallbackEndpoint, + } + // GET /checkprescriptionstatusupdates — conditional Lambda proxy integration if (props.deployCheckPrescriptionStatusUpdate) { - new LambdaEndpoint(this, "CheckPrescriptionStatusUpdatesEndpoint", { + const checkPsu = new LambdaEndpoint(this, "CheckPrescriptionStatusUpdatesEndpoint", { parentResource: rootResource, resourceName: "checkprescriptionstatusupdates", method: HttpMethod.GET, restApiGatewayRole: apiGateway.role, lambdaFunction: props.functions.checkPrescriptionStatusUpdates }) + this.endpoints.checkPrescriptionStatusUpdates = checkPsu } // POST /get-status-updates — conditional Lambda integration (non-proxy) @@ -147,5 +152,9 @@ export class Apis extends Construct { } ) } + + this.apis = { + api: apiGateway + } } } From f41c8966e63e92285bef950d3a06a18e7a5b736a Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:33:28 +0000 Subject: [PATCH 11/31] fix: Sonar style issues --- packages/cdk/resources/Apis.ts | 4 +-- .../tests/testDatabaseClient.test.ts | 26 ++++++++++--------- .../tests/testHandler.test.ts | 18 ++++++------- .../tests/utils/testUtils.ts | 2 +- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/cdk/resources/Apis.ts b/packages/cdk/resources/Apis.ts index 288ae9d0ab..bd77824ae2 100644 --- a/packages/cdk/resources/Apis.ts +++ b/packages/cdk/resources/Apis.ts @@ -79,7 +79,7 @@ export class Apis extends Construct { }) // POST /notification-delivery-status-callback — Lambda proxy integration - const notificationDeliveryStatusCallbackEndpoint = new LambdaEndpoint(this, "NotificationDeliveryStatusCallbackEndpoint", { + const notifyCallbackEndpoint = new LambdaEndpoint(this, "NotificationDeliveryStatusCallbackEndpoint", { parentResource: rootResource, resourceName: "notification-delivery-status-callback", method: HttpMethod.POST, @@ -110,7 +110,7 @@ export class Apis extends Construct { format1UpdatePrescriptionStatusEndpoint: format1PsuEndpoint, status: statusEndpoint, capabilityStatement: capabilityStatementEndpoint, - notificationDeliveryStatusCallback: notificationDeliveryStatusCallbackEndpoint, + notificationDeliveryStatusCallback: notifyCallbackEndpoint, } // GET /checkprescriptionstatusupdates — conditional Lambda proxy integration diff --git a/packages/updatePrescriptionStatus/tests/testDatabaseClient.test.ts b/packages/updatePrescriptionStatus/tests/testDatabaseClient.test.ts index 4b93bb2838..2d2f2afdea 100644 --- a/packages/updatePrescriptionStatus/tests/testDatabaseClient.test.ts +++ b/packages/updatePrescriptionStatus/tests/testDatabaseClient.test.ts @@ -144,7 +144,7 @@ describe("Unit test persistDataItems", () => { ApplicationID: "550e8400-e29b-41d4-a716-446655440000", ExpiryTime: 10 } - const dataItems = Array(150).fill(dataItem) + const dataItems = new Array(150).fill(dataItem) const loggerSpy = vi.spyOn(logger, "error") @@ -367,7 +367,7 @@ describe("Unit test rollbackDataItems", () => { const items = [makeItem(), makeItem({PrescriptionID: "PrescriptionID_2", TaskID: "TaskID_2"})] // success for each conditioned delete - mockSend.mockImplementation(async () => Promise.resolve()) + mockSend.mockImplementation(async () => {}) const loggerWarn = vi.spyOn(logger, "warn") const loggerError = vi.spyOn(logger, "error") @@ -384,14 +384,14 @@ describe("Unit test rollbackDataItems", () => { // First delete succeeds, second hits conditional check failure (skip it) mockSend - .mockImplementationOnce(async () => Promise.resolve()) - .mockImplementationOnce(async () => Promise.reject( - new TransactionCanceledException({ + .mockImplementationOnce(async () => {}) + .mockImplementationOnce(async () => { + throw new TransactionCanceledException({ $metadata: {}, message: "Conditional check failed", CancellationReasons: [{Code: "ConditionalCheckFailedException"}] }) - )) + }) const loggerWarn = vi.spyOn(logger, "warn") const loggerError = vi.spyOn(logger, "error") @@ -406,7 +406,9 @@ describe("Unit test rollbackDataItems", () => { it("returns false when an unexpected error occurs", async () => { const items = [makeItem()] - mockSend.mockImplementationOnce(async () => Promise.reject(new Error("error"))) + mockSend.mockImplementationOnce(async () => { + throw new Error("error") + }) const loggerError = vi.spyOn(logger, "error") @@ -420,15 +422,15 @@ describe("Unit test rollbackDataItems", () => { const items = [makeItem({TaskID: "A"}), makeItem({TaskID: "B"}), makeItem({TaskID: "C"})] mockSend - .mockImplementationOnce(async () => Promise.resolve()) // A: delete ok - .mockImplementationOnce(async () => Promise.reject( // B: condition failure -> skip - new TransactionCanceledException({ + .mockImplementationOnce(async () => {}) // A: delete ok + .mockImplementationOnce(async () => { // B: condition failure -> skip + throw new TransactionCanceledException({ $metadata: {}, message: "Conditional check failed", CancellationReasons: [{Code: "ConditionalCheckFailedException"}] }) - )) - .mockImplementationOnce(async () => Promise.resolve()) // C: delete ok + }) + .mockImplementationOnce(async () => {}) // C: delete ok const loggerWarn = vi.spyOn(logger, "warn") const loggerError = vi.spyOn(logger, "error") diff --git a/packages/updatePrescriptionStatus/tests/testHandler.test.ts b/packages/updatePrescriptionStatus/tests/testHandler.test.ts index 300cb9e987..b9831815de 100644 --- a/packages/updatePrescriptionStatus/tests/testHandler.test.ts +++ b/packages/updatePrescriptionStatus/tests/testHandler.test.ts @@ -39,12 +39,10 @@ import {QueryCommand, TransactionCanceledException, TransactWriteItemsCommand} f const {dynamoDBMockSend, mockPushPrescriptionToNotificationSQS, mockGetParametersByName, mockInitiatedSSMProvider} = vi.hoisted(() => { - const mockGetParametersByName = vi.fn(async () => Promise.resolve( - {[process.env.ENABLE_NOTIFICATIONS_PARAM!]: "false"} - )) + const mockGetParametersByName = vi.fn(async () => ({[process.env.ENABLE_NOTIFICATIONS_PARAM!]: "false"})) return { dynamoDBMockSend: vi.fn(), - mockPushPrescriptionToNotificationSQS: vi.fn().mockImplementation(async () => Promise.resolve()), + mockPushPrescriptionToNotificationSQS: vi.fn().mockImplementation(async () => {}), mockGetParametersByName, mockInitiatedSSMProvider: {getParametersByName: mockGetParametersByName} } @@ -97,12 +95,10 @@ describe("Integration tests for updatePrescriptionStatus handler", () => { }) mockPushPrescriptionToNotificationSQS.mockReset() - mockPushPrescriptionToNotificationSQS.mockImplementation(async () => Promise.resolve()) + mockPushPrescriptionToNotificationSQS.mockImplementation(async () => {}) mockGetParametersByName.mockReset() - mockGetParametersByName.mockImplementation(async () => Promise.resolve( - {[process.env.ENABLE_NOTIFICATIONS_PARAM!]: "false"} - )) + mockGetParametersByName.mockImplementation(async () => ({[process.env.ENABLE_NOTIFICATIONS_PARAM!]: "false"})) }) it("when request doesn't have correct resourceType and type, expect 400 status code and appropriate message", async () => { @@ -277,7 +273,7 @@ describe("Integration tests for updatePrescriptionStatus handler", () => { it("when dynamo call fails, expect 500 status code and internal server error message", async () => { const event = generateMockEvent(requestDispatched) - dynamoDBMockSend.mockRejectedValue(new Error() as never) + dynamoDBMockSend.mockRejectedValue(new Error() as never) // NOSONAR const response: APIGatewayProxyResult = await handler(event, {}) @@ -532,7 +528,9 @@ describe("Integration tests for updatePrescriptionStatus handler", () => { }) it("When the get parameter call throws an error, the request succeeds and the sqs queue is untouched", async () => { - mockGetParametersByName.mockImplementation(async () => Promise.reject(new Error("Failed"))) + mockGetParametersByName.mockImplementation(async () => { + throw new Error("Failed") + }) const {handler: tmpfn} = await import("../src/updatePrescriptionStatus") const rejected_event: APIGatewayProxyEvent = generateMockEvent(requestDispatched) diff --git a/packages/updatePrescriptionStatus/tests/utils/testUtils.ts b/packages/updatePrescriptionStatus/tests/utils/testUtils.ts index 90db173ce7..2f1ee46d56 100644 --- a/packages/updatePrescriptionStatus/tests/utils/testUtils.ts +++ b/packages/updatePrescriptionStatus/tests/utils/testUtils.ts @@ -77,7 +77,7 @@ export const TASK_VALUES = [ ] export function deepCopy(toCopy: object) { - return JSON.parse(JSON.stringify(toCopy)) + return structuredClone(toCopy) } export function validTask(): Task { From fdeff2794bdf10b5759f53ae85760ffad1d1c075 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:54:06 +0000 Subject: [PATCH 12/31] feat: cdk stateful resources --- .github/scripts/delete_stacks.sh | 10 +- .../workflows/cdk_stateful_release_code.yml | 181 ++++++ .github/workflows/pull_request.yml | 33 + .github/workflows/release.yml | 184 ++++++ Makefile | 10 + packages/cdk/bin/PsuStatefulApp.ts | 45 ++ packages/cdk/resources/Apis.ts | 2 +- packages/cdk/resources/Messaging.ts | 255 ++++++++ packages/cdk/resources/Parameters.ts | 192 ++++++ packages/cdk/resources/Secrets.ts | 210 +++++++ packages/cdk/resources/Tables.ts | 575 ++++++++++++++++++ packages/cdk/stacks/PsuStatefulStack.ts | 63 ++ 12 files changed, 1755 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/cdk_stateful_release_code.yml create mode 100644 packages/cdk/bin/PsuStatefulApp.ts create mode 100644 packages/cdk/resources/Messaging.ts create mode 100644 packages/cdk/resources/Parameters.ts create mode 100644 packages/cdk/resources/Secrets.ts create mode 100644 packages/cdk/resources/Tables.ts create mode 100644 packages/cdk/stacks/PsuStatefulStack.ts diff --git a/.github/scripts/delete_stacks.sh b/.github/scripts/delete_stacks.sh index b79c5971b0..7feb84d13d 100755 --- a/.github/scripts/delete_stacks.sh +++ b/.github/scripts/delete_stacks.sh @@ -6,11 +6,12 @@ REPO_NAME=eps-prescription-status-update-api # this should be a regex used in jq command that parses the output from aws cloudformation list-stacks and just captures stacks we are interested in -CAPTURE_REGEX="^psu-pr-(\\d+)(-sandbox)?$" +CAPTURE_REGEX="^psu-(cdk-)?pr-([0-9]+)(-sandbox|-stateful)?$" +# TODO: no longer needed? # this should be a regex that is used to get the pull request id from the cloud formation stack name # this is used in a replace command to replace the stack name so what is left is just the pull request id -PULL_REQUEST_STACK_REGEX=psu-pr- +#PULL_REQUEST_STACK_REGEX=psu-pr- CNAME_QUERY=psu-pr- @@ -29,8 +30,9 @@ delete_cloudformation_stacks() { for i in "${ACTIVE_STACKS_ARRAY[@]}" do echo "Checking if stack $i has open pull request" - PULL_REQUEST=${i//${PULL_REQUEST_STACK_REGEX}/} - PULL_REQUEST=${PULL_REQUEST//-sandbox/} + # Extract PR number from stack names like psu-pr-123, psu-pr-123-sandbox, + # psu-cdk-pr-123, or psu-cdk-pr-123-stateful + PULL_REQUEST=$(echo "${i}" | sed 's/.*-pr-//' | sed 's/-.*//') echo "Checking pull request id ${PULL_REQUEST}" URL="https://api.github.com/repos/NHSDigital/${REPO_NAME}/pulls/${PULL_REQUEST}" RESPONSE=$(curl "${URL}" 2>/dev/null) diff --git a/.github/workflows/cdk_stateful_release_code.yml b/.github/workflows/cdk_stateful_release_code.yml new file mode 100644 index 0000000000..81ce42c22f --- /dev/null +++ b/.github/workflows/cdk_stateful_release_code.yml @@ -0,0 +1,181 @@ +name: cdk stateful release code + +on: + workflow_call: + inputs: + BRANCH_NAME: + required: true + type: string + STACK_NAME: + required: true + type: string + AWS_ENVIRONMENT: + required: true + type: string + VERSION_NUMBER: + required: true + type: string + COMMIT_ID: + required: true + type: string + LOG_RETENTION_IN_DAYS: + required: true + type: string + IS_PULL_REQUEST: + type: boolean + required: true + ENABLE_DYNAMODB_AUTO_SCALING: + type: boolean + required: true + ENABLE_BACKUP: + type: boolean + required: true + ENABLED_SITE_ODS_CODES: + type: string + required: false + default: " " + ENABLED_SYSTEMS: + type: string + required: false + default: " " + ENABLED_SUPPLIER_APPLICATION_IDS: + type: string + required: false + default: " " + BLOCKED_SITE_ODS_CODES: + type: string + required: false + default: " " + NOTIFY_ROUTING_PLAN_ID: + type: string + required: false + default: " " + NOTIFY_API_BASE_URL: + type: string + required: false + default: " " + ENABLE_NOTIFICATIONS_EXTERNAL: + type: boolean + required: false + default: false + ENABLE_NOTIFICATIONS_INTERNAL: + type: boolean + required: false + default: false + TEST_PRESCRIPTIONS_1: + type: string + required: false + default: "PLACEHOLDER" + TEST_PRESCRIPTIONS_2: + type: string + required: false + default: "PLACEHOLDER" + TEST_PRESCRIPTIONS_3: + type: string + required: false + default: "PLACEHOLDER" + TEST_PRESCRIPTIONS_4: + type: string + required: false + default: "PLACEHOLDER" + pinned_image: + required: true + type: string + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: + required: true +permissions: {} + +jobs: + release_code: + runs-on: ubuntu-22.04 + environment: ${{ inputs.AWS_ENVIRONMENT }} + container: + image: ${{ inputs.pinned_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + name: deploy cdk stateful app + permissions: + id-token: write + contents: read + + steps: + - name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" + + - name: build_artifact download + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: build_artifact + + - name: extract build_artifact + run: tar -xf artifact.tar + + - name: install dependencies + uses: ./.github/actions/install_dependencies + with: + npm-required: false + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure AWS Credentials + id: connect-aws-deploy + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 + with: + aws-region: eu-west-2 + role-to-assume: ${{ secrets.CLOUD_FORMATION_DEPLOY_ROLE }} + role-session-name: psu-stateful-deployment + + - name: Show diff + env: + CDK_APP_NAME: "PsuStatefulApp" + CDK_CONFIG_stackName: "${{ inputs.STACK_NAME }}" + CDK_CONFIG_versionNumber: "${{ inputs.VERSION_NUMBER }}" + CDK_CONFIG_commitId: "${{ inputs.COMMIT_ID }}" + CDK_CONFIG_isPullRequest: "${{ inputs.IS_PULL_REQUEST }}" + CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}" + CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" + CDK_CONFIG_enableDynamoDBAutoScaling: "${{ inputs.ENABLE_DYNAMODB_AUTO_SCALING }}" + CDK_CONFIG_enableBackup: "${{ inputs.ENABLE_BACKUP }}" + CDK_CONFIG_enabledSiteOdsCodes: "${{ inputs.ENABLED_SITE_ODS_CODES }}" + CDK_CONFIG_enabledSystems: "${{ inputs.ENABLED_SYSTEMS }}" + CDK_CONFIG_enabledSupplierApplicationIds: "${{ inputs.ENABLED_SUPPLIER_APPLICATION_IDS }}" + CDK_CONFIG_blockedSiteOdsCodes: "${{ inputs.BLOCKED_SITE_ODS_CODES }}" + CDK_CONFIG_notifyRoutingPlanId: "${{ inputs.NOTIFY_ROUTING_PLAN_ID }}" + CDK_CONFIG_notifyApiBaseUrl: "${{ inputs.NOTIFY_API_BASE_URL }}" + CDK_CONFIG_enableNotificationsExternal: "${{ inputs.ENABLE_NOTIFICATIONS_EXTERNAL }}" + CDK_CONFIG_enableNotificationsInternal: "${{ inputs.ENABLE_NOTIFICATIONS_INTERNAL }}" + CDK_CONFIG_testPrescriptions1: "${{ inputs.TEST_PRESCRIPTIONS_1 }}" + CDK_CONFIG_testPrescriptions2: "${{ inputs.TEST_PRESCRIPTIONS_2 }}" + CDK_CONFIG_testPrescriptions3: "${{ inputs.TEST_PRESCRIPTIONS_3 }}" + CDK_CONFIG_testPrescriptions4: "${{ inputs.TEST_PRESCRIPTIONS_4 }}" + run: npm run cdk-diff --workspace packages/cdk + + - name: Deploy AWS stateful infrastructure + run: npm run cdk-deploy --workspace packages/cdk + shell: bash + env: + CDK_APP_NAME: "PsuStatefulApp" + CDK_CONFIG_stackName: "${{ inputs.STACK_NAME }}" + CDK_CONFIG_versionNumber: "${{ inputs.VERSION_NUMBER }}" + CDK_CONFIG_commitId: "${{ inputs.COMMIT_ID }}" + CDK_CONFIG_isPullRequest: "${{ inputs.IS_PULL_REQUEST }}" + CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}" + CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" + CDK_CONFIG_enableDynamoDBAutoScaling: "${{ inputs.ENABLE_DYNAMODB_AUTO_SCALING }}" + CDK_CONFIG_enableBackup: "${{ inputs.ENABLE_BACKUP }}" + CDK_CONFIG_enabledSiteOdsCodes: "${{ inputs.ENABLED_SITE_ODS_CODES }}" + CDK_CONFIG_enabledSystems: "${{ inputs.ENABLED_SYSTEMS }}" + CDK_CONFIG_enabledSupplierApplicationIds: "${{ inputs.ENABLED_SUPPLIER_APPLICATION_IDS }}" + CDK_CONFIG_blockedSiteOdsCodes: "${{ inputs.BLOCKED_SITE_ODS_CODES }}" + CDK_CONFIG_notifyRoutingPlanId: "${{ inputs.NOTIFY_ROUTING_PLAN_ID }}" + CDK_CONFIG_notifyApiBaseUrl: "${{ inputs.NOTIFY_API_BASE_URL }}" + CDK_CONFIG_enableNotificationsExternal: "${{ inputs.ENABLE_NOTIFICATIONS_EXTERNAL }}" + CDK_CONFIG_enableNotificationsInternal: "${{ inputs.ENABLE_NOTIFICATIONS_INTERNAL }}" + CDK_CONFIG_testPrescriptions1: "${{ inputs.TEST_PRESCRIPTIONS_1 }}" + CDK_CONFIG_testPrescriptions2: "${{ inputs.TEST_PRESCRIPTIONS_2 }}" + CDK_CONFIG_testPrescriptions3: "${{ inputs.TEST_PRESCRIPTIONS_3 }}" + CDK_CONFIG_testPrescriptions4: "${{ inputs.TEST_PRESCRIPTIONS_4 }}" + REQUIRE_APPROVAL: "never" diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 7fa9f9976c..69ee0eb89b 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -135,6 +135,39 @@ jobs: secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + cdk_stateful_release_code: + needs: + [get_issue_number, cdk_package_code, get_commit_id, get_config_values] + uses: ./.github/workflows/cdk_stateful_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + STACK_NAME: psu-cdk-pr-${{needs.get_issue_number.outputs.issue_number}}-stateful + AWS_ENVIRONMENT: dev + VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + LOG_RETENTION_IN_DAYS: "30" + IS_PULL_REQUEST: true + ENABLE_DYNAMODB_AUTO_SCALING: false + ENABLE_BACKUP: false + ENABLED_SITE_ODS_CODES: "A83008,FA565" + ENABLED_SYSTEMS: "Internal Test System" + ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" + BLOCKED_SITE_ODS_CODES: "XXXXX" + NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" + NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" + ENABLE_NOTIFICATIONS_EXTERNAL: "false" + ENABLE_NOTIFICATIONS_INTERNAL: "true" + TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} + TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} + TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} + TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + sam_package_code: needs: [get_issue_number, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 11da192a67..84d706c218 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,6 +94,38 @@ jobs: secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + cdk_stateful_release_dev: + needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_stateful_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: main + STACK_NAME: psu-cdk-stateful + AWS_ENVIRONMENT: dev + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + LOG_RETENTION_IN_DAYS: "30" + IS_PULL_REQUEST: false + ENABLE_DYNAMODB_AUTO_SCALING: false + ENABLE_BACKUP: true + ENABLED_SITE_ODS_CODES: "A83008,FA565" + ENABLED_SYSTEMS: "Internal Test System" + ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" + BLOCKED_SITE_ODS_CODES: "XXXXX" + NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" + NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" + ENABLE_NOTIFICATIONS_EXTERNAL: "false" + ENABLE_NOTIFICATIONS_INTERNAL: "false" + TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} + TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} + TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} + TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + sam_package_code: needs: [tag_release, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml @@ -259,6 +291,45 @@ jobs: PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} + cdk_stateful_release_ref: + needs: + [ + cdk_stateful_release_dev, + cdk_package_code, + get_commit_id, + tag_release, + get_config_values, + ] + uses: ./.github/workflows/cdk_stateful_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: main + STACK_NAME: psu-cdk-stateful + AWS_ENVIRONMENT: ref + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + LOG_RETENTION_IN_DAYS: "30" + IS_PULL_REQUEST: false + ENABLE_DYNAMODB_AUTO_SCALING: true + ENABLE_BACKUP: false + ENABLED_SITE_ODS_CODES: "A83008,FA565" + ENABLED_SYSTEMS: "Internal Test System, Apotec Ltd - Apotec CRM - Production, CrxPatientApp, nhsPrescriptionApp, Titan PSU Prod" + ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" + BLOCKED_SITE_ODS_CODES: "B3J1Z" + NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" + NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" + ENABLE_NOTIFICATIONS_EXTERNAL: "false" + ENABLE_NOTIFICATIONS_INTERNAL: "false" + TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} + TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} + TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} + TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.REF_CLOUD_FORMATION_DEPLOY_ROLE }} + release_qa: needs: [ @@ -315,6 +386,45 @@ jobs: PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} + cdk_stateful_release_qa: + needs: + [ + cdk_stateful_release_dev, + cdk_package_code, + get_commit_id, + tag_release, + get_config_values, + ] + uses: ./.github/workflows/cdk_stateful_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: main + STACK_NAME: psu-cdk-stateful + AWS_ENVIRONMENT: qa + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + LOG_RETENTION_IN_DAYS: "30" + IS_PULL_REQUEST: false + ENABLE_DYNAMODB_AUTO_SCALING: false + ENABLE_BACKUP: false + ENABLED_SITE_ODS_CODES: "A83008,FA565" + ENABLED_SYSTEMS: "Internal Test System, Apotec Ltd - Apotec CRM - Production, CrxPatientApp, nhsPrescriptionApp, Titan PSU Prod" + ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" + BLOCKED_SITE_ODS_CODES: "B3J1Z" + NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" + NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" + ENABLE_NOTIFICATIONS_EXTERNAL: "false" + ENABLE_NOTIFICATIONS_INTERNAL: "false" + TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} + TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} + TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} + TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.QA_CLOUD_FORMATION_DEPLOY_ROLE }} + release_int: needs: [ @@ -374,6 +484,45 @@ jobs: PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PROD_ROLE }} REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} + cdk_stateful_release_int: + needs: + [ + cdk_stateful_release_qa, + cdk_package_code, + get_commit_id, + tag_release, + get_config_values, + ] + uses: ./.github/workflows/cdk_stateful_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: main + STACK_NAME: psu-cdk-stateful + AWS_ENVIRONMENT: int + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + LOG_RETENTION_IN_DAYS: "30" + IS_PULL_REQUEST: false + ENABLE_DYNAMODB_AUTO_SCALING: false + ENABLE_BACKUP: true + ENABLED_SITE_ODS_CODES: "A83008,FA565" + ENABLED_SYSTEMS: "CrxPatientApp" + ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" + BLOCKED_SITE_ODS_CODES: "XXXXX" + NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" + NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" + ENABLE_NOTIFICATIONS_EXTERNAL: "true" + ENABLE_NOTIFICATIONS_INTERNAL: "true" + TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} + TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} + TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} + TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.INT_CLOUD_FORMATION_DEPLOY_ROLE }} + release_int_sandbox: needs: [ @@ -482,3 +631,38 @@ jobs: DEV_CLOUD_FORMATION_EXECUTE_LAMBDA_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_EXECUTE_LAMBDA_ROLE }} PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PROD_ROLE }} REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} + + cdk_stateful_release_prod: + needs: + [ + cdk_stateful_release_int, + cdk_package_code, + get_commit_id, + tag_release, + get_config_values, + ] + uses: ./.github/workflows/cdk_stateful_release_code.yml + permissions: + contents: write + id-token: write + with: + pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: main + STACK_NAME: psu-cdk-stateful + AWS_ENVIRONMENT: prod + VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} + COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + LOG_RETENTION_IN_DAYS: "731" + IS_PULL_REQUEST: false + ENABLE_DYNAMODB_AUTO_SCALING: true + ENABLE_BACKUP: true + ENABLED_SITE_ODS_CODES: "A83008,FA565" + ENABLED_SYSTEMS: "CrxPatientApp" + ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" + BLOCKED_SITE_ODS_CODES: "XXXXX" + NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" + NOTIFY_API_BASE_URL: "https://api.service.nhs.uk" + ENABLE_NOTIFICATIONS_EXTERNAL: "true" + ENABLE_NOTIFICATIONS_INTERNAL: "true" + secrets: + CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.PROD_CLOUD_FORMATION_DEPLOY_ROLE }} diff --git a/Makefile b/Makefile index f06b8951e5..ff7017f07f 100644 --- a/Makefile +++ b/Makefile @@ -257,6 +257,7 @@ cdk-deploy: npm run cdk-deploy --workspace packages/cdk cdk-synth: + CDK_APP_NAME=PsuStatelessApp \ CDK_CONFIG_stackName=psu-cdk \ CDK_CONFIG_samStackName=psu \ CDK_CONFIG_logRetentionInDays=30 \ @@ -270,6 +271,15 @@ cdk-synth: CDK_CONFIG_enableBackup=false \ npm run cdk-synth --workspace packages/cdk +cdk-stateful-synth: + CDK_APP_NAME=PsuStatefulApp \ + CDK_CONFIG_stackName=psu-cdk-stateful \ + CDK_CONFIG_logRetentionInDays=30 \ + CDK_CONFIG_environment=dev \ + CDK_CONFIG_enableDynamoDBAutoScaling=false \ + CDK_CONFIG_enableBackup=false \ + npm run cdk-synth --workspace packages/cdk + cdk-diff: npm run cdk-diff --workspace packages/cdk diff --git a/packages/cdk/bin/PsuStatefulApp.ts b/packages/cdk/bin/PsuStatefulApp.ts new file mode 100644 index 0000000000..9c71de83f8 --- /dev/null +++ b/packages/cdk/bin/PsuStatefulApp.ts @@ -0,0 +1,45 @@ +import { + createApp, + getBooleanConfigFromEnvVar, + getConfigFromEnvVar, + getNumberConfigFromEnvVar +} from "@nhsdigital/eps-cdk-constructs" +import {PsuStatefulStack} from "../stacks/PsuStatefulStack" + +async function main() { + const {app, props} = createApp({ + productName: "Prescription Status Update API", + appName: "PsuStatefulApp", + repoName: "eps-prescription-status-update-api", + driftDetectionGroup: "psu-api" + }) + + // Stateful stacks use a stable (non-versioned) stack name so that the same + // CloudFormation stack is updated in-place on every deployment rather than + // creating a new stack per version. + new PsuStatefulStack(app, "PsuStatefulStack", { + ...props, + stackName: getConfigFromEnvVar("stackName"), + logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), + environment: getConfigFromEnvVar("environment"), + enableDynamoDBAutoScaling: getBooleanConfigFromEnvVar("enableDynamoDBAutoScaling", undefined, "false"), + enableBackup: getBooleanConfigFromEnvVar("enableBackup", undefined, "false"), + enabledSiteOdsCodes: getConfigFromEnvVar("enabledSiteOdsCodes", undefined, " "), + enabledSystems: getConfigFromEnvVar("enabledSystems", undefined, " "), + enabledSupplierApplicationIds: getConfigFromEnvVar("enabledSupplierApplicationIds", undefined, " "), + blockedSiteOdsCodes: getConfigFromEnvVar("blockedSiteOdsCodes", undefined, " "), + notifyRoutingPlanId: getConfigFromEnvVar("notifyRoutingPlanId", undefined, " "), + notifyApiBaseUrl: getConfigFromEnvVar("notifyApiBaseUrl", undefined, " "), + enableNotificationsExternal: getConfigFromEnvVar("enableNotificationsExternal", undefined, "false"), + enableNotificationsInternal: getConfigFromEnvVar("enableNotificationsInternal", undefined, "false"), + testPrescriptions1: getConfigFromEnvVar("testPrescriptions1", undefined, "PLACEHOLDER"), + testPrescriptions2: getConfigFromEnvVar("testPrescriptions2", undefined, "PLACEHOLDER"), + testPrescriptions3: getConfigFromEnvVar("testPrescriptions3", undefined, "PLACEHOLDER"), + testPrescriptions4: getConfigFromEnvVar("testPrescriptions4", undefined, "PLACEHOLDER") + }) +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/packages/cdk/resources/Apis.ts b/packages/cdk/resources/Apis.ts index bd77824ae2..c48b5ac1b9 100644 --- a/packages/cdk/resources/Apis.ts +++ b/packages/cdk/resources/Apis.ts @@ -110,7 +110,7 @@ export class Apis extends Construct { format1UpdatePrescriptionStatusEndpoint: format1PsuEndpoint, status: statusEndpoint, capabilityStatement: capabilityStatementEndpoint, - notificationDeliveryStatusCallback: notifyCallbackEndpoint, + notificationDeliveryStatusCallback: notifyCallbackEndpoint } // GET /checkprescriptionstatusupdates — conditional Lambda proxy integration diff --git a/packages/cdk/resources/Messaging.ts b/packages/cdk/resources/Messaging.ts new file mode 100644 index 0000000000..99e30cf42d --- /dev/null +++ b/packages/cdk/resources/Messaging.ts @@ -0,0 +1,255 @@ +import {CfnOutput, Duration, RemovalPolicy} from "aws-cdk-lib" +import { + ManagedPolicy, + PolicyStatement, + AccountRootPrincipal, + PolicyDocument +} from "aws-cdk-lib/aws-iam" +import {Key, Alias} from "aws-cdk-lib/aws-kms" +import {Queue} from "aws-cdk-lib/aws-sqs" +import {Construct} from "constructs" +import {NagSuppressions} from "cdk-nag" + +export interface MessagingProps { + readonly stackName: string +} + +export class Messaging extends Construct { + public constructor(scope: Construct, id: string, props: MessagingProps) { + super(scope, id) + + // KMS key used to encrypt all four SQS queues. + // Root-only key policy mirrors SAMtemplates/messaging/main.yaml. + const notificationSqsKmsKey = new Key(this, "NotificationSQSQueueKMSKey", { + enableKeyRotation: true, + removalPolicy: RemovalPolicy.RETAIN, + policy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + sid: "EnableIAMUserPermissions", + principals: [new AccountRootPrincipal()], + actions: ["kms:*"], + resources: ["*"] + }) + ] + }) + }) + + new Alias(this, "NotificationSQSQueueKMSKeyAlias", { + aliasName: `alias/${props.stackName}-NotificationSQSQueueKMSKey`, + targetKey: notificationSqsKmsKey + }) + + const useNotificationSqsKmsPolicy = new ManagedPolicy( + this, "UseNotificationSQSQueueKMSKeyPolicy", { + managedPolicyName: `${props.stackName}-UseNotificationSQSQueueKMSKey`, + statements: [ + new PolicyStatement({ + sid: "AllowKmsForSqsEncryption", + actions: [ + "kms:DescribeKey", + "kms:GenerateDataKey*", + "kms:Encrypt", + "kms:Decrypt" + ], + resources: [notificationSqsKmsKey.keyArn] + }) + ] + }) + NagSuppressions.addResourceSuppressions(useNotificationSqsKmsPolicy, [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard KMS GenerateDataKey action is required by SQS envelope encryption semantics." + } + ]) + + // ── NHS Notify prescriptions queue pair ────────────────────────────────── + + const nhsNotifyPrescriptionsDlq = new Queue( + this, "NHSNotifyPrescriptionsDeadLetterQueue", { + queueName: `${props.stackName}-NHSNotifyPrescriptionsDeadLetter.fifo`, + fifo: true, + contentBasedDeduplication: false, + enforceSSL: true, + encryptionMasterKey: notificationSqsKmsKey, + retentionPeriod: Duration.days(7), + visibilityTimeout: Duration.seconds(300) + }) + nhsNotifyPrescriptionsDlq.applyRemovalPolicy(RemovalPolicy.RETAIN) + + const nhsNotifyPrescriptionsQueue = new Queue( + this, "NHSNotifyPrescriptionsSQSQueue", { + queueName: `${props.stackName}-NHSNotifyPrescriptions.fifo`, + fifo: true, + contentBasedDeduplication: false, + enforceSSL: true, + encryptionMasterKey: notificationSqsKmsKey, + retentionPeriod: Duration.days(1), + visibilityTimeout: Duration.seconds(300), + deadLetterQueue: { + queue: nhsNotifyPrescriptionsDlq, + maxReceiveCount: 5 + } + }) + nhsNotifyPrescriptionsQueue.applyRemovalPolicy(RemovalPolicy.RETAIN) + + const readNhsNotifyPrescriptionsPolicy = new ManagedPolicy( + this, "ReadNHSNotifyPrescriptionsSQSQueuePolicy", { + statements: [ + new PolicyStatement({ + actions: [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "kms:GenerateDataKey", + "kms:Decrypt" + ], + resources: [nhsNotifyPrescriptionsQueue.queueArn] + }) + ] + }) + + const writeNhsNotifyPrescriptionsPolicy = new ManagedPolicy( + this, "WriteNHSNotifyPrescriptionsSQSQueuePolicy", { + managedPolicyName: `${props.stackName}-NHSNotifyPrescriptionsSendMessagePolicy`, + statements: [ + new PolicyStatement({ + actions: [ + "sqs:SendMessage", + "sqs:SendMessageBatch", + "sqs:DeleteMessage", + "sqs:GetQueueUrl", + "kms:GenerateDataKey", + "kms:Decrypt" + ], + resources: [nhsNotifyPrescriptionsQueue.queueArn] + }) + ] + }) + + // ── Post-dated notifications queue pair ────────────────────────────────── + + const postDatedNotificationsDlq = new Queue( + this, "PostDatedNotificationsDeadLetterQueue", { + queueName: `${props.stackName}-PostDatedNotificationsDeadLetter.fifo`, + fifo: true, + contentBasedDeduplication: false, + enforceSSL: true, + encryptionMasterKey: notificationSqsKmsKey, + retentionPeriod: Duration.days(7), + visibilityTimeout: Duration.seconds(300) + }) + postDatedNotificationsDlq.applyRemovalPolicy(RemovalPolicy.RETAIN) + + const postDatedNotificationsQueue = new Queue( + this, "PostDatedNotificationsSQSQueue", { + queueName: `${props.stackName}-PostDatedNotifications.fifo`, + fifo: true, + contentBasedDeduplication: false, + enforceSSL: true, + encryptionMasterKey: notificationSqsKmsKey, + retentionPeriod: Duration.days(1), + visibilityTimeout: Duration.seconds(300), + deadLetterQueue: { + queue: postDatedNotificationsDlq, + maxReceiveCount: 166 // ~999 hours / 6-hour delay steps + } + }) + postDatedNotificationsQueue.applyRemovalPolicy(RemovalPolicy.RETAIN) + + const readPostDatedNotificationsPolicy = new ManagedPolicy( + this, "ReadPostDatedNotificationsSQSQueuePolicy", { + statements: [ + new PolicyStatement({ + actions: [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "kms:GenerateDataKey", + "kms:Decrypt" + ], + resources: [postDatedNotificationsQueue.queueArn] + }) + ] + }) + + const writePostDatedNotificationsPolicy = new ManagedPolicy( + this, "WritePostDatedNotificationsSQSQueuePolicy", { + managedPolicyName: `${props.stackName}-PostDatedNotificationsSendMessagePolicy`, + statements: [ + new PolicyStatement({ + actions: [ + "sqs:SendMessage", + "sqs:SendMessageBatch", + "sqs:DeleteMessage", + "sqs:GetQueueUrl", + "kms:GenerateDataKey", + "kms:Decrypt" + ], + resources: [postDatedNotificationsQueue.queueArn] + }) + ] + }) + + // Exports — names must match exactly what Functions.ts imports via Fn.importValue + new CfnOutput(this, "NHSNotifyPrescriptionsSQSQueueUrlOutput", { + description: "The URL of the NHS Notify Prescriptions SQS Queue", + value: nhsNotifyPrescriptionsQueue.queueUrl, + exportName: `${props.stackName}-NHSNotifyPrescriptionsSQSQueueUrl` + }) + new CfnOutput(this, "NHSNotifyPrescriptionsSQSQueueArnOutput", { + description: "The ARN of the NHS Notify Prescriptions SQS Queue", + value: nhsNotifyPrescriptionsQueue.queueArn, + exportName: `${props.stackName}-NHSNotifyPrescriptionsSQSQueueArn` + }) + new CfnOutput(this, "NHSNotifyPrescriptionsDeadLetterQueueArnOutput", { + description: "The ARN of the NHS Notify Prescriptions Dead Letter Queue", + value: nhsNotifyPrescriptionsDlq.queueArn, + exportName: `${props.stackName}-NHSNotifyPrescriptionsDeadLetterQueueArn` + }) + new CfnOutput(this, "ReadNHSNotifyPrescriptionsSQSQueuePolicyArnOutput", { + description: "ARN of policy granting permission to read the prescriptions queue", + value: readNhsNotifyPrescriptionsPolicy.managedPolicyArn, + exportName: `${props.stackName}-ReadNHSNotifyPrescriptionsSQSQueuePolicyArn` + }) + new CfnOutput(this, "WriteNHSNotifyPrescriptionsSQSQueuePolicyArnOutput", { + description: "ARN of policy granting permission to write to the prescriptions queue", + value: writeNhsNotifyPrescriptionsPolicy.managedPolicyArn, + exportName: `${props.stackName}-WriteNHSNotifyPrescriptionsSQSQueuePolicyArn` + }) + new CfnOutput(this, "PostDatedNotificationsSQSQueueUrlOutput", { + description: "The URL of the Post-Dated Notifications SQS Queue", + value: postDatedNotificationsQueue.queueUrl, + exportName: `${props.stackName}-PostDatedNotificationsSQSQueueUrl` + }) + new CfnOutput(this, "PostDatedNotificationsSQSQueueArnOutput", { + description: "The ARN of the Post-Dated Notifications SQS Queue", + value: postDatedNotificationsQueue.queueArn, + exportName: `${props.stackName}-PostDatedNotificationsSQSQueueArn` + }) + new CfnOutput(this, "PostDatedNotificationsDeadLetterQueueArnOutput", { + description: "The ARN of the Post-Dated Notifications Dead Letter Queue", + value: postDatedNotificationsDlq.queueArn, + exportName: `${props.stackName}-PostDatedNotificationsDeadLetterQueueArn` + }) + new CfnOutput(this, "ReadPostDatedNotificationsSQSQueuePolicyArnOutput", { + description: "ARN of policy granting permission to read the post-dated queue", + value: readPostDatedNotificationsPolicy.managedPolicyArn, + exportName: `${props.stackName}-ReadPostDatedNotificationsSQSQueuePolicyArn` + }) + new CfnOutput(this, "WritePostDatedNotificationsSQSQueuePolicyArnOutput", { + description: "ARN of policy granting permission to write to the post-dated queue", + value: writePostDatedNotificationsPolicy.managedPolicyArn, + exportName: `${props.stackName}-WritePostDatedNotificationsSQSQueuePolicyArn` + }) + new CfnOutput(this, "UseNotificationSQSQueueKMSKeyPolicyArnOutput", { + description: "ARN of managed policy granting prescriptions queue KMS usage", + value: useNotificationSqsKmsPolicy.managedPolicyArn, + exportName: `${props.stackName}-UseNotificationSQSQueueKMSKeyPolicyArn` + }) + } +} diff --git a/packages/cdk/resources/Parameters.ts b/packages/cdk/resources/Parameters.ts new file mode 100644 index 0000000000..7d9440cee2 --- /dev/null +++ b/packages/cdk/resources/Parameters.ts @@ -0,0 +1,192 @@ +import {CfnOutput, RemovalPolicy, Stack} from "aws-cdk-lib" +import {ManagedPolicy, PolicyStatement} from "aws-cdk-lib/aws-iam" +import {StringParameter} from "aws-cdk-lib/aws-ssm" +import {Construct} from "constructs" + +export interface ParametersProps { + readonly stackName: string + readonly enabledSiteOdsCodes: string + readonly enabledSystems: string + readonly enabledSupplierApplicationIds: string + readonly blockedSiteOdsCodes: string + readonly notifyRoutingPlanId: string + readonly notifyApiBaseUrl: string + readonly enableNotificationsExternal: string + readonly enableNotificationsInternal: string + readonly testPrescriptions1: string + readonly testPrescriptions2: string + readonly testPrescriptions3: string + readonly testPrescriptions4: string +} + +export class Parameters extends Construct { + public constructor(scope: Construct, id: string, props: ParametersProps) { + super(scope, id) + + const {region, account} = Stack.of(this) + + const enabledSiteOdsCodes = new StringParameter(this, "EnabledSiteODSCodesParameter", { + parameterName: `${props.stackName}-PSUNotifyEnabledSiteODSCodes`, + description: "List of site ODS codes for which notifications are enabled", + stringValue: props.enabledSiteOdsCodes + }) + enabledSiteOdsCodes.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const enabledSystems = new StringParameter(this, "EnabledSystemsParameter", { + parameterName: `${props.stackName}-PSUNotifyEnabledSystems`, + description: "List of application names for which notifications are enabled", + stringValue: props.enabledSystems + }) + enabledSystems.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const enabledSupplierApplicationIds = new StringParameter( + this, "EnabledSupplierApplicationIDsParameter", { + parameterName: `${props.stackName}-PSUNotifyEnabledSupplierApplicationIDs`, + description: "List of supplier application IDs for which notifications are enabled", + stringValue: props.enabledSupplierApplicationIds + }) + enabledSupplierApplicationIds.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const blockedSiteOdsCodes = new StringParameter(this, "BlockedSiteODSCodesParameter", { + parameterName: `${props.stackName}-PSUNotifyBlockedSiteODSCodes`, + description: "List of site ODS codes for which notifications are blocked", + stringValue: props.blockedSiteOdsCodes + }) + blockedSiteOdsCodes.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const notifyRoutingPlanId = new StringParameter(this, "NotifyRoutingPlanIDParameter", { + parameterName: `${props.stackName}-PSUNotifyRoutingPlanID`, + description: "Notify Routing Plan ID", + stringValue: props.notifyRoutingPlanId + }) + notifyRoutingPlanId.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const notifyApiBaseUrl = new StringParameter(this, "NotifyAPIBaseURLParameter", { + parameterName: `${props.stackName}-PSUNotifyApiBaseUrl`, + description: "Notify API Base URL", + stringValue: props.notifyApiBaseUrl + }) + notifyApiBaseUrl.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const enableNotificationsExternal = new StringParameter(this, "EnableNotificationsExternal", { + parameterName: `${props.stackName}-EnableNotificationsExternal`, + description: "Toggle on or off if we make real requests to the NHS notify service", + stringValue: props.enableNotificationsExternal + }) + enableNotificationsExternal.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const enableNotificationsInternal = new StringParameter(this, "EnableNotificationsInternal", { + parameterName: `${props.stackName}-EnableNotificationsInternal`, + description: "Toggle the notifications integration entirely", + stringValue: props.enableNotificationsInternal + }) + enableNotificationsInternal.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const testPrescriptions1 = new StringParameter(this, "TestPrescriptionsParameter1", { + parameterName: `${props.stackName}-TEST_PRESCRIPTIONS_1`, + description: "Test prescriptions parameter 1", + stringValue: props.testPrescriptions1 + }) + testPrescriptions1.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const testPrescriptions2 = new StringParameter(this, "TestPrescriptionsParameter2", { + parameterName: `${props.stackName}-TEST_PRESCRIPTIONS_2`, + description: "Test prescriptions parameter 2", + stringValue: props.testPrescriptions2 + }) + testPrescriptions2.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const testPrescriptions3 = new StringParameter(this, "TestPrescriptionsParameter3", { + parameterName: `${props.stackName}-TEST_PRESCRIPTIONS_3`, + description: "Test prescriptions parameter 3", + stringValue: props.testPrescriptions3 + }) + testPrescriptions3.applyRemovalPolicy(RemovalPolicy.DESTROY) + + const testPrescriptions4 = new StringParameter(this, "TestPrescriptionsParameter4", { + parameterName: `${props.stackName}-TEST_PRESCRIPTIONS_4`, + description: "Test prescriptions parameter 4", + stringValue: props.testPrescriptions4 + }) + testPrescriptions4.applyRemovalPolicy(RemovalPolicy.DESTROY) + + // IAM policy granting ssm:GetParameter on all 12 parameters above + const getNotificationsParameterPolicy = new ManagedPolicy( + this, "GetNotificationsParameterPolicy", { + description: "Allows reading SSM parameters", + statements: [ + new PolicyStatement({ + actions: ["ssm:GetParameter", "ssm:GetParameters"], + resources: [ + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-PSUNotifyEnabledSiteODSCodes`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-PSUNotifyEnabledSystems`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-PSUNotifyEnabledSupplierApplicationIDs`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-PSUNotifyBlockedSiteODSCodes`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-PSUNotifyRoutingPlanID`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-PSUNotifyApiBaseUrl`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-EnableNotificationsExternal`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-EnableNotificationsInternal`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-TEST_PRESCRIPTIONS_1`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-TEST_PRESCRIPTIONS_2`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-TEST_PRESCRIPTIONS_3`, + `arn:aws:ssm:${region}:${account}:parameter/${props.stackName}-TEST_PRESCRIPTIONS_4` + ] + }) + ] + }) + + // Exports — names must match exactly what Functions.ts imports via Fn.importValue + new CfnOutput(this, "GetNotificationsParameterPolicyOutput", { + value: getNotificationsParameterPolicy.managedPolicyArn, + exportName: `${props.stackName}-GetNotificationsParameterPolicy` + }) + new CfnOutput(this, "EnabledSiteODSCodesParameterNameOutput", { + value: enabledSiteOdsCodes.parameterName, + exportName: `${props.stackName}-PSUNotifyEnabledSiteODSCodesParam` + }) + new CfnOutput(this, "EnabledSystemsParameterNameOutput", { + value: enabledSystems.parameterName, + exportName: `${props.stackName}-PSUNotifyEnabledSystemsParam` + }) + new CfnOutput(this, "EnabledSupplierApplicationIDsParameterNameOutput", { + value: enabledSupplierApplicationIds.parameterName, + exportName: `${props.stackName}-PSUNotifyEnabledSupplierApplicationIDsParam` + }) + new CfnOutput(this, "BlockedSiteODSCodesParameterNameOutput", { + value: blockedSiteOdsCodes.parameterName, + exportName: `${props.stackName}-PSUNotifyBlockedSiteODSCodesParam` + }) + new CfnOutput(this, "NotifyRoutingPlanIDParameterNameOutput", { + value: notifyRoutingPlanId.parameterName, + exportName: `${props.stackName}-PSUNotifyRoutingPlanIDParam` + }) + new CfnOutput(this, "NotifyAPIBaseURLParameterNameOutput", { + value: notifyApiBaseUrl.parameterName, + exportName: `${props.stackName}-PSUNotifyApiBaseUrlParam` + }) + new CfnOutput(this, "EnableNotificationsExternalNameOutput", { + value: enableNotificationsExternal.parameterName, + exportName: `${props.stackName}-EnableNotificationsExternalName` + }) + new CfnOutput(this, "EnableNotificationsInternalNameOutput", { + value: enableNotificationsInternal.parameterName, + exportName: `${props.stackName}-EnableNotificationsInternalName` + }) + new CfnOutput(this, "TestPrescriptionsParameterName1Output", { + value: testPrescriptions1.parameterName, + exportName: `${props.stackName}-TestPrescriptionsParameterName1` + }) + new CfnOutput(this, "TestPrescriptionsParameterName2Output", { + value: testPrescriptions2.parameterName, + exportName: `${props.stackName}-TestPrescriptionsParameterName2` + }) + new CfnOutput(this, "TestPrescriptionsParameterName3Output", { + value: testPrescriptions3.parameterName, + exportName: `${props.stackName}-TestPrescriptionsParameterName3` + }) + new CfnOutput(this, "TestPrescriptionsParameterName4Output", { + value: testPrescriptions4.parameterName, + exportName: `${props.stackName}-TestPrescriptionsParameterName4` + }) + } +} diff --git a/packages/cdk/resources/Secrets.ts b/packages/cdk/resources/Secrets.ts new file mode 100644 index 0000000000..82a07e7ae7 --- /dev/null +++ b/packages/cdk/resources/Secrets.ts @@ -0,0 +1,210 @@ +import {CfnOutput, RemovalPolicy, Stack} from "aws-cdk-lib" +import { + ManagedPolicy, + PolicyStatement, + AnyPrincipal, + AccountRootPrincipal +} from "aws-cdk-lib/aws-iam" +import {Key, Alias} from "aws-cdk-lib/aws-kms" +import {PolicyDocument} from "aws-cdk-lib/aws-iam" +import {Secret} from "aws-cdk-lib/aws-secretsmanager" +import {Construct} from "constructs" +import {NagSuppressions} from "cdk-nag" + +export interface SecretsProps { + readonly stackName: string +} + +export class Secrets extends Construct { + public constructor(scope: Construct, id: string, props: SecretsProps) { + super(scope, id) + + const {account, region} = Stack.of(this) + + // KMS key encrypting all 4 NHS Notify secrets. + // The SSO ReadOnly condition mirrors the key policy in SAMtemplates/secrets/main.yaml. + const psuSecretsKmsKey = new Key(this, "PSUSecretsKMSKey", { + enableKeyRotation: true, + removalPolicy: RemovalPolicy.RETAIN, + policy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + sid: "EnableIAMUserPermissions", + principals: [new AccountRootPrincipal()], + actions: ["kms:*"], + resources: ["*"] + }), + new PolicyStatement({ + sid: "Enable read only decrypt", + principals: [new AnyPrincipal()], + actions: ["kms:DescribeKey", "kms:Decrypt"], + resources: ["*"], + conditions: { + ArnLike: { + "aws:PrincipalArn": + `arn:aws:iam::${account}:role/aws-reserved/sso.amazonaws.com/${region}` + + "/AWSReservedSSO_ReadOnly*" + } + } + }) + ] + }) + }) + + new Alias(this, "PSUSecretsKMSKeyAlias", { + aliasName: `alias/${props.stackName}-PSUSecretsKMSKey`, + targetKey: psuSecretsKmsKey + }) + const usePsuSecretsKmsKeyPolicy = new ManagedPolicy(this, "UsePSUSecretsKMSKeyPolicy", { + managedPolicyName: `-UsePSUSecretsKMSKey`, + statements: [ + new PolicyStatement({ + sid: "AllowKmsForSecretsEncryption", + actions: [ + "kms:DescribeKey", + "kms:GenerateDataKey*", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:Decrypt" + ], + resources: [psuSecretsKmsKey.keyArn] + }) + ] + }) + NagSuppressions.addResourceSuppressions(usePsuSecretsKmsKeyPolicy, [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard KMS actions are required for Secrets Manager encryption and data key generation." + } + ]) + + // Salt used to derive SQS message group / deduplication IDs. + // No CMK — AWS-managed key intentional to avoid bootstrap ordering issues. + const sqsSaltSecret = new Secret(this, "SQSSaltSecret", { + secretName: `${props.stackName}-SqsSaltSecret`, + description: "Auto-generated salt for SQS_SALT", + generateSecretString: { + secretStringTemplate: "{}", + generateStringKey: "salt", + passwordLength: 32, + excludePunctuation: true + }, + removalPolicy: RemovalPolicy.RETAIN + }) + + const psuNotifyKidSecret = new Secret(this, "PSUNotifyKIDSecret", { + secretName: `${props.stackName}-PSU-Notify-KID`, + description: "The id of the key (KID) used to sign JWT to NHS Notify and sent in the header", + encryptionKey: psuSecretsKmsKey, + removalPolicy: RemovalPolicy.RETAIN + }) + + const psuNotifyPrivateKeySecret = new Secret(this, "PSUNotifyPrivateKeySecret", { + secretName: `${props.stackName}-PSU-Notify-PrivateKey`, + description: "RSA private key (PEM) for signing JWT to NHS Notify", + encryptionKey: psuSecretsKmsKey, + removalPolicy: RemovalPolicy.RETAIN + }) + + const psuNotifyApplicationIdSecret = new Secret(this, "PSUNotifyApplicationIDSecret", { + secretName: `${props.stackName}-PSU-Notify-Application-ID`, + description: "The application ID for the DoS application to use when sending notifications to NHS Notify", + encryptionKey: psuSecretsKmsKey, + removalPolicy: RemovalPolicy.RETAIN + }) + + const psuNotifyApiKeySecret = new Secret(this, "PSUNotifyAPIKeySecret", { + secretName: `${props.stackName}-PSU-Notify-API-Key`, + description: "API Key for NHS Notify", + encryptionKey: psuSecretsKmsKey, + removalPolicy: RemovalPolicy.RETAIN + }) + + NagSuppressions.addResourceSuppressions(sqsSaltSecret, [ + { + id: "AwsSolutions-SMG4", + reason: "SQS_SALT is a static application secret with no supported automatic rotator." + }, + { + id: "EpsNagPack-EPS16", + reason: "SQS_SALT intentionally uses AWS-managed encryption to avoid bootstrap dependency on the PSU CMK." + } + ]) + NagSuppressions.addResourceSuppressions( + [psuNotifyKidSecret, psuNotifyPrivateKeySecret, psuNotifyApplicationIdSecret, psuNotifyApiKeySecret], + [ + { + id: "AwsSolutions-SMG4", + reason: "NHS Notify secrets are externally managed with no AWS-supported automatic rotation workflow." + } + ] + ) + + const getSqsSaltSecretPolicy = new ManagedPolicy(this, "GetSQSSaltSecretPolicy", { + description: "Allows reading secret parameters", + statements: [ + new PolicyStatement({ + actions: ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"], + resources: [sqsSaltSecret.secretArn] + }) + ] + }) + + const getPsuSecretPolicy = new ManagedPolicy(this, "GetPSUSecretPolicy", { + description: "Allows reading PSU secret parameters", + statements: [ + new PolicyStatement({ + actions: ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"], + resources: [ + psuNotifyKidSecret.secretArn, + psuNotifyPrivateKeySecret.secretArn, + psuNotifyApplicationIdSecret.secretArn, + psuNotifyApiKeySecret.secretArn + ] + }) + ] + }) + + // Exports — names must match exactly what Functions.ts imports via Fn.importValue + new CfnOutput(this, "SQSSaltSecretOutput", { + description: "The name of the randomly generated SQS salt secret", + value: sqsSaltSecret.secretName, + exportName: `${props.stackName}-SQSSaltSecret` + }) + new CfnOutput(this, "PSUNotifyKIDSecretOutput", { + description: "The name of the PSU Notify KID secret", + value: psuNotifyKidSecret.secretName, + exportName: `${props.stackName}-PSU-Notify-KID` + }) + new CfnOutput(this, "PSUNotifyPrivateKeySecretOutput", { + description: "The name of the PSU Notify Private Key secret", + value: psuNotifyPrivateKeySecret.secretName, + exportName: `${props.stackName}-PSU-Notify-PrivateKey` + }) + new CfnOutput(this, "PSUNotifyApplicationIDSecretOutput", { + description: "The name of the PSU Notify Application ID secret", + value: psuNotifyApplicationIdSecret.secretName, + exportName: `${props.stackName}-PSU-Notify-Application-ID` + }) + new CfnOutput(this, "PSUNotifyAPIKeySecretOutput", { + description: "The name of the PSU Notify API Key secret", + value: psuNotifyApiKeySecret.secretName, + exportName: `${props.stackName}-PSU-Notify-API-Key` + }) + new CfnOutput(this, "GetPSUSecretPolicyOutput", { + description: "ARN of policy granting permission to read secrets", + value: getPsuSecretPolicy.managedPolicyArn, + exportName: `${props.stackName}-GetPSUSecretPolicy` + }) + new CfnOutput(this, "GetSQSSaltSecretPolicyOutput", { + description: "ARN of policy granting permission to read the SQS salt secret", + value: getSqsSaltSecretPolicy.managedPolicyArn, + exportName: `${props.stackName}-GetSQSSaltSecretPolicy` + }) + new CfnOutput(this, "UsePSUSecretsKMSKeyPolicyArnOutput", { + description: "ARN of managed policy granting PSU secrets KMS usage", + value: usePsuSecretsKmsKeyPolicy.managedPolicyArn, + exportName: `${props.stackName}-UsePSUSecretsKMSKeyPolicyArn` + }) + } +} diff --git a/packages/cdk/resources/Tables.ts b/packages/cdk/resources/Tables.ts new file mode 100644 index 0000000000..a69e1a16df --- /dev/null +++ b/packages/cdk/resources/Tables.ts @@ -0,0 +1,575 @@ +import {CfnOutput, RemovalPolicy, Stack} from "aws-cdk-lib" +import { + AttributeType, + BillingMode, + ProjectionType, + Table, + TableEncryption +} from "aws-cdk-lib/aws-dynamodb" +import { + ManagedPolicy, + PolicyStatement, + AnyPrincipal, + AccountRootPrincipal, + PolicyDocument +} from "aws-cdk-lib/aws-iam" +import {Key, Alias} from "aws-cdk-lib/aws-kms" +import {Duration} from "aws-cdk-lib" +import {Construct} from "constructs" +import {NagSuppressions} from "cdk-nag" + +// Autoscaling constants — mirrored from SAMtemplates/tables/main.yaml defaults +const MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY = 50 +const MAX_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY = 600 +const MIN_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY = 50 +const MAX_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY = 600 + +export interface TablesProps { + readonly stackName: string + readonly enableDynamoDBAutoScaling: boolean + /** When true, sets NHSE-Enable-Backup: True on PrescriptionStatusUpdatesTable */ + readonly enableBackup: boolean +} + +export class Tables extends Construct { + public constructor(scope: Construct, id: string, props: TablesProps) { + super(scope, id) + + const {account, region} = Stack.of(this) + + // ── Shared autoscaling IAM role ────────────────────────────────────────── + // Used by ApplicationAutoScaling when enableDynamoDBAutoScaling is true. + // Equivalent to DynamoDbScalingRole in SAMtemplates/tables/main.yaml. + // CDK manages this automatically via table.autoScaleWriteCapacity() but we + // reference the tables explicitly to keep the policy document paritymatched. + + // ── PrescriptionStatusUpdates table ───────────────────────────────────── + + const prescriptionStatusUpdatesKey = new Key( + this, "PrescriptionStatusUpdatesKMSKey", { + enableKeyRotation: true, + removalPolicy: RemovalPolicy.RETAIN, + policy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + sid: "Enable IAM User Permissions", + principals: [new AccountRootPrincipal()], + actions: ["kms:*"], + resources: ["*"] + }), + new PolicyStatement({ + sid: "Enable read only decrypt", + principals: [new AnyPrincipal()], + actions: ["kms:DescribeKey", "kms:Decrypt"], + resources: ["*"], + conditions: { + ArnLike: { + "aws:PrincipalArn": + `arn:aws:iam::${account}:role/aws-reserved/sso.amazonaws.com/${region}` + + "/AWSReservedSSO_ReadOnly*" + } + } + }) + ] + }) + }) + + new Alias(this, "PrescriptionStatusUpdatesKMSKeyAlias", { + aliasName: `alias/${props.stackName}-PrescriptionStatusUpdatesKMSKeyAlias`, + targetKey: prescriptionStatusUpdatesKey + }) + + const usePrescriptionStatusUpdatesKmsKeyPolicy = new ManagedPolicy( + this, "UsePrescriptionStatusUpdatesKMSKeyPolicy", { + statements: [ + new PolicyStatement({ + actions: [ + "kms:DescribeKey", + "kms:GenerateDataKey*", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:Decrypt" + ], + resources: [prescriptionStatusUpdatesKey.keyArn] + }) + ] + }) + NagSuppressions.addResourceSuppressions(usePrescriptionStatusUpdatesKmsKeyPolicy, [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard KMS actions are required for DynamoDB encryption/decryption flows." + } + ]) + + const prescriptionStatusUpdatesTableName = + `${props.stackName}-PrescriptionStatusUpdates` + + const prescriptionStatusUpdatesTableProps = { + tableName: prescriptionStatusUpdatesTableName, + partitionKey: {name: "PrescriptionID", type: AttributeType.STRING}, + sortKey: {name: "TaskID", type: AttributeType.STRING}, + pointInTimeRecovery: true, + encryption: TableEncryption.CUSTOMER_MANAGED, + encryptionKey: prescriptionStatusUpdatesKey, + timeToLiveAttribute: "ExpiryTime", + removalPolicy: RemovalPolicy.RETAIN, + billingMode: props.enableDynamoDBAutoScaling + ? BillingMode.PROVISIONED + : BillingMode.PAY_PER_REQUEST, + ...(props.enableDynamoDBAutoScaling + ? { + readCapacity: 1, + writeCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY + } + : {}) + } + + const prescriptionStatusUpdatesTable = new Table( + this, "PrescriptionStatusUpdatesTable", + prescriptionStatusUpdatesTableProps + ) + + prescriptionStatusUpdatesTable.addGlobalSecondaryIndex({ + indexName: "PharmacyODSCodePrescriptionIDIndex", + partitionKey: {name: "PharmacyODSCode", type: AttributeType.STRING}, + sortKey: {name: "PrescriptionID", type: AttributeType.STRING}, + projectionType: ProjectionType.INCLUDE, + nonKeyAttributes: [ + "PatientNHSNumber", "LineItemID", "TerminalStatus", "LastModified", "Status" + ], + ...(props.enableDynamoDBAutoScaling + ? { + readCapacity: 1, + writeCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY + } + : {}) + }) + + prescriptionStatusUpdatesTable.addGlobalSecondaryIndex({ + indexName: "PatientNHSNumberIndex", + partitionKey: {name: "PatientNHSNumber", type: AttributeType.STRING}, + projectionType: ProjectionType.KEYS_ONLY, + ...(props.enableDynamoDBAutoScaling + ? { + readCapacity: 1, + writeCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY + } + : {}) + }) + + // TODO: Remove this index when post-dated prescriptions are deprecated. + prescriptionStatusUpdatesTable.addGlobalSecondaryIndex({ + indexName: "PrescriptionIDPostDatedIndex", + partitionKey: {name: "PrescriptionID", type: AttributeType.STRING}, + projectionType: ProjectionType.INCLUDE, + nonKeyAttributes: [ + "PatientNHSNumber", "LineItemID", "TerminalStatus", "LastModified", + "Status", "PostDatedLastModifiedSetAt" + ], + ...(props.enableDynamoDBAutoScaling + ? { + readCapacity: 1, + writeCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY + } + : {}) + }) + + // TODO: Remove PostDatedLastModifiedSetAt attribute when post-dated prescriptions are deprecated. + prescriptionStatusUpdatesTable.addGlobalSecondaryIndex({ + indexName: "PharmacyODSCodePrescriptionIDIndexIncPostDated", + partitionKey: {name: "PharmacyODSCode", type: AttributeType.STRING}, + sortKey: {name: "PrescriptionID", type: AttributeType.STRING}, + projectionType: ProjectionType.INCLUDE, + nonKeyAttributes: [ + "PatientNHSNumber", "LineItemID", "TerminalStatus", "LastModified", + "Status", "PostDatedLastModifiedSetAt" + ], + ...(props.enableDynamoDBAutoScaling + ? { + readCapacity: 1, + writeCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY + } + : {}) + }) + + prescriptionStatusUpdatesTable.applyRemovalPolicy(RemovalPolicy.RETAIN) + + // Tags on the underlying CfnTable (CDK Tags API applies to all constructs) + if (prescriptionStatusUpdatesTable.node.defaultChild) { + (prescriptionStatusUpdatesTable.node.defaultChild as import("aws-cdk-lib/aws-dynamodb").CfnTable) + .addPropertyOverride("Tags", [ + {Key: "EPS-Tablename", Value: "PrescriptionStatusUpdates"}, + {Key: "NHSE-Enable-Backup", Value: props.enableBackup ? "True" : "False"} + ]) + } + + if (props.enableDynamoDBAutoScaling) { + const tableWriteScaling = prescriptionStatusUpdatesTable.autoScaleWriteCapacity({ + minCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY, + maxCapacity: MAX_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY + }) + tableWriteScaling.scaleOnUtilization({ + targetUtilizationPercent: 50, + scaleInCooldown: Duration.seconds(600), + scaleOutCooldown: Duration.seconds(0) + }) + + const tableReadScaling = prescriptionStatusUpdatesTable.autoScaleReadCapacity({ + minCapacity: 1, + maxCapacity: 100 + }) + tableReadScaling.scaleOnUtilization({ + targetUtilizationPercent: 70, + scaleInCooldown: Duration.seconds(60), + scaleOutCooldown: Duration.seconds(60) + }) + + const pharmacyIndexWriteScaling = + prescriptionStatusUpdatesTable.autoScaleGlobalSecondaryIndexWriteCapacity( + "PharmacyODSCodePrescriptionIDIndex", { + minCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY, + maxCapacity: MAX_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY + }) + pharmacyIndexWriteScaling.scaleOnUtilization({ + targetUtilizationPercent: 50, + scaleInCooldown: Duration.seconds(600), + scaleOutCooldown: Duration.seconds(0) + }) + + const pharmacyIndexReadScaling = + prescriptionStatusUpdatesTable.autoScaleGlobalSecondaryIndexReadCapacity( + "PharmacyODSCodePrescriptionIDIndex", { + minCapacity: 1, + maxCapacity: 100 + }) + pharmacyIndexReadScaling.scaleOnUtilization({ + targetUtilizationPercent: 70, + scaleInCooldown: Duration.seconds(60), + scaleOutCooldown: Duration.seconds(60) + }) + } + + const prescriptionStatusUpdatesReadPolicy = new ManagedPolicy( + this, "PrescriptionStatusUpdatesTableReadPolicy", { + statements: [ + new PolicyStatement({ + actions: [ + "dynamodb:GetItem", + "dynamodb:BatchGetItem", + "dynamodb:Scan", + "dynamodb:Query", + "dynamodb:ConditionCheckItem", + "dynamodb:DescribeTable" + ], + resources: [ + prescriptionStatusUpdatesTable.tableArn, + `${prescriptionStatusUpdatesTable.tableArn}/index/*` + ] + }) + ] + }) + NagSuppressions.addResourceSuppressions(prescriptionStatusUpdatesReadPolicy, [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard index ARN is required to permit Query/Scan across all GSIs." + } + ]) + + const prescriptionStatusUpdatesWritePolicy = new ManagedPolicy( + this, "PrescriptionStatusUpdatesTableWritePolicy", { + statements: [ + new PolicyStatement({ + actions: [ + "dynamodb:PutItem", + "dynamodb:BatchWriteItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + resources: [ + prescriptionStatusUpdatesTable.tableArn, + `${prescriptionStatusUpdatesTable.tableArn}/index/*` + ] + }) + ] + }) + NagSuppressions.addResourceSuppressions(prescriptionStatusUpdatesWritePolicy, [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard index ARN is required to permit write operations targeting GSIs." + } + ]) + + // ── PrescriptionNotificationStatesV1 table ─────────────────────────────── + + const prescriptionNotificationStatesKey = new Key( + this, "PrescriptionNotificationStatesKMSKey", { + enableKeyRotation: true, + removalPolicy: RemovalPolicy.RETAIN, + policy: new PolicyDocument({ + statements: [ + new PolicyStatement({ + sid: "Enable IAM User Permissions", + principals: [new AccountRootPrincipal()], + actions: ["kms:*"], + resources: ["*"] + }), + new PolicyStatement({ + sid: "Enable read only decrypt", + principals: [new AnyPrincipal()], + actions: ["kms:DescribeKey", "kms:Decrypt"], + resources: ["*"], + conditions: { + ArnLike: { + "aws:PrincipalArn": + `arn:aws:iam::${account}:role/aws-reserved/sso.amazonaws.com/${region}` + + "/AWSReservedSSO_ReadOnly*" + } + } + }) + ] + }) + }) + + new Alias(this, "PrescriptionNotificationStatesKMSKeyAlias", { + aliasName: `alias/${props.stackName}-PrescriptionNotificationStatesKMSKeyAlias`, + targetKey: prescriptionNotificationStatesKey + }) + + const usePrescriptionNotificationStatesKmsKeyPolicy = new ManagedPolicy( + this, "UsePrescriptionNotificationStatesKMSKeyPolicy", { + statements: [ + new PolicyStatement({ + actions: [ + "kms:DescribeKey", + "kms:GenerateDataKey*", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:Decrypt" + ], + resources: [prescriptionNotificationStatesKey.keyArn] + }) + ] + }) + NagSuppressions.addResourceSuppressions(usePrescriptionNotificationStatesKmsKeyPolicy, [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard KMS actions are required for DynamoDB encryption/decryption flows." + } + ]) + + const prescriptionNotificationStatesTableName = + `${props.stackName}-PrescriptionNotificationStatesV1` + + const prescriptionNotificationStatesTable = new Table( + this, "PrescriptionNotificationStatesTableV1", { + tableName: prescriptionNotificationStatesTableName, + partitionKey: {name: "NHSNumber", type: AttributeType.STRING}, + sortKey: {name: "RequestId", type: AttributeType.STRING}, + pointInTimeRecovery: true, + encryption: TableEncryption.CUSTOMER_MANAGED, + encryptionKey: prescriptionNotificationStatesKey, + timeToLiveAttribute: "ExpiryTime", + removalPolicy: RemovalPolicy.RETAIN, + billingMode: props.enableDynamoDBAutoScaling + ? BillingMode.PROVISIONED + : BillingMode.PAY_PER_REQUEST, + ...(props.enableDynamoDBAutoScaling + ? { + readCapacity: 1, + writeCapacity: MIN_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY + } + : {}) + }) + + prescriptionNotificationStatesTable.addGlobalSecondaryIndex({ + indexName: "PatientPharmacyIndex", + partitionKey: {name: "NHSNumber", type: AttributeType.STRING}, + sortKey: {name: "ODSCode", type: AttributeType.STRING}, + projectionType: ProjectionType.ALL, + ...(props.enableDynamoDBAutoScaling + ? { + readCapacity: 1, + writeCapacity: MIN_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY + } + : {}) + }) + + prescriptionNotificationStatesTable.addGlobalSecondaryIndex({ + indexName: "NotifyMessageIDIndex", + partitionKey: {name: "NotifyMessageID", type: AttributeType.STRING}, + projectionType: ProjectionType.ALL, + ...(props.enableDynamoDBAutoScaling + ? { + readCapacity: 1, + writeCapacity: MIN_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY + } + : {}) + }) + + prescriptionNotificationStatesTable.applyRemovalPolicy(RemovalPolicy.RETAIN) + + // NHSE-Enable-Backup is hardcoded False for the notification states table (matches SAM) + if (prescriptionNotificationStatesTable.node.defaultChild) { + (prescriptionNotificationStatesTable.node.defaultChild as import("aws-cdk-lib/aws-dynamodb").CfnTable) + .addPropertyOverride("Tags", [ + {Key: "NHSE-Enable-Backup", Value: "False"} + ]) + } + + if (props.enableDynamoDBAutoScaling) { + const notifStatesTableWriteScaling = + prescriptionNotificationStatesTable.autoScaleWriteCapacity({ + minCapacity: MIN_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY, + maxCapacity: MAX_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY + }) + notifStatesTableWriteScaling.scaleOnUtilization({ + targetUtilizationPercent: 50, + scaleInCooldown: Duration.seconds(600), + scaleOutCooldown: Duration.seconds(0) + }) + + const notifStatesTableReadScaling = + prescriptionNotificationStatesTable.autoScaleReadCapacity({ + minCapacity: 1, + maxCapacity: 100 + }) + notifStatesTableReadScaling.scaleOnUtilization({ + targetUtilizationPercent: 70, + scaleInCooldown: Duration.seconds(60), + scaleOutCooldown: Duration.seconds(60) + }) + + const patientPharmacyIndexWriteScaling = + prescriptionNotificationStatesTable.autoScaleGlobalSecondaryIndexWriteCapacity( + "PatientPharmacyIndex", { + minCapacity: MIN_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY, + maxCapacity: MAX_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY + }) + patientPharmacyIndexWriteScaling.scaleOnUtilization({ + targetUtilizationPercent: 50, + scaleInCooldown: Duration.seconds(600), + scaleOutCooldown: Duration.seconds(0) + }) + + const patientPharmacyIndexReadScaling = + prescriptionNotificationStatesTable.autoScaleGlobalSecondaryIndexReadCapacity( + "PatientPharmacyIndex", { + minCapacity: 1, + maxCapacity: 100 + }) + patientPharmacyIndexReadScaling.scaleOnUtilization({ + targetUtilizationPercent: 70, + scaleInCooldown: Duration.seconds(60), + scaleOutCooldown: Duration.seconds(60) + }) + + const notifyMessageIdIndexWriteScaling = + prescriptionNotificationStatesTable.autoScaleGlobalSecondaryIndexWriteCapacity( + "NotifyMessageIDIndex", { + minCapacity: MIN_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY, + maxCapacity: MAX_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY + }) + notifyMessageIdIndexWriteScaling.scaleOnUtilization({ + targetUtilizationPercent: 50, + scaleInCooldown: Duration.seconds(600), + scaleOutCooldown: Duration.seconds(0) + }) + + const notifyMessageIdIndexReadScaling = + prescriptionNotificationStatesTable.autoScaleGlobalSecondaryIndexReadCapacity( + "NotifyMessageIDIndex", { + minCapacity: 1, + maxCapacity: 100 + }) + notifyMessageIdIndexReadScaling.scaleOnUtilization({ + targetUtilizationPercent: 70, + scaleInCooldown: Duration.seconds(60), + scaleOutCooldown: Duration.seconds(60) + }) + } + + const prescriptionNotificationStatesReadPolicy = new ManagedPolicy( + this, "PrescriptionNotificationStatesTableReadPolicy", { + statements: [ + new PolicyStatement({ + actions: [ + "dynamodb:GetItem", + "dynamodb:BatchGetItem", + "dynamodb:Scan", + "dynamodb:Query", + "dynamodb:ConditionCheckItem", + "dynamodb:DescribeTable" + ], + resources: [ + prescriptionNotificationStatesTable.tableArn, + `${prescriptionNotificationStatesTable.tableArn}/index/*` + ] + }) + ] + }) + NagSuppressions.addResourceSuppressions(prescriptionNotificationStatesReadPolicy, [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard index ARN is required to permit Query/Scan across all GSIs." + } + ]) + + const prescriptionNotificationStatesWritePolicy = new ManagedPolicy( + this, "PrescriptionNotificationStatesTableWritePolicy", { + statements: [ + new PolicyStatement({ + actions: [ + "dynamodb:PutItem", + "dynamodb:BatchWriteItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + resources: [ + prescriptionNotificationStatesTable.tableArn, + `${prescriptionNotificationStatesTable.tableArn}/index/*` + ] + }) + ] + }) + NagSuppressions.addResourceSuppressions(prescriptionNotificationStatesWritePolicy, [ + { + id: "AwsSolutions-IAM5", + reason: "Wildcard index ARN is required to permit write operations targeting GSIs." + } + ]) + + // ── Exports ────────────────────────────────────────────────────────────── + // Names must match exactly what Functions.ts imports via Fn.importValue. + + new CfnOutput(this, "UsePrescriptionStatusUpdatesKMSKeyPolicyArnOutput", { + description: "Use KMS key policy ARN for PrescriptionStatusUpdates", + value: usePrescriptionStatusUpdatesKmsKeyPolicy.managedPolicyArn, + exportName: `${props.stackName}:tables:UsePrescriptionStatusUpdatesKMSKeyPolicyArn` + }) + new CfnOutput(this, "PrescriptionStatusUpdatesTableReadPolicyArnOutput", { + description: "Table read policy ARN for PrescriptionStatusUpdates", + value: prescriptionStatusUpdatesReadPolicy.managedPolicyArn, + exportName: `${props.stackName}:tables:${prescriptionStatusUpdatesTableName}:TableReadPolicyArn` + }) + new CfnOutput(this, "PrescriptionStatusUpdatesTableWritePolicyArnOutput", { + description: "Table write policy ARN for PrescriptionStatusUpdates", + value: prescriptionStatusUpdatesWritePolicy.managedPolicyArn, + exportName: `${props.stackName}:tables:${prescriptionStatusUpdatesTableName}:TableWritePolicyArn` + }) + new CfnOutput(this, "UsePrescriptionNotificationStatesKMSKeyPolicyArnOutput", { + description: "Use KMS key policy ARN for PrescriptionNotificationStates", + value: usePrescriptionNotificationStatesKmsKeyPolicy.managedPolicyArn, + exportName: `${props.stackName}:tables:UsePrescriptionNotificationStatesKMSKeyPolicyArn` + }) + new CfnOutput(this, "PrescriptionNotificationStatesTableReadPolicyArnOutput", { + description: "Table read policy ARN for PrescriptionNotificationStatesV1", + value: prescriptionNotificationStatesReadPolicy.managedPolicyArn, + exportName: `${props.stackName}:tables:${prescriptionNotificationStatesTableName}:TableReadPolicyArn` + }) + new CfnOutput(this, "PrescriptionNotificationStatesTableWritePolicyArnOutput", { + description: "Table write policy ARN for PrescriptionNotificationStatesV1", + value: prescriptionNotificationStatesWritePolicy.managedPolicyArn, + exportName: `${props.stackName}:tables:${prescriptionNotificationStatesTableName}:TableWritePolicyArn` + }) + } +} diff --git a/packages/cdk/stacks/PsuStatefulStack.ts b/packages/cdk/stacks/PsuStatefulStack.ts new file mode 100644 index 0000000000..2dfe0b8905 --- /dev/null +++ b/packages/cdk/stacks/PsuStatefulStack.ts @@ -0,0 +1,63 @@ +import {App, Stack} from "aws-cdk-lib" +import {StandardStackProps} from "@nhsdigital/eps-cdk-constructs" +import {Parameters} from "../resources/Parameters" +import {Tables} from "../resources/Tables" +import {Secrets} from "../resources/Secrets" +import {Messaging} from "../resources/Messaging" + +export interface PsuStatefulStackProps extends StandardStackProps { + readonly stackName: string + readonly logRetentionInDays: number + readonly environment: string + readonly enableDynamoDBAutoScaling: boolean + readonly enableBackup: boolean + // SSM Parameter values — deployed as configuration alongside infrastructure + readonly enabledSiteOdsCodes: string + readonly enabledSystems: string + readonly enabledSupplierApplicationIds: string + readonly blockedSiteOdsCodes: string + readonly notifyRoutingPlanId: string + readonly notifyApiBaseUrl: string + readonly enableNotificationsExternal: string + readonly enableNotificationsInternal: string + readonly testPrescriptions1: string + readonly testPrescriptions2: string + readonly testPrescriptions3: string + readonly testPrescriptions4: string +} + +export class PsuStatefulStack extends Stack { + public constructor(scope: App, id: string, props: PsuStatefulStackProps) { + super(scope, id, props) + + new Parameters(this, "Parameters", { + stackName: props.stackName, + enabledSiteOdsCodes: props.enabledSiteOdsCodes, + enabledSystems: props.enabledSystems, + enabledSupplierApplicationIds: props.enabledSupplierApplicationIds, + blockedSiteOdsCodes: props.blockedSiteOdsCodes, + notifyRoutingPlanId: props.notifyRoutingPlanId, + notifyApiBaseUrl: props.notifyApiBaseUrl, + enableNotificationsExternal: props.enableNotificationsExternal, + enableNotificationsInternal: props.enableNotificationsInternal, + testPrescriptions1: props.testPrescriptions1, + testPrescriptions2: props.testPrescriptions2, + testPrescriptions3: props.testPrescriptions3, + testPrescriptions4: props.testPrescriptions4 + }) + + new Tables(this, "Tables", { + stackName: props.stackName, + enableDynamoDBAutoScaling: props.enableDynamoDBAutoScaling, + enableBackup: props.enableBackup + }) + + new Secrets(this, "Secrets", { + stackName: props.stackName + }) + + new Messaging(this, "Messaging", { + stackName: props.stackName + }) + } +} From 0c6502534f5ba5caf476ae479d3511879119e639 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 16 Apr 2026 16:45:04 +0000 Subject: [PATCH 13/31] refactor(cdk): unify stateless and stateful in PsuApiApp --- .../languages/cdk.instructions.md | 1 + .github/workflows/cdk_release_code.yml | 140 +++++++++++--- .../workflows/cdk_stateful_release_code.yml | 181 ------------------ .github/workflows/ci.yml | 2 +- .github/workflows/pull_request.yml | 6 +- .github/workflows/release.yml | 22 ++- Makefile | 9 +- .../bin/{PsuStatefulApp.ts => PsuApiApp.ts} | 38 +++- packages/cdk/bin/PsuStatelessApp.ts | 39 ---- 9 files changed, 177 insertions(+), 261 deletions(-) delete mode 100644 .github/workflows/cdk_stateful_release_code.yml rename packages/cdk/bin/{PsuStatefulApp.ts => PsuApiApp.ts} (55%) delete mode 100644 packages/cdk/bin/PsuStatelessApp.ts diff --git a/.github/instructions/languages/cdk.instructions.md b/.github/instructions/languages/cdk.instructions.md index b441a02fa0..150d01e5fd 100644 --- a/.github/instructions/languages/cdk.instructions.md +++ b/.github/instructions/languages/cdk.instructions.md @@ -33,6 +33,7 @@ This file provides instructions for generating, reviewing, and maintaining AWS C - Variables: camelCase - Stacks: Suffix with `Stack` (e.g., `CptsApiAppStack`) - Entry points: Suffix with `App` (e.g., `CptsApiApp.ts`) +- CDK app entry points must follow `[Sandbox]App` naming (e.g., `PsuApiApp`, `PsuApiSandboxApp`) ### File Organization diff --git a/.github/workflows/cdk_release_code.yml b/.github/workflows/cdk_release_code.yml index ccd24991db..967c8249b5 100644 --- a/.github/workflows/cdk_release_code.yml +++ b/.github/workflows/cdk_release_code.yml @@ -18,41 +18,107 @@ on: COMMIT_ID: required: true type: string - CDK_APP_NAME: - required: true + STACK_MODE: + required: false type: string + default: "stateless" LOG_RETENTION_IN_DAYS: required: true type: string LOG_LEVEL: type: string + required: false + default: "DEBUG" IS_PULL_REQUEST: type: boolean required: true TRUSTSTORE_FILE: type: string - required: true + required: false + default: "" FORWARD_CSOC_LOGS: type: boolean - required: true + required: false + default: false DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: type: boolean - required: true + required: false + default: false EXPOSE_GET_STATUS_UPDATES: type: boolean - required: true + required: false + default: false SAM_STACK_NAME: type: string - required: true + required: false + default: "" ENABLE_POST_DATED_NOTIFICATIONS: type: boolean - required: true + required: false + default: false REQUIRE_APPLICATION_NAME: type: boolean - required: true + required: false + default: false ENABLE_BACKUP: type: boolean required: true + ENABLE_DYNAMODB_AUTO_SCALING: + type: boolean + required: false + default: false + ENABLED_SITE_ODS_CODES: + type: string + required: false + default: " " + ENABLED_SYSTEMS: + type: string + required: false + default: " " + ENABLED_SUPPLIER_APPLICATION_IDS: + type: string + required: false + default: " " + BLOCKED_SITE_ODS_CODES: + type: string + required: false + default: " " + NOTIFY_ROUTING_PLAN_ID: + type: string + required: false + default: " " + NOTIFY_API_BASE_URL: + type: string + required: false + default: " " + ENABLE_NOTIFICATIONS_EXTERNAL: + type: boolean + required: false + default: false + ENABLE_NOTIFICATIONS_INTERNAL: + type: boolean + required: false + default: false + TEST_PRESCRIPTIONS_1: + type: string + required: false + default: "PLACEHOLDER" + TEST_PRESCRIPTIONS_2: + type: string + required: false + default: "PLACEHOLDER" + TEST_PRESCRIPTIONS_3: + type: string + required: false + default: "PLACEHOLDER" + TEST_PRESCRIPTIONS_4: + type: string + required: false + default: "PLACEHOLDER" + RUN_DIFF: + type: boolean + required: false + default: false pinned_image: required: true type: string @@ -71,7 +137,39 @@ jobs: defaults: run: shell: bash - name: deploy cdk app ${{ inputs.CDK_APP_NAME }} + name: deploy cdk app ${{ inputs.STACK_MODE }} + env: + CDK_APP_NAME: "PsuApiApp" + CDK_CONFIG_stackMode: "${{ inputs.STACK_MODE }}" + CDK_CONFIG_stackName: "${{ inputs.STACK_NAME }}" + CDK_CONFIG_versionNumber: "${{ inputs.VERSION_NUMBER }}" + CDK_CONFIG_commitId: "${{ inputs.COMMIT_ID }}" + CDK_CONFIG_isPullRequest: "${{ inputs.IS_PULL_REQUEST }}" + CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}" + CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" + CDK_CONFIG_logLevel: "${{ inputs.LOG_LEVEL }}" + CDK_CONFIG_trustStoreFile: "${{ inputs.TRUSTSTORE_FILE }}" + CDK_CONFIG_forwardCsocLogs: "${{ inputs.FORWARD_CSOC_LOGS }}" + CDK_CONFIG_deployCheckPrescriptionStatusUpdate: "${{ inputs.DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE }}" + CDK_CONFIG_exposeGetStatusUpdates: "${{ inputs.EXPOSE_GET_STATUS_UPDATES }}" + CDK_CONFIG_samStackName: "${{ inputs.SAM_STACK_NAME }}" + CDK_CONFIG_enablePostDatedNotifications: "${{ inputs.ENABLE_POST_DATED_NOTIFICATIONS }}" + CDK_CONFIG_requireApplicationName: "${{ inputs.REQUIRE_APPLICATION_NAME }}" + CDK_CONFIG_enableBackup: "${{ inputs.ENABLE_BACKUP }}" + CDK_CONFIG_enableDynamoDBAutoScaling: "${{ inputs.ENABLE_DYNAMODB_AUTO_SCALING }}" + CDK_CONFIG_enabledSiteOdsCodes: "${{ inputs.ENABLED_SITE_ODS_CODES }}" + CDK_CONFIG_enabledSystems: "${{ inputs.ENABLED_SYSTEMS }}" + CDK_CONFIG_enabledSupplierApplicationIds: "${{ inputs.ENABLED_SUPPLIER_APPLICATION_IDS }}" + CDK_CONFIG_blockedSiteOdsCodes: "${{ inputs.BLOCKED_SITE_ODS_CODES }}" + CDK_CONFIG_notifyRoutingPlanId: "${{ inputs.NOTIFY_ROUTING_PLAN_ID }}" + CDK_CONFIG_notifyApiBaseUrl: "${{ inputs.NOTIFY_API_BASE_URL }}" + CDK_CONFIG_enableNotificationsExternal: "${{ inputs.ENABLE_NOTIFICATIONS_EXTERNAL }}" + CDK_CONFIG_enableNotificationsInternal: "${{ inputs.ENABLE_NOTIFICATIONS_INTERNAL }}" + CDK_CONFIG_testPrescriptions1: "${{ inputs.TEST_PRESCRIPTIONS_1 }}" + CDK_CONFIG_testPrescriptions2: "${{ inputs.TEST_PRESCRIPTIONS_2 }}" + CDK_CONFIG_testPrescriptions3: "${{ inputs.TEST_PRESCRIPTIONS_3 }}" + CDK_CONFIG_testPrescriptions4: "${{ inputs.TEST_PRESCRIPTIONS_4 }}" + REQUIRE_APPROVAL: "never" permissions: id-token: write contents: read @@ -103,26 +201,12 @@ jobs: role-to-assume: ${{ secrets.CLOUD_FORMATION_DEPLOY_ROLE }} role-session-name: psu-deployment + - name: Show diff + if: ${{ inputs.RUN_DIFF }} + run: npm run cdk-diff --workspace packages/cdk + - name: Deploy AWS infrastructure and code run: npm run cdk-deploy --workspace packages/cdk shell: bash - env: - CDK_APP_NAME: "${{ inputs.CDK_APP_NAME }}" - CDK_CONFIG_stackName: "${{ inputs.STACK_NAME }}" - CDK_CONFIG_versionNumber: "${{ inputs.VERSION_NUMBER }}" - CDK_CONFIG_commitId: "${{ inputs.COMMIT_ID }}" - CDK_CONFIG_isPullRequest: "${{ inputs.IS_PULL_REQUEST }}" - CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}" - CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" - CDK_CONFIG_logLevel: "${{ inputs.LOG_LEVEL }}" - CDK_CONFIG_trustStoreFile: "${{ inputs.TRUSTSTORE_FILE }}" - CDK_CONFIG_forwardCsocLogs: "${{ inputs.FORWARD_CSOC_LOGS }}" - CDK_CONFIG_deployCheckPrescriptionStatusUpdate: "${{ inputs.DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE }}" - CDK_CONFIG_exposeGetStatusUpdates: "${{ inputs.EXPOSE_GET_STATUS_UPDATES }}" - CDK_CONFIG_samStackName: "${{ inputs.SAM_STACK_NAME }}" - CDK_CONFIG_enablePostDatedNotifications: "${{ inputs.ENABLE_POST_DATED_NOTIFICATIONS }}" - CDK_CONFIG_requireApplicationName: "${{ inputs.REQUIRE_APPLICATION_NAME }}" - CDK_CONFIG_enableBackup: "${{ inputs.ENABLE_BACKUP }}" - REQUIRE_APPROVAL: "never" # later, there will be API deployment steps c.f. https://github.com/NHSDigital/electronic-prescription-service-clinical-prescription-tracker/blob/main/.github/workflows/cdk_release_code.yml diff --git a/.github/workflows/cdk_stateful_release_code.yml b/.github/workflows/cdk_stateful_release_code.yml deleted file mode 100644 index 81ce42c22f..0000000000 --- a/.github/workflows/cdk_stateful_release_code.yml +++ /dev/null @@ -1,181 +0,0 @@ -name: cdk stateful release code - -on: - workflow_call: - inputs: - BRANCH_NAME: - required: true - type: string - STACK_NAME: - required: true - type: string - AWS_ENVIRONMENT: - required: true - type: string - VERSION_NUMBER: - required: true - type: string - COMMIT_ID: - required: true - type: string - LOG_RETENTION_IN_DAYS: - required: true - type: string - IS_PULL_REQUEST: - type: boolean - required: true - ENABLE_DYNAMODB_AUTO_SCALING: - type: boolean - required: true - ENABLE_BACKUP: - type: boolean - required: true - ENABLED_SITE_ODS_CODES: - type: string - required: false - default: " " - ENABLED_SYSTEMS: - type: string - required: false - default: " " - ENABLED_SUPPLIER_APPLICATION_IDS: - type: string - required: false - default: " " - BLOCKED_SITE_ODS_CODES: - type: string - required: false - default: " " - NOTIFY_ROUTING_PLAN_ID: - type: string - required: false - default: " " - NOTIFY_API_BASE_URL: - type: string - required: false - default: " " - ENABLE_NOTIFICATIONS_EXTERNAL: - type: boolean - required: false - default: false - ENABLE_NOTIFICATIONS_INTERNAL: - type: boolean - required: false - default: false - TEST_PRESCRIPTIONS_1: - type: string - required: false - default: "PLACEHOLDER" - TEST_PRESCRIPTIONS_2: - type: string - required: false - default: "PLACEHOLDER" - TEST_PRESCRIPTIONS_3: - type: string - required: false - default: "PLACEHOLDER" - TEST_PRESCRIPTIONS_4: - type: string - required: false - default: "PLACEHOLDER" - pinned_image: - required: true - type: string - secrets: - CLOUD_FORMATION_DEPLOY_ROLE: - required: true -permissions: {} - -jobs: - release_code: - runs-on: ubuntu-22.04 - environment: ${{ inputs.AWS_ENVIRONMENT }} - container: - image: ${{ inputs.pinned_image }} - options: --user 1001:1001 --group-add 128 - defaults: - run: - shell: bash - name: deploy cdk stateful app - permissions: - id-token: write - contents: read - - steps: - - name: copy .tool-versions - run: | - cp /home/vscode/.tool-versions "$HOME/.tool-versions" - - - name: build_artifact download - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c - with: - name: build_artifact - - - name: extract build_artifact - run: tar -xf artifact.tar - - - name: install dependencies - uses: ./.github/actions/install_dependencies - with: - npm-required: false - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure AWS Credentials - id: connect-aws-deploy - uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 - with: - aws-region: eu-west-2 - role-to-assume: ${{ secrets.CLOUD_FORMATION_DEPLOY_ROLE }} - role-session-name: psu-stateful-deployment - - - name: Show diff - env: - CDK_APP_NAME: "PsuStatefulApp" - CDK_CONFIG_stackName: "${{ inputs.STACK_NAME }}" - CDK_CONFIG_versionNumber: "${{ inputs.VERSION_NUMBER }}" - CDK_CONFIG_commitId: "${{ inputs.COMMIT_ID }}" - CDK_CONFIG_isPullRequest: "${{ inputs.IS_PULL_REQUEST }}" - CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}" - CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" - CDK_CONFIG_enableDynamoDBAutoScaling: "${{ inputs.ENABLE_DYNAMODB_AUTO_SCALING }}" - CDK_CONFIG_enableBackup: "${{ inputs.ENABLE_BACKUP }}" - CDK_CONFIG_enabledSiteOdsCodes: "${{ inputs.ENABLED_SITE_ODS_CODES }}" - CDK_CONFIG_enabledSystems: "${{ inputs.ENABLED_SYSTEMS }}" - CDK_CONFIG_enabledSupplierApplicationIds: "${{ inputs.ENABLED_SUPPLIER_APPLICATION_IDS }}" - CDK_CONFIG_blockedSiteOdsCodes: "${{ inputs.BLOCKED_SITE_ODS_CODES }}" - CDK_CONFIG_notifyRoutingPlanId: "${{ inputs.NOTIFY_ROUTING_PLAN_ID }}" - CDK_CONFIG_notifyApiBaseUrl: "${{ inputs.NOTIFY_API_BASE_URL }}" - CDK_CONFIG_enableNotificationsExternal: "${{ inputs.ENABLE_NOTIFICATIONS_EXTERNAL }}" - CDK_CONFIG_enableNotificationsInternal: "${{ inputs.ENABLE_NOTIFICATIONS_INTERNAL }}" - CDK_CONFIG_testPrescriptions1: "${{ inputs.TEST_PRESCRIPTIONS_1 }}" - CDK_CONFIG_testPrescriptions2: "${{ inputs.TEST_PRESCRIPTIONS_2 }}" - CDK_CONFIG_testPrescriptions3: "${{ inputs.TEST_PRESCRIPTIONS_3 }}" - CDK_CONFIG_testPrescriptions4: "${{ inputs.TEST_PRESCRIPTIONS_4 }}" - run: npm run cdk-diff --workspace packages/cdk - - - name: Deploy AWS stateful infrastructure - run: npm run cdk-deploy --workspace packages/cdk - shell: bash - env: - CDK_APP_NAME: "PsuStatefulApp" - CDK_CONFIG_stackName: "${{ inputs.STACK_NAME }}" - CDK_CONFIG_versionNumber: "${{ inputs.VERSION_NUMBER }}" - CDK_CONFIG_commitId: "${{ inputs.COMMIT_ID }}" - CDK_CONFIG_isPullRequest: "${{ inputs.IS_PULL_REQUEST }}" - CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}" - CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" - CDK_CONFIG_enableDynamoDBAutoScaling: "${{ inputs.ENABLE_DYNAMODB_AUTO_SCALING }}" - CDK_CONFIG_enableBackup: "${{ inputs.ENABLE_BACKUP }}" - CDK_CONFIG_enabledSiteOdsCodes: "${{ inputs.ENABLED_SITE_ODS_CODES }}" - CDK_CONFIG_enabledSystems: "${{ inputs.ENABLED_SYSTEMS }}" - CDK_CONFIG_enabledSupplierApplicationIds: "${{ inputs.ENABLED_SUPPLIER_APPLICATION_IDS }}" - CDK_CONFIG_blockedSiteOdsCodes: "${{ inputs.BLOCKED_SITE_ODS_CODES }}" - CDK_CONFIG_notifyRoutingPlanId: "${{ inputs.NOTIFY_ROUTING_PLAN_ID }}" - CDK_CONFIG_notifyApiBaseUrl: "${{ inputs.NOTIFY_API_BASE_URL }}" - CDK_CONFIG_enableNotificationsExternal: "${{ inputs.ENABLE_NOTIFICATIONS_EXTERNAL }}" - CDK_CONFIG_enableNotificationsInternal: "${{ inputs.ENABLE_NOTIFICATIONS_INTERNAL }}" - CDK_CONFIG_testPrescriptions1: "${{ inputs.TEST_PRESCRIPTIONS_1 }}" - CDK_CONFIG_testPrescriptions2: "${{ inputs.TEST_PRESCRIPTIONS_2 }}" - CDK_CONFIG_testPrescriptions3: "${{ inputs.TEST_PRESCRIPTIONS_3 }}" - CDK_CONFIG_testPrescriptions4: "${{ inputs.TEST_PRESCRIPTIONS_4 }}" - REQUIRE_APPROVAL: "never" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7528f64153..8c360e1c0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: AWS_ENVIRONMENT: dev VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - CDK_APP_NAME: PsuStatelessApp + STACK_MODE: stateless LOG_RETENTION_IN_DAYS: "30" LOG_LEVEL: DEBUG IS_PULL_REQUEST: false diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 69ee0eb89b..381f279417 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -120,7 +120,7 @@ jobs: AWS_ENVIRONMENT: dev VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - CDK_APP_NAME: PsuStatelessApp + STACK_MODE: stateless LOG_RETENTION_IN_DAYS: "30" LOG_LEVEL: DEBUG IS_PULL_REQUEST: true @@ -138,7 +138,7 @@ jobs: cdk_stateful_release_code: needs: [get_issue_number, cdk_package_code, get_commit_id, get_config_values] - uses: ./.github/workflows/cdk_stateful_release_code.yml + uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write id-token: write @@ -149,6 +149,8 @@ jobs: AWS_ENVIRONMENT: dev VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + STACK_MODE: stateful + RUN_DIFF: true LOG_RETENTION_IN_DAYS: "30" IS_PULL_REQUEST: true ENABLE_DYNAMODB_AUTO_SCALING: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84d706c218..e3cd6e5421 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,7 +79,7 @@ jobs: AWS_ENVIRONMENT: dev VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - CDK_APP_NAME: PsuStatelessApp + STACK_MODE: stateless LOG_RETENTION_IN_DAYS: "30" LOG_LEVEL: DEBUG IS_PULL_REQUEST: false @@ -96,7 +96,7 @@ jobs: cdk_stateful_release_dev: needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] - uses: ./.github/workflows/cdk_stateful_release_code.yml + uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write id-token: write @@ -107,6 +107,8 @@ jobs: AWS_ENVIRONMENT: dev VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + STACK_MODE: stateful + RUN_DIFF: true LOG_RETENTION_IN_DAYS: "30" IS_PULL_REQUEST: false ENABLE_DYNAMODB_AUTO_SCALING: false @@ -300,7 +302,7 @@ jobs: tag_release, get_config_values, ] - uses: ./.github/workflows/cdk_stateful_release_code.yml + uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write id-token: write @@ -311,6 +313,8 @@ jobs: AWS_ENVIRONMENT: ref VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + STACK_MODE: stateful + RUN_DIFF: true LOG_RETENTION_IN_DAYS: "30" IS_PULL_REQUEST: false ENABLE_DYNAMODB_AUTO_SCALING: true @@ -395,7 +399,7 @@ jobs: tag_release, get_config_values, ] - uses: ./.github/workflows/cdk_stateful_release_code.yml + uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write id-token: write @@ -406,6 +410,8 @@ jobs: AWS_ENVIRONMENT: qa VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + STACK_MODE: stateful + RUN_DIFF: true LOG_RETENTION_IN_DAYS: "30" IS_PULL_REQUEST: false ENABLE_DYNAMODB_AUTO_SCALING: false @@ -493,7 +499,7 @@ jobs: tag_release, get_config_values, ] - uses: ./.github/workflows/cdk_stateful_release_code.yml + uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write id-token: write @@ -504,6 +510,8 @@ jobs: AWS_ENVIRONMENT: int VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + STACK_MODE: stateful + RUN_DIFF: true LOG_RETENTION_IN_DAYS: "30" IS_PULL_REQUEST: false ENABLE_DYNAMODB_AUTO_SCALING: false @@ -641,7 +649,7 @@ jobs: tag_release, get_config_values, ] - uses: ./.github/workflows/cdk_stateful_release_code.yml + uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write id-token: write @@ -652,6 +660,8 @@ jobs: AWS_ENVIRONMENT: prod VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} + STACK_MODE: stateful + RUN_DIFF: true LOG_RETENTION_IN_DAYS: "731" IS_PULL_REQUEST: false ENABLE_DYNAMODB_AUTO_SCALING: true diff --git a/Makefile b/Makefile index ff7017f07f..753128c4dd 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ SHELL = /bin/bash .SHELLFLAGS = -o pipefail -c -export CDK_APP_NAME=PsuStatelessApp +export CDK_APP_NAME=PsuApiApp +export CDK_CONFIG_stackMode=stateless export CDK_CONFIG_stackName=${stack_name} export CDK_CONFIG_samStackName=${stack_name} export CDK_CONFIG_versionNumber=undefined @@ -257,7 +258,8 @@ cdk-deploy: npm run cdk-deploy --workspace packages/cdk cdk-synth: - CDK_APP_NAME=PsuStatelessApp \ + CDK_APP_NAME=PsuApiApp \ + CDK_CONFIG_stackMode=stateless \ CDK_CONFIG_stackName=psu-cdk \ CDK_CONFIG_samStackName=psu \ CDK_CONFIG_logRetentionInDays=30 \ @@ -272,7 +274,8 @@ cdk-synth: npm run cdk-synth --workspace packages/cdk cdk-stateful-synth: - CDK_APP_NAME=PsuStatefulApp \ + CDK_APP_NAME=PsuApiApp \ + CDK_CONFIG_stackMode=stateful \ CDK_CONFIG_stackName=psu-cdk-stateful \ CDK_CONFIG_logRetentionInDays=30 \ CDK_CONFIG_environment=dev \ diff --git a/packages/cdk/bin/PsuStatefulApp.ts b/packages/cdk/bin/PsuApiApp.ts similarity index 55% rename from packages/cdk/bin/PsuStatefulApp.ts rename to packages/cdk/bin/PsuApiApp.ts index 9c71de83f8..ef274f5886 100644 --- a/packages/cdk/bin/PsuStatefulApp.ts +++ b/packages/cdk/bin/PsuApiApp.ts @@ -1,19 +1,55 @@ import { + calculateVersionedStackName, createApp, getBooleanConfigFromEnvVar, getConfigFromEnvVar, getNumberConfigFromEnvVar } from "@nhsdigital/eps-cdk-constructs" +import {PsuStatelessStack} from "../stacks/PsuStatelessStack" import {PsuStatefulStack} from "../stacks/PsuStatefulStack" +type StackMode = "stateless" | "stateful" + +function getStackMode(): StackMode { + const stackMode = getConfigFromEnvVar("stackMode", undefined, "stateless") + + if (stackMode === "stateless" || stackMode === "stateful") { + return stackMode + } + + throw new Error(`Invalid CDK_CONFIG_stackMode: ${stackMode}. Expected 'stateless' or 'stateful'.`) +} + async function main() { const {app, props} = createApp({ productName: "Prescription Status Update API", - appName: "PsuStatefulApp", + appName: "PsuApiApp", repoName: "eps-prescription-status-update-api", driftDetectionGroup: "psu-api" }) + const stackMode = getStackMode() + + if (stackMode === "stateless") { + new PsuStatelessStack(app, "PsuStatelessStack", { + ...props, + stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props), + samStackName: getConfigFromEnvVar("samStackName"), // TODO: REMOVE THE NEED FOR THIS + logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), + logLevel: getConfigFromEnvVar("logLevel"), + environment: getConfigFromEnvVar("environment"), + mutualTlsTrustStoreKey: props.isPullRequest ? undefined : getConfigFromEnvVar("trustStoreFile"), + csocApiGatewayDestination: "arn:aws:logs:eu-west-2:693466633220:destination:api_gateway_log_destination", + forwardCsocLogs: getBooleanConfigFromEnvVar("forwardCsocLogs"), + deployCheckPrescriptionStatusUpdate: getBooleanConfigFromEnvVar("deployCheckPrescriptionStatusUpdate"), + exposeGetStatusUpdates: getBooleanConfigFromEnvVar("exposeGetStatusUpdates"), + enablePostDatedNotifications: getConfigFromEnvVar("enablePostDatedNotifications", undefined, "false"), + requireApplicationName: getConfigFromEnvVar("requireApplicationName", undefined, "false"), + enableBackup: getBooleanConfigFromEnvVar("enableBackup", undefined, "false") + }) + return + } + // Stateful stacks use a stable (non-versioned) stack name so that the same // CloudFormation stack is updated in-place on every deployment rather than // creating a new stack per version. diff --git a/packages/cdk/bin/PsuStatelessApp.ts b/packages/cdk/bin/PsuStatelessApp.ts deleted file mode 100644 index 7116cc73c0..0000000000 --- a/packages/cdk/bin/PsuStatelessApp.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - calculateVersionedStackName, - createApp, - getBooleanConfigFromEnvVar, - getConfigFromEnvVar, - getNumberConfigFromEnvVar -} from "@nhsdigital/eps-cdk-constructs" -import {PsuStatelessStack} from "../stacks/PsuStatelessStack" - -async function main() { - const {app, props} = createApp({ - productName: "Prescription Status Update API", - appName: "PsuStatelessApp", - repoName: "eps-prescription-status-update-api", - driftDetectionGroup: "psu-api" - }) - - new PsuStatelessStack(app, "PsuStatelessStack", { - ...props, - stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props), - samStackName: getConfigFromEnvVar("samStackName"), // TODO: REMOVE THE NEED FOR THIS - logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), - logLevel: getConfigFromEnvVar("logLevel"), - environment: getConfigFromEnvVar("environment"), - mutualTlsTrustStoreKey: props.isPullRequest ? undefined : getConfigFromEnvVar("trustStoreFile"), - csocApiGatewayDestination: "arn:aws:logs:eu-west-2:693466633220:destination:api_gateway_log_destination", - forwardCsocLogs: getBooleanConfigFromEnvVar("forwardCsocLogs"), - deployCheckPrescriptionStatusUpdate: getBooleanConfigFromEnvVar("deployCheckPrescriptionStatusUpdate"), - exposeGetStatusUpdates: getBooleanConfigFromEnvVar("exposeGetStatusUpdates"), - enablePostDatedNotifications: getConfigFromEnvVar("enablePostDatedNotifications", undefined, "false"), - requireApplicationName: getConfigFromEnvVar("requireApplicationName", undefined, "false"), - enableBackup: getBooleanConfigFromEnvVar("enableBackup", undefined, "false") - }) -} - -main().catch((error) => { - console.error(error) - process.exit(1) -}) From 31cc8d4f794292c0386a4c886fc2358d3f930013 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:06:42 +0000 Subject: [PATCH 14/31] feat(cdk): add sandbox stack and deploy via unified app --- .github/workflows/cdk_release_code.yml | 23 ++- .github/workflows/ci.yml | 37 +--- .github/workflows/pull_request.yml | 39 ++-- .github/workflows/release.yml | 84 ++------ Makefile | 18 +- package-lock.json | 10 +- packages/cdk/bin/PsuApiApp.ts | 8 +- packages/cdk/bin/PsuApiSandboxApp.ts | 33 +++ packages/cdk/nagSuppressions.ts | 41 +++- packages/cdk/stacks/PsuApiSandboxStack.ts | 195 ++++++++++++++++++ ...tatefulStack.ts => PsuApiStatefulStack.ts} | 6 +- ...telessStack.ts => PsuApiStatelessStack.ts} | 6 +- 12 files changed, 358 insertions(+), 142 deletions(-) create mode 100644 packages/cdk/bin/PsuApiSandboxApp.ts create mode 100644 packages/cdk/stacks/PsuApiSandboxStack.ts rename packages/cdk/stacks/{PsuStatefulStack.ts => PsuApiStatefulStack.ts} (91%) rename packages/cdk/stacks/{PsuStatelessStack.ts => PsuApiStatelessStack.ts} (91%) diff --git a/.github/workflows/cdk_release_code.yml b/.github/workflows/cdk_release_code.yml index 967c8249b5..89606fdcc9 100644 --- a/.github/workflows/cdk_release_code.yml +++ b/.github/workflows/cdk_release_code.yml @@ -22,6 +22,22 @@ on: required: false type: string default: "stateless" + APP_NAME: + required: false + type: string + default: "PsuApiApp" + TRUST_STORE_VERSION: + type: string + required: false + default: "none" + ENABLE_MUTUAL_TLS: + type: boolean + required: false + default: false + ENABLE_SPLUNK: + type: boolean + required: false + default: false LOG_RETENTION_IN_DAYS: required: true type: string @@ -137,9 +153,9 @@ jobs: defaults: run: shell: bash - name: deploy cdk app ${{ inputs.STACK_MODE }} + name: deploy cdk app ${{ inputs.APP_NAME }} ${{ inputs.STACK_MODE }} env: - CDK_APP_NAME: "PsuApiApp" + CDK_APP_NAME: "${{ inputs.APP_NAME }}" CDK_CONFIG_stackMode: "${{ inputs.STACK_MODE }}" CDK_CONFIG_stackName: "${{ inputs.STACK_NAME }}" CDK_CONFIG_versionNumber: "${{ inputs.VERSION_NUMBER }}" @@ -149,6 +165,9 @@ jobs: CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}" CDK_CONFIG_logLevel: "${{ inputs.LOG_LEVEL }}" CDK_CONFIG_trustStoreFile: "${{ inputs.TRUSTSTORE_FILE }}" + CDK_CONFIG_trustStoreVersion: "${{ inputs.TRUST_STORE_VERSION }}" + CDK_CONFIG_enableMutualTls: "${{ inputs.ENABLE_MUTUAL_TLS }}" + CDK_CONFIG_enableSplunk: "${{ inputs.ENABLE_SPLUNK }}" CDK_CONFIG_forwardCsocLogs: "${{ inputs.FORWARD_CSOC_LOGS }}" CDK_CONFIG_deployCheckPrescriptionStatusUpdate: "${{ inputs.DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE }}" CDK_CONFIG_exposeGetStatusUpdates: "${{ inputs.EXPOSE_GET_STATUS_UPDATES }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c360e1c0f..fec7ac0778 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,47 +152,30 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_sandbox_dev: - needs: [tag_release, sam_package_code, get_commit_id, get_config_values] - uses: ./.github/workflows/run_release_code_and_api.yml + needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write id-token: write with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - ARTIFACT_BUCKET_PREFIX: ${{needs.tag_release.outputs.version_tag}}-sandbox + BRANCH_NAME: main + APP_NAME: PsuApiSandboxApp STACK_NAME: psu-sandbox AWS_ENVIRONMENT: dev - APIGEE_ENVIRONMENT: internal-dev-sandbox - ENABLE_MUTUAL_TLS: true - BUILD_ARTIFACT: packaged_sandbox_code - TRUSTSTORE_FILE: psu-sandbox-truststore.pem VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{needs.get_commit_id.outputs.commit_id}} - LOG_RETENTION_DAYS: 30 - DEPLOY_APIGEE: true - DYNAMODB_AUTOSCALE: false - DEPLOY_APIGEE_CPSU: true - DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true - EXPOSE_GET_STATUS_UPDATES: false - RUN_REGRESSION_TEST: false - STATE_MACHINE_LOG_LEVEL: ALL + LOG_RETENTION_IN_DAYS: "30" LOG_LEVEL: DEBUG - ENABLE_BACKUP: "False" - ENABLE_NOTIFICATIONS_INTERNAL: false - ENABLE_NOTIFICATIONS_EXTERNAL: false - ENABLE_POST_DATED_NOTIFICATIONS: true - ENABLED_SYSTEMS: "Internal Test System" - BLOCKED_SITE_ODS_CODES: "B3J1Z" - ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" # Workaround empty string handling - NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" - NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" - MTLS_KEY: psu-mtls-1 IS_PULL_REQUEST: false + TRUSTSTORE_FILE: psu-sandbox-truststore.pem + TRUST_STORE_VERSION: "none" + ENABLE_MUTUAL_TLS: true + ENABLE_SPLUNK: false + ENABLE_BACKUP: false FORWARD_CSOC_LOGS: false secrets: - REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} - PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} release_qa: needs: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 381f279417..1c1e1eb3fd 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -230,44 +230,29 @@ jobs: PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} - release_sandbox_code: + cdk_sandbox_release_code: needs: - [get_issue_number, sam_package_code, get_commit_id, get_config_values] - uses: ./.github/workflows/run_release_code_and_api.yml + [get_issue_number, cdk_package_code, get_commit_id, get_config_values] + uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write id-token: write with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} + APP_NAME: PsuApiSandboxApp STACK_NAME: psu-pr-${{needs.get_issue_number.outputs.issue_number}}-sandbox - ARTIFACT_BUCKET_PREFIX: PR-${{needs.get_issue_number.outputs.issue_number}}-sandbox AWS_ENVIRONMENT: dev - APIGEE_ENVIRONMENT: internal-dev-sandbox - ENABLE_MUTUAL_TLS: false - BUILD_ARTIFACT: packaged_sandbox_code - TRUSTSTORE_FILE: psu-sandbox-truststore.pem VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - LOG_RETENTION_DAYS: 30 - DEPLOY_APIGEE: true - DYNAMODB_AUTOSCALE: false - DEPLOY_APIGEE_CPSU: true - DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true - EXPOSE_GET_STATUS_UPDATES: false - RUN_REGRESSION_TEST: false - STATE_MACHINE_LOG_LEVEL: ALL - ENABLE_BACKUP: "False" - ENABLE_NOTIFICATIONS_INTERNAL: false - ENABLE_NOTIFICATIONS_EXTERNAL: false - ENABLED_SYSTEMS: "Internal Test System" - BLOCKED_SITE_ODS_CODES: "B3J1Z" - ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" # Workaround empty string handling - NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" - NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" - MTLS_KEY: psu-mtls-1 + LOG_RETENTION_IN_DAYS: "30" + LOG_LEVEL: DEBUG IS_PULL_REQUEST: true + TRUSTSTORE_FILE: psu-sandbox-truststore.pem + TRUST_STORE_VERSION: "none" + ENABLE_MUTUAL_TLS: false + ENABLE_SPLUNK: false + ENABLE_BACKUP: false FORWARD_CSOC_LOGS: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} - PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} - REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e3cd6e5421..019898b31e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -192,50 +192,30 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_dev_sandbox: - needs: [tag_release, sam_package_code, get_commit_id, get_config_values] - uses: ./.github/workflows/run_release_code_and_api.yml + needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] + uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write id-token: write with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - ARTIFACT_BUCKET_PREFIX: ${{needs.tag_release.outputs.version_tag}}-sandbox + BRANCH_NAME: main + APP_NAME: PsuApiSandboxApp STACK_NAME: psu-sandbox AWS_ENVIRONMENT: dev - APIGEE_ENVIRONMENT: internal-dev-sandbox - ENABLE_MUTUAL_TLS: true - BUILD_ARTIFACT: packaged_sandbox_code - TRUSTSTORE_FILE: psu-sandbox-truststore.pem VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{needs.get_commit_id.outputs.commit_id}} - LOG_RETENTION_DAYS: 30 - DEPLOY_APIGEE: true - DYNAMODB_AUTOSCALE: false - DEPLOY_APIGEE_CPSU: true - DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true - EXPOSE_GET_STATUS_UPDATES: false - RUN_REGRESSION_TEST: false - STATE_MACHINE_LOG_LEVEL: ALL - ENABLE_BACKUP: "False" - ENABLE_NOTIFICATIONS_INTERNAL: false - ENABLE_NOTIFICATIONS_EXTERNAL: false - ENABLE_POST_DATED_NOTIFICATIONS: false - ENABLED_SYSTEMS: "Internal Test System" - BLOCKED_SITE_ODS_CODES: "B3J1Z" - ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" - NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" - NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" - MTLS_KEY: psu-mtls-1 + LOG_RETENTION_IN_DAYS: "30" + LOG_LEVEL: DEBUG IS_PULL_REQUEST: false - TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} - TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} - TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} - TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} + TRUSTSTORE_FILE: psu-sandbox-truststore.pem + TRUST_STORE_VERSION: "none" + ENABLE_MUTUAL_TLS: true + ENABLE_SPLUNK: false + ENABLE_BACKUP: false FORWARD_CSOC_LOGS: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} - PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} - REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_ref: needs: @@ -534,55 +514,35 @@ jobs: release_int_sandbox: needs: [ + cdk_package_code, tag_release, release_qa, - sam_package_code, get_commit_id, get_config_values, ] - uses: ./.github/workflows/run_release_code_and_api.yml + uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write id-token: write with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - ARTIFACT_BUCKET_PREFIX: ${{needs.tag_release.outputs.version_tag}}-sandbox + BRANCH_NAME: main + APP_NAME: PsuApiSandboxApp STACK_NAME: psu-sandbox AWS_ENVIRONMENT: int - APIGEE_ENVIRONMENT: sandbox - ENABLE_MUTUAL_TLS: true - BUILD_ARTIFACT: packaged_sandbox_code - TRUSTSTORE_FILE: psu-sandbox-truststore.pem VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{needs.get_commit_id.outputs.commit_id}} - LOG_RETENTION_DAYS: 30 - DEPLOY_APIGEE: true - DYNAMODB_AUTOSCALE: false - DEPLOY_APIGEE_CPSU: true - DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true - EXPOSE_GET_STATUS_UPDATES: false - RUN_REGRESSION_TEST: false - STATE_MACHINE_LOG_LEVEL: ALL - ENABLE_BACKUP: "False" - ENABLE_NOTIFICATIONS_INTERNAL: false - ENABLE_NOTIFICATIONS_EXTERNAL: false - ENABLE_POST_DATED_NOTIFICATIONS: false - ENABLED_SYSTEMS: "Internal Test System, Apotec Ltd - Apotec CRM - Production, CrxPatientApp, nhsPrescriptionApp, Titan PSU Prod" - BLOCKED_SITE_ODS_CODES: "B3J1Z" - ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" - NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" - NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" - MTLS_KEY: psu-mtls-1 + LOG_RETENTION_IN_DAYS: "30" + LOG_LEVEL: DEBUG IS_PULL_REQUEST: false - TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} - TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} - TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} - TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} + TRUSTSTORE_FILE: psu-sandbox-truststore.pem + TRUST_STORE_VERSION: "none" + ENABLE_MUTUAL_TLS: true + ENABLE_SPLUNK: false + ENABLE_BACKUP: false FORWARD_CSOC_LOGS: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.INT_CLOUD_FORMATION_DEPLOY_ROLE }} - PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PROD_ROLE }} - REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_prod: needs: diff --git a/Makefile b/Makefile index 753128c4dd..2dced555aa 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,9 @@ export CDK_CONFIG_environment=dev export CDK_CONFIG_logRetentionInDays=30 export CDK_CONFIG_logLevel=DEBUG export CDK_CONFIG_trustStoreFile=psu-truststore.pem +export CDK_CONFIG_trustStoreVersion=none +export CDK_CONFIG_enableMutualTls=false +export CDK_CONFIG_enableSplunk=false export CDK_CONFIG_forwardCsocLogs=false export CDK_CONFIG_deployCheckPrescriptionStatusUpdate=true export CDK_CONFIG_exposeGetStatusUpdates=false @@ -257,7 +260,7 @@ cdk-deploy: REQUIRE_APPROVAL="$${REQUIRE_APPROVAL:-any-change}" && \ npm run cdk-deploy --workspace packages/cdk -cdk-synth: +cdk-stateless-synth: CDK_APP_NAME=PsuApiApp \ CDK_CONFIG_stackMode=stateless \ CDK_CONFIG_stackName=psu-cdk \ @@ -283,6 +286,19 @@ cdk-stateful-synth: CDK_CONFIG_enableBackup=false \ npm run cdk-synth --workspace packages/cdk +cdk-sandbox-synth: + CDK_APP_NAME=PsuApiSandboxApp \ + CDK_CONFIG_stackName=psu-sandbox \ + CDK_CONFIG_logRetentionInDays=30 \ + CDK_CONFIG_logLevel=DEBUG \ + CDK_CONFIG_environment=dev \ + CDK_CONFIG_trustStoreFile=psu-sandbox-truststore.pem \ + CDK_CONFIG_trustStoreVersion=none \ + CDK_CONFIG_enableMutualTls=false \ + CDK_CONFIG_enableSplunk=false \ + CDK_CONFIG_enableBackup=false \ + npm run cdk-synth --workspace packages/cdk + cdk-diff: npm run cdk-diff --workspace packages/cdk diff --git a/package-lock.json b/package-lock.json index fbfeb14199..f84612b738 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5241,7 +5241,9 @@ "license": "MIT" }, "node_modules/dompurify": { - "version": "3.3.3", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", + "integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -6118,6 +6120,7 @@ }, "node_modules/ignore": { "version": "7.0.5", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -7417,7 +7420,9 @@ } }, "node_modules/protobufjs": { - "version": "7.5.4", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", + "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -9236,6 +9241,7 @@ }, "node_modules/yaml": { "version": "2.8.3", + "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/packages/cdk/bin/PsuApiApp.ts b/packages/cdk/bin/PsuApiApp.ts index ef274f5886..83c38b0090 100644 --- a/packages/cdk/bin/PsuApiApp.ts +++ b/packages/cdk/bin/PsuApiApp.ts @@ -5,8 +5,8 @@ import { getConfigFromEnvVar, getNumberConfigFromEnvVar } from "@nhsdigital/eps-cdk-constructs" -import {PsuStatelessStack} from "../stacks/PsuStatelessStack" -import {PsuStatefulStack} from "../stacks/PsuStatefulStack" +import {PsuApiStatelessStack} from "../stacks/PsuApiStatelessStack" +import {PsuApiStatefulStack} from "../stacks/PsuApiStatefulStack" type StackMode = "stateless" | "stateful" @@ -31,7 +31,7 @@ async function main() { const stackMode = getStackMode() if (stackMode === "stateless") { - new PsuStatelessStack(app, "PsuStatelessStack", { + new PsuApiStatelessStack(app, "PsuApiStatelessStack", { ...props, stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props), samStackName: getConfigFromEnvVar("samStackName"), // TODO: REMOVE THE NEED FOR THIS @@ -53,7 +53,7 @@ async function main() { // Stateful stacks use a stable (non-versioned) stack name so that the same // CloudFormation stack is updated in-place on every deployment rather than // creating a new stack per version. - new PsuStatefulStack(app, "PsuStatefulStack", { + new PsuApiStatefulStack(app, "PsuApiStatefulStack", { ...props, stackName: getConfigFromEnvVar("stackName"), logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), diff --git a/packages/cdk/bin/PsuApiSandboxApp.ts b/packages/cdk/bin/PsuApiSandboxApp.ts new file mode 100644 index 0000000000..9817c665df --- /dev/null +++ b/packages/cdk/bin/PsuApiSandboxApp.ts @@ -0,0 +1,33 @@ +import { + createApp, + getBooleanConfigFromEnvVar, + getConfigFromEnvVar, + getNumberConfigFromEnvVar +} from "@nhsdigital/eps-cdk-constructs" +import {PsuApiSandboxStack} from "../stacks/PsuApiSandboxStack" + +async function main() { + const {app, props} = createApp({ + productName: "Prescription Status Update API", + appName: "PsuApiSandboxApp", + repoName: "eps-prescription-status-update-api", + driftDetectionGroup: "psu-api" + }) + + new PsuApiSandboxStack(app, "PsuApiSandboxStack", { + ...props, + stackName: getConfigFromEnvVar("stackName"), + logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), + logLevel: getConfigFromEnvVar("logLevel"), + environment: getConfigFromEnvVar("environment"), + trustStoreFile: getConfigFromEnvVar("trustStoreFile", undefined, "psu-sandbox-truststore.pem"), + trustStoreVersion: getConfigFromEnvVar("trustStoreVersion", undefined, "none"), + enableMutualTls: getBooleanConfigFromEnvVar("enableMutualTls", undefined, "false"), + enableSplunk: getBooleanConfigFromEnvVar("enableSplunk", undefined, "false") + }) +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/packages/cdk/nagSuppressions.ts b/packages/cdk/nagSuppressions.ts index d03ec56507..b76c69cdfc 100644 --- a/packages/cdk/nagSuppressions.ts +++ b/packages/cdk/nagSuppressions.ts @@ -7,8 +7,8 @@ export const nagSuppressions = (stack: Stack) => { safeAddNagSuppressionGroup( stack, [ - "/PsuStatelessStack/StateMachines/UpdatePrescriptionStatusStateMachine/StateMachinePutLogsManagedPolicy/Resource", - "/PsuStatelessStack/StateMachines/Format1UpdatePrescriptionsStatusStateMachine/StateMachinePutLogsManagedPolicy/Resource" + "/PsuApiStatelessStack/StateMachines/UpdatePrescriptionStatusStateMachine/StateMachinePutLogsManagedPolicy/Resource", + "/PsuApiStatelessStack/StateMachines/Format1UpdatePrescriptionsStatusStateMachine/StateMachinePutLogsManagedPolicy/Resource" ], [ { @@ -21,7 +21,7 @@ export const nagSuppressions = (stack: Stack) => { // API Gateway does not use request validation — validation is handled by service logic safeAddNagSuppression( stack, - "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Resource", + "/PsuApiStatelessStack/Apis/RestApiGateway/ApiGateway/Resource", [ { id: "AwsSolutions-APIG2", @@ -33,7 +33,7 @@ export const nagSuppressions = (stack: Stack) => { // API Gateway CloudWatch role uses AWS managed policy safeAddNagSuppression( stack, - "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/CloudWatchRole/Resource", + "/PsuApiStatelessStack/Apis/RestApiGateway/ApiGateway/CloudWatchRole/Resource", [ { id: "AwsSolutions-IAM4", @@ -46,7 +46,7 @@ export const nagSuppressions = (stack: Stack) => { // API Gateway stage is not associated with WAFv2 — WAF is managed externally via Apigee safeAddNagSuppression( stack, - "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/DeploymentStage.prod/Resource", + "/PsuApiStatelessStack/Apis/RestApiGateway/ApiGateway/DeploymentStage.prod/Resource", [ { id: "AwsSolutions-APIG3", @@ -59,12 +59,12 @@ export const nagSuppressions = (stack: Stack) => { safeAddNagSuppressionGroup( stack, [ - "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/POST/Resource", - "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/format-1/POST/Resource", - "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/notification-delivery-status-callback/POST/Resource", - "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/_status/GET/Resource", - "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/metadata/GET/Resource", - "/PsuStatelessStack/Apis/RestApiGateway/ApiGateway/Default/checkprescriptionstatusupdates/GET/Resource" + "/PsuApiStatelessStack/Apis/RestApiGateway/ApiGateway/Default/POST/Resource", + "/PsuApiStatelessStack/Apis/RestApiGateway/ApiGateway/Default/format-1/POST/Resource", + "/PsuApiStatelessStack/Apis/RestApiGateway/ApiGateway/Default/notification-delivery-status-callback/POST/Resource", + "/PsuApiStatelessStack/Apis/RestApiGateway/ApiGateway/Default/_status/GET/Resource", + "/PsuApiStatelessStack/Apis/RestApiGateway/ApiGateway/Default/metadata/GET/Resource", + "/PsuApiStatelessStack/Apis/RestApiGateway/ApiGateway/Default/checkprescriptionstatusupdates/GET/Resource" ], [ { @@ -77,4 +77,23 @@ export const nagSuppressions = (stack: Stack) => { } ] ) + +} + +export const nagSuppressionsSandbox = (stack: Stack) => { + // Sandbox HTTP API methods do not use API Gateway authorizers — auth is external via mTLS/Apigee + safeAddNagSuppressionGroup( + stack, + [ + "/PsuApiSandboxStack/HttpApiGateway/POST--/Resource", + "/PsuApiSandboxStack/HttpApiGateway/GET--_status/Resource", + "/PsuApiSandboxStack/HttpApiGateway/GET--metadata/Resource" + ], + [ + { + id: "AwsSolutions-APIG4", + reason: "Authorization is handled externally via mutual TLS and the Apigee API gateway proxy. API Gateway methods do not require an additional authorizer." + } + ] + ) } diff --git a/packages/cdk/stacks/PsuApiSandboxStack.ts b/packages/cdk/stacks/PsuApiSandboxStack.ts new file mode 100644 index 0000000000..25a7dda46b --- /dev/null +++ b/packages/cdk/stacks/PsuApiSandboxStack.ts @@ -0,0 +1,195 @@ +import {Fn, Stack} from "aws-cdk-lib" +import {StandardStackProps, TypescriptLambdaFunction} from "@nhsdigital/eps-cdk-constructs" +import {ManagedPolicy} from "aws-cdk-lib/aws-iam" +import {Construct} from "constructs" +import {resolve} from "node:path" +import { + HttpApi, + CfnDomainName, + CfnApiMapping, + CfnStage, + HttpMethod +} from "aws-cdk-lib/aws-apigatewayv2" +import {HttpLambdaIntegration} from "aws-cdk-lib/aws-apigatewayv2-integrations" +import {Certificate, CertificateValidation} from "aws-cdk-lib/aws-certificatemanager" +import {PublicHostedZone, CfnRecordSet} from "aws-cdk-lib/aws-route53" +import {CfnLogGroup, CfnSubscriptionFilter} from "aws-cdk-lib/aws-logs" +import {nagSuppressionsSandbox} from "../nagSuppressions" + +const baseDir = resolve(__dirname, "../../..") + +export interface PsuApiSandboxStackProps extends StandardStackProps { + readonly stackName: string + readonly logRetentionInDays: number + readonly logLevel: string + readonly environment: string + readonly trustStoreFile: string + readonly trustStoreVersion: string + readonly enableMutualTls: boolean + readonly enableSplunk: boolean +} + +export class PsuApiSandboxStack extends Stack { + public constructor(scope: Construct, id: string, props: PsuApiSandboxStackProps) { + super(scope, id, props) + + const lambdaAccessSecretsPolicy = ManagedPolicy.fromManagedPolicyArn( + this, "LambdaAccessSecretsPolicy", + Fn.importValue("account-resources:LambdaAccessSecretsPolicy") + ) + + const sandboxLambda = new TypescriptLambdaFunction(this, "Sandbox", { + functionName: `${props.stackName}-UpdatePrescriptionStatusSandbox`, + projectBaseDir: baseDir, + packageBasePath: "packages/nhsd-psu-sandbox", + entryPoint: "src/sandbox.ts", + environmentVariables: {}, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const statusLambda = new TypescriptLambdaFunction(this, "Status", { + functionName: `${props.stackName}-status`, + projectBaseDir: baseDir, + packageBasePath: "packages/statusLambda", + entryPoint: "src/statusLambda.ts", + environmentVariables: { + VERSION_NUMBER: props.version, + COMMIT_ID: props.commitId + }, + additionalPolicies: [lambdaAccessSecretsPolicy], + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const capabilityStatementLambda = new TypescriptLambdaFunction(this, "CapabilityStatement", { + functionName: `${props.stackName}-CapabilityStatement`, + projectBaseDir: baseDir, + packageBasePath: "packages/capabilityStatement", + entryPoint: "src/capabilityStatement.ts", + environmentVariables: {}, + logRetentionInDays: props.logRetentionInDays, + logLevel: props.logLevel, + version: props.version, + commitId: props.commitId + }) + + const httpApi = new HttpApi(this, "HttpApiGateway", { + disableExecuteApiEndpoint: props.enableMutualTls, + createDefaultStage: false + }) + + httpApi.addRoutes({ + path: "/", + methods: [HttpMethod.POST], + integration: new HttpLambdaIntegration("SandboxIntegration", sandboxLambda.function) + }) + httpApi.addRoutes({ + path: "/_status", + methods: [HttpMethod.GET], + integration: new HttpLambdaIntegration("StatusIntegration", statusLambda.function) + }) + httpApi.addRoutes({ + path: "/metadata", + methods: [HttpMethod.GET], + integration: new HttpLambdaIntegration("CapabilityStatementIntegration", capabilityStatementLambda.function) + }) + + const cloudWatchKmsKeyArn = Fn.importValue("account-resources:CloudwatchLogsKmsKeyArn") + const apiGwAccessLogs = new CfnLogGroup(this, "ApiGwAccessLogs", { + logGroupName: Fn.join("/", ["", "aws", "apigateway", props.stackName, httpApi.httpApiId]), + retentionInDays: props.logRetentionInDays, + kmsKeyId: cloudWatchKmsKeyArn + }) + + new CfnStage(this, "HttpApiProdStage", { + apiId: httpApi.httpApiId, + stageName: "Prod", + autoDeploy: true, + defaultRouteSettings: { + detailedMetricsEnabled: true + }, + accessLogSettings: { + destinationArn: apiGwAccessLogs.attrArn, + format: JSON.stringify({ + requestTime: "$context.requestTime", + apiId: "$context.apiId", + accountId: "$context.accountId", + requestId: "$context.requestId", + status: "$context.status", + httpMethod: "$context.httpMethod", + protocol: "$context.protocol", + path: "$context.path", + responseLatency: "$context.responseLatency", + responseLength: "$context.responseLength" + }) + } + }) + + if (props.enableSplunk) { + new CfnSubscriptionFilter(this, "ApiGwAccessLogsSplunkSubscriptionFilter", { + logGroupName: apiGwAccessLogs.ref, + filterPattern: "", + destinationArn: Fn.importValue("lambda-resources:SplunkDeliveryStream"), + roleArn: Fn.importValue("lambda-resources:SplunkSubscriptionFilterRole") + }) + } + + const epsDomain = Fn.importValue("eps-route53-resources:EPS-domain") + const epsZoneId = Fn.importValue("eps-route53-resources:EPS-ZoneID") + const sandboxDomainName = Fn.join(".", [props.stackName, epsDomain]) + + const hostedZone = PublicHostedZone.fromHostedZoneAttributes(this, "EpsPublicHostedZone", { + hostedZoneId: epsZoneId, + zoneName: epsDomain + }) + + const certificate = new Certificate(this, "GenerateCertificate", { + domainName: sandboxDomainName, + validation: CertificateValidation.fromDns(hostedZone) + }) + + const trustStoreBucketArn = Fn.importValue("account-resources:TrustStoreBucket") + const trustStoreBucketName = Fn.select(5, Fn.split(":", trustStoreBucketArn)) + + const domainName = new CfnDomainName(this, "HttpApiCustomDomain", { + domainName: sandboxDomainName, + domainNameConfigurations: [ + { + endpointType: "REGIONAL", + certificateArn: certificate.certificateArn, + securityPolicy: "TLS_1_2" + } + ], + mutualTlsAuthentication: props.enableMutualTls + ? { + truststoreUri: Fn.join("", ["s3://", trustStoreBucketName, "/", props.trustStoreFile]), + truststoreVersion: props.trustStoreVersion + } + : undefined + }) + + new CfnApiMapping(this, "HttpApiDomainMapping", { + apiId: httpApi.httpApiId, + domainName: domainName.ref, + stage: "Prod" + }) + + new CfnRecordSet(this, "HttpApiDomainAliasRecord", { + hostedZoneId: epsZoneId, + name: sandboxDomainName, + type: "A", + aliasTarget: { + dnsName: domainName.attrRegionalDomainName, + hostedZoneId: domainName.attrRegionalHostedZoneId, + evaluateTargetHealth: false + } + }) + + nagSuppressionsSandbox(this) + } +} diff --git a/packages/cdk/stacks/PsuStatefulStack.ts b/packages/cdk/stacks/PsuApiStatefulStack.ts similarity index 91% rename from packages/cdk/stacks/PsuStatefulStack.ts rename to packages/cdk/stacks/PsuApiStatefulStack.ts index 2dfe0b8905..b5b7d67fca 100644 --- a/packages/cdk/stacks/PsuStatefulStack.ts +++ b/packages/cdk/stacks/PsuApiStatefulStack.ts @@ -5,7 +5,7 @@ import {Tables} from "../resources/Tables" import {Secrets} from "../resources/Secrets" import {Messaging} from "../resources/Messaging" -export interface PsuStatefulStackProps extends StandardStackProps { +export interface PsuApiStatefulStackProps extends StandardStackProps { readonly stackName: string readonly logRetentionInDays: number readonly environment: string @@ -26,8 +26,8 @@ export interface PsuStatefulStackProps extends StandardStackProps { readonly testPrescriptions4: string } -export class PsuStatefulStack extends Stack { - public constructor(scope: App, id: string, props: PsuStatefulStackProps) { +export class PsuApiStatefulStack extends Stack { + public constructor(scope: App, id: string, props: PsuApiStatefulStackProps) { super(scope, id, props) new Parameters(this, "Parameters", { diff --git a/packages/cdk/stacks/PsuStatelessStack.ts b/packages/cdk/stacks/PsuApiStatelessStack.ts similarity index 91% rename from packages/cdk/stacks/PsuStatelessStack.ts rename to packages/cdk/stacks/PsuApiStatelessStack.ts index c1a681484a..246921c6f8 100644 --- a/packages/cdk/stacks/PsuStatelessStack.ts +++ b/packages/cdk/stacks/PsuApiStatelessStack.ts @@ -5,7 +5,7 @@ import {Functions} from "../resources/Functions" import {StateMachines} from "../resources/StateMachines" import {Apis} from "../resources/Apis" -export interface PsuStatelessStackProps extends StandardStackProps { +export interface PsuApiStatelessStackProps extends StandardStackProps { readonly stackName: string readonly samStackName: string readonly logRetentionInDays: number @@ -21,8 +21,8 @@ export interface PsuStatelessStackProps extends StandardStackProps { readonly enableBackup: boolean } -export class PsuStatelessStack extends Stack { - public constructor(scope: App, id: string, props: PsuStatelessStackProps) { +export class PsuApiStatelessStack extends Stack { + public constructor(scope: App, id: string, props: PsuApiStatelessStackProps) { super(scope, id, props) const functions = new Functions(this, "Functions", { From 5da0cbf49d6acac29b4bdf7f64a7e4d74ee76425 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:30:53 +0000 Subject: [PATCH 15/31] feat(cdk): temporary stateful sam template and script to compare with cdk confirming no diffs --- SAMtemplates/stateful_template.yaml | 116 ++++++ packages/cdk/resources/Tables.ts | 136 ++++++- scripts/compare_stateful_templates.py | 506 ++++++++++++++++++++++++++ 3 files changed, 747 insertions(+), 11 deletions(-) create mode 100644 SAMtemplates/stateful_template.yaml create mode 100755 scripts/compare_stateful_templates.py diff --git a/SAMtemplates/stateful_template.yaml b/SAMtemplates/stateful_template.yaml new file mode 100644 index 0000000000..637b5b30ca --- /dev/null +++ b/SAMtemplates/stateful_template.yaml @@ -0,0 +1,116 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: | + PSU stateful resources + +Parameters: + EnableDynamoDBAutoScaling: + Type: String + Default: true + AllowedValues: + - true + - false + + EnableBackup: + Type: String + Default: "False" + AllowedValues: + - "True" + - "False" + + EnableNotificationsInternal: + Type: String + Default: false + AllowedValues: + - true + - false + + EnableNotificationsExternal: + Type: String + Default: false + AllowedValues: + - true + - false + + EnabledSiteODSCodesValue: + Type: String + Default: " " + + EnabledSystemsValue: + Type: String + Default: " " + + EnabledSupplierApplicationIDsValue: + Type: String + Default: " " + + BlockedSiteODSCodesValue: + Type: String + Default: " " + + NotifyRoutingPlanIDValue: + Type: String + Default: " " + + NotifyAPIBaseURLValue: + Type: String + Default: " " + + TestPresciptionsParamValue1: + Type: String + Default: PLACEHOLDER + + TestPresciptionsParamValue2: + Type: String + Default: PLACEHOLDER + + TestPresciptionsParamValue3: + Type: String + Default: PLACEHOLDER + + TestPresciptionsParamValue4: + Type: String + Default: PLACEHOLDER + +Resources: + Secrets: + Type: AWS::Serverless::Application + Properties: + Location: secrets/main.yaml + Parameters: + StackName: !Ref AWS::StackName + + Parameters: + Type: AWS::Serverless::Application + Properties: + Location: parameters/main.yaml + Parameters: + StackName: !Ref AWS::StackName + EnableNotificationsInternalValue: !Ref EnableNotificationsInternal + EnableNotificationsExternalValue: !Ref EnableNotificationsExternal + EnabledSiteODSCodesValue: !Ref EnabledSiteODSCodesValue + EnabledSystemsValue: !Ref EnabledSystemsValue + EnabledSupplierApplicationIDsValue: !Ref EnabledSupplierApplicationIDsValue + BlockedSiteODSCodesValue: !Ref BlockedSiteODSCodesValue + NotifyRoutingPlanIDValue: !Ref NotifyRoutingPlanIDValue + NotifyAPIBaseURLValue: !Ref NotifyAPIBaseURLValue + TestPresciptionsParamValue1: !Ref TestPresciptionsParamValue1 + TestPresciptionsParamValue2: !Ref TestPresciptionsParamValue2 + TestPresciptionsParamValue3: !Ref TestPresciptionsParamValue3 + TestPresciptionsParamValue4: !Ref TestPresciptionsParamValue4 + + Tables: + Type: AWS::Serverless::Application + Properties: + Location: tables/main.yaml + Parameters: + StackName: !Ref AWS::StackName + EnableDynamoDBAutoScaling: !Ref EnableDynamoDBAutoScaling + EnableBackup: !Ref EnableBackup + + Messaging: + Type: AWS::Serverless::Application + Properties: + Location: messaging/main.yaml + Parameters: + StackName: !Ref AWS::StackName diff --git a/packages/cdk/resources/Tables.ts b/packages/cdk/resources/Tables.ts index a69e1a16df..adc8aeb989 100644 --- a/packages/cdk/resources/Tables.ts +++ b/packages/cdk/resources/Tables.ts @@ -11,7 +11,9 @@ import { PolicyStatement, AnyPrincipal, AccountRootPrincipal, - PolicyDocument + PolicyDocument, + Role, + ServicePrincipal } from "aws-cdk-lib/aws-iam" import {Key, Alias} from "aws-cdk-lib/aws-kms" import {Duration} from "aws-cdk-lib" @@ -37,6 +39,48 @@ export class Tables extends Construct { const {account, region} = Stack.of(this) + const dynamoDbScalingRolePolicy = props.enableDynamoDBAutoScaling + ? new ManagedPolicy(this, "DynamoDbScalingRolePolicy", { + statements: [ + new PolicyStatement({ + actions: [ + "dynamodb:DescribeTable", + "dynamodb:UpdateTable" + ], + resources: [ + `arn:aws:dynamodb:${region}:${account}:table/${props.stackName}-*` + ] + }), + new PolicyStatement({ + actions: [ + "cloudwatch:PutMetricAlarm", + "cloudwatch:DescribeAlarms", + "cloudwatch:DeleteAlarms" + ], + resources: ["*"] + }) + ] + }) + : undefined + + if (dynamoDbScalingRolePolicy) { + NagSuppressions.addResourceSuppressions(dynamoDbScalingRolePolicy, [ + { + id: "AwsSolutions-IAM5", + reason: + "Stack-scoped wildcard is required for autoscaling permissions " + + "across stateful DynamoDB tables and indexes." + } + ]) + } + + const dynamoDbScalingRole = props.enableDynamoDBAutoScaling + ? new Role(this, "DynamoDbScalingRole", { + assumedBy: new ServicePrincipal("dynamodb.application-autoscaling.amazonaws.com"), + managedPolicies: dynamoDbScalingRolePolicy ? [dynamoDbScalingRolePolicy] : [] + }) + : undefined + // ── Shared autoscaling IAM role ────────────────────────────────────────── // Used by ApplicationAutoScaling when enableDynamoDBAutoScaling is true. // Equivalent to DynamoDbScalingRole in SAMtemplates/tables/main.yaml. @@ -204,9 +248,14 @@ export class Tables extends Construct { } if (props.enableDynamoDBAutoScaling) { + const scalingRoleOptions = dynamoDbScalingRole + ? {role: dynamoDbScalingRole} + : {} + const tableWriteScaling = prescriptionStatusUpdatesTable.autoScaleWriteCapacity({ minCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY, - maxCapacity: MAX_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY + maxCapacity: MAX_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY, + ...scalingRoleOptions }) tableWriteScaling.scaleOnUtilization({ targetUtilizationPercent: 50, @@ -216,7 +265,8 @@ export class Tables extends Construct { const tableReadScaling = prescriptionStatusUpdatesTable.autoScaleReadCapacity({ minCapacity: 1, - maxCapacity: 100 + maxCapacity: 100, + ...scalingRoleOptions }) tableReadScaling.scaleOnUtilization({ targetUtilizationPercent: 70, @@ -228,7 +278,8 @@ export class Tables extends Construct { prescriptionStatusUpdatesTable.autoScaleGlobalSecondaryIndexWriteCapacity( "PharmacyODSCodePrescriptionIDIndex", { minCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY, - maxCapacity: MAX_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY + maxCapacity: MAX_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY, + ...scalingRoleOptions }) pharmacyIndexWriteScaling.scaleOnUtilization({ targetUtilizationPercent: 50, @@ -240,13 +291,66 @@ export class Tables extends Construct { prescriptionStatusUpdatesTable.autoScaleGlobalSecondaryIndexReadCapacity( "PharmacyODSCodePrescriptionIDIndex", { minCapacity: 1, - maxCapacity: 100 + maxCapacity: 100, + ...scalingRoleOptions }) pharmacyIndexReadScaling.scaleOnUtilization({ targetUtilizationPercent: 70, scaleInCooldown: Duration.seconds(60), scaleOutCooldown: Duration.seconds(60) }) + + const nhsNumberIndexWriteScaling = + prescriptionStatusUpdatesTable.autoScaleGlobalSecondaryIndexWriteCapacity( + "PatientNHSNumberIndex", { + minCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY, + maxCapacity: MAX_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY, + ...scalingRoleOptions + }) + nhsNumberIndexWriteScaling.scaleOnUtilization({ + targetUtilizationPercent: 50, + scaleInCooldown: Duration.seconds(600), + scaleOutCooldown: Duration.seconds(0) + }) + + const nhsNumberIndexReadScaling = + prescriptionStatusUpdatesTable.autoScaleGlobalSecondaryIndexReadCapacity( + "PatientNHSNumberIndex", { + minCapacity: 1, + maxCapacity: 100, + ...scalingRoleOptions + }) + nhsNumberIndexReadScaling.scaleOnUtilization({ + targetUtilizationPercent: 70, + scaleInCooldown: Duration.seconds(60), + scaleOutCooldown: Duration.seconds(60) + }) + + const pharmacyIndexIncPostDatedWriteScaling = + prescriptionStatusUpdatesTable.autoScaleGlobalSecondaryIndexWriteCapacity( + "PharmacyODSCodePrescriptionIDIndexIncPostDated", { + minCapacity: MIN_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY, + maxCapacity: MAX_WRITE_PRESCRIPTION_STATUS_UPDATES_CAPACITY, + ...scalingRoleOptions + }) + pharmacyIndexIncPostDatedWriteScaling.scaleOnUtilization({ + targetUtilizationPercent: 50, + scaleInCooldown: Duration.seconds(600), + scaleOutCooldown: Duration.seconds(0) + }) + + const pharmacyIndexIncPostDatedReadScaling = + prescriptionStatusUpdatesTable.autoScaleGlobalSecondaryIndexReadCapacity( + "PharmacyODSCodePrescriptionIDIndexIncPostDated", { + minCapacity: 1, + maxCapacity: 100, + ...scalingRoleOptions + }) + pharmacyIndexIncPostDatedReadScaling.scaleOnUtilization({ + targetUtilizationPercent: 70, + scaleInCooldown: Duration.seconds(60), + scaleOutCooldown: Duration.seconds(60) + }) } const prescriptionStatusUpdatesReadPolicy = new ManagedPolicy( @@ -417,10 +521,15 @@ export class Tables extends Construct { } if (props.enableDynamoDBAutoScaling) { + const scalingRoleOptions = dynamoDbScalingRole + ? {role: dynamoDbScalingRole} + : {} + const notifStatesTableWriteScaling = prescriptionNotificationStatesTable.autoScaleWriteCapacity({ minCapacity: MIN_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY, - maxCapacity: MAX_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY + maxCapacity: MAX_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY, + ...scalingRoleOptions }) notifStatesTableWriteScaling.scaleOnUtilization({ targetUtilizationPercent: 50, @@ -431,7 +540,8 @@ export class Tables extends Construct { const notifStatesTableReadScaling = prescriptionNotificationStatesTable.autoScaleReadCapacity({ minCapacity: 1, - maxCapacity: 100 + maxCapacity: 100, + ...scalingRoleOptions }) notifStatesTableReadScaling.scaleOnUtilization({ targetUtilizationPercent: 70, @@ -443,7 +553,8 @@ export class Tables extends Construct { prescriptionNotificationStatesTable.autoScaleGlobalSecondaryIndexWriteCapacity( "PatientPharmacyIndex", { minCapacity: MIN_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY, - maxCapacity: MAX_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY + maxCapacity: MAX_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY, + ...scalingRoleOptions }) patientPharmacyIndexWriteScaling.scaleOnUtilization({ targetUtilizationPercent: 50, @@ -455,7 +566,8 @@ export class Tables extends Construct { prescriptionNotificationStatesTable.autoScaleGlobalSecondaryIndexReadCapacity( "PatientPharmacyIndex", { minCapacity: 1, - maxCapacity: 100 + maxCapacity: 100, + ...scalingRoleOptions }) patientPharmacyIndexReadScaling.scaleOnUtilization({ targetUtilizationPercent: 70, @@ -467,7 +579,8 @@ export class Tables extends Construct { prescriptionNotificationStatesTable.autoScaleGlobalSecondaryIndexWriteCapacity( "NotifyMessageIDIndex", { minCapacity: MIN_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY, - maxCapacity: MAX_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY + maxCapacity: MAX_WRITE_PRESCRIPTION_NOTIFICATION_STATES_CAPACITY, + ...scalingRoleOptions }) notifyMessageIdIndexWriteScaling.scaleOnUtilization({ targetUtilizationPercent: 50, @@ -479,7 +592,8 @@ export class Tables extends Construct { prescriptionNotificationStatesTable.autoScaleGlobalSecondaryIndexReadCapacity( "NotifyMessageIDIndex", { minCapacity: 1, - maxCapacity: 100 + maxCapacity: 100, + ...scalingRoleOptions }) notifyMessageIdIndexReadScaling.scaleOnUtilization({ targetUtilizationPercent: 70, diff --git a/scripts/compare_stateful_templates.py b/scripts/compare_stateful_templates.py new file mode 100755 index 0000000000..8624002145 --- /dev/null +++ b/scripts/compare_stateful_templates.py @@ -0,0 +1,506 @@ +#!/usr/bin/env python3 +"""Build and export stateful SAM/CDK templates into ./tmp as sorted JSON.""" + +from __future__ import annotations + +import copy +import json +import os +import re +import subprocess +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +import yaml + + +REPO_ROOT = Path(__file__).resolve().parent.parent +TMP_DIR = REPO_ROOT / "tmp" +SAM_TEMPLATE = REPO_ROOT / "SAMtemplates" / "stateful_template.yaml" +SAM_BUILD_TEMPLATE = REPO_ROOT / ".aws-sam" / "build" / "template.yaml" +CDK_TEMPLATE = REPO_ROOT / "cdk.out" / "PsuApiStatefulStack.template.json" +TMP_SAM_JSON = TMP_DIR / "stateful_sam.template.json" +TMP_CDK_JSON = TMP_DIR / "PsuApiStatefulStack.template.json" +SUB_TOKEN_PATTERN = re.compile(r"\$\{([^}]+)\}") +CDK_LOGICAL_ID_SUFFIX_PATTERN = re.compile(r'^(?P.+?)(?P[A-F0-9]{8})$') +SAM_NESTED_TABLE_POLICY_PATTERN = re.compile( + r'^(?P.+)ResourcesTable(?PRead|Write)ManagedPolicy$' +) +RESOURCE_NAME_ALIASES = { + 'TablesPrescriptionNotificationStatesTableReadPolicy': ( + 'TablesPrescriptionNotificationStatesV1TableReadPolicy' + ), + 'TablesPrescriptionNotificationStatesTableWritePolicy': ( + 'TablesPrescriptionNotificationStatesV1TableWritePolicy' + ), + 'TablesPrescriptionNotificationStatesTableV1NotifyMessageIDIndex': ( + 'TablesNotifyMessageIDIndex' + ), + 'TablesPrescriptionNotificationStatesTableV1PatientPharmacyIndex': ( + 'TablesPatientPharmacyIndex' + ), + 'TablesPrescriptionStatusUpdatesTablePharmacyODSCodePrescriptionIDIndex': ( + 'TablesPharmacyIndex' + ), + 'TablesPrescriptionStatusUpdatesTablePatientNHSNumberIndex': ( + 'TablesNHSNumberIndex' + ) +} +SAM_SCALING_NAME_PATTERN = re.compile( + r'^(?P.+)Scaling(?PRead|Write)(?PPolicy|Target)$' +) +EXCLUDED_CANONICAL_RESOURCE_NAMES = { + 'CDKMetadata', + # These QueuePolicy resources are intentionally CDK-only. + # They enforce deny-on-insecure-transport (aws:SecureTransport=false), + # which is stronger in-transit assurance than the current SAM baseline. + 'MessagingNHSNotifyPrescriptionsDeadLetterQueuePolicy', + 'MessagingNHSNotifyPrescriptionsSQSQueuePolicy', + 'MessagingPostDatedNotificationsDeadLetterQueuePolicy', + 'MessagingPostDatedNotificationsSQSQueuePolicy' +} +REF_KEY = 'Ref' +GETATT_KEY = 'Fn::GetAtt' +SUB_KEY = 'Fn::Sub' +IF_KEY = 'Fn::If' +EQUALS_KEY = 'Fn::Equals' + + +@dataclass +class TemplateContext: + prefix: str + parameter_bindings: dict[str, Any] + parameter_defaults: dict[str, Any] + passthrough_parameter_names: set[str] + local_resource_names: set[str] + nested_application_names: set[str] + local_condition_names: set[str] + nested_output_values: dict[str, dict[str, Any]] + + +@dataclass +class ExpandedTemplate: + resources: dict[str, Any] + outputs: dict[str, Any] + output_values: dict[str, Any] + conditions: dict[str, Any] + + +def prefixed_name(prefix: str, name: str) -> str: + return f"{prefix}{name}" if prefix else name + + +def deep_copy(value: Any) -> Any: + return copy.deepcopy(value) + + +def run_command(command: list[str], env: dict[str, str] | None = None) -> None: + subprocess.run(command, cwd=REPO_ROOT, check=True, env=env) + + +def write_sorted_json(source_data: object, destination: Path) -> None: + destination.write_text(json.dumps(source_data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def load_yaml_template(template_path: Path) -> dict[str, Any]: + with template_path.open("r", encoding="utf-8") as handle: + return yaml.safe_load(handle) + + +def build_stateful_sam() -> None: + run_command([ + "sam", + "build", + "--template-file", + str(SAM_TEMPLATE.relative_to(REPO_ROOT)), + "--region", + "eu-west-2" + ]) + + +def synthesize_stateful_cdk() -> None: + env = dict(os.environ) + env.update({ + "CDK_APP_NAME": "PsuApiApp", + "CDK_CONFIG_stackMode": "stateful", + "CDK_CONFIG_stackName": "psu-cdk-stateful", + "CDK_CONFIG_versionNumber": "undefined", + "CDK_CONFIG_commitId": "undefined", + "CDK_CONFIG_isPullRequest": "true", + "CDK_CONFIG_logRetentionInDays": "30", + "CDK_CONFIG_environment": "dev", + "CDK_CONFIG_enableDynamoDBAutoScaling": "true", + "CDK_CONFIG_enableBackup": "false" + }) + run_command(["npm", "run", "cdk-synth", "--workspace", "packages/cdk"], env=env) + + +def rewrite_condition_name(condition_name: str, context: TemplateContext) -> str: + if condition_name in context.local_condition_names: + return prefixed_name(context.prefix, condition_name) + return condition_name + + +def rewrite_ref(reference_name: str, context: TemplateContext) -> Any: + if reference_name in context.passthrough_parameter_names: + return {REF_KEY: reference_name} + + if reference_name in context.parameter_bindings: + return deep_copy(context.parameter_bindings[reference_name]) + + if reference_name in context.parameter_defaults: + return deep_copy(context.parameter_defaults[reference_name]) + + if reference_name in context.local_resource_names and reference_name not in context.nested_application_names: + return {REF_KEY: prefixed_name(context.prefix, reference_name)} + + return {REF_KEY: reference_name} + + +def parse_getatt(getatt_value: Any) -> tuple[str, str] | None: + if isinstance(getatt_value, list) and len(getatt_value) == 2: + return str(getatt_value[0]), str(getatt_value[1]) + + if isinstance(getatt_value, str) and "." in getatt_value: + target, attribute = getatt_value.split(".", 1) + return target, attribute + + return None + + +def rewrite_getatt(getatt_value: Any, context: TemplateContext) -> Any: + parsed_getatt = parse_getatt(getatt_value) + if not parsed_getatt: + return {GETATT_KEY: getatt_value} + + target_name, attribute_name = parsed_getatt + + if target_name in context.nested_output_values and attribute_name.startswith("Outputs."): + output_name = attribute_name.split(".", 1)[1] + if output_name in context.nested_output_values[target_name]: + return deep_copy(context.nested_output_values[target_name][output_name]) + + if target_name in context.local_resource_names and target_name not in context.nested_application_names: + return {GETATT_KEY: [prefixed_name(context.prefix, target_name), attribute_name]} + + return {GETATT_KEY: getatt_value} + + +def rewrite_resource_sub_phrase( + phrase: str, + template_string: str, + context: TemplateContext +) -> tuple[str, dict[str, Any] | None]: + if "." in phrase: + target_name, attribute_name = phrase.split(".", 1) + if target_name in context.local_resource_names and target_name not in context.nested_application_names: + return ( + template_string.replace( + f"${{{phrase}}}", + f"${{{prefixed_name(context.prefix, target_name)}.{attribute_name}}}" + ), + None + ) + + if target_name in context.nested_output_values and attribute_name.startswith("Outputs."): + output_name = attribute_name.split(".", 1)[1] + if output_name in context.nested_output_values[target_name]: + return template_string, deep_copy(context.nested_output_values[target_name][output_name]) + + if phrase in context.local_resource_names and phrase not in context.nested_application_names: + return template_string.replace( + f"${{{phrase}}}", + f"${{{prefixed_name(context.prefix, phrase)}}}" + ), None + + return template_string, None + + +def rewrite_parameter_sub_phrase(phrase: str, context: TemplateContext) -> Any | None: + if phrase in context.parameter_bindings: + return deep_copy(context.parameter_bindings[phrase]) + + if phrase in context.parameter_defaults: + return deep_copy(context.parameter_defaults[phrase]) + + return None + + +def rewrite_substitution(substitution_value: Any, context: TemplateContext) -> Any: + if isinstance(substitution_value, str): + template_string = substitution_value + variables: dict[str, Any] = {} + original_used_map = False + else: + template_string = substitution_value[0] + variables = {key: rewrite_node(value, context) for key, value in substitution_value[1].items()} + original_used_map = True + + for token in SUB_TOKEN_PATTERN.findall(template_string): + if token in variables: + continue + + template_string, resource_value = rewrite_resource_sub_phrase(token, template_string, context) + if resource_value is not None: + variables[token] = resource_value + continue + + parameter_value = rewrite_parameter_sub_phrase(token, context) + if parameter_value is not None: + variables[token] = parameter_value + + if variables or original_used_map: + return {SUB_KEY: [template_string, variables]} + + return {SUB_KEY: template_string} + + +def rewrite_node(node: Any, context: TemplateContext) -> Any: + if isinstance(node, list): + return [rewrite_node(item, context) for item in node] + + if not isinstance(node, dict): + return node + + if set(node.keys()) == {REF_KEY}: + return rewrite_ref(node[REF_KEY], context) + + if set(node.keys()) == {GETATT_KEY}: + return rewrite_getatt(node[GETATT_KEY], context) + + if set(node.keys()) == {SUB_KEY}: + return rewrite_substitution(node[SUB_KEY], context) + + if set(node.keys()) == {IF_KEY}: + condition_name, true_value, false_value = node[IF_KEY] + return { + IF_KEY: [ + rewrite_condition_name(condition_name, context), + rewrite_node(true_value, context), + rewrite_node(false_value, context) + ] + } + + if set(node.keys()) == {EQUALS_KEY}: + return {EQUALS_KEY: rewrite_node(node[EQUALS_KEY], context)} + + rewritten_node: dict[str, Any] = {} + for key, value in node.items(): + if key == "Condition" and isinstance(value, str): + rewritten_node[key] = rewrite_condition_name(value, context) + else: + rewritten_node[key] = rewrite_node(value, context) + return rewritten_node + + +def expand_built_template( + template_path: Path, + prefix: str = "", + parameter_bindings: dict[str, Any] | None = None, + passthrough_parameter_names: set[str] | None = None +) -> ExpandedTemplate: + template = load_yaml_template(template_path) + template_directory = template_path.parent + resource_map = template.get("Resources", {}) + nested_application_names = { + logical_id + for logical_id, resource in resource_map.items() + if resource.get("Type") == "AWS::Serverless::Application" + } + parameter_defaults = { + parameter_name: parameter_definition["Default"] + for parameter_name, parameter_definition in template.get("Parameters", {}).items() + if isinstance(parameter_definition, dict) and "Default" in parameter_definition + } + + base_context = TemplateContext( + prefix=prefix, + parameter_bindings=parameter_bindings or {}, + parameter_defaults=parameter_defaults, + passthrough_parameter_names=passthrough_parameter_names or set(), + local_resource_names=set(resource_map.keys()), + nested_application_names=nested_application_names, + local_condition_names=set(template.get("Conditions", {}).keys()), + nested_output_values={} + ) + + expanded_resources: dict[str, Any] = {} + expanded_outputs: dict[str, Any] = {} + expanded_conditions: dict[str, Any] = {} + nested_output_values: dict[str, dict[str, Any]] = {} + + for logical_id in nested_application_names: + nested_application = resource_map[logical_id] + nested_location = nested_application["Properties"]["Location"] + nested_parameter_bindings = { + name: rewrite_node(value, base_context) + for name, value in nested_application["Properties"].get("Parameters", {}).items() + } + nested_template = expand_built_template( + template_directory / nested_location, + prefix=prefixed_name(prefix, logical_id), + parameter_bindings=nested_parameter_bindings, + passthrough_parameter_names=passthrough_parameter_names + ) + expanded_resources.update(nested_template.resources) + expanded_outputs.update(nested_template.outputs) + expanded_conditions.update(nested_template.conditions) + nested_output_values[logical_id] = nested_template.output_values + + rewrite_context = TemplateContext( + prefix=prefix, + parameter_bindings=parameter_bindings or {}, + parameter_defaults=parameter_defaults, + passthrough_parameter_names=passthrough_parameter_names or set(), + local_resource_names=set(resource_map.keys()), + nested_application_names=nested_application_names, + local_condition_names=set(template.get("Conditions", {}).keys()), + nested_output_values=nested_output_values + ) + + for condition_name, condition_definition in template.get("Conditions", {}).items(): + expanded_conditions[prefixed_name(prefix, condition_name)] = rewrite_node(condition_definition, rewrite_context) + + for logical_id, resource_definition in resource_map.items(): + if logical_id in nested_application_names: + continue + expanded_resources[prefixed_name(prefix, logical_id)] = rewrite_node(resource_definition, rewrite_context) + + output_values: dict[str, Any] = {} + for output_name, output_definition in template.get("Outputs", {}).items(): + rewritten_output = rewrite_node(output_definition, rewrite_context) + expanded_outputs[prefixed_name(prefix, output_name)] = rewritten_output + output_values[output_name] = rewritten_output["Value"] + + return ExpandedTemplate( + resources=expanded_resources, + outputs=expanded_outputs, + output_values=output_values, + conditions=expanded_conditions + ) + + +def export_sam_json() -> None: + root_template = load_yaml_template(SAM_BUILD_TEMPLATE) + expanded_template = expand_built_template( + SAM_BUILD_TEMPLATE, + passthrough_parameter_names=set(root_template.get("Parameters", {}).keys()) + ) + + sam_template: dict[str, Any] = { + "AWSTemplateFormatVersion": root_template.get("AWSTemplateFormatVersion", "2010-09-09"), + "Description": "Flattened stateful SAM resources for comparison", + "Parameters": root_template.get("Parameters", {}), + "Resources": expanded_template.resources, + "Outputs": expanded_template.outputs + } + + if expanded_template.conditions: + sam_template["Conditions"] = expanded_template.conditions + + write_sorted_json(sam_template, TMP_SAM_JSON) + + +def export_cdk_json() -> None: + with CDK_TEMPLATE.open("r", encoding="utf-8") as handle: + cdk_template = json.load(handle) + write_sorted_json(cdk_template, TMP_CDK_JSON) + + +def canonicalize_resource_name(resource_name: str) -> str: + matched_name = CDK_LOGICAL_ID_SUFFIX_PATTERN.match(resource_name) + if matched_name: + resource_name = matched_name.group('base') + + if resource_name.endswith('ScalingTargetTracking'): + resource_name = f"{resource_name.removesuffix('ScalingTargetTracking')}ScalingPolicy" + + sam_scaling_name_match = SAM_SCALING_NAME_PATTERN.match(resource_name) + if sam_scaling_name_match: + resource_name = ( + f"{sam_scaling_name_match.group('prefix')}" + f"{sam_scaling_name_match.group('access')}Scaling" + f"{sam_scaling_name_match.group('kind')}" + ) + + nested_table_policy_match = SAM_NESTED_TABLE_POLICY_PATTERN.match(resource_name) + if nested_table_policy_match: + resource_name = ( + f"{nested_table_policy_match.group('prefix')}" + f"Table{nested_table_policy_match.group('access')}Policy" + ) + + for original_prefix, canonical_prefix in RESOURCE_NAME_ALIASES.items(): + if resource_name.startswith(original_prefix): + return resource_name.replace(original_prefix, canonical_prefix, 1) + + return resource_name + + +def build_canonical_name_map(resource_names: set[str]) -> dict[str, set[str]]: + canonical_name_map: dict[str, set[str]] = {} + for resource_name in resource_names: + canonical_name = canonicalize_resource_name(resource_name) + if canonical_name in EXCLUDED_CANONICAL_RESOURCE_NAMES: + continue + canonical_name_map.setdefault(canonical_name, set()).add(resource_name) + return canonical_name_map + + +def print_resource_group(title: str, resource_names_by_canonical_name: dict[str, set[str]]) -> None: + print(title) + if not resource_names_by_canonical_name: + print(' (none)') + return + + for canonical_name in sorted(resource_names_by_canonical_name): + actual_names = sorted(resource_names_by_canonical_name[canonical_name]) + if len(actual_names) == 1 and actual_names[0] == canonical_name: + print(f' - {canonical_name}') + else: + print(f" - {canonical_name}: {', '.join(actual_names)}") + + +def assert_resource_equivalence() -> None: + sam_template = json.loads(TMP_SAM_JSON.read_text(encoding='utf-8')) + cdk_template = json.loads(TMP_CDK_JSON.read_text(encoding='utf-8')) + + sam_resource_names = set(sam_template.get('Resources', {}).keys()) + cdk_resource_names = set(cdk_template.get('Resources', {}).keys()) + + sam_resource_names_by_canonical_name = build_canonical_name_map(sam_resource_names) + cdk_resource_names_by_canonical_name = build_canonical_name_map(cdk_resource_names) + + only_in_cdk = { + canonical_name: cdk_resource_names_by_canonical_name[canonical_name] + for canonical_name in cdk_resource_names_by_canonical_name.keys() - sam_resource_names_by_canonical_name.keys() + } + only_in_sam = { + canonical_name: sam_resource_names_by_canonical_name[canonical_name] + for canonical_name in sam_resource_names_by_canonical_name.keys() - cdk_resource_names_by_canonical_name.keys() + } + + print_resource_group('Canonical resource names present in CDK but absent from SAM:', only_in_cdk) + print_resource_group('Canonical resource names present in SAM but absent from CDK:', only_in_sam) + + +def main() -> int: + TMP_DIR.mkdir(exist_ok=True) + build_stateful_sam() + synthesize_stateful_cdk() + export_sam_json() + export_cdk_json() + assert_resource_equivalence() + print(f"Wrote {TMP_SAM_JSON.relative_to(REPO_ROOT)}") + print(f"Wrote {TMP_CDK_JSON.relative_to(REPO_ROOT)}") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except subprocess.CalledProcessError as error: + print(f"Command failed with exit code {error.returncode}: {' '.join(error.cmd)}", file=sys.stderr) + raise SystemExit(error.returncode) from error From 1f07f8ec0fc2c09c32be5d59a0d3ee547231933a Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:46:04 +0000 Subject: [PATCH 16/31] fix(cdk): deprecated pointInTimeRecovery flag --- packages/cdk/resources/Tables.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/cdk/resources/Tables.ts b/packages/cdk/resources/Tables.ts index adc8aeb989..9e85f16a10 100644 --- a/packages/cdk/resources/Tables.ts +++ b/packages/cdk/resources/Tables.ts @@ -152,7 +152,9 @@ export class Tables extends Construct { tableName: prescriptionStatusUpdatesTableName, partitionKey: {name: "PrescriptionID", type: AttributeType.STRING}, sortKey: {name: "TaskID", type: AttributeType.STRING}, - pointInTimeRecovery: true, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true + }, encryption: TableEncryption.CUSTOMER_MANAGED, encryptionKey: prescriptionStatusUpdatesKey, timeToLiveAttribute: "ExpiryTime", @@ -469,7 +471,9 @@ export class Tables extends Construct { tableName: prescriptionNotificationStatesTableName, partitionKey: {name: "NHSNumber", type: AttributeType.STRING}, sortKey: {name: "RequestId", type: AttributeType.STRING}, - pointInTimeRecovery: true, + pointInTimeRecoverySpecification: { + pointInTimeRecoveryEnabled: true + }, encryption: TableEncryption.CUSTOMER_MANAGED, encryptionKey: prescriptionNotificationStatesKey, timeToLiveAttribute: "ExpiryTime", From 4617c824e05682ddbef20734fbd0172a92bf5853 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:50:05 +0000 Subject: [PATCH 17/31] ops: revert changes to ci and release.yml --- .github/workflows/ci.yml | 90 +++------ .github/workflows/release.yml | 339 +++++++--------------------------- 2 files changed, 96 insertions(+), 333 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fec7ac0778..527d56b536 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,48 +51,7 @@ jobs: branch_name: main tag_format: ${{ needs.get_config_values.outputs.tag_format }} - cdk_package_code: - needs: [get_commit_id, tag_release, get_config_values] - uses: ./.github/workflows/cdk_package_code.yml - permissions: - contents: read - packages: read - id-token: write - with: - BRANCH_NAME: main - 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 }} - - cdk_release_dev: - needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] - uses: ./.github/workflows/cdk_release_code.yml - permissions: - contents: write - id-token: write - with: - pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - BRANCH_NAME: main - STACK_NAME: psu-cdk - AWS_ENVIRONMENT: dev - VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} - COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - STACK_MODE: stateless - LOG_RETENTION_IN_DAYS: "30" - LOG_LEVEL: DEBUG - IS_PULL_REQUEST: false - TRUSTSTORE_FILE: psu-truststore.pem - FORWARD_CSOC_LOGS: false - DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true - EXPOSE_GET_STATUS_UPDATES: false - SAM_STACK_NAME: psu - ENABLE_POST_DATED_NOTIFICATIONS: true - REQUIRE_APPLICATION_NAME: false - ENABLE_BACKUP: true - secrets: - CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} - - sam_package_code: + package_code: needs: [tag_release, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml permissions: @@ -103,7 +62,7 @@ jobs: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} release_dev: - needs: [tag_release, sam_package_code, get_commit_id, get_config_values] + needs: [tag_release, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -152,40 +111,51 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_sandbox_dev: - needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] - uses: ./.github/workflows/cdk_release_code.yml + needs: [tag_release, package_code, get_commit_id, get_config_values] + uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write id-token: write with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - BRANCH_NAME: main - APP_NAME: PsuApiSandboxApp + ARTIFACT_BUCKET_PREFIX: ${{needs.tag_release.outputs.version_tag}}-sandbox STACK_NAME: psu-sandbox AWS_ENVIRONMENT: dev + APIGEE_ENVIRONMENT: internal-dev-sandbox + ENABLE_MUTUAL_TLS: true + BUILD_ARTIFACT: packaged_sandbox_code + TRUSTSTORE_FILE: psu-sandbox-truststore.pem VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{needs.get_commit_id.outputs.commit_id}} - LOG_RETENTION_IN_DAYS: "30" + LOG_RETENTION_DAYS: 30 + DEPLOY_APIGEE: true + DYNAMODB_AUTOSCALE: false + DEPLOY_APIGEE_CPSU: true + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true + EXPOSE_GET_STATUS_UPDATES: false + RUN_REGRESSION_TEST: false + STATE_MACHINE_LOG_LEVEL: ALL LOG_LEVEL: DEBUG + ENABLE_BACKUP: "False" + ENABLE_NOTIFICATIONS_INTERNAL: false + ENABLE_NOTIFICATIONS_EXTERNAL: false + ENABLE_POST_DATED_NOTIFICATIONS: true + ENABLED_SYSTEMS: "Internal Test System" + BLOCKED_SITE_ODS_CODES: "B3J1Z" + ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" # Workaround empty string handling + NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" + NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" + MTLS_KEY: psu-mtls-1 IS_PULL_REQUEST: false - TRUSTSTORE_FILE: psu-sandbox-truststore.pem - TRUST_STORE_VERSION: "none" - ENABLE_MUTUAL_TLS: true - ENABLE_SPLUNK: false - ENABLE_BACKUP: false FORWARD_CSOC_LOGS: false secrets: + REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} release_qa: needs: - [ - tag_release, - release_dev, - sam_package_code, - get_commit_id, - get_config_values, - ] + [tag_release, release_dev, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 019898b31e..fb268dc86a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,82 +53,7 @@ jobs: secrets: EXECUTE_JIRA_LAMBDA_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_EXECUTE_LAMBDA_ROLE }} - cdk_package_code: - needs: [get_commit_id, tag_release, get_config_values] - uses: ./.github/workflows/cdk_package_code.yml - permissions: - contents: read - packages: read - id-token: write - with: - BRANCH_NAME: main - 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 }} - - cdk_release_dev: - needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] - uses: ./.github/workflows/cdk_release_code.yml - permissions: - contents: write - id-token: write - with: - pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - BRANCH_NAME: main - STACK_NAME: psu-cdk - AWS_ENVIRONMENT: dev - VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} - COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - STACK_MODE: stateless - LOG_RETENTION_IN_DAYS: "30" - LOG_LEVEL: DEBUG - IS_PULL_REQUEST: false - TRUSTSTORE_FILE: psu-truststore.pem - FORWARD_CSOC_LOGS: false - DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true - EXPOSE_GET_STATUS_UPDATES: false - SAM_STACK_NAME: psu - ENABLE_POST_DATED_NOTIFICATIONS: true - REQUIRE_APPLICATION_NAME: false - ENABLE_BACKUP: true - secrets: - CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} - - cdk_stateful_release_dev: - needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] - uses: ./.github/workflows/cdk_release_code.yml - permissions: - contents: write - id-token: write - with: - pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - BRANCH_NAME: main - STACK_NAME: psu-cdk-stateful - AWS_ENVIRONMENT: dev - VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} - COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - STACK_MODE: stateful - RUN_DIFF: true - LOG_RETENTION_IN_DAYS: "30" - IS_PULL_REQUEST: false - ENABLE_DYNAMODB_AUTO_SCALING: false - ENABLE_BACKUP: true - ENABLED_SITE_ODS_CODES: "A83008,FA565" - ENABLED_SYSTEMS: "Internal Test System" - ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" - BLOCKED_SITE_ODS_CODES: "XXXXX" - NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" - NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" - ENABLE_NOTIFICATIONS_EXTERNAL: "false" - ENABLE_NOTIFICATIONS_INTERNAL: "false" - TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} - TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} - TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} - TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} - secrets: - CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} - - sam_package_code: + package_code: needs: [tag_release, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml permissions: @@ -139,7 +64,7 @@ jobs: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} release_dev: - needs: [tag_release, sam_package_code, get_commit_id, get_config_values] + needs: [tag_release, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -192,30 +117,50 @@ jobs: REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_dev_sandbox: - needs: [cdk_package_code, get_commit_id, tag_release, get_config_values] - uses: ./.github/workflows/cdk_release_code.yml + needs: [tag_release, package_code, get_commit_id, get_config_values] + uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write id-token: write with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - BRANCH_NAME: main - APP_NAME: PsuApiSandboxApp + ARTIFACT_BUCKET_PREFIX: ${{needs.tag_release.outputs.version_tag}}-sandbox STACK_NAME: psu-sandbox AWS_ENVIRONMENT: dev + APIGEE_ENVIRONMENT: internal-dev-sandbox + ENABLE_MUTUAL_TLS: true + BUILD_ARTIFACT: packaged_sandbox_code + TRUSTSTORE_FILE: psu-sandbox-truststore.pem VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} COMMIT_ID: ${{needs.get_commit_id.outputs.commit_id}} - LOG_RETENTION_IN_DAYS: "30" - LOG_LEVEL: DEBUG + LOG_RETENTION_DAYS: 30 + DEPLOY_APIGEE: true + DYNAMODB_AUTOSCALE: false + DEPLOY_APIGEE_CPSU: true + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true + EXPOSE_GET_STATUS_UPDATES: false + RUN_REGRESSION_TEST: false + STATE_MACHINE_LOG_LEVEL: ALL + ENABLE_BACKUP: "False" + ENABLE_NOTIFICATIONS_INTERNAL: false + ENABLE_NOTIFICATIONS_EXTERNAL: false + ENABLE_POST_DATED_NOTIFICATIONS: false + ENABLED_SYSTEMS: "Internal Test System" + BLOCKED_SITE_ODS_CODES: "B3J1Z" + ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" + NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" + NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" + MTLS_KEY: psu-mtls-1 IS_PULL_REQUEST: false - TRUSTSTORE_FILE: psu-sandbox-truststore.pem - TRUST_STORE_VERSION: "none" - ENABLE_MUTUAL_TLS: true - ENABLE_SPLUNK: false - ENABLE_BACKUP: false + TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} + TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} + TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} + TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} FORWARD_CSOC_LOGS: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} + PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} + REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_ref: needs: @@ -223,7 +168,7 @@ jobs: tag_release, release_dev, release_dev_sandbox, - sam_package_code, + package_code, get_commit_id, get_config_values, ] @@ -273,54 +218,13 @@ jobs: PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} - cdk_stateful_release_ref: - needs: - [ - cdk_stateful_release_dev, - cdk_package_code, - get_commit_id, - tag_release, - get_config_values, - ] - uses: ./.github/workflows/cdk_release_code.yml - permissions: - contents: write - id-token: write - with: - pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - BRANCH_NAME: main - STACK_NAME: psu-cdk-stateful - AWS_ENVIRONMENT: ref - VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} - COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - STACK_MODE: stateful - RUN_DIFF: true - LOG_RETENTION_IN_DAYS: "30" - IS_PULL_REQUEST: false - ENABLE_DYNAMODB_AUTO_SCALING: true - ENABLE_BACKUP: false - ENABLED_SITE_ODS_CODES: "A83008,FA565" - ENABLED_SYSTEMS: "Internal Test System, Apotec Ltd - Apotec CRM - Production, CrxPatientApp, nhsPrescriptionApp, Titan PSU Prod" - ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" - BLOCKED_SITE_ODS_CODES: "B3J1Z" - NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" - NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" - ENABLE_NOTIFICATIONS_EXTERNAL: "false" - ENABLE_NOTIFICATIONS_INTERNAL: "false" - TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} - TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} - TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} - TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} - secrets: - CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.REF_CLOUD_FORMATION_DEPLOY_ROLE }} - release_qa: needs: [ tag_release, release_dev, release_dev_sandbox, - sam_package_code, + package_code, get_commit_id, get_config_values, ] @@ -370,56 +274,9 @@ jobs: PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PTL_ROLE }} REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} - cdk_stateful_release_qa: - needs: - [ - cdk_stateful_release_dev, - cdk_package_code, - get_commit_id, - tag_release, - get_config_values, - ] - uses: ./.github/workflows/cdk_release_code.yml - permissions: - contents: write - id-token: write - with: - pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - BRANCH_NAME: main - STACK_NAME: psu-cdk-stateful - AWS_ENVIRONMENT: qa - VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} - COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - STACK_MODE: stateful - RUN_DIFF: true - LOG_RETENTION_IN_DAYS: "30" - IS_PULL_REQUEST: false - ENABLE_DYNAMODB_AUTO_SCALING: false - ENABLE_BACKUP: false - ENABLED_SITE_ODS_CODES: "A83008,FA565" - ENABLED_SYSTEMS: "Internal Test System, Apotec Ltd - Apotec CRM - Production, CrxPatientApp, nhsPrescriptionApp, Titan PSU Prod" - ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" - BLOCKED_SITE_ODS_CODES: "B3J1Z" - NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" - NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" - ENABLE_NOTIFICATIONS_EXTERNAL: "false" - ENABLE_NOTIFICATIONS_INTERNAL: "false" - TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} - TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} - TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} - TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} - secrets: - CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.QA_CLOUD_FORMATION_DEPLOY_ROLE }} - release_int: needs: - [ - tag_release, - release_qa, - sam_package_code, - get_commit_id, - get_config_values, - ] + [tag_release, release_qa, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write @@ -470,79 +327,52 @@ jobs: PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PROD_ROLE }} REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} - cdk_stateful_release_int: + release_int_sandbox: needs: - [ - cdk_stateful_release_qa, - cdk_package_code, - get_commit_id, - tag_release, - get_config_values, - ] - uses: ./.github/workflows/cdk_release_code.yml + [tag_release, release_qa, package_code, get_commit_id, get_config_values] + uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write id-token: write with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - BRANCH_NAME: main - STACK_NAME: psu-cdk-stateful + ARTIFACT_BUCKET_PREFIX: ${{needs.tag_release.outputs.version_tag}}-sandbox + STACK_NAME: psu-sandbox AWS_ENVIRONMENT: int - VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} - COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - STACK_MODE: stateful - RUN_DIFF: true - LOG_RETENTION_IN_DAYS: "30" - IS_PULL_REQUEST: false - ENABLE_DYNAMODB_AUTO_SCALING: false - ENABLE_BACKUP: true - ENABLED_SITE_ODS_CODES: "A83008,FA565" - ENABLED_SYSTEMS: "CrxPatientApp" + APIGEE_ENVIRONMENT: sandbox + ENABLE_MUTUAL_TLS: true + BUILD_ARTIFACT: packaged_sandbox_code + TRUSTSTORE_FILE: psu-sandbox-truststore.pem + VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} + COMMIT_ID: ${{needs.get_commit_id.outputs.commit_id}} + LOG_RETENTION_DAYS: 30 + DEPLOY_APIGEE: true + DYNAMODB_AUTOSCALE: false + DEPLOY_APIGEE_CPSU: true + DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true + EXPOSE_GET_STATUS_UPDATES: false + RUN_REGRESSION_TEST: false + STATE_MACHINE_LOG_LEVEL: ALL + ENABLE_BACKUP: "False" + ENABLE_NOTIFICATIONS_INTERNAL: false + ENABLE_NOTIFICATIONS_EXTERNAL: false + ENABLE_POST_DATED_NOTIFICATIONS: false + ENABLED_SYSTEMS: "Internal Test System, Apotec Ltd - Apotec CRM - Production, CrxPatientApp, nhsPrescriptionApp, Titan PSU Prod" + BLOCKED_SITE_ODS_CODES: "B3J1Z" ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" - BLOCKED_SITE_ODS_CODES: "XXXXX" NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" - ENABLE_NOTIFICATIONS_EXTERNAL: "true" - ENABLE_NOTIFICATIONS_INTERNAL: "true" + MTLS_KEY: psu-mtls-1 + IS_PULL_REQUEST: false TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} TEST_PRESCRIPTIONS_4: ${{ vars.TEST_PRESCRIPTIONS_4_VALUES }} - secrets: - CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.INT_CLOUD_FORMATION_DEPLOY_ROLE }} - - release_int_sandbox: - needs: - [ - cdk_package_code, - tag_release, - release_qa, - get_commit_id, - get_config_values, - ] - uses: ./.github/workflows/cdk_release_code.yml - permissions: - contents: write - id-token: write - with: - pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - BRANCH_NAME: main - APP_NAME: PsuApiSandboxApp - STACK_NAME: psu-sandbox - AWS_ENVIRONMENT: int - VERSION_NUMBER: ${{needs.tag_release.outputs.version_tag}} - COMMIT_ID: ${{needs.get_commit_id.outputs.commit_id}} - LOG_RETENTION_IN_DAYS: "30" - LOG_LEVEL: DEBUG - IS_PULL_REQUEST: false - TRUSTSTORE_FILE: psu-sandbox-truststore.pem - TRUST_STORE_VERSION: "none" - ENABLE_MUTUAL_TLS: true - ENABLE_SPLUNK: false - ENABLE_BACKUP: false FORWARD_CSOC_LOGS: false secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.INT_CLOUD_FORMATION_DEPLOY_ROLE }} + PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PROD_ROLE }} + REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} release_prod: needs: @@ -550,7 +380,7 @@ jobs: tag_release, release_int, release_int_sandbox, - sam_package_code, + package_code, get_commit_id, get_config_values, ] @@ -599,40 +429,3 @@ jobs: DEV_CLOUD_FORMATION_EXECUTE_LAMBDA_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_EXECUTE_LAMBDA_ROLE }} PROXYGEN_ROLE: ${{ secrets.PROXYGEN_PROD_ROLE }} REGRESSION_TESTS_PEM: ${{ secrets.REGRESSION_TESTS_PEM }} - - cdk_stateful_release_prod: - needs: - [ - cdk_stateful_release_int, - cdk_package_code, - get_commit_id, - tag_release, - get_config_values, - ] - uses: ./.github/workflows/cdk_release_code.yml - permissions: - contents: write - id-token: write - with: - pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - BRANCH_NAME: main - STACK_NAME: psu-cdk-stateful - AWS_ENVIRONMENT: prod - VERSION_NUMBER: ${{ needs.tag_release.outputs.version_tag }} - COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - STACK_MODE: stateful - RUN_DIFF: true - LOG_RETENTION_IN_DAYS: "731" - IS_PULL_REQUEST: false - ENABLE_DYNAMODB_AUTO_SCALING: true - ENABLE_BACKUP: true - ENABLED_SITE_ODS_CODES: "A83008,FA565" - ENABLED_SYSTEMS: "CrxPatientApp" - ENABLED_SUPPLIER_APPLICATION_IDS: "XXXXX" - BLOCKED_SITE_ODS_CODES: "XXXXX" - NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" - NOTIFY_API_BASE_URL: "https://api.service.nhs.uk" - ENABLE_NOTIFICATIONS_EXTERNAL: "true" - ENABLE_NOTIFICATIONS_INTERNAL: "true" - secrets: - CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.PROD_CLOUD_FORMATION_DEPLOY_ROLE }} From cdf4477ba2e707bbf8662390ebad922ceea2474d Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:07:59 +0000 Subject: [PATCH 18/31] fix(cdk): split deploy into statless and stateful --- .github/workflows/pull_request.yml | 4 ++-- Makefile | 30 ++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 4e1d4f67cb..fd3713dd8a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -108,7 +108,7 @@ jobs: cdk_release_code: needs: - [get_issue_number, cdk_package_code, get_commit_id, get_config_values] + [get_issue_number, cdk_package_code, get_commit_id, get_config_values, cdk_release_stateful_code] uses: ./.github/workflows/cdk_release_code.yml permissions: contents: write @@ -135,7 +135,7 @@ jobs: secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} - cdk_stateful_release_code: + cdk_release_stateful_code: needs: [get_issue_number, cdk_package_code, get_commit_id, get_config_values] uses: ./.github/workflows/cdk_release_code.yml diff --git a/Makefile b/Makefile index 2dced555aa..b9a1782e56 100644 --- a/Makefile +++ b/Makefile @@ -256,8 +256,34 @@ deep-clean: clean find . -name 'node_modules' -type d -prune -exec rm -rf '{}' + poetry env remove --all -cdk-deploy: - REQUIRE_APPROVAL="$${REQUIRE_APPROVAL:-any-change}" && \ +cdk-deploy: cdk-stateful-deploy cdk-stateless-deploy + +cdk-stateful-deploy: + CDK_APP_NAME=PsuApiApp \ + CDK_CONFIG_stackMode=stateful \ + CDK_CONFIG_stackName=psu-cdk-stateful \ + CDK_CONFIG_logRetentionInDays=30 \ + CDK_CONFIG_environment=dev \ + CDK_CONFIG_enableDynamoDBAutoScaling=false \ + CDK_CONFIG_enableBackup=false \ + REQUIRE_APPROVAL="$${REQUIRE_APPROVAL:-any-change}" \ + npm run cdk-deploy --workspace packages/cdk + +cdk-stateless-deploy: + CDK_APP_NAME=PsuApiApp \ + CDK_CONFIG_stackMode=stateless \ + CDK_CONFIG_stackName=psu-cdk \ + CDK_CONFIG_samStackName=psu \ + CDK_CONFIG_logRetentionInDays=30 \ + CDK_CONFIG_logLevel=DEBUG \ + CDK_CONFIG_environment=dev \ + CDK_CONFIG_forwardCsocLogs=false \ + CDK_CONFIG_deployCheckPrescriptionStatusUpdate=true \ + CDK_CONFIG_exposeGetStatusUpdates=false \ + CDK_CONFIG_enablePostDatedNotifications=false \ + CDK_CONFIG_requireApplicationName=false \ + CDK_CONFIG_enableBackup=false \ + REQUIRE_APPROVAL="$${REQUIRE_APPROVAL:-any-change}" \ npm run cdk-deploy --workspace packages/cdk cdk-stateless-synth: From 163b50e4db4ba93e7935fa0a2b09d65bd2c9763b Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:08:48 +0000 Subject: [PATCH 19/31] fix(cdk): migrate statemachine to JSONata --- .../Format1UpdatePrescriptionsStatus.ts | 58 +++++++++---------- .../UpdatePrescriptionStatus.ts | 24 ++++---- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts b/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts index 7e3192549e..8b89b04992 100644 --- a/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts +++ b/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts @@ -24,19 +24,17 @@ export class Format1UpdatePrescriptionsStatusDefinition extends Construct { super(scope, id) const catchAllError = new Pass(this, "CatchAllError", { - result: { - value: { - Payload: { - statusCode: 500, - headers: { - "Content-Type": "application/fhir+json", - "Cache-Control": "no-cache" - }, - body: JSON.stringify({ - resourceType: "OperationOutcome", - issue: [{severity: "error", code: "processing", diagnostics: "System error"}] - }) - } + outputs: { + Payload: { + statusCode: 500, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: JSON.stringify({ + resourceType: "OperationOutcome", + issue: [{severity: "error", code: "processing", diagnostics: "System error"}] + }) } } }) @@ -79,24 +77,22 @@ export class Format1UpdatePrescriptionsStatusDefinition extends Construct { callUpdatePrescriptionStatus.addCatch(catchAllError) const translate409To202 = new Pass(this, "Translate 409 to 202", { - result: { - value: { - Payload: { - statusCode: 202, - headers: { - "Content-Type": "application/fhir+json", - "Cache-Control": "no-cache" - }, - body: JSON.stringify({ - resourceType: "OperationOutcome", - issue: [{ - severity: "information", - code: "informational", - diagnostics: - "Duplicate update detected. The message was valid but did not result in an update." - }] - }) - } + outputs: { + Payload: { + statusCode: 202, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: JSON.stringify({ + resourceType: "OperationOutcome", + issue: [{ + severity: "information", + code: "informational", + diagnostics: + "Duplicate update detected. The message was valid but did not result in an update." + }] + }) } } }) diff --git a/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts b/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts index 11847d5284..d06b58cd17 100644 --- a/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts +++ b/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts @@ -23,19 +23,17 @@ export class UpdatePrescriptionStatusDefinition extends Construct { super(scope, id) const catchAllError = new Pass(this, "CatchAllError", { - result: { - value: { - Payload: { - statusCode: 500, - headers: { - "Content-Type": "application/fhir+json", - "Cache-Control": "no-cache" - }, - body: JSON.stringify({ - resourceType: "OperationOutcome", - issue: [{severity: "error", code: "processing", diagnostics: "System error"}] - }) - } + outputs: { + Payload: { + statusCode: 500, + headers: { + "Content-Type": "application/fhir+json", + "Cache-Control": "no-cache" + }, + body: JSON.stringify({ + resourceType: "OperationOutcome", + issue: [{severity: "error", code: "processing", diagnostics: "System error"}] + }) } } }) From 6a4781204b1928a0d2ad943b409161bfd5d1fdf9 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:55:27 +0000 Subject: [PATCH 20/31] fix: stylistic changes following pfp --- packages/cdk/resources/Apis.ts | 47 +++++++--- packages/cdk/resources/Functions.ts | 12 ++- .../Format1UpdatePrescriptionsStatus.ts | 90 ++++++++++++------- .../UpdatePrescriptionStatus.ts | 57 ++++++++---- packages/cdk/resources/StateMachines.ts | 10 ++- 5 files changed, 153 insertions(+), 63 deletions(-) diff --git a/packages/cdk/resources/Apis.ts b/packages/cdk/resources/Apis.ts index c48b5ac1b9..b665989775 100644 --- a/packages/cdk/resources/Apis.ts +++ b/packages/cdk/resources/Apis.ts @@ -18,13 +18,31 @@ export interface ApisProps { readonly csocApiGatewayDestination: string readonly deployCheckPrescriptionStatusUpdate: boolean readonly exposeGetStatusUpdates: boolean - functions: {[key: string]: TypescriptLambdaFunction} - stateMachines: {[key: string]: ExpressStateMachine} + readonly functions: { + readonly status: TypescriptLambdaFunction + readonly capabilityStatement: TypescriptLambdaFunction + readonly nhsNotifyUpdateCallback: TypescriptLambdaFunction + readonly getStatusUpdates: TypescriptLambdaFunction + readonly checkPrescriptionStatusUpdates?: TypescriptLambdaFunction + } + readonly stateMachines: { + readonly updatePrescriptionStatus: ExpressStateMachine + readonly format1UpdatePrescriptionsStatus: ExpressStateMachine + } } export class Apis extends Construct { - apis: {[key: string]: RestApiGateway} - endpoints: {[key: string]: Construct} + public readonly apis: { + readonly api: RestApiGateway + } + public readonly endpoints: { + readonly rootResource: Construct + readonly format1UpdatePrescriptionStatusEndpoint: StateMachineEndpoint + readonly status: LambdaEndpoint + readonly capabilityStatement: LambdaEndpoint + readonly notificationDeliveryStatusCallback: LambdaEndpoint + readonly checkPrescriptionStatusUpdates?: LambdaEndpoint + } public constructor(scope: Construct, id: string, props: ApisProps) { super(scope, id) @@ -105,24 +123,25 @@ export class Apis extends Construct { lambdaFunction: props.functions.capabilityStatement }) - this.endpoints = { - rootResource, - format1UpdatePrescriptionStatusEndpoint: format1PsuEndpoint, - status: statusEndpoint, - capabilityStatement: capabilityStatementEndpoint, - notificationDeliveryStatusCallback: notifyCallbackEndpoint - } - // GET /checkprescriptionstatusupdates — conditional Lambda proxy integration + let checkPsu : LambdaEndpoint | undefined if (props.deployCheckPrescriptionStatusUpdate) { - const checkPsu = new LambdaEndpoint(this, "CheckPrescriptionStatusUpdatesEndpoint", { + checkPsu = new LambdaEndpoint(this, "CheckPrescriptionStatusUpdatesEndpoint", { parentResource: rootResource, resourceName: "checkprescriptionstatusupdates", method: HttpMethod.GET, restApiGatewayRole: apiGateway.role, lambdaFunction: props.functions.checkPrescriptionStatusUpdates }) - this.endpoints.checkPrescriptionStatusUpdates = checkPsu + } + + this.endpoints = { + rootResource, + format1UpdatePrescriptionStatusEndpoint: format1PsuEndpoint, + status: statusEndpoint, + capabilityStatement: capabilityStatementEndpoint, + notificationDeliveryStatusCallback: notifyCallbackEndpoint, + checkPrescriptionStatusUpdates: checkPsu } // POST /get-status-updates — conditional Lambda integration (non-proxy) diff --git a/packages/cdk/resources/Functions.ts b/packages/cdk/resources/Functions.ts index 1c73242a6b..3e90f808c5 100644 --- a/packages/cdk/resources/Functions.ts +++ b/packages/cdk/resources/Functions.ts @@ -31,7 +31,17 @@ export interface FunctionsProps { const baseDir = resolve(__dirname, "../../..") export class Functions extends Construct { - functions: {[key: string]: TypescriptLambdaFunction} + public readonly functions: { + readonly updatePrescriptionStatus: TypescriptLambdaFunction + readonly convertRequestToFhirFormat: TypescriptLambdaFunction + readonly getStatusUpdates: TypescriptLambdaFunction + readonly status: TypescriptLambdaFunction + readonly capabilityStatement: TypescriptLambdaFunction + readonly nhsNotifyUpdateCallback: TypescriptLambdaFunction + readonly notifyProcessor: TypescriptLambdaFunction + readonly postDatedNotifyLambda: TypescriptLambdaFunction + readonly checkPrescriptionStatusUpdates?: TypescriptLambdaFunction + } public constructor(scope: Construct, id: string, props: FunctionsProps) { super(scope, id) diff --git a/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts b/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts index 8b89b04992..42dc79c963 100644 --- a/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts +++ b/packages/cdk/resources/StateMachineDefinitions/Format1UpdatePrescriptionsStatus.ts @@ -23,7 +23,40 @@ export class Format1UpdatePrescriptionsStatusDefinition extends Construct { ) { super(scope, id) - const catchAllError = new Pass(this, "CatchAllError", { + const catchAllError = this.createCatchAllError() + const callConvertRequestToFhirFormat = this.createCallConvertRequestToFhirFormat( + props.convertRequestToFhirFormatFunction, + catchAllError + ) + const callUpdatePrescriptionStatus = this.createCallUpdatePrescriptionStatus( + props.updatePrescriptionStatusFunction, + catchAllError + ) + + this.definition = Chain + .start(callConvertRequestToFhirFormat) + .next( + new Choice(this, "Convert Request to FHIR result") + .when( + Condition.jsonata("{% $convertStatusCode != 200 %}"), + this.createFailedConvertRequestToFhir() + ) + .otherwise( + callUpdatePrescriptionStatus + .next( + new Choice(this, "Check Update Prescription Status Result") + .when( + Condition.jsonata("{% $updateStatusCode = 409 %}"), + this.createTranslate409To202() + ) + .otherwise(new Pass(this, "End State")) + ) + ) + ) + } + + private createCatchAllError(): Pass { + return new Pass(this, "CatchAllError", { outputs: { Payload: { statusCode: 500, @@ -38,10 +71,17 @@ export class Format1UpdatePrescriptionsStatusDefinition extends Construct { } } }) + } + private createCallConvertRequestToFhirFormat( + convertRequestToFhirFormatFunction: IFunction, + catchAllError: Pass + ): LambdaInvoke { const callConvertRequestToFhirFormat = new LambdaInvoke( - this, "Call Convert Request To Fhir Format", { - lambdaFunction: props.convertRequestToFhirFormatFunction, + this, + "Call Convert Request To Fhir Format", + { + lambdaFunction: convertRequestToFhirFormatFunction, assign: { convertStatusCode: "{% $states.result.Payload.statusCode %}", convertHeaders: "{% $states.result.Payload.headers %}", @@ -50,8 +90,11 @@ export class Format1UpdatePrescriptionsStatusDefinition extends Construct { } ) callConvertRequestToFhirFormat.addCatch(catchAllError) + return callConvertRequestToFhirFormat + } - const failedConvertRequestToFhir = new Pass(this, "Failed Convert Request to FHIR", { + private createFailedConvertRequestToFhir(): Pass { + return new Pass(this, "Failed Convert Request to FHIR", { outputs: { Payload: { statusCode: "{% $convertStatusCode %}", @@ -60,10 +103,17 @@ export class Format1UpdatePrescriptionsStatusDefinition extends Construct { } } }) + } + private createCallUpdatePrescriptionStatus( + updatePrescriptionStatusFunction: IFunction, + catchAllError: Pass + ): LambdaInvoke { const callUpdatePrescriptionStatus = new LambdaInvoke( - this, "Call Update Prescription Status", { - lambdaFunction: props.updatePrescriptionStatusFunction, + this, + "Call Update Prescription Status", + { + lambdaFunction: updatePrescriptionStatusFunction, payload: TaskInput.fromObject({ body: "{% $string($convertBody) %}", headers: "{% $convertHeaders %}" @@ -75,8 +125,11 @@ export class Format1UpdatePrescriptionsStatusDefinition extends Construct { } ) callUpdatePrescriptionStatus.addCatch(catchAllError) + return callUpdatePrescriptionStatus + } - const translate409To202 = new Pass(this, "Translate 409 to 202", { + private createTranslate409To202(): Pass { + return new Pass(this, "Translate 409 to 202", { outputs: { Payload: { statusCode: 202, @@ -96,28 +149,5 @@ export class Format1UpdatePrescriptionsStatusDefinition extends Construct { } } }) - - const endState = new Pass(this, "End State") - - const checkConvertResult = new Choice(this, "Convert Request to FHIR result") - const convertNotOk = Condition.jsonata("{% $convertStatusCode != 200 %}") - - const checkUpdateResult = new Choice(this, "Check Update Prescription Status Result") - const updateIs409 = Condition.jsonata("{% $updateStatusCode = 409 %}") - - this.definition = Chain - .start(callConvertRequestToFhirFormat) - .next( - checkConvertResult - .when(convertNotOk, failedConvertRequestToFhir) - .otherwise( - callUpdatePrescriptionStatus - .next( - checkUpdateResult - .when(updateIs409, translate409To202) - .otherwise(endState) - ) - ) - ) } } diff --git a/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts b/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts index d06b58cd17..a3a03d42ee 100644 --- a/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts +++ b/packages/cdk/resources/StateMachineDefinitions/UpdatePrescriptionStatus.ts @@ -22,7 +22,30 @@ export class UpdatePrescriptionStatusDefinition extends Construct { ) { super(scope, id) - const catchAllError = new Pass(this, "CatchAllError", { + const catchAllError = this.createCatchAllError() + const callFhirValidation = this.createCallFhirValidation( + props.fhirValidationFunction, + catchAllError + ) + const callUpdatePrescriptionStatus = this.createCallUpdatePrescriptionStatus( + props.updatePrescriptionStatusFunction, + catchAllError + ) + + this.definition = Chain + .start(callFhirValidation) + .next( + new Choice(this, "Do FHIR Validation Errors Exist") + .when( + Condition.jsonata("{% $fhirValidationErrorCount > 0 %}"), + this.createReturnFailedFhirValidationErrors() + ) + .otherwise(callUpdatePrescriptionStatus) + ) + } + + private createCatchAllError(): Pass { + return new Pass(this, "CatchAllError", { outputs: { Payload: { statusCode: 500, @@ -37,9 +60,11 @@ export class UpdatePrescriptionStatusDefinition extends Construct { } } }) + } + private createCallFhirValidation(fhirValidationFunction: IFunction, catchAllError: Pass): LambdaInvoke { const callFhirValidation = new LambdaInvoke(this, "Call FHIR Validation", { - lambdaFunction: props.fhirValidationFunction, + lambdaFunction: fhirValidationFunction, assign: { fhirValidationResponse: "{% $states.result.Payload %}", fhirValidationErrorCount: @@ -47,8 +72,11 @@ export class UpdatePrescriptionStatusDefinition extends Construct { } }) callFhirValidation.addCatch(catchAllError) + return callFhirValidation + } - const returnFailedFhirValidationErrors = new Pass(this, "Return Failed FHIR Validation Errors", { + private createReturnFailedFhirValidationErrors(): Pass { + return new Pass(this, "Return Failed FHIR Validation Errors", { outputs: { Payload: { statusCode: 400, @@ -60,23 +88,20 @@ export class UpdatePrescriptionStatusDefinition extends Construct { } } }) + } + private createCallUpdatePrescriptionStatus( + updatePrescriptionStatusFunction: IFunction, + catchAllError: Pass + ): LambdaInvoke { const callUpdatePrescriptionStatus = new LambdaInvoke( - this, "Call Update Prescription Status", { - lambdaFunction: props.updatePrescriptionStatusFunction + this, + "Call Update Prescription Status", + { + lambdaFunction: updatePrescriptionStatusFunction } ) callUpdatePrescriptionStatus.addCatch(catchAllError) - - const doFhirValidationErrorsExist = new Choice(this, "Do FHIR Validation Errors Exist") - const hasErrors = Condition.jsonata("{% $fhirValidationErrorCount > 0 %}") - - this.definition = Chain - .start(callFhirValidation) - .next( - doFhirValidationErrorsExist - .when(hasErrors, returnFailedFhirValidationErrors) - .otherwise(callUpdatePrescriptionStatus) - ) + return callUpdatePrescriptionStatus } } diff --git a/packages/cdk/resources/StateMachines.ts b/packages/cdk/resources/StateMachines.ts index 4fabb92215..f232d0c549 100644 --- a/packages/cdk/resources/StateMachines.ts +++ b/packages/cdk/resources/StateMachines.ts @@ -9,11 +9,17 @@ import {Format1UpdatePrescriptionsStatusDefinition} from "./StateMachineDefiniti export interface StateMachinesProps { readonly stackName: string readonly logRetentionInDays: number - readonly functions: {[key: string]: TypescriptLambdaFunction} + readonly functions: { + readonly updatePrescriptionStatus: TypescriptLambdaFunction + readonly convertRequestToFhirFormat: TypescriptLambdaFunction + } } export class StateMachines extends Construct { - stateMachines: {[key: string]: ExpressStateMachine} + public readonly stateMachines: { + readonly updatePrescriptionStatus: ExpressStateMachine + readonly format1UpdatePrescriptionsStatus: ExpressStateMachine + } public constructor(scope: Construct, id: string, props: StateMachinesProps) { super(scope, id) From 312b75d0b23271f47afbecf1263e3c51f2a250c5 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:51:47 +0000 Subject: [PATCH 21/31] chore: override generic cdk make targets --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index b9a1782e56..8d961bda1b 100644 --- a/Makefile +++ b/Makefile @@ -257,6 +257,10 @@ deep-clean: clean poetry env remove --all cdk-deploy: cdk-stateful-deploy cdk-stateless-deploy + @true + +cdk-synth: cdk-stateful-synth cdk-stateless-synth + @true cdk-stateful-deploy: CDK_APP_NAME=PsuApiApp \ From 1d49060e4a5c6246ed3537d6a5b032a3082755a0 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:15:12 +0000 Subject: [PATCH 22/31] chore: preserve sam pr.yml job names --- .github/workflows/pull_request.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ce46bff6f9..8dbcd847d8 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -151,7 +151,7 @@ jobs: secrets: CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }} - sam_package_code: + package_code: needs: [get_issue_number, get_config_values] uses: ./.github/workflows/run_package_code_and_api.yml permissions: @@ -161,9 +161,9 @@ jobs: with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} - sam_release_code: + release_code: needs: - [get_issue_number, sam_package_code, get_commit_id, get_config_values] + [get_issue_number, package_code, get_commit_id, get_config_values] uses: ./.github/workflows/run_release_code_and_api.yml permissions: contents: write From 44efd5260439bfec19663a4be34eac4cecf70d42 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:35:26 +0000 Subject: [PATCH 23/31] chore: booleans not strings in pr.yml --- .github/workflows/pull_request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8dbcd847d8..7e0a0a4fcb 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -142,8 +142,8 @@ jobs: BLOCKED_SITE_ODS_CODES: "XXXXX" NOTIFY_ROUTING_PLAN_ID: "e57fe5cc-0567-4854-abe2-b7dd9014a50c" NOTIFY_API_BASE_URL: "https://int.api.service.nhs.uk" - ENABLE_NOTIFICATIONS_EXTERNAL: "false" - ENABLE_NOTIFICATIONS_INTERNAL: "true" + ENABLE_NOTIFICATIONS_EXTERNAL: false + ENABLE_NOTIFICATIONS_INTERNAL: true TEST_PRESCRIPTIONS_1: ${{ vars.TEST_PRESCRIPTIONS_1_VALUES }} TEST_PRESCRIPTIONS_2: ${{ vars.TEST_PRESCRIPTIONS_2_VALUES }} TEST_PRESCRIPTIONS_3: ${{ vars.TEST_PRESCRIPTIONS_3_VALUES }} From 92904e90bf267c57df28995bb26990ac92733e42 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:56:58 +0000 Subject: [PATCH 24/31] docs: template.env file --- template.env | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 template.env diff --git a/template.env b/template.env new file mode 100644 index 0000000000..ed77393e90 --- /dev/null +++ b/template.env @@ -0,0 +1,9 @@ +export AWS_DEFAULT_PROFILE=eps-dev +export AWS_ENVIRONMENT="dev" +export AWS_PROFILE=eps-dev +export CDK_CONFIG_stackName=psu-cdk- +export STATE_MACHINE_LOG_LEVEL="ALL" +export TEST_PRESCRIPTIONS_1="" +export TEST_PRESCRIPTIONS_2="" +export TEST_PRESCRIPTIONS_3="" +export TEST_PRESCRIPTIONS_4="" From f0e551bfc0b684acbc9d176baadaed0d9eb5f5d1 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:58:39 +0000 Subject: [PATCH 25/31] chore: new lock for cdk imports --- package-lock.json | 6189 ++++++++++++++++++++++++++++----------------- 1 file changed, 3825 insertions(+), 2364 deletions(-) diff --git a/package-lock.json b/package-lock.json index af8e20d428..d2857e89ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "workspaces": [ + "packages/cdk", "packages/gsul", "packages/nhsd-psu-sandbox", "packages/specification", @@ -53,6 +54,131 @@ "vitest": "^4.1.2" } }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.273", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.273.tgz", + "integrity": "sha512-X57HYUtHt9BQrlrzUNcMyRsDUCoakYNnY6qh5lNwRCHPtQoTfXmuISkfLk0AjLkcbS5lw1LLTQFiQhTDXfiTvg==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "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": "53.20.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-53.20.0.tgz", + "integrity": "sha512-4kLAUO+I8b4nlk1Z2P4n3Ye8UtqCiXk0kJMLUThBnyHLbdz06rwAb+qlb9WZOie7NtPluemVS243ifcBh/NVsQ==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.4" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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/@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/@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": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "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", @@ -296,6 +422,57 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-cloudformation": { + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.1039.0.tgz", + "integrity": "sha512-iT3IEdr2CriBy2h5LR/+O5PadxAoLNFepQ+I7pwPam77e3OhkivMUQJ/7vOfh6HGU0Mh2SENJBOO6xaAenC9fA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-node": "^3.972.38", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.37", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.23", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@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.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-dynamodb": { "version": "3.1038.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.1038.0.tgz", @@ -349,6 +526,124 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-route-53": { + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-route-53/-/client-route-53-3.1039.0.tgz", + "integrity": "sha512-Ph6b3J8OkbhXIs/Xg3og+rG9v58gsE6qEHY5APi4nlRn6CJ1J5qRI5NWpDVE1fFR6e9dIGPuxCcGr/kN34Eo4A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-node": "^3.972.38", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-sdk-route53": "^3.972.12", + "@aws-sdk/middleware-user-agent": "^3.972.37", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.23", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@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.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1039.0.tgz", + "integrity": "sha512-PVH9v0pHYBQnBADSR/m88NgcuJcYqPXfpmkcME66vRF75Y4swwbEVVFbTBFuvxu0YcZiLFXu3lw0FDK00vEa3A==", + "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.974.7", + "@aws-sdk/credential-provider-node": "^3.972.38", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.10", + "@aws-sdk/middleware-expect-continue": "^3.972.10", + "@aws-sdk/middleware-flexible-checksums": "^3.974.15", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-location-constraint": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-sdk-s3": "^3.972.36", + "@aws-sdk/middleware-ssec": "^3.972.10", + "@aws-sdk/middleware-user-agent": "^3.972.37", + "@aws-sdk/region-config-resolver": "^3.972.13", + "@aws-sdk/signature-v4-multi-region": "^3.996.24", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.8", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.23", + "@smithy/config-resolver": "^4.4.17", + "@smithy/core": "^3.23.17", + "@smithy/eventstream-serde-browser": "^4.2.14", + "@smithy/eventstream-serde-config-resolver": "^4.3.14", + "@smithy/eventstream-serde-node": "^4.2.14", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-blob-browser": "^4.2.15", + "@smithy/hash-node": "^4.2.14", + "@smithy/hash-stream-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/md5-js": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.32", + "@smithy/middleware-retry": "^4.5.7", + "@smithy/middleware-serde": "^4.2.20", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.6.1", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.13", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@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.49", + "@smithy/util-defaults-mode-node": "^4.2.54", + "@smithy/util-endpoints": "^3.4.2", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.6", + "@smithy/util-stream": "^4.5.25", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-secrets-manager": { "version": "3.1038.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.1038.0.tgz", @@ -503,13 +798,13 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.974.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.6.tgz", - "integrity": "sha512-8Vu7zGxu+39ChR/s5J7nXBw3a2kMHAi0OfKT8ohgTVjX0qYed/8mIfdBb638oBmKrWCwwKjYAM5J/4gMJ8nAJA==", + "version": "3.974.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.7.tgz", + "integrity": "sha512-YhRC90ofz5oolTJZlA8voU/oUrCB2azi8Usx51k8hhB5LpWbYQMMXKUqSqkoL0Cru+RQJgWTHpAfEDDIwfUhJw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.8", - "@aws-sdk/xml-builder": "^3.972.20", + "@aws-sdk/xml-builder": "^3.972.22", "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", @@ -519,7 +814,7 @@ "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.6", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -527,13 +822,26 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.972.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.7.tgz", + "integrity": "sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.32", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.32.tgz", - "integrity": "sha512-7vA4GHg8NSmQxquJHSBcSM3RgB4ZaaRi6u4+zGFKOmOH6aqlgr2Sda46clkZDYzlirgfY96w15Zj0jh6PT48ng==", + "version": "3.972.33", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.33.tgz", + "integrity": "sha512-bJV7eViSJV6GSuuN+VIdNVPdwPsNSf75BiC2v5alPrjR/OCcqgKwSZInKbDFz9mNeizldsyf67jt6YSIiv53Cw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", @@ -544,12 +852,12 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.34.tgz", - "integrity": "sha512-vBrhWujFCLp1u8ptJRWYlipMutzPptb8pDQ00rKVH9q67T7rGd3VTWIj63aKrlLuY6qSsw1Rt5F/D/7wnNgryA==", + "version": "3.972.35", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.35.tgz", + "integrity": "sha512-x/BQGEIdq0oI+4WxLjKmnQvT7CnF9r8ezdGt7wXwxb7ckHXQz0Zmgxt8v3Ne0JaT3R5YefmuybHX6E8EnsDXyA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/types": "^3.973.8", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.1", @@ -565,19 +873,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.36.tgz", - "integrity": "sha512-FBHyCmV8EB0gUvh1d+CZm87zt2PrdC7OyWexLRoH3I5zWSOUGa+9t58Y5jbxRfwUp3AWpHAFvKY6YzgR845sVA==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.37.tgz", + "integrity": "sha512-eUTpmWfd/BKsq9medhCRcu+GRAhFP2Zrn7/2jKDHHOOjCkhrMoTp/t4cEthqFoG7gE0VGp5wUxrXTdvBCmSmJg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-env": "^3.972.32", - "@aws-sdk/credential-provider-http": "^3.972.34", - "@aws-sdk/credential-provider-login": "^3.972.36", - "@aws-sdk/credential-provider-process": "^3.972.32", - "@aws-sdk/credential-provider-sso": "^3.972.36", - "@aws-sdk/credential-provider-web-identity": "^3.972.36", - "@aws-sdk/nested-clients": "^3.997.4", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-env": "^3.972.33", + "@aws-sdk/credential-provider-http": "^3.972.35", + "@aws-sdk/credential-provider-login": "^3.972.37", + "@aws-sdk/credential-provider-process": "^3.972.33", + "@aws-sdk/credential-provider-sso": "^3.972.37", + "@aws-sdk/credential-provider-web-identity": "^3.972.37", + "@aws-sdk/nested-clients": "^3.997.5", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", @@ -590,13 +898,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.36.tgz", - "integrity": "sha512-IFap01lJKxQc0C/OHmZwZQr/cKq0DhrcmKedRrdnnl42D+P0SImnnnWQjv07uIPqpEdtqmkPXb9TiPYTU+prxQ==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.37.tgz", + "integrity": "sha512-Ty68y8ISSC+g5Q3D0K8uAaoINwvfaOslnNpsF/LgVUxyosYXHawcK2yV4HLXDVugiTTYLQfJfcw0ce5meAGkKw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/nested-clients": "^3.997.4", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/nested-clients": "^3.997.5", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", @@ -609,17 +917,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.37.tgz", - "integrity": "sha512-/WFixFAAiw8WpmjZcI0l4t3DerXLmVinOIfuotmRZnu2qmsFPoqqmstASz0z8bi1pGdFXzeLzf6bwucM3mZcUQ==", + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.38.tgz", + "integrity": "sha512-BQ9XYnBDVxR2HuV5huXYQYF/PZMTsY+EnwfGnCU2cA8Zw63XpkOtPY8WqiMIZMQCrKPQQEiFURS/o9CIolRLqg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.32", - "@aws-sdk/credential-provider-http": "^3.972.34", - "@aws-sdk/credential-provider-ini": "^3.972.36", - "@aws-sdk/credential-provider-process": "^3.972.32", - "@aws-sdk/credential-provider-sso": "^3.972.36", - "@aws-sdk/credential-provider-web-identity": "^3.972.36", + "@aws-sdk/credential-provider-env": "^3.972.33", + "@aws-sdk/credential-provider-http": "^3.972.35", + "@aws-sdk/credential-provider-ini": "^3.972.37", + "@aws-sdk/credential-provider-process": "^3.972.33", + "@aws-sdk/credential-provider-sso": "^3.972.37", + "@aws-sdk/credential-provider-web-identity": "^3.972.37", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", @@ -632,12 +940,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.32", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.32.tgz", - "integrity": "sha512-uZp4tlGbpczV8QxmtIwOpSkcyGtBRR8/T4BAumRKfAt1nwCig3FSCZvrKl6ARDIDVRYn5p2oRcAsfFR01EgMGA==", + "version": "3.972.33", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.33.tgz", + "integrity": "sha512-yfjGksI9WQbdMObb0VeLXqzTLI+a0qXLJT9gCDiv0+X/xjPpI3mTz6a5FibrhpuEKIe0gSgvs3MaoFZy5cx4WA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", @@ -649,14 +957,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.36.tgz", - "integrity": "sha512-DsLr0UHMyKzRJKe2bjlwU8q1cfoXg8TIJKV/xwvnalAemiZLOZunFzj/whGnFDZIBVLdnbLiwv5SvRf1+CSwkg==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.37.tgz", + "integrity": "sha512-fpwE+20ntpp3i9Xb9vUuQfXLDKYHH+5I2V+ZG96SX1nBzrruhy10RXDgmN7t1etOz3c55stlA3TeQASUA451NQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/nested-clients": "^3.997.4", - "@aws-sdk/token-providers": "3.1038.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/nested-clients": "^3.997.5", + "@aws-sdk/token-providers": "3.1039.0", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", @@ -668,13 +976,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.36.tgz", - "integrity": "sha512-uzrURO7frJhHQVVNR5zBJcCYeMYflmXcWBK1+MiBym2Dfjh6nXATrMixrmGZi+97Q7ETZ+y/4lUwAy0Nfnznjw==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.37.tgz", + "integrity": "sha512-aryawqyebf+3WhAFNHfF62rekFpYtVcVN7dQ89qnAWsa4n5hJst8qBG6gXC24WHtW7Nnhkf9ScYnjwo0Brn3bw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/nested-clients": "^3.997.4", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/nested-clients": "^3.997.5", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", @@ -734,29 +1042,87 @@ "@aws-sdk/client-dynamodb": "^3.1038.0" } }, - "node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.972.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.11.tgz", - "integrity": "sha512-vXARCZVFQHdsd6qPPZyC/hh+5x2XsCYKqUQDCqnUlpGpChMpDojOOacQWdLJ+FFXKN8X3cmLOGrtgx/zysCKqQ==", + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.10.tgz", + "integrity": "sha512-Vbc2frZH7wXlMNd+ZZSXUEs/l1Sv8Jj4zUnIfwrYF5lwaLdXHZ9xx4U3rjUcaye3HRhFVc+E5DbBxpRAbB16BA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/endpoint-cache": "^3.972.5", "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", + "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.10.tgz", - "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", - "license": "Apache-2.0", - "dependencies": { + "node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.11.tgz", + "integrity": "sha512-vXARCZVFQHdsd6qPPZyC/hh+5x2XsCYKqUQDCqnUlpGpChMpDojOOacQWdLJ+FFXKN8X3cmLOGrtgx/zysCKqQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/endpoint-cache": "^3.972.5", + "@aws-sdk/types": "^3.973.8", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.10.tgz", + "integrity": "sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.974.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.15.tgz", + "integrity": "sha512-j4Zp7rA1HfhDTteICnx/tPax4N/v5wmytgguXExUGyEwQ8Ug4EBA4kjp9puFAN1UZoBVpxoiXMiuTFvjaHjeEw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/crc64-nvme": "^3.972.7", + "@aws-sdk/types": "^3.973.8", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-stream": "^4.5.25", + "@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.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.10.tgz", + "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", + "license": "Apache-2.0", + "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", @@ -766,6 +1132,20 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.10.tgz", + "integrity": "sha512-rI3NZvJcEvjoD0+0PI0iUAwlPw2IlSlhyvgBK/3WkKJQE/YiKFedd9dMN2lVacdNxPNhxL/jzQaKQdrGtQagjQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-logger": { "version": "3.972.10", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.10.tgz", @@ -796,13 +1176,27 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-sdk-route53": { + "version": "3.972.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-route53/-/middleware-sdk-route53-3.972.12.tgz", + "integrity": "sha512-nj08j4q/Rp8zb3SqwxE+dex22NdXoSKJAh445x0SLGAI23lYfDTujFDG1JRYLRc1uR2/FPPr76L/ki/VE4J9ig==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.35", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.35.tgz", - "integrity": "sha512-lLppaNTAz+wNgLdi4FtHzrlwrGF0ODTnBWHBaFg85SKs0eJ+M+tP5ifrA8f/0lNd+Ak3MC1NGC6RavV3ny4HTg==", + "version": "3.972.36", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.36.tgz", + "integrity": "sha512-YhPix+0x/MdQrb1Ug1GDKeS5fqylIy+naz800asX8II4jqfTk2KY2KhmmYCwZcky8YWtRQQwWCGdoqeAnip8Uw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.17", @@ -838,19 +1232,33 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.10.tgz", + "integrity": "sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.36.tgz", - "integrity": "sha512-O2beToxguBvrZFFZ+fFgPbbae8MvyIBjQ6lImee4APHEXXNAD5ZJ2ayLF1mb7rsKw86TM81y5czg82bZncjSjg==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.37.tgz", + "integrity": "sha512-N1oNpdiLoVAWYD3WFBnUi3LlfoDA06ZHo4ozyjbsJNLvILzvt//0CnR8N+CZ0NWeYgVB/5V59ivixHCWCx2ALw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@smithy/core": "^3.23.17", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.6", "tslib": "^2.6.2" }, "engines": { @@ -858,24 +1266,24 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.4.tgz", - "integrity": "sha512-4Sf+WY1lMJzXlw5MiyCMe/UzdILCwvuaHThbqMXS6dfh9gZy3No360I42RXquOI/ULUOhWy2HCyU0Fp20fQGPQ==", + "version": "3.997.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.5.tgz", + "integrity": "sha512-jGFr6DxtcMTmzOkG/a0jCZYv4BBDmeNYVeO+/memSoDkYCJu4Y58xviYmzwJfYyIVSts+X/BVjJm1uGBnwHEMg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.37", "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/signature-v4-multi-region": "^3.996.23", + "@aws-sdk/signature-v4-multi-region": "^3.996.24", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.23", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -883,7 +1291,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.7", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -899,7 +1307,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.6", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -924,12 +1332,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.23", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.23.tgz", - "integrity": "sha512-wBbys3Y53Ikly556vyADurKpYQHXS7Jjaskbz+Ga9PZCz7PB/9f3VdKbDlz7dqIzn+xwz7L/a6TR4iXcOi8IRw==", + "version": "3.996.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.24.tgz", + "integrity": "sha512-amP7tLikppN940wbBFISYqiuzVmpzMS9U3mcgtmVLjX4fdWI/SNCvrXv6ZxfVzTT4cT0rPKOLhFah2xLwzREWw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.35", + "@aws-sdk/middleware-sdk-s3": "^3.972.36", "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", @@ -941,13 +1349,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1038.0.tgz", - "integrity": "sha512-Qniru+9oGGb/HNK/gGZWbV3jsD0k71ngE7qMQ/x6gYNYLd2EOwHCS6E2E6jfkaqO4i0d+nNKmfRy8bNcshKdGQ==", + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1039.0.tgz", + "integrity": "sha512-NMSFL2HwkAOoCeLCQiqoOq5pT3vVbSjww2QZTuYgYknVwhhv125PSDzZIcL5EYnlxuPWjEOdauZK+FspkZDVdw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/nested-clients": "^3.997.4", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/nested-clients": "^3.997.5", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", @@ -1039,12 +1447,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.22", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.22.tgz", - "integrity": "sha512-YTYqTmOUrwbm1h99Ee4y/mVYpFRl0oSO/amtP5cc1BZZWdaAVWs9zj3TkyRHWvR9aI/ZS8m3mS6awXtYUlWyaw==", + "version": "3.973.23", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.23.tgz", + "integrity": "sha512-gGwq8L2Euw0aNG6Ey4EktiAo3fSCVoDy1CaBIthd+oeaKHPXUrNaApMewQ6La5Hv0lcznOtECZaNvYyc5LXXfA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.37", "@aws-sdk/types": "^3.973.8", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", @@ -1064,9 +1472,9 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.21", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.21.tgz", - "integrity": "sha512-qxNiHUtlrsjTeSlrPWiFkWps7uD6YB4eKzg7eLAFH8jbiHTlt0ePNlo2Xu+WlftP38JIcMaIX4jTUjOlE2ySWw==", + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz", + "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", "license": "Apache-2.0", "dependencies": { "@nodable/entities": "2.1.0", @@ -2054,6 +2462,21 @@ "@middy/core": "^7.3.3" } }, + "node_modules/@nhsdigital/eps-cdk-constructs": { + "version": "1.8.1", + "resolved": "https://npm.pkg.github.com/download/@nhsdigital/eps-cdk-constructs/1.8.1/ae72a2c13cc0d4514d68a8591face03857d99369", + "integrity": "sha512-tAxvjkZPyttYjEsgVyZCbfWh3w3GtO7rhMHLNyNSKPTS19U5E/7e8K0KB7S+EHLOUvI6gRgDn0ofeAf5+dikRQ==", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-cloudformation": "^3.1031.0", + "@aws-sdk/client-route-53": "^3.1031.0", + "@aws-sdk/client-s3": "^3.1031.0", + "aws-cdk": "^2.1118.2", + "aws-cdk-lib": "^2.250.0", + "cdk-nag": "^2.37.52", + "constructs": "^10.6.0" + } + }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -2943,6 +3366,31 @@ "node": ">=4" } }, + "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.17", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.17.tgz", @@ -2997,6 +3445,76 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.1", + "@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.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.14.tgz", + "integrity": "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.14.tgz", + "integrity": "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.14.tgz", + "integrity": "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.14.tgz", + "integrity": "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/fetch-http-handler": { "version": "5.3.17", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.17.tgz", @@ -3013,6 +3531,21 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.15.tgz", + "integrity": "sha512-0PJ4Al3fg2nM4qKrAIxyNcApgqHAXcBkN8FeizOz69z0rb26uZ6lMESYtxegaTlXB5Hj84JfwMPavMrwDMjucA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.2", + "@smithy/chunked-blob-reader-native": "^4.2.3", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/hash-node": { "version": "4.2.14", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.14.tgz", @@ -3028,6 +3561,20 @@ "node": ">=18.0.0" } }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.14.tgz", + "integrity": "sha512-tw4GANWkZPb6+BdD4Fgucqzey2+r73Z/GRo9zklsCdwrnxxumUV83ZIaBDdudV4Ylazw3EPTiJZhpX42105ruQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.14.1", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@smithy/invalid-dependency": { "version": "4.2.14", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.14.tgz", @@ -3101,9 +3648,9 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.6.tgz", - "integrity": "sha512-5zhmo2AkstmM/RMKYP0NHfmuYWBR+/umlmSuALgajLxf0X0rLE6d17MfzTxpzkILWVhwvCJkCyPH0AfMlbaucQ==", + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.7.tgz", + "integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==", "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.23.17", @@ -3113,7 +3660,7 @@ "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.6", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" }, @@ -3456,9 +4003,9 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.5.tgz", - "integrity": "sha512-h1IJsbgMDA+jaTjrco/JsyfWOgHRJBv8myB1y4AEI2fjIzD6ktZ7pFAyTw+gwN9GKIAygvC6db0mq0j8N2rFOg==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.6.tgz", + "integrity": "sha512-p6/FO1n2KxMeQyna067i0uJ6TSbb165ZhnRtCpWh4Foxqbfc6oW+XITaL8QkFJj3KFnDe2URt4gOhgU06EP9ew==", "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^4.3.1", @@ -4291,504 +4838,566 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-lambda": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/aws-lambda/-/aws-lambda-1.0.7.tgz", - "integrity": "sha512-9GNFMRrEMG5y3Jvv+V4azWvc+qNWdWLTjDdhf/zgMlz8haaaLWv0xeAIWxz9PuWUBawsVxy0zZotjCdR3Xq+2w==", - "license": "MIT", + "node_modules/aws-cdk": { + "version": "2.1120.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1120.0.tgz", + "integrity": "sha512-vDVa0IX0FhizARdY/GLSParFglKbdHCIhM8IDmynrAv9w8uLLljzWMeLUOhC1XpMErDZ/npYEihAOjfKxTaMIw==", + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.252.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.252.0.tgz", + "integrity": "sha512-bRLyTtJxhVgsx2JrL2B/KYYWf+Rg0s68UgQp+VRZK0h5fXeaPqDVEJGPMr7FiOgrmYwEadjfbxsTsZKNAloAvg==", + "bundleDependencies": [ + "@balena/dockerignore", + "@aws-cdk/cloud-assembly-api", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "license": "Apache-2.0", "dependencies": { - "aws-sdk": "^2.814.0", - "commander": "^3.0.2", - "js-yaml": "^3.14.1", - "watchpack": "^2.0.0-beta.10" + "@aws-cdk/asset-awscli-v1": "2.2.273", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.1", + "@aws-cdk/cloud-assembly-api": "^2.2.2", + "@aws-cdk/cloud-assembly-schema": "^53.18.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.3.3", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", + "minimatch": "^10.2.3", + "punycode": "^2.3.1", + "semver": "^7.7.4", + "table": "^6.9.0", + "yaml": "1.10.3" + }, + "engines": { + "node": ">= 20.0.0" }, - "bin": { - "lambda": "bin/lambda" + "peerDependencies": { + "constructs": "^10.5.0" } }, - "node_modules/aws-sdk": { - "version": "2.1693.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1693.0.tgz", - "integrity": "sha512-cJmb8xEnVLT+R6fBS5sn/EFJiX7tUnDaPtOPZ1vFbOJtd0fnZn/Ky2XGgsvvoeliWeH7mL3TWSX5zXXGSQV6gQ==", - "deprecated": "The AWS SDK for JavaScript (v2) has reached end-of-support, and no longer receives updates. Please migrate your code to use AWS SDK for JavaScript (v3). More info https://a.co/cUPnyil", - "hasInstallScript": true, + "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": { + "version": "2.2.2", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "inBundle": true, "license": "Apache-2.0", "dependencies": { - "buffer": "4.9.2", - "events": "1.1.1", - "ieee754": "1.1.13", - "jmespath": "0.16.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "util": "^0.12.4", - "uuid": "8.0.0", - "xml2js": "0.6.2" + "jsonschema": "~1.4.1", + "semver": "^7.7.4" }, "engines": { - "node": ">= 10.0.0" + "node": ">= 18.0.0" + }, + "peerDependencies": { + "@aws-cdk/cloud-assembly-schema": ">=53.15.0" } }, - "node_modules/aws-sdk-client-mock": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-4.1.0.tgz", - "integrity": "sha512-h/tOYTkXEsAcV3//6C1/7U4ifSpKyJvb6auveAepqqNJl6TdZaPFEtKjBQNf8UxQdDP850knB2i/whq4zlsxJw==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.18.0", + "inBundle": true, "license": "MIT", "dependencies": { - "@types/sinon": "^17.0.3", - "sinon": "^18.0.1", - "tslib": "^2.1.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^2.1.0" + "engines": { + "node": ">=8" } }, - "node_modules/axios-mock-adapter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.1.0.tgz", - "integrity": "sha512-AZUe4OjECGCNNssH8SOdtneiQELsqTsat3SQQCWLPjN436/H+L9AjWfV7bF+Zg/YL9cgbhrz5671hoh+Tbn98w==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "is-buffer": "^2.0.5" + "color-convert": "^2.0.1" }, - "peerDependencies": { - "axios": ">= 0.17.0" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/axios-retry": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", - "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", - "license": "Apache-2.0", - "dependencies": { - "is-retry-allowed": "^2.2.0" - }, - "peerDependencies": { - "axios": "0.x || 1.x" + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/balanced-match": { + "node_modules/aws-cdk-lib/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==", + "inBundle": true, "license": "MIT", "engines": { "node": "18 || 20 || >=22" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "5.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.1.0", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "url": "https://github.com/sponsors/fastify" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "opencollective", + "url": "https://opencollective.com/fastify" } ], - "license": "MIT" + "inBundle": true, + "license": "BSD-3-Clause" }, - "node_modules/better-ajv-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-1.2.0.tgz", - "integrity": "sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA==", - "license": "Apache-2.0", + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.3", + "inBundle": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.16.0", - "@humanwhocodes/momoa": "^2.0.2", - "chalk": "^4.1.2", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0 < 4" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">= 12.13.0" - }, - "peerDependencies": { - "ajv": "4.11.8 - 8" + "node": ">=14.14" } }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "license": "MIT" + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" }, - "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, "engines": { - "node": "18 || 20 || >=22" + "node": ">= 4" } }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, "license": "MIT", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/call-bind": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.2.0", + "inBundle": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" + "universalify": "^2.0.0" }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "inBundle": true, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "*" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, - "node_modules/call-me-maybe": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "license": "MIT" - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "license": "MIT", + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "10.2.5", + "inBundle": true, + "license": "BlueOak-1.0.0", "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=6" } }, - "node_modules/capabilityStatement": { - "resolved": "packages/capabilityStatement", - "link": true - }, - "node_modules/capital-case": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", - "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=18" + "node": ">=10" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/change-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", - "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, "license": "MIT", "dependencies": { - "camel-case": "^4.1.2", - "capital-case": "^1.0.4", - "constant-case": "^3.0.4", - "dot-case": "^3.0.4", - "header-case": "^2.0.4", - "no-case": "^3.0.4", - "param-case": "^3.0.4", - "pascal-case": "^3.1.2", - "path-case": "^3.0.4", - "sentence-case": "^3.0.4", - "snake-case": "^3.0.4", - "tslib": "^2.0.3" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/checkPrescriptionStatusUpdates": { - "resolved": "packages/checkPrescriptionStatusUpdates", - "link": true - }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "license": "MIT" - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "license": "ISC", + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=6" + "node": ">=10.0.0" } }, - "node_modules/color-convert": { + "node_modules/aws-cdk-lib/node_modules/universalify": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "inBundle": true, "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">= 10.0.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "license": "MIT" + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.3", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/aws-lambda": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/aws-lambda/-/aws-lambda-1.0.7.tgz", + "integrity": "sha512-9GNFMRrEMG5y3Jvv+V4azWvc+qNWdWLTjDdhf/zgMlz8haaaLWv0xeAIWxz9PuWUBawsVxy0zZotjCdR3Xq+2w==", "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "aws-sdk": "^2.814.0", + "commander": "^3.0.2", + "js-yaml": "^3.14.1", + "watchpack": "^2.0.0-beta.10" }, - "engines": { - "node": ">= 0.8" + "bin": { + "lambda": "bin/lambda" } }, - "node_modules/commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "license": "MIT" - }, - "node_modules/constant-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", - "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", - "license": "MIT", + "node_modules/aws-sdk": { + "version": "2.1693.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1693.0.tgz", + "integrity": "sha512-cJmb8xEnVLT+R6fBS5sn/EFJiX7tUnDaPtOPZ1vFbOJtd0fnZn/Ky2XGgsvvoeliWeH7mL3TWSX5zXXGSQV6gQ==", + "deprecated": "The AWS SDK for JavaScript (v2) has reached end-of-support, and no longer receives updates. Please migrate your code to use AWS SDK for JavaScript (v3). More info https://a.co/cUPnyil", + "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case": "^2.0.2" + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.6.2" + }, + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/aws-sdk-client-mock": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-4.1.0.tgz", + "integrity": "sha512-h/tOYTkXEsAcV3//6C1/7U4ifSpKyJvb6auveAepqqNJl6TdZaPFEtKjBQNf8UxQdDP850knB2i/whq4zlsxJw==", "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@types/sinon": "^17.0.3", + "sinon": "^18.0.1", + "tslib": "^2.1.0" } }, - "node_modules/core-js": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", - "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", - "hasInstallScript": true, + "node_modules/axios": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "license": "MIT", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" } }, - "node_modules/cpsuLambda": { - "resolved": "packages/cpsuLambda", - "link": true - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/axios-mock-adapter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.1.0.tgz", + "integrity": "sha512-AZUe4OjECGCNNssH8SOdtneiQELsqTsat3SQQCWLPjN436/H+L9AjWfV7bF+Zg/YL9cgbhrz5671hoh+Tbn98w==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "axios": ">= 0.17.0" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "license": "ISC", - "engines": { - "node": ">=4" + "node_modules/axios-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", + "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", + "license": "Apache-2.0", + "dependencies": { + "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" } }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT" }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", + "node_modules/better-ajv-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-1.2.0.tgz", + "integrity": "sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA==", + "license": "Apache-2.0", "dependencies": { - "ms": "^2.1.3" + "@babel/code-frame": "^7.16.0", + "@humanwhocodes/momoa": "^2.0.2", + "chalk": "^4.1.2", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0 < 4" }, "engines": { - "node": ">=6.0" + "node": ">= 12.13.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "peerDependencies": { + "ajv": "4.11.8 - 8" } }, - "node_modules/decko": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", - "integrity": "sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==" - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", "license": "MIT" }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -4797,677 +5406,553 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, "engines": { - "node": ">=0.4.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": ">= 0.4" } }, - "node_modules/diff": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", - "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, "engines": { - "node": ">=0.3.1" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "license": "MIT" }, - "node_modules/dompurify": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", - "integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "license": "MIT", "dependencies": { - "no-case": "^3.0.4", + "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, - "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", "funding": { - "url": "https://dotenvx.com" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/capabilityStatement": { + "resolved": "packages/capabilityStatement", + "link": true + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" + "node_modules/cdk": { + "resolved": "packages/cdk", + "link": true }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", + "node_modules/cdk-nag": { + "version": "2.38.2", + "resolved": "https://registry.npmjs.org/cdk-nag/-/cdk-nag-2.38.2.tgz", + "integrity": "sha512-Ddim1r8IwAPQn95KB2owbHoU4YHveHg4PfM+k7TdcANqfVmoHem6cTFMpcIuoDM16BkQQ+xshxYxZgcAoeVKGw==", + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" + "node": ">= 18.12.0" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.176.0", + "constructs": "^10.5.1" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/es-module-lexer": { - "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/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" } }, - "node_modules/es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", + "node_modules/checkPrescriptionStatusUpdates": { + "resolved": "packages/checkPrescriptionStatusUpdates", + "link": true + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", "license": "MIT" }, - "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "color-name": "~1.1.4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=7.0.0" } }, - "node_modules/eslint": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz", - "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", - "dev": true, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.5.5", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" + "delayed-stream": "~1.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "node": ">= 0.8" } }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", - "dev": true, + "node_modules/commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "license": "MIT" + }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" } }, - "node_modules/eslint-plugin-import-newlines": { + "node_modules/constructs": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.6.0.tgz", + "integrity": "sha512-TxHOnBO5zMo/G76ykzGF/wMpEHu257TbWiIxP9K0Yv/+t70UzgBQiTqjkAsWOPC6jW91DzJI0+ehQV6xDRNBuQ==", + "license": "Apache-2.0" + }, + "node_modules/convert-source-map": { "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==", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "bin": { - "import-linter": "lib/index.js" - }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "peerDependencies": { - "eslint": ">=10.0.0" + "node": ">= 0.6" } }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", - "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", - "dev": true, + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.1", - "synckit": "^0.11.12" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, + "peer": true, "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "node_modules/cpsuLambda": { + "resolved": "packages/cpsuLambda", + "link": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 8" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=4" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "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": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">= 4" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/decko": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", + "integrity": "sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT" }, - "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "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", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=0.4.0" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, + "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": ">=4" + "node": ">=8" } }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, "engines": { - "node": ">=0.10" + "node": ">=0.3.1" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", + "license": "MIT" }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" + "node_modules/dompurify": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", + "integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" + "no-case": "^3.0.4", + "tslib": "^2.0.3" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "license": "BSD-2-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { - "node": ">=0.4.x" + "node": ">= 0.4" } }, - "node_modules/expect-type": { + "node_modules/es-errors": { "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", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">= 0.4" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "node_modules/es-module-lexer": { + "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/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-xml-builder": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", - "integrity": "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.1.3" + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/fast-xml-parser": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", - "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.5", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">= 0.4" } }, - "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, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "hasInstallScript": true, "license": "MIT", - "engines": { - "node": ">=12.0.0" + "bin": { + "esbuild": "bin/esbuild" }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, "engines": { - "node": ">=16.0.0" + "node": ">=6" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, "engines": { "node": ">=10" }, @@ -5475,559 +5960,548 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "node_modules/eslint": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz", + "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=16" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, - "license": "ISC" - }, - "node_modules/fluent-transpiler": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fluent-transpiler/-/fluent-transpiler-0.2.1.tgz", - "integrity": "sha512-pdF72/XOg0NHj97E3aXu9k5PI8FmpRRrk4HMDeo9KRm7eoNczBTHnQjQMuqsAJ06ejT9teAA5fL8qUIhtUe6oA==", "license": "MIT", - "dependencies": { - "@fluent/syntax": "0.18.1", - "change-case": "4.1.2", - "commander": "9.4.0" - }, "bin": { - "ftl": "cli.js" + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/fluent-transpiler/node_modules/commander": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", - "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", + "node_modules/eslint-plugin-import-newlines": { + "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": "^12.20.0 || >=14" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "peerDependencies": { + "eslint": ">=10.0.0" } }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/eslint-plugin-prettier": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", + "dev": true, "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" + }, "engines": { - "node": ">=4.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { - "debug": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { "optional": true } } }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", + "node_modules/eslint-scope": { + "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": { - "is-callable": "^1.2.7" + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/foreach": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", - "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", - "license": "MIT" + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "node_modules/eslint/node_modules/ajv": { + "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": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">= 6" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "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/eslint/node_modules/eslint-visitor-keys": { + "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, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "license": "Apache-2.0", "engines": { - "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==", - "license": "MIT", + "node": "^20.19.0 || ^22.13.0 || >=24" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">= 4" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "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": ">= 0.4" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/esquery": { + "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": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "is-glob": "^4.0.3" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=0.10" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" - }, - "node_modules/globals": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", - "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=4.0" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "MIT" - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/gsul": { - "resolved": "packages/gsul", - "link": true - }, - "node_modules/handlebars": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", - "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "@types/estree": "^1.0.0" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.4.x" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, + "node_modules/expect-type": { + "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": { - "node": ">= 0.4" + "node": ">=12.0.0" } }, - "node_modules/header-case": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", - "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", - "license": "MIT", - "dependencies": { - "capital-case": "^1.0.4", - "tslib": "^2.0.3" - } + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, "license": "MIT" }, - "node_modules/http2-client": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", - "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, "license": "MIT" }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" }, - "node_modules/ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "BSD-3-Clause" }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, + "node_modules/fast-xml-builder": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", + "integrity": "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "path-expression-matcher": "^1.1.3" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true, + "node_modules/fast-xml-parser": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", + "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "url": "https://github.com/sponsors/NaturalIntelligence" } ], "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "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": ">=0.10.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=16" } }, - "node_modules/is-node-process": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", - "license": "MIT" + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "node_modules/fluent-transpiler": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fluent-transpiler/-/fluent-transpiler-0.2.1.tgz", + "integrity": "sha512-pdF72/XOg0NHj97E3aXu9k5PI8FmpRRrk4HMDeo9KRm7eoNczBTHnQjQMuqsAJ06ejT9teAA5fL8qUIhtUe6oA==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "@fluent/syntax": "0.18.1", + "change-case": "4.1.2", + "commander": "9.4.0" }, + "bin": { + "ftl": "cli.js" + } + }, + "node_modules/fluent-transpiler/node_modules/commander": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", + "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^12.20.0 || >=14" } }, - "node_modules/is-retry-allowed": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", - "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "license": "MIT", "engines": { - "node": ">=10" + "node": ">=4.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.16" + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -6036,213 +6510,705 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", "license": "MIT" }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" }, "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jmespath": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", - "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", - "license": "Apache-2.0", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.6.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/jose": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", - "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "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==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/panva" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/js-tokens": { - "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/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "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/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-pointer": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", - "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { - "foreach": "^2.0.4" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" } }, - "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==", + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" + "resolve-pkg-maps": "^1.0.0" }, - "engines": { - "node": ">=16" + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "license": "ISC" - }, - "node_modules/jsonpath-rfc9535": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsonpath-rfc9535/-/jsonpath-rfc9535-1.3.0.tgz", - "integrity": "sha512-3jFHya7oZ45aDxIIdx+/zQARahHXxFSMWBkcBUldfXpLS9VCXDJyTKt35kQfEXLqh0K3Ixw/9xFnvcDStaxh7Q==", - "license": "Apache-2.0", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, "engines": { - "node": ">=20" + "node": ">=10.13.0" } }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "node_modules/globals": { + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", + "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", "dev": true, "license": "MIT" }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gsul": { + "resolved": "packages/gsul", + "link": true + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, "engines": { - "node": ">=6" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/lightningcss": { - "version": "1.32.0", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "license": "MIT", + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http2-client": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "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", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "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": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "license": "MIT", + "dependencies": { + "foreach": "^2.0.4" + } + }, + "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": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsonpath-rfc9535": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-rfc9535/-/jsonpath-rfc9535-1.3.0.tgz", + "integrity": "sha512-3jFHya7oZ45aDxIIdx+/zQARahHXxFSMWBkcBUldfXpLS9VCXDJyTKt35kQfEXLqh0K3Ixw/9xFnvcDStaxh7Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, @@ -7244,212 +8210,521 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/polished": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/postDatedLambda": { + "resolved": "packages/postDatedLambda", + "link": true + }, + "node_modules/pratica": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pratica/-/pratica-2.3.0.tgz", + "integrity": "sha512-iQTsBYIZjvzzn5Oyf9QUOgkRR1+Gu4oEImVomYdSSj1RufEg8FoSxDs70P07OtIxBlDOkumMJaOFOAWwF0Zifg==", + "license": "Apache-2.0" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz", + "integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/protobufjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", + "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/psuRestoreValidationLambda": { + "resolved": "packages/psuRestoreValidationLambda", + "link": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", + "license": "CC0-1.0" + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "license": "MIT", "engines": { - "node": ">=4" + "node": ">=0.12" } }, - "node_modules/polished": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", - "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.17.8" - }, - "engines": { - "node": ">=10" + "safe-buffer": "^5.1.0" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=0.10.0" } }, - "node_modules/postcss": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", - "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "scheduler": "^0.27.0" }, - "engines": { - "node": "^10 || ^12 || >=14" + "peerDependencies": { + "react": "^19.2.5" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/postDatedLambda": { - "resolved": "packages/postDatedLambda", - "link": true - }, - "node_modules/pratica": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pratica/-/pratica-2.3.0.tgz", - "integrity": "sha512-iQTsBYIZjvzzn5Oyf9QUOgkRR1+Gu4oEImVomYdSSj1RufEg8FoSxDs70P07OtIxBlDOkumMJaOFOAWwF0Zifg==", - "license": "Apache-2.0" + "node_modules/react-tabs": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.1.1.tgz", + "integrity": "sha512-CPiuKoMFf89B7QlbFfdBD9XmUWiE3qudQputMVZB8GQvPJZRX/gqjDaDWOPDwGinEfpJKEuBCkGt83Tt4efeyA==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, "engines": { - "node": ">= 0.8.0" + "node": ">= 6" } }, - "node_modules/prettier": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz", - "integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==", - "dev": true, + "node_modules/redoc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.5.1.tgz", + "integrity": "sha512-LmqA+4A3CmhTllGG197F0arUpmChukAj9klfSdxNRemT9Hr07xXr7OGKu4PHzBs359sgrJ+4JwmOlM7nxLPGMg==", "license": "MIT", - "peer": true, - "bin": { - "prettier": "bin/prettier.cjs" + "dependencies": { + "@redocly/openapi-core": "^1.4.0", + "classnames": "^2.3.2", + "decko": "^1.2.0", + "dompurify": "^3.2.4", + "eventemitter3": "^5.0.1", + "json-pointer": "^0.6.2", + "lunr": "^2.3.9", + "mark.js": "^8.11.1", + "marked": "^4.3.0", + "mobx-react": "9.2.0", + "openapi-sampler": "^1.5.0", + "path-browserify": "^1.0.1", + "perfect-scrollbar": "^1.5.5", + "polished": "^4.2.2", + "prismjs": "^1.29.0", + "prop-types": "^15.8.1", + "react-tabs": "^6.0.2", + "slugify": "~1.4.7", + "stickyfill": "^1.1.1", + "swagger2openapi": "^7.0.8", + "url-template": "^2.0.8" }, "engines": { - "node": ">=14" + "node": ">=6.9", + "npm": ">=3.0.0" + }, + "peerDependencies": { + "core-js": "^3.1.4", + "mobx": "^6.0.4", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5" + } + }, + "node_modules/redoc/node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", - "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", - "dev": true, + "node_modules/redoc/node_modules/@redocly/config": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz", + "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==", + "license": "MIT" + }, + "node_modules/redoc/node_modules/@redocly/openapi-core": { + "version": "1.34.11", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.11.tgz", + "integrity": "sha512-V09ayfnb5GyysmvARbt+voFZAjGcf7hSYxOYxSkCc4fbH/DTfq5YWoec8cflvmHHqyIFbqvmGKmYFzqhr9zxDg==", "license": "MIT", "dependencies": { - "fast-diff": "^1.1.2" + "@redocly/ajv": "8.11.2", + "@redocly/config": "0.22.0", + "colorette": "1.4.0", + "https-proxy-agent": "7.0.6", + "js-levenshtein": "1.1.6", + "js-yaml": "4.1.1", + "minimatch": "5.1.9", + "pluralize": "8.0.0", + "yaml-ast-parser": "0.0.43" }, "engines": { - "node": ">=6.0.0" + "node": ">=18.17.0", + "npm": ">=9.5.0" } }, - "node_modules/prismjs": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "node_modules/redoc/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==", + "license": "Python-2.0" + }, + "node_modules/redoc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/redoc/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "node_modules/redoc/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==", "license": "MIT", "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/redoc/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/protobufjs": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", - "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", - "hasInstallScript": true, + "node_modules/reftools": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">=0.10.0" } }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/psuRestoreValidationLambda": { - "resolved": "packages/psuRestoreValidationLambda", - "link": true + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.12" } }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, "engines": { - "node": ">=0.4.x" + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -7466,926 +8741,1102 @@ ], "license": "MIT" }, - "node_modules/railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", - "license": "CC0-1.0" - }, - "node_modules/randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "license": "MIT", "dependencies": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" }, "engines": { - "node": ">=0.12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", + "license": "ISC" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/schemes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/schemes/-/schemes-1.4.0.tgz", + "integrity": "sha512-ImFy9FbCsQlVgnE3TCWmLPCFnVzx0lHL/l+umHplDqAKd0dzFpnS6lFZIpagBlYhKwzVmlV36ec0Y1XTu8JBAQ==", "license": "MIT", "dependencies": { - "safe-buffer": "^5.1.0" + "extend": "^3.0.0" } }, - "node_modules/react": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", - "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", - "license": "MIT", + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", "license": "MIT", "dependencies": { - "scheduler": "^0.27.0" + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, - "peerDependencies": { - "react": "^19.2.5" + "engines": { + "node": ">= 0.4" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", "license": "MIT" }, - "node_modules/react-tabs": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.1.1.tgz", - "integrity": "sha512-CPiuKoMFf89B7QlbFfdBD9XmUWiE3qudQputMVZB8GQvPJZRX/gqjDaDWOPDwGinEfpJKEuBCkGt83Tt4efeyA==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { - "clsx": "^2.0.0", - "prop-types": "^15.5.0" + "shebang-regex": "^3.0.0" }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0" + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "license": "MIT", + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "should-type": "^1.4.0" } }, - "node_modules/redoc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.5.1.tgz", - "integrity": "sha512-LmqA+4A3CmhTllGG197F0arUpmChukAj9klfSdxNRemT9Hr07xXr7OGKu4PHzBs359sgrJ+4JwmOlM7nxLPGMg==", + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", "license": "MIT", "dependencies": { - "@redocly/openapi-core": "^1.4.0", - "classnames": "^2.3.2", - "decko": "^1.2.0", - "dompurify": "^3.2.4", - "eventemitter3": "^5.0.1", - "json-pointer": "^0.6.2", - "lunr": "^2.3.9", - "mark.js": "^8.11.1", - "marked": "^4.3.0", - "mobx-react": "9.2.0", - "openapi-sampler": "^1.5.0", - "path-browserify": "^1.0.1", - "perfect-scrollbar": "^1.5.5", - "polished": "^4.2.2", - "prismjs": "^1.29.0", - "prop-types": "^15.8.1", - "react-tabs": "^6.0.2", - "slugify": "~1.4.7", - "stickyfill": "^1.1.1", - "swagger2openapi": "^7.0.8", - "url-template": "^2.0.8" - }, - "engines": { - "node": ">=6.9", - "npm": ">=3.0.0" - }, - "peerDependencies": { - "core-js": "^3.1.4", - "mobx": "^6.0.4", - "react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5" + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" } }, - "node_modules/redoc/node_modules/@redocly/ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", + "license": "MIT" + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js-replace": "^1.0.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "should-type": "^1.3.0", + "should-util": "^1.0.0" } }, - "node_modules/redoc/node_modules/@redocly/config": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz", - "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==", + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", "license": "MIT" }, - "node_modules/redoc/node_modules/@redocly/openapi-core": { - "version": "1.34.11", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.11.tgz", - "integrity": "sha512-V09ayfnb5GyysmvARbt+voFZAjGcf7hSYxOYxSkCc4fbH/DTfq5YWoec8cflvmHHqyIFbqvmGKmYFzqhr9zxDg==", + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-websocket": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", + "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "@redocly/ajv": "8.11.2", - "@redocly/config": "0.22.0", - "colorette": "1.4.0", - "https-proxy-agent": "7.0.6", - "js-levenshtein": "1.1.6", - "js-yaml": "4.1.1", - "minimatch": "5.1.9", - "pluralize": "8.0.0", - "yaml-ast-parser": "0.0.43" - }, - "engines": { - "node": ">=18.17.0", - "npm": ">=9.5.0" + "debug": "^4.3.1", + "queue-microtask": "^1.2.2", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0", + "ws": "^7.4.2" } }, - "node_modules/redoc/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==", - "license": "Python-2.0" - }, - "node_modules/redoc/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "node_modules/sinon": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.1.tgz", + "integrity": "sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.2.0", + "nise": "^6.0.0", + "supports-color": "^7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } }, - "node_modules/redoc/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "node_modules/slugify": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz", + "integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==", "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=8.0.0" } }, - "node_modules/redoc/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==", + "node_modules/smtp-address-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/smtp-address-parser/-/smtp-address-parser-1.1.0.tgz", + "integrity": "sha512-Gz11jbNU0plrReU9Sj7fmshSBxxJ9ShdD2q4ktHIHo/rpTH6lFyQoYHYKINPJtPe8aHFnsbtW46Ls0tCCBsIZg==", "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "nearley": "^2.20.1" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=0.10" } }, - "node_modules/redoc/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "license": "ISC", + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" - }, + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/reftools": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", - "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statusLambda": { + "resolved": "packages/statusLambda", + "link": true + }, + "node_modules/std-env": { + "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" + }, + "node_modules/stickyfill": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz", + "integrity": "sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==" + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "safe-buffer": "~5.2.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=0.12" + "node": ">=8" } }, - "node_modules/rolldown": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", - "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", - "dev": true, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/styled-components": { + "version": "6.3.9", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.9.tgz", + "integrity": "sha512-J72R4ltw0UBVUlEjTzI0gg2STOqlI9JBhQOL4Dxt7aJOnnSesy0qJDn4PYfMCafk9cWOaVg129Pesl5o+DIh0Q==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.124.0", - "@rolldown/pluginutils": "1.0.0-rc.15" - }, - "bin": { - "rolldown": "bin/cli.mjs" + "@emotion/is-prop-valid": "1.4.0", + "@emotion/unitless": "0.10.0", + "@types/stylis": "4.2.7", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.6", + "tslib": "2.8.1" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 16" }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-x64": "1.0.0-rc.15", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/feross" + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "github", + "url": "https://github.com/sponsors/ai" } ], - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^10 || ^12 || >=14" } }, - "node_modules/sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", - "license": "ISC" - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", "license": "MIT" }, - "node_modules/schemes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/schemes/-/schemes-1.4.0.tgz", - "integrity": "sha512-ImFy9FbCsQlVgnE3TCWmLPCFnVzx0lHL/l+umHplDqAKd0dzFpnS6lFZIpagBlYhKwzVmlV36ec0Y1XTu8JBAQ==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "license": "MIT", "dependencies": { - "extend": "^3.0.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", + "node_modules/swagger2openapi": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", + "license": "BSD-3-Clause", + "dependencies": { + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, "bin": { - "semver": "bin/semver.js" + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/swagger2openapi/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/sentence-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, "license": "MIT", "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, "license": "MIT" }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">= 0.4" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, - "node_modules/shebang-command": { + "node_modules/ts-algebra": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "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.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "node_modules/ts-md5": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-2.0.1.tgz", + "integrity": "sha512-yF35FCoEOFBzOclSkMNEUbFQZuv89KEQ+5Xz03HrMSGUGB1+r+El+JiGOFwsP4p9RFNzwlrydYoTLvPOuICl9w==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/should": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", - "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, "license": "MIT", "dependencies": { - "should-equal": "^2.0.0", - "should-format": "^3.0.3", - "should-type": "^1.4.0", - "should-type-adaptors": "^1.0.1", - "should-util": "^1.0.0" + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, - "node_modules/should-equal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", - "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", - "license": "MIT", - "dependencies": { - "should-type": "^1.4.0" + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, - "node_modules/should-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "license": "MIT", "dependencies": { - "should-type": "^1.3.0", - "should-type-adaptors": "^1.0.1" + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" } }, - "node_modules/should-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", - "license": "MIT" - }, - "node_modules/should-type-adaptors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", - "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "should-type": "^1.3.0", - "should-util": "^1.0.0" + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, - "node_modules/should-util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", - "license": "MIT" + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/simple-websocket": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", - "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" ], "license": "MIT", - "dependencies": { - "debug": "^4.3.1", - "queue-microtask": "^1.2.2", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0", - "ws": "^7.4.2" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/sinon": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.1.tgz", - "integrity": "sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.2.0", - "nise": "^6.0.0", - "supports-color": "^7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/slugify": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz", - "integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==", + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8.0.0" + "node": ">=18" } }, - "node_modules/smtp-address-parser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/smtp-address-parser/-/smtp-address-parser-1.1.0.tgz", - "integrity": "sha512-Gz11jbNU0plrReU9Sj7fmshSBxxJ9ShdD2q4ktHIHo/rpTH6lFyQoYHYKINPJtPe8aHFnsbtW46Ls0tCCBsIZg==", + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "nearley": "^2.20.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=0.10" + "node": ">=18" } }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/statusLambda": { - "resolved": "packages/statusLambda", - "link": true - }, - "node_modules/std-env": { - "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" - }, - "node_modules/stickyfill": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz", - "integrity": "sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==" - }, - "node_modules/strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", - "license": "MIT" + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/strnum": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", - "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" ], - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/styled-components": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.9.tgz", - "integrity": "sha512-J72R4ltw0UBVUlEjTzI0gg2STOqlI9JBhQOL4Dxt7aJOnnSesy0qJDn4PYfMCafk9cWOaVg129Pesl5o+DIh0Q==", + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], "license": "MIT", - "dependencies": { - "@emotion/is-prop-valid": "1.4.0", - "@emotion/unitless": "0.10.0", - "@types/stylis": "4.2.7", - "css-to-react-native": "3.2.0", - "csstype": "3.2.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.6", - "tslib": "2.8.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } + "node": ">=18" } }, - "node_modules/styled-components/node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" ], "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=18" } }, - "node_modules/stylis": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", - "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/swagger2openapi": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", - "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", - "license": "BSD-3-Clause", - "dependencies": { - "call-me-maybe": "^1.0.1", - "node-fetch": "^2.6.1", - "node-fetch-h2": "^2.3.0", - "node-readfiles": "^0.2.0", - "oas-kit-common": "^1.0.8", - "oas-resolver": "^2.5.6", - "oas-schema-walker": "^1.1.5", - "oas-validator": "^5.0.8", - "reftools": "^1.1.9", - "yaml": "^1.10.0", - "yargs": "^17.0.1" - }, - "bin": { - "boast": "boast.js", - "oas-validate": "oas-validate.js", - "swagger2openapi": "swagger2openapi.js" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/swagger2openapi/node_modules/yaml": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", - "license": "ISC", + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 6" + "node": ">=18" } }, - "node_modules/synckit": { - "version": "0.11.12", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", - "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", - "dev": true, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" + "node": ">=18" } }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", - "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", - "dev": true, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { "node": ">=18" } }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "node": ">=18" } }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "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.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" + "node": ">=18" } }, - "node_modules/ts-md5": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-2.0.1.tgz", - "integrity": "sha512-yF35FCoEOFBzOclSkMNEUbFQZuv89KEQ+5Xz03HrMSGUGB1+r+El+JiGOFwsP4p9RFNzwlrydYoTLvPOuICl9w==", + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "esbuild": "bin/esbuild" }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "dev": true, - "license": "BSD-3-Clause", "engines": { - "node": ">=0.3.1" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8963,7 +10414,6 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -9045,6 +10495,17 @@ "@psu-common/testing": "^1.0.0" } }, + "packages/cdk": { + "version": "0.1.0", + "dependencies": { + "@nhsdigital/eps-cdk-constructs": "^1.6.1", + "aws-cdk": "^2.1114.1", + "aws-cdk-lib": "^2.245.0", + "cdk-nag": "^2.37.55", + "constructs": "^10.6.0", + "tsx": "^4.21.0" + } + }, "packages/checkPrescriptionStatusUpdates": { "version": "1.0.0", "license": "MIT", From fe1ee4b0d8c353662cb54286b095734e83a51afd Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:20:20 +0000 Subject: [PATCH 26/31] chore: zizmor --- zizmor.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/zizmor.yml b/zizmor.yml index 728f741a11..ff88a985fc 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -2,13 +2,14 @@ rules: unpinned-images: # these workflows use unpinned images because they are using a full image passed in that contains the tag ignore: - - run_release_code_and_api.yml:146:7 - - run_regression_tests.yml:26:7 - - run_package_code_and_api.yml:16:7 - - cdk_package_code.yml:25:7 - - cdk_release_code.yml:45:7 - - cdk_release_code.yml:57:7 - - cdk_release_code.yml:69:7 + - run_release_code_and_api.yml:146:18 + - run_regression_tests.yml:26:18 + - run_package_code_and_api.yml:16:18 + - cdk_package_code.yml:25:18 + - cdk_release_code.yml:45:18 + - cdk_release_code.yml:57:18 + - cdk_release_code.yml:69:18 + - cdk_release_code.yml:151:18 secrets-outside-env: # these are ignored because they are using known secrets ignore: From ca4479c68ab5322db120a411841e8bf01d9f2d22 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:58:29 +0000 Subject: [PATCH 27/31] fix: DRY stack name and mode --- .github/workflows/pull_request.yml | 10 +++++----- Makefile | 18 ++++++++++-------- packages/cdk/bin/PsuApiApp.ts | 10 ++++++++-- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 7e0a0a4fcb..568bf741ab 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -87,7 +87,7 @@ jobs: VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} - cdk_release_code: + cdk_release_stateless_code: needs: [get_issue_number, cdk_package_code, get_commit_id, get_config_values, cdk_release_stateful_code] uses: ./.github/workflows/cdk_release_code.yml @@ -97,7 +97,7 @@ jobs: with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} BRANCH_NAME: ${{ github.event.pull_request.head.ref }} - STACK_NAME: psu-cdk-pr-${{needs.get_issue_number.outputs.issue_number}} + STACK_NAME: psu-api-pr-${{needs.get_issue_number.outputs.issue_number}} AWS_ENVIRONMENT: dev VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} @@ -109,7 +109,7 @@ jobs: FORWARD_CSOC_LOGS: false DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true EXPOSE_GET_STATUS_UPDATES: false - SAM_STACK_NAME: psu-pr-${{needs.get_issue_number.outputs.issue_number}} + SAM_STACK_NAME: psu-api-pr-${{needs.get_issue_number.outputs.issue_number}} ENABLE_POST_DATED_NOTIFICATIONS: true REQUIRE_APPLICATION_NAME: false ENABLE_BACKUP: false @@ -126,7 +126,7 @@ jobs: with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} BRANCH_NAME: ${{ github.event.pull_request.head.ref }} - STACK_NAME: psu-cdk-pr-${{needs.get_issue_number.outputs.issue_number}}-stateful + STACK_NAME: psu-api-pr-${{needs.get_issue_number.outputs.issue_number}} AWS_ENVIRONMENT: dev VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} @@ -222,7 +222,7 @@ jobs: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} BRANCH_NAME: ${{ github.event.pull_request.head.ref }} APP_NAME: PsuApiSandboxApp - STACK_NAME: psu-pr-${{needs.get_issue_number.outputs.issue_number}}-sandbox + STACK_NAME: psu-api-pr-${{needs.get_issue_number.outputs.issue_number}}-sandbox AWS_ENVIRONMENT: dev VERSION_NUMBER: PR-${{ needs.get_issue_number.outputs.issue_number }} COMMIT_ID: ${{ needs.get_commit_id.outputs.commit_id }} diff --git a/Makefile b/Makefile index 8d961bda1b..eb84dde8ed 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,10 @@ SHELL = /bin/bash .SHELLFLAGS = -o pipefail -c export CDK_APP_NAME=PsuApiApp export CDK_CONFIG_stackMode=stateless -export CDK_CONFIG_stackName=${stack_name} -export CDK_CONFIG_samStackName=${stack_name} +CDK_CONFIG_stackName ?= ${stack_name} +export CDK_CONFIG_stackName +CDK_CONFIG_samStackName ?= ${stack_name} +export CDK_CONFIG_samStackName export CDK_CONFIG_versionNumber=undefined export CDK_CONFIG_commitId=undefined export CDK_CONFIG_isPullRequest=true @@ -265,7 +267,7 @@ cdk-synth: cdk-stateful-synth cdk-stateless-synth cdk-stateful-deploy: CDK_APP_NAME=PsuApiApp \ CDK_CONFIG_stackMode=stateful \ - CDK_CONFIG_stackName=psu-cdk-stateful \ + CDK_CONFIG_stackName="$${CDK_CONFIG_stackName:-$${stack_name:-psu-cdk}}" \ CDK_CONFIG_logRetentionInDays=30 \ CDK_CONFIG_environment=dev \ CDK_CONFIG_enableDynamoDBAutoScaling=false \ @@ -276,8 +278,8 @@ cdk-stateful-deploy: cdk-stateless-deploy: CDK_APP_NAME=PsuApiApp \ CDK_CONFIG_stackMode=stateless \ - CDK_CONFIG_stackName=psu-cdk \ - CDK_CONFIG_samStackName=psu \ + CDK_CONFIG_stackName="$${CDK_CONFIG_stackName:-$${stack_name:-psu-cdk}}" \ + CDK_CONFIG_samStackName="$${CDK_CONFIG_samStackName:-$${CDK_CONFIG_stackName:-$${stack_name:-psu-cdk}}}" \ CDK_CONFIG_logRetentionInDays=30 \ CDK_CONFIG_logLevel=DEBUG \ CDK_CONFIG_environment=dev \ @@ -293,8 +295,8 @@ cdk-stateless-deploy: cdk-stateless-synth: CDK_APP_NAME=PsuApiApp \ CDK_CONFIG_stackMode=stateless \ - CDK_CONFIG_stackName=psu-cdk \ - CDK_CONFIG_samStackName=psu \ + CDK_CONFIG_stackName="$${CDK_CONFIG_stackName:-$${stack_name:-psu-cdk}}" \ + CDK_CONFIG_samStackName="$${CDK_CONFIG_samStackName:-$${CDK_CONFIG_stackName:-$${stack_name:-psu-cdk}}}" \ CDK_CONFIG_logRetentionInDays=30 \ CDK_CONFIG_logLevel=DEBUG \ CDK_CONFIG_environment=dev \ @@ -309,7 +311,7 @@ cdk-stateless-synth: cdk-stateful-synth: CDK_APP_NAME=PsuApiApp \ CDK_CONFIG_stackMode=stateful \ - CDK_CONFIG_stackName=psu-cdk-stateful \ + CDK_CONFIG_stackName="$${CDK_CONFIG_stackName:-$${stack_name:-psu-cdk}}" \ CDK_CONFIG_logRetentionInDays=30 \ CDK_CONFIG_environment=dev \ CDK_CONFIG_enableDynamoDBAutoScaling=false \ diff --git a/packages/cdk/bin/PsuApiApp.ts b/packages/cdk/bin/PsuApiApp.ts index 83c38b0090..9c01890d37 100644 --- a/packages/cdk/bin/PsuApiApp.ts +++ b/packages/cdk/bin/PsuApiApp.ts @@ -10,6 +10,10 @@ import {PsuApiStatefulStack} from "../stacks/PsuApiStatefulStack" type StackMode = "stateless" | "stateful" +function mergeStackModeIntoStackName(baseStackName: string, stackMode: StackMode): string { + return `${baseStackName}-${stackMode}` +} + function getStackMode(): StackMode { const stackMode = getConfigFromEnvVar("stackMode", undefined, "stateless") @@ -29,11 +33,13 @@ async function main() { }) const stackMode = getStackMode() + const baseStackName = getConfigFromEnvVar("stackName") + const modeAwareStackName = mergeStackModeIntoStackName(baseStackName, stackMode) if (stackMode === "stateless") { new PsuApiStatelessStack(app, "PsuApiStatelessStack", { ...props, - stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props), + stackName: calculateVersionedStackName(modeAwareStackName, props), samStackName: getConfigFromEnvVar("samStackName"), // TODO: REMOVE THE NEED FOR THIS logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), logLevel: getConfigFromEnvVar("logLevel"), @@ -55,7 +61,7 @@ async function main() { // creating a new stack per version. new PsuApiStatefulStack(app, "PsuApiStatefulStack", { ...props, - stackName: getConfigFromEnvVar("stackName"), + stackName: modeAwareStackName, logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), environment: getConfigFromEnvVar("environment"), enableDynamoDBAutoScaling: getBooleanConfigFromEnvVar("enableDynamoDBAutoScaling", undefined, "false"), From 60e3e382a9027a6453a37d79da691faae0df88db Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:36:09 +0000 Subject: [PATCH 28/31] chore: fix unused cdk instantiation --- packages/cdk/bin/PsuApiApp.ts | 7 ++++--- packages/cdk/bin/PsuApiSandboxApp.ts | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/cdk/bin/PsuApiApp.ts b/packages/cdk/bin/PsuApiApp.ts index 9c01890d37..86b84337d0 100644 --- a/packages/cdk/bin/PsuApiApp.ts +++ b/packages/cdk/bin/PsuApiApp.ts @@ -37,7 +37,7 @@ async function main() { const modeAwareStackName = mergeStackModeIntoStackName(baseStackName, stackMode) if (stackMode === "stateless") { - new PsuApiStatelessStack(app, "PsuApiStatelessStack", { + const psuApiStatelessStack = new PsuApiStatelessStack(app, "PsuApiStatelessStack", { ...props, stackName: calculateVersionedStackName(modeAwareStackName, props), samStackName: getConfigFromEnvVar("samStackName"), // TODO: REMOVE THE NEED FOR THIS @@ -53,13 +53,13 @@ async function main() { requireApplicationName: getConfigFromEnvVar("requireApplicationName", undefined, "false"), enableBackup: getBooleanConfigFromEnvVar("enableBackup", undefined, "false") }) - return + return psuApiStatelessStack } // Stateful stacks use a stable (non-versioned) stack name so that the same // CloudFormation stack is updated in-place on every deployment rather than // creating a new stack per version. - new PsuApiStatefulStack(app, "PsuApiStatefulStack", { + const psuApiStatefulStack = new PsuApiStatefulStack(app, "PsuApiStatefulStack", { ...props, stackName: modeAwareStackName, logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), @@ -79,6 +79,7 @@ async function main() { testPrescriptions3: getConfigFromEnvVar("testPrescriptions3", undefined, "PLACEHOLDER"), testPrescriptions4: getConfigFromEnvVar("testPrescriptions4", undefined, "PLACEHOLDER") }) + return psuApiStatefulStack } main().catch((error) => { diff --git a/packages/cdk/bin/PsuApiSandboxApp.ts b/packages/cdk/bin/PsuApiSandboxApp.ts index 9817c665df..7476fbddc1 100644 --- a/packages/cdk/bin/PsuApiSandboxApp.ts +++ b/packages/cdk/bin/PsuApiSandboxApp.ts @@ -14,7 +14,7 @@ async function main() { driftDetectionGroup: "psu-api" }) - new PsuApiSandboxStack(app, "PsuApiSandboxStack", { + const psuApiSandboxStack = new PsuApiSandboxStack(app, "PsuApiSandboxStack", { ...props, stackName: getConfigFromEnvVar("stackName"), logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), @@ -25,6 +25,7 @@ async function main() { enableMutualTls: getBooleanConfigFromEnvVar("enableMutualTls", undefined, "false"), enableSplunk: getBooleanConfigFromEnvVar("enableSplunk", undefined, "false") }) + return psuApiSandboxStack } main().catch((error) => { From 21758cf93289e57e1dc395b535c53b682191f544 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Fri, 1 May 2026 11:50:04 +0000 Subject: [PATCH 29/31] chore: makefile and eslint config for packages/cdk --- Makefile | 3 +++ eslint.config.mjs | 2 +- sonar-project.properties | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index eb84dde8ed..c22b14537d 100644 --- a/Makefile +++ b/Makefile @@ -212,6 +212,7 @@ lint-python: lint: lint-node lint-python lint-specification test: compile + npm run test --workspace packages/cdk npm run test --workspace packages/updatePrescriptionStatus npm run test --workspace packages/gsul npm run test --workspace packages/nhsd-psu-sandbox @@ -227,6 +228,8 @@ test: compile npm run test --workspace packages/common/utilities clean: + rm -rf packages/cdk/coverage + rm -rf packages/cdk/lib rm -rf packages/updatePrescriptionStatus/coverage rm -rf packages/updatePrescriptionStatus/lib rm -rf packages/nhsd-psu-sandbox/coverage diff --git a/eslint.config.mjs b/eslint.config.mjs index ce941ced8e..23decba52e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -96,7 +96,7 @@ const commonConfig = { export default [ { - ignores: ["**/lib/*", "**/coverage/*"], + ignores: ["**/lib/*", "**/coverage/*", "**/cdk.out/**"], }, { rules: eslintJsPlugin.configs.recommended.rules, diff --git a/sonar-project.properties b/sonar-project.properties index 0eb5a3fa1e..5760684645 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,6 +2,13 @@ sonar.organization=nhsdigital sonar.projectKey=NHSDigital_eps-prescription-status-update-api sonar.host.url=https://sonarcloud.io +# Ignore rule that requires `new Construct` to be assigned to a variable. +# Although this appears to be a useless object creation, in fact +# CDK emits Cloud Formation as a side effect on object creation. +sonar.issue.ignore.multicriteria=cdk_s1848 +sonar.issue.ignore.multicriteria.cdk_s1848.ruleKey=typescript:S1848 +sonar.issue.ignore.multicriteria.cdk_s1848.resourceKey=packages/cdk/**/* + sonar.coverage.exclusions=\ **/*.test.*, \ **/mock*, \ From 1e58164c19fbe90e9cfd5bf31fc756330a953f11 Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Fri, 1 May 2026 16:26:35 +0000 Subject: [PATCH 30/31] chore: script to copy state between stacks --- scripts/migrate_between_stacks.sh | 46 +++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 scripts/migrate_between_stacks.sh diff --git a/scripts/migrate_between_stacks.sh b/scripts/migrate_between_stacks.sh new file mode 100755 index 0000000000..4853db64c8 --- /dev/null +++ b/scripts/migrate_between_stacks.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -e + +SRC_STACK="psu" +TRGT_STACK="${CDK_CONFIG_stackName:-psu-api-tstephen-nhs}-stateful" + +SECRET_SUFFIXES=( + 'SqsSaltSecret' + 'PSU-Notify-KID' + 'PSU-Notify-PrivateKey' + 'PSU-Notify-Application-ID' + 'PSU-Notify-API-Key' +) + +PARAMETER_SUFFIXES=( + 'PSUNotifyEnabledSiteODSCodes' + 'PSUNotifyEnabledSupplierApplicationIDs' + 'PSUNotifyBlockedSiteODSCodes' + 'PSUNotifyRoutingPlanID' + 'PSUNotifyApiBaseUrl' + 'EnableNotificationsExternal' + 'EnableNotificationsInternal' + 'TEST_PRESCRIPTIONS_1' + 'TEST_PRESCRIPTIONS_2' + 'TEST_PRESCRIPTIONS_3' + 'TEST_PRESCRIPTIONS_4' +) + +for SUFFIX in "${SECRET_SUFFIXES[@]}"; do + SRC_SECRET_NAME="$SRC_STACK-$SUFFIX" + TRGT_SECRET_NAME="$TRGT_STACK-$SUFFIX" + echo "Migrating secret: $SRC_SECRET_NAME to $TRGT_SECRET_NAME" + if ! SECRET=$(aws secretsmanager get-secret-value --secret-id "$SRC_SECRET_NAME" --query 'SecretString' --output text 2>&1); then + echo "ERROR: Failed to read secret $SRC_SECRET_NAME: $SECRET" >&2 + continue + fi + aws secretsmanager put-secret-value --secret-id "$TRGT_SECRET_NAME" --secret-string "$SECRET" +done + +for SUFFIX in "${PARAMETER_SUFFIXES[@]}"; do + SRC_PARAM_NAME="$SRC_STACK-$SUFFIX" + TRGT_PARAM_NAME="$TRGT_STACK-$SUFFIX" + echo "Migrating parameter: $SRC_PARAM_NAME to $TRGT_PARAM_NAME" + PARAM_VALUE=$(aws ssm get-parameter --name "$SRC_PARAM_NAME" --query 'Parameter.Value' --output text) + aws ssm put-parameter --name "$TRGT_PARAM_NAME" --value "$PARAM_VALUE" --type String --overwrite +done From d3d01403c0c182ea9ce2ed5713c0ab70ff6366bb Mon Sep 17 00:00:00 2001 From: tstephen-nhs <231503406+tstephen-nhs@users.noreply.github.com> Date: Fri, 1 May 2026 16:28:35 +0000 Subject: [PATCH 31/31] fix: stateless reference to stateful stack --- packages/cdk/bin/PsuApiApp.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/cdk/bin/PsuApiApp.ts b/packages/cdk/bin/PsuApiApp.ts index 86b84337d0..52f6a5edb8 100644 --- a/packages/cdk/bin/PsuApiApp.ts +++ b/packages/cdk/bin/PsuApiApp.ts @@ -14,6 +14,10 @@ function mergeStackModeIntoStackName(baseStackName: string, stackMode: StackMode return `${baseStackName}-${stackMode}` } +function ensureStatefulSuffix(stackName: string): string { + return stackName.endsWith("-stateful") ? stackName : `${stackName}-stateful` +} + function getStackMode(): StackMode { const stackMode = getConfigFromEnvVar("stackMode", undefined, "stateless") @@ -40,7 +44,7 @@ async function main() { const psuApiStatelessStack = new PsuApiStatelessStack(app, "PsuApiStatelessStack", { ...props, stackName: calculateVersionedStackName(modeAwareStackName, props), - samStackName: getConfigFromEnvVar("samStackName"), // TODO: REMOVE THE NEED FOR THIS + samStackName: ensureStatefulSuffix(getConfigFromEnvVar("samStackName")), logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"), logLevel: getConfigFromEnvVar("logLevel"), environment: getConfigFromEnvVar("environment"),