diff --git a/infrastructure/terraform/components/acct/ecr_repository_main.tf b/infrastructure/terraform/components/acct/ecr_repository_main.tf index 369ffcb6a..a51974a1c 100644 --- a/infrastructure/terraform/components/acct/ecr_repository_main.tf +++ b/infrastructure/terraform/components/acct/ecr_repository_main.tf @@ -11,3 +11,66 @@ resource "aws_ecr_repository" "main" { scan_on_push = true } } + +resource "aws_ecr_lifecycle_policy" "main" { + repository = aws_ecr_repository.main.name + + policy = < [cognito\_user\_pool\_additional\_callback\_urls](#input\_cognito\_user\_pool\_additional\_callback\_urls) | A list of additional callback\_urls for the cognito user pool | `list(string)` | `[]` | no | | [commit\_id](#input\_commit\_id) | The commit to deploy. Must be in the tree for branch\_name | `string` | `"HEAD"` | no | | [component](#input\_component) | The variable encapsulating the name of this component | `string` | `"app"` | no | +| [container\_lambda\_ecr\_repo](#input\_container\_lambda\_ecr\_repo) | ECR repository name for container-based lambda images | `string` | `"nhs-notify-main-acct"` | no | | [control\_plane\_bus\_arn](#input\_control\_plane\_bus\_arn) | Data plane event bus arn | `string` | n/a | yes | | [data\_plane\_bus\_arn](#input\_data\_plane\_bus\_arn) | Data plane event bus arn | `string` | n/a | yes | | [default\_tags](#input\_default\_tags) | A map of default tags to apply to all taggable resources within the component | `map(string)` | `{}` | no | @@ -36,6 +37,7 @@ | [event\_delivery\_logging\_success\_sample\_percentage](#input\_event\_delivery\_logging\_success\_sample\_percentage) | Enable caching of events to an S3 bucket | `number` | `0` | no | | [external\_email\_domain](#input\_external\_email\_domain) | Externally managed domain used to create an SES identity for sending emails from. Validation DNS records will need to be manually configured in the DNS provider. | `string` | `null` | no | | [group](#input\_group) | The group variables are being inherited from (often synonymous with account short-name) | `string` | n/a | yes | +| [image\_tag\_suffix](#input\_image\_tag\_suffix) | The short SHA or Release Tag to append to the container lambda image tag | `string` | n/a | yes | | [kms\_deletion\_window](#input\_kms\_deletion\_window) | When a kms key is deleted, how long should it wait in the pending deletion state? | `string` | `"30"` | no | | [letter\_suppliers](#input\_letter\_suppliers) | Letter suppliers enabled in the environment |
map(object({
email_addresses = list(string)
enable_polling = bool
default_supplier = optional(bool)
}))
| `{}` | no | | [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite | `number` | `0` | no | @@ -55,6 +57,7 @@ | [eventpub](#module\_eventpub) | git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/eventpub | v2.0.28 | | [kms](#module\_kms) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/terraform-kms.zip | n/a | | [kms\_us\_east\_1](#module\_kms\_us\_east\_1) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/terraform-kms.zip | n/a | +| [letter\_preview\_renderer\_lambda](#module\_letter\_preview\_renderer\_lambda) | git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/lambda | feature/CCM-14149_Support_Container_Based_Lambdas | | [nhse\_backup\_vault](#module\_nhse\_backup\_vault) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.25/terraform-aws-backup-source.zip | n/a | | [s3bucket\_cf\_logs](#module\_s3bucket\_cf\_logs) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.20/terraform-s3bucket.zip | n/a | | [ses](#module\_ses) | ../../modules/ses | n/a | diff --git a/infrastructure/terraform/components/app/module_letter_preview_renderer_lambda.tf b/infrastructure/terraform/components/app/module_letter_preview_renderer_lambda.tf new file mode 100644 index 000000000..7ecb8ec07 --- /dev/null +++ b/infrastructure/terraform/components/app/module_letter_preview_renderer_lambda.tf @@ -0,0 +1,27 @@ +module "letter_preview_renderer_lambda" { + source = "git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/lambda?ref=feature/CCM-14149_Support_Container_Based_Lambdas" + + project = var.project + environment = var.environment + component = var.component + aws_account_id = var.aws_account_id + region = var.region + group = var.group + + function_name = "letter-preview-renderer" + description = "Letter preview renderer Lambda" + + kms_key_arn = module.kms.key_arn + + package_type = "Image" + image_uri = "${var.aws_account_id}.dkr.ecr.${var.region}.amazonaws.com/${var.container_lambda_ecr_repo}:${var.project}-${var.environment}-${var.component}-letter-preview-renderer-${var.image_tag_suffix}" + image_repository_names = [var.container_lambda_ecr_repo] + + memory = 1024 + timeout = 30 + + log_retention_in_days = var.log_retention_in_days + + log_destination_arn = local.log_destination_arn + log_subscription_role_arn = local.acct.log_subscription_role_arn +} diff --git a/infrastructure/terraform/components/app/pre.sh b/infrastructure/terraform/components/app/pre.sh index c887517a4..42bcfbb9b 100755 --- a/infrastructure/terraform/components/app/pre.sh +++ b/infrastructure/terraform/components/app/pre.sh @@ -1,7 +1,19 @@ # pre.sh runs in the same shell as terraform.sh, not in a subshell # any variables set or changed, any change of directory will persist once this script exits and returns control to terraform.sh +REGION=$1 +ENVIRONMENT=$2 +ACTION=$3 echo "Running app pre.sh" +echo "ACTION=$ACTION" +echo "component_name=$component_name" +echo "project=$project" +echo "aws_account_id=$aws_account_id" +echo "environment=$environment" +echo "region=$region" + +# Export values so subprocesses (e.g. npm run lambda-build -> docker.sh) can access them. +export component_name project aws_account_id environment region # change to monorepo root cd $(git rev-parse --show-toplevel) @@ -10,7 +22,42 @@ npm ci npm run generate-dependencies --workspaces --if-present +## Set TF_VAR_image_tag_suffix based on git tag or short SHA for unique lambda image tagging in ECR. +#This ensures that each build produces a uniquely identifiable image, and tagged releases are easily traceable. +echo "Checking if current commit is a tag..." +GIT_TAG="$(git describe --tags --exact-match 2>/dev/null || true)" +if [ -n "$GIT_TAG" ]; then + TAGGED="tag-$GIT_TAG" + echo "On tag: $GIT_TAG, exporting TF_VAR_image_tag_suffix as tag: $TAGGED" + export TF_VAR_image_tag_suffix="$TAGGED" + +else + SHORT_SHA="sha-$(git rev-parse --short HEAD)" + echo "Not on a tag, exporting TF_VAR_image_tag_suffix as short SHA: $SHORT_SHA" + export TF_VAR_image_tag_suffix="$SHORT_SHA" +fi + +echo "Checking if ACTION is 'apply' to set PUBLISH_LAMBDA_IMAGE..." +if [ "$ACTION" = "apply" ]; then + echo "Setting PUBLISH_LAMBDA_IMAGE to true for apply action" + export PUBLISH_LAMBDA_IMAGE="true" +else + echo "Not setting PUBLISH_LAMBDA_IMAGE for non-apply action (e.g. plan)" +fi + + +if [ "$ACTION" = "apply" ]; then + echo "Setting PUBLISH_LAMBDA_IMAGE to true for apply action" + export PUBLISH_LAMBDA_IMAGE="true" +else + echo "Not setting PUBLISH_LAMBDA_IMAGE for non-apply action (e.g. plan)" +fi + npm run lambda-build --workspaces --if-present +if [ $? -ne 0 ]; then + echo "npm run lambda-build failed!" >&2 + exit 1 +fi lambdas/layers/pdfjs/build.sh diff --git a/infrastructure/terraform/components/app/variables.tf b/infrastructure/terraform/components/app/variables.tf index f6f54c2bf..e6f8f9bb6 100644 --- a/infrastructure/terraform/components/app/variables.tf +++ b/infrastructure/terraform/components/app/variables.tf @@ -173,6 +173,17 @@ variable "event_delivery_logging_success_sample_percentage" { default = 0 } +variable "container_lambda_ecr_repo" { + type = string + description = "ECR repository name for container-based lambda images" + default = "nhs-notify-main-acct" +} + +variable "image_tag_suffix" { + type = string + description = "The short SHA or Release Tag to append to the container lambda image tag" +} + variable "data_plane_bus_arn" { type = string description = "Data plane event bus arn" diff --git a/infrastructure/terraform/components/sandbox/pre.sh b/infrastructure/terraform/components/sandbox/pre.sh index ebec8fafa..aab83e4c5 100755 --- a/infrastructure/terraform/components/sandbox/pre.sh +++ b/infrastructure/terraform/components/sandbox/pre.sh @@ -10,11 +10,31 @@ echo "REGION=$REGION" echo "ENVIRONMENT=$ENVIRONMENT" echo "ACTION=$ACTION" +# Export values so subprocesses (e.g. npm run lambda-build -> docker.sh) can access them. +export component_name project aws_account_id environment region + # change to monorepo root cd $(git rev-parse --show-toplevel) +## Set TF_VAR_image_tag_suffix based on git tag or short SHA for unique lambda image tagging in ECR. +## This ensures that each build produces a uniquely identifiable image, and tagged releases are easily traceable. +echo "Checking if current commit is a tag..." +GIT_TAG="$(git describe --tags --exact-match 2>/dev/null || true)" +if [ -n "$GIT_TAG" ]; then + TAGGED="tag-$GIT_TAG" + echo "On tag: $GIT_TAG, exporting TF_VAR_image_tag_suffix as tag: $TAGGED" + export TF_VAR_image_tag_suffix="$TAGGED" + +else + SHORT_SHA="sha-$(git rev-parse --short HEAD)" + echo "Not on a tag, exporting TF_VAR_image_tag_suffix as short SHA: $SHORT_SHA" + export TF_VAR_image_tag_suffix="$SHORT_SHA" +fi + if [ "${ACTION}" == "apply" ]; then echo "Building lambdas for distribution" + echo "Setting PUBLISH_LAMBDA_IMAGE to true for apply action" + export PUBLISH_LAMBDA_IMAGE="true" if [[ -z $SKIP_SANDBOX_INSTALL ]]; then echo "Installing dependencies" @@ -26,10 +46,15 @@ if [ "${ACTION}" == "apply" ]; then npm run generate-dependencies --workspaces --if-present npm run lambda-build --workspaces --if-present + if [ $? -ne 0 ]; then + echo "npm run lambda-build failed!" >&2 + exit 1 + fi lambdas/layers/pdfjs/build.sh else echo "Skipping lambda build for action $ACTION" + echo "Not setting PUBLISH_LAMBDA_IMAGE for non-apply action (e.g. plan)" fi # revert back to original directory diff --git a/lambdas/letter-preview-renderer/.eslintignore b/lambdas/letter-preview-renderer/.eslintignore new file mode 100644 index 000000000..1521c8b76 --- /dev/null +++ b/lambdas/letter-preview-renderer/.eslintignore @@ -0,0 +1 @@ +dist diff --git a/lambdas/letter-preview-renderer/.gitignore b/lambdas/letter-preview-renderer/.gitignore new file mode 100644 index 000000000..80323f7cf --- /dev/null +++ b/lambdas/letter-preview-renderer/.gitignore @@ -0,0 +1,4 @@ +coverage +node_modules +dist +.reports diff --git a/lambdas/letter-preview-renderer/__tests__/index.test.ts b/lambdas/letter-preview-renderer/__tests__/index.test.ts new file mode 100644 index 000000000..768b0f2c8 --- /dev/null +++ b/lambdas/letter-preview-renderer/__tests__/index.test.ts @@ -0,0 +1,17 @@ +import { handler } from '../index'; +import type { Context } from 'aws-lambda'; +import { mockDeep } from 'jest-mock-extended'; + +describe('event-logging Lambda', () => { + it('logs the input event and returns 200', async () => { + const event = { foo: 'bar' }; + const context = mockDeep(); + const callback = jest.fn(); + const result = await handler(event, context, callback); + + expect(result).toEqual({ + statusCode: 200, + body: 'Event logged', + }); + }); +}); diff --git a/lambdas/letter-preview-renderer/build.sh b/lambdas/letter-preview-renderer/build.sh new file mode 100644 index 000000000..232d68e6a --- /dev/null +++ b/lambdas/letter-preview-renderer/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -euo pipefail + +rm -rf dist + +npx esbuild \ + --bundle \ + --minify \ + --sourcemap \ + --target=es2020 \ + --platform=node \ + --loader:.node=file \ + --entry-names=[name] \ + --outdir=dist \ + src/index.ts diff --git a/lambdas/letter-preview-renderer/docker.sh b/lambdas/letter-preview-renderer/docker.sh new file mode 100755 index 000000000..241a815a4 --- /dev/null +++ b/lambdas/letter-preview-renderer/docker.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Fail fast on errors, unset variables, and pipeline failures. +set -euo pipefail + +# Ensure build.sh is executable and build the lambda artifacts before producing the Docker image. +chmod +x ./build.sh +./build.sh + +# Set Variables required for Docker Build. TF_REGION and TF_ENVIRONMENT are set in pre.sh and exported for use here. COMPONENT is passed in the reusable workflow. +BASE_IMAGE="$1" +CSI="${project}-${environment}-${component_name}" +ECR_REPO="${ECR_REPO:-nhs-notify-main-acct}" +GHCR_LOGIN_TOKEN="${GITHUB_TOKEN}" +GHCR_LOGIN_USER="${GITHUB_ACTOR}" +IMAGE_TAG_SUFFIX="${TF_VAR_image_tag_suffix}" +LAMBDA_NAME="${LAMBDA_NAME:-$(basename "$(cd "$(dirname "$0")" && pwd)")}" + +# Ensure required AWS/ECR configuration is present. +echo "BASE_IMAGE: ${BASE_IMAGE:-}" +echo "aws_account_id: ${aws_account_id:-}" +echo "aws_region: ${region:-}" +echo "component_name: ${component_name:-}" +echo "CSI: ${CSI:-}" +echo "ECR_REPO: ${ECR_REPO:-}" +echo "environment: ${environment:-}" +echo "GHCR_LOGIN_TOKEN: ${GHCR_LOGIN_TOKEN:-}" +echo "GHCR_LOGIN_USER: ${GHCR_LOGIN_USER:-}" +echo "IMAGE_TAG_SUFFIX: ${IMAGE_TAG_SUFFIX:-}" +echo "LAMBDA_NAME: ${LAMBDA_NAME:-}" + +# Authenticate Docker with AWS ECR using an ephemeral login token. +aws ecr get-login-password --region "${region}" | docker login --username AWS --password-stdin "${aws_account_id}".dkr.ecr."${region}".amazonaws.com + +# Optionally authenticate to GitHub Container Registry for base images. +if [ -n "${GHCR_LOGIN_USER:-}" ] && [ -n "${GHCR_LOGIN_TOKEN:-}" ]; then + echo "Attempting GHCR login as ${GHCR_LOGIN_USER}..." + if echo "${GHCR_LOGIN_TOKEN}" | docker login ghcr.io --username "${GHCR_LOGIN_USER}" --password-stdin; then + echo "GHCR login successful." + else + echo "GHCR login failed!" >&2 + fi +fi + +# Namespace tag by CSI and lambda name to avoid cross-environment collisions. +IMAGE_TAG="${CSI}-${LAMBDA_NAME}-${IMAGE_TAG_SUFFIX}" + +# Compose the full ECR image references. +ECR_REPO_URI="${aws_account_id}.dkr.ecr.${region}.amazonaws.com/${ECR_REPO}" +ECR_IMAGE="${ECR_REPO_URI}:${IMAGE_TAG}" +# Use only the first input argument for BASE_IMAGE_ARG (no fallback) +BASE_IMAGE_ARG="$1" + +# Build and tag the Docker image for the lambda. +docker buildx build \ + -f docker/lambda/Dockerfile \ + --build-arg BASE_IMAGE="${BASE_IMAGE}" \ + -t "${ECR_IMAGE}" \ + . + +# Push the image tag to ECR on apply only. The Terraform configuration will reference this tag for the lambda image. +if [ "${PUBLISH_LAMBDA_IMAGE:-false}" = "true" ]; then + echo "PUBLISH_LAMBDA_IMAGE is set to true. Pushing Docker image to ECR: ${ECR_IMAGE}" + docker push "${ECR_IMAGE}" +else + echo "PUBLISH_LAMBDA_IMAGE is not set to true (we are most likely running in the context of a TF Plan). Skipping Docker push." + exit 0 +fi diff --git a/lambdas/letter-preview-renderer/docker/lambda/Dockerfile b/lambdas/letter-preview-renderer/docker/lambda/Dockerfile new file mode 100644 index 000000000..5f2a170e4 --- /dev/null +++ b/lambdas/letter-preview-renderer/docker/lambda/Dockerfile @@ -0,0 +1,8 @@ +ARG BASE_IMAGE=ghcr.io/nhsdigital/nhs-notify/letter-renderer-node-22:latest + +FROM ${BASE_IMAGE} + +# Copy the built output from the build context (docker.sh should have run build.sh already) +COPY dist/index.js ${LAMBDA_TASK_ROOT}/index.js + +CMD [ "index.handler" ] diff --git a/lambdas/letter-preview-renderer/index.ts b/lambdas/letter-preview-renderer/index.ts new file mode 100644 index 000000000..3b5a5bff8 --- /dev/null +++ b/lambdas/letter-preview-renderer/index.ts @@ -0,0 +1,10 @@ +// Replace me with the actual code for your Lambda function +import { Handler } from 'aws-lambda'; + +export const handler: Handler = async (event) => { + console.log('Received event:', event); + return { + statusCode: 200, + body: 'Event logged', + }; +}; diff --git a/lambdas/letter-preview-renderer/jest.config.ts b/lambdas/letter-preview-renderer/jest.config.ts new file mode 100644 index 000000000..d30f4cd1c --- /dev/null +++ b/lambdas/letter-preview-renderer/jest.config.ts @@ -0,0 +1,60 @@ +import type { Config } from 'jest'; + +export const baseJestConfig: Config = { + preset: 'ts-jest', + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // The directory where Jest should output its coverage files + coverageDirectory: './.reports/unit/coverage', + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: 'babel', + + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: -10, + }, + }, + + coveragePathIgnorePatterns: ['/__tests__/'], + transform: { '^.+\\.ts$': 'ts-jest' }, + testPathIgnorePatterns: ['.build'], + testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], + + // Use this configuration option to add custom reporters to Jest + reporters: [ + 'default', + [ + 'jest-html-reporter', + { + pageTitle: 'Test Report', + outputPath: './.reports/unit/test-report.html', + includeFailureMsg: true, + }, + ], + ], + + // The test environment that will be used for testing + testEnvironment: 'jsdom', +}; + +const utilsJestConfig = { + ...baseJestConfig, + + testEnvironment: 'node', + + coveragePathIgnorePatterns: [ + ...(baseJestConfig.coveragePathIgnorePatterns ?? []), + 'zod-validators.ts', + ], +}; + +export default utilsJestConfig; diff --git a/lambdas/letter-preview-renderer/package.json b/lambdas/letter-preview-renderer/package.json new file mode 100644 index 000000000..362d895fe --- /dev/null +++ b/lambdas/letter-preview-renderer/package.json @@ -0,0 +1,23 @@ +{ + "dependencies": { + "esbuild": "^0.25.0" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/aws-lambda": "^8.10.148", + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "jest-mock-extended": "^3.0.7", + "typescript": "^5.8.2" + }, + "name": "nhs-notify-templates-letter-preview-renderer", + "private": true, + "scripts": { + "lambda-build": "./docker.sh ghcr.io/nhsdigital/nhs-notify/libreoffice-node-22:latest", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "test:unit": "jest", + "typecheck": "tsc --noEmit" + }, + "version": "0.0.1" +} diff --git a/lambdas/letter-preview-renderer/src/__tests__/index.test.ts b/lambdas/letter-preview-renderer/src/__tests__/index.test.ts new file mode 100644 index 000000000..768b0f2c8 --- /dev/null +++ b/lambdas/letter-preview-renderer/src/__tests__/index.test.ts @@ -0,0 +1,17 @@ +import { handler } from '../index'; +import type { Context } from 'aws-lambda'; +import { mockDeep } from 'jest-mock-extended'; + +describe('event-logging Lambda', () => { + it('logs the input event and returns 200', async () => { + const event = { foo: 'bar' }; + const context = mockDeep(); + const callback = jest.fn(); + const result = await handler(event, context, callback); + + expect(result).toEqual({ + statusCode: 200, + body: 'Event logged', + }); + }); +}); diff --git a/lambdas/letter-preview-renderer/src/index.ts b/lambdas/letter-preview-renderer/src/index.ts new file mode 100644 index 000000000..3b5a5bff8 --- /dev/null +++ b/lambdas/letter-preview-renderer/src/index.ts @@ -0,0 +1,10 @@ +// Replace me with the actual code for your Lambda function +import { Handler } from 'aws-lambda'; + +export const handler: Handler = async (event) => { + console.log('Received event:', event); + return { + statusCode: 200, + body: 'Event logged', + }; +}; diff --git a/lambdas/letter-preview-renderer/tsconfig.json b/lambdas/letter-preview-renderer/tsconfig.json new file mode 100644 index 000000000..ea37d6966 --- /dev/null +++ b/lambdas/letter-preview-renderer/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "include": [ + "src/**/*", + "jest.config.ts" + ] +} diff --git a/package-lock.json b/package-lock.json index b426ccbc7..4ce2778df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "lambdas/backend-client", "lambdas/download-authorizer", "lambdas/sftp-letters", + "lambdas/letter-preview-renderer", "packages/event-schemas", "tests/accessibility", "tests/contracts/provider", @@ -662,6 +663,21 @@ "node": ">=18.0.0" } }, + "lambdas/letter-preview-renderer": { + "name": "nhs-notify-templates-letter-preview-renderer", + "version": "0.0.1", + "dependencies": { + "esbuild": "^0.25.0" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/aws-lambda": "^8.10.148", + "@types/jest": "^29.5.14", + "jest": "^29.7.0", + "jest-mock-extended": "^3.0.7", + "typescript": "^5.8.2" + } + }, "lambdas/sftp-letters": { "name": "nhs-notify-sftp-letters-lambdas", "version": "0.0.1", @@ -6498,7 +6514,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6515,7 +6530,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6532,7 +6546,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6549,7 +6562,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6566,7 +6578,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6583,7 +6594,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6600,7 +6610,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6617,7 +6626,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6634,7 +6642,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6651,7 +6658,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6668,7 +6674,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6685,7 +6690,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6702,7 +6706,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6719,7 +6722,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6736,7 +6738,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6753,7 +6754,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6770,7 +6770,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6787,7 +6786,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6804,7 +6802,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6821,7 +6818,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6838,7 +6834,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6855,7 +6850,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6872,7 +6866,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6889,7 +6882,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6906,7 +6898,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6923,7 +6914,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13217,13 +13207,13 @@ } }, "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -15459,7 +15449,6 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -20537,20 +20526,20 @@ } }, "node_modules/jsonpath": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", - "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.2.1.tgz", + "integrity": "sha512-Jl6Jhk0jG+kP3yk59SSeGq7LFPR4JQz1DU0K+kXTysUhMostbhU3qh5mjTuf0PqFcXpAT7kvmMt9WxV10NyIgQ==", "license": "MIT", "dependencies": { - "esprima": "1.2.2", - "static-eval": "2.0.2", - "underscore": "1.12.1" + "esprima": "1.2.5", + "static-eval": "2.1.1", + "underscore": "1.13.6" } }, "node_modules/jsonpath/node_modules/esprima": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", - "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", + "integrity": "sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -21650,6 +21639,10 @@ "resolved": "lambdas/event-publisher", "link": true }, + "node_modules/nhs-notify-templates-letter-preview-renderer": { + "resolved": "lambdas/letter-preview-renderer", + "link": true + }, "node_modules/nhs-notify-web-template-management-accessibility-test": { "resolved": "tests/accessibility", "link": true @@ -25155,93 +25148,12 @@ } }, "node_modules/static-eval": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", - "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", - "license": "MIT", - "dependencies": { - "escodegen": "^1.8.1" - } - }, - "node_modules/static-eval/node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/static-eval/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/static-eval/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "license": "MIT", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", + "integrity": "sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==", "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" + "escodegen": "^2.1.0" } }, "node_modules/statuses": { @@ -26809,9 +26721,9 @@ } }, "node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "license": "MIT" }, "node_modules/undici": { diff --git a/package.json b/package.json index d7d4a590b..915554d9f 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "lambdas/backend-client", "lambdas/download-authorizer", "lambdas/sftp-letters", + "lambdas/letter-preview-renderer", "packages/event-schemas", "tests/accessibility", "tests/contracts/provider",