Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
- [ ] I have added tests to cover my changes
- [ ] I have updated the documentation accordingly
- [ ] This PR is a result of pair or mob programming
<!-- - [ ] If I have used the 'skip-trivy-package' label I have done so responsibly and in the knowledge that this is being fixed as part of a separate ticket/PR. -->
<!-- - [ ] If I have used the 'skip-trivy-package' label I have done so responsibly and in the knowledge that this is being fixed as part of a separate ticket/PR. TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549 -->

---

Expand Down
2 changes: 1 addition & 1 deletion .github/actions/trivy-iac/action.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
#TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
# name: "Trivy IaC Scan"
# description: "Scan Terraform IaC using Trivy"
# runs:
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/trivy-package/action.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
#TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
# name: "Trivy Package Scan"
# description: "Scan project packages using Trivy"
# runs:
Expand Down
5 changes: 2 additions & 3 deletions .github/scripts/dispatch_internal_repo_workflow.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# All arguments are required except terraformAction, and internalRef.
# Example:
# ./dispatch_internal_repo_workflow.sh \
# --infraRepoName "nhs-notify-web-template-management" \
# --infraRepoName "nhs-notify-dns" \
# --releaseVersion "v1.2.3" \
# --targetWorkflow "deploy.yaml" \
# --targetEnvironment "prod" \
Expand Down Expand Up @@ -86,7 +86,7 @@ while [[ $# -gt 0 ]]; do
;;
esac
done
# Validate required parameters

if [[ -z "$APP_PEM_FILE" ]]; then
echo "[ERROR] PEM_FILE environment variable is not set or is empty."
exit 1
Expand Down Expand Up @@ -140,7 +140,6 @@ PR_TRIGGER_PAT=$(curl --request POST \
-H "Authorization: Bearer ${JWT}" \
-H "X-GitHub-Api-Version: 2022-11-28" | jq -r '.token')


# Set default values if not provided
if [[ -z "$PR_TRIGGER_PAT" ]]; then
echo "[ERROR] PR_TRIGGER_PAT environment variable is not set or is empty."
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/scheduled-repository-template-sync.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:

steps:
- name: Check out the repository
uses: actions/checkout@v5.0.0
uses: actions/checkout@v4

- name: Check out external repository
uses: actions/checkout@v5.0.0
uses: actions/checkout@v4
with:
repository: NHSDigital/nhs-notify-repository-template
path: nhs-notify-repository-template
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ jobs:

steps:
- name: "Checkout code"
uses: actions/checkout@v5.0.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: "Run analysis"
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
with:
results_file: results.sarif
results_format: sarif
Expand Down Expand Up @@ -68,6 +68,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9
uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
with:
sarif_file: results.sarif
6 changes: 0 additions & 6 deletions .trivyignore
Original file line number Diff line number Diff line change
@@ -1,6 +0,0 @@
# All CVEs below are tracked for remediation under the following Jira ticket:
# https://nhsd-jira.digital.nhs.uk/browse/CCM-14700

CVE-2026-26996 # https://avd.aquasec.com/nvd/cve-2026-26996 - minimatch - used by several dependencies, most notably eslint. we need to do the eslint v9 upgrade.
CVE-2026-27903 # https://avd.aquasec.com/nvd/cve-2026-27903 - minimatch - used by several dependencies, most notably eslint. we need to do the eslint v9 upgrade.
CVE-2026-27904 # https://avd.aquasec.com/nvd/cve-2026-27904 - minimatch - used by several dependencies, most notably eslint. we need to do the eslint v9 upgrade.
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,22 @@ Agents should look for a nested `AGENTS.md` in or near these areas before making
The root `package.json` is the orchestration manifestgit co for this repo. It does not ship application code; it wires up shared dev tooling and delegates to workspace-level projects.

- Workspaces: Declares the set of npm workspaces (e.g. under `lambdas/`, `utils/`, `tests/`, `scripts/`). Agents should add a new workspace path here when introducing a new npm project.
- Scripts: Provides top-level commands that fan out across workspaces using `--workspaces` (lint, typecheck, unit tests) and project-specific runners (e.g. `build:archive`).
- Scripts: Provides top-level commands that fan out across workspaces using `--workspaces` (lint, typecheck, unit tests) and project-specific runners (e.g. `build-archive`).
- Dev tool dependencies: Centralises Jest, TypeScript, ESLint configurations and plugins to keep versions consistent across workspaces. Workspace projects should rely on these unless a local override is strictly needed.
- Overrides/resolutions: Pins transitive dependencies (e.g. Jest/react-is) to avoid ecosystem conflicts. Agents must not remove overrides without verifying tests across all workspaces.

Agent guidance:

- Before adding or removing a workspace, update the root `workspaces` array and ensure CI scripts still succeed with `npm run lint`, `npm run typecheck`, and `npm run test:unit` at the repo root.
- When adding repo-wide scripts, keep names consistent with existing patterns (e.g. `lint`, `lint:fix`, `typecheck`, `test:unit`, `build:archive`) and prefer `--workspaces` fan-out.
- When adding repo-wide scripts, keep names consistent with existing patterns (e.g. `lint`, `lint:fix`, `typecheck`, `test:unit`, `build-archive`) and prefer `--workspaces` fan-out.
- Do not publish from the root. If adding a new workspace intended for publication, mark that workspace package as `private: false` and keep the root as private.
- Validate changes by running the repo pre-commit hooks: `make githooks-run`.

Success criteria for changes affecting the root `package.json`:

- `npm run lint`, `npm run typecheck`, and `npm run test:unit` pass at the repo root.
- Workspace discovery is correct (new projects appear under `npm run typecheck --workspaces`).
- No regression in lambda build tooling (`npm run build:archive`).
- No regression in lambda build tooling (`npm run build-archive`).

## What Agents Can / Can’t Do

Expand Down
15 changes: 15 additions & 0 deletions containers/example-app/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

set -euo pipefail

rm -rf dist

npx esbuild \
--bundle \
--minify \
--sourcemap \
--target=es2022 \
--platform=node \
--entry-names=[name] \
--outdir=dist \
src/server.ts
11 changes: 11 additions & 0 deletions containers/example-app/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ARG BASE_IMAGE

FROM ${BASE_IMAGE}

WORKDIR /app

COPY dist/ .

EXPOSE 8080

CMD ["node", "server.js"]
49 changes: 49 additions & 0 deletions containers/example-app/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { Config } from 'jest';

const config: 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: 0,
functions: 100,
lines: 90,
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: 'node',
};

export default config;
61 changes: 61 additions & 0 deletions containers/example-app/src/__tests__/server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import http from 'http';
import { createRequestHandler, startServer } from '../server';

describe('example-app server', () => {
describe('createRequestHandler', () => {
it('returns a request handler function', () => {
const handler = createRequestHandler();
expect(typeof handler).toBe('function');
});

it('responds with 200 status and JSON body', (done) => {
const handler = createRequestHandler();
const mockReq = {} as http.IncomingMessage;
const mockRes = {
writeHead: jest.fn(),
end: jest.fn(),
} as unknown as http.ServerResponse;

handler(mockReq, mockRes);

expect(mockRes.writeHead).toHaveBeenCalledWith(200, { 'Content-Type': 'application/json' });
expect(mockRes.end).toHaveBeenCalledWith(JSON.stringify({ status: 'ok' }));
done();
});
});

describe('startServer', () => {
let server: http.Server;
const port = 8888;

afterEach((done) => {
if (server) {
server.close(done);
} else {
done();
}
});

it('starts server on specified port and responds correctly', (done) => {
server = startServer(port);

// Wait a bit for server to start
setTimeout(() => {
http.get(`http://localhost:${port}`, (res) => {
expect(res.statusCode).toBe(200);
expect(res.headers['content-type']).toBe('application/json');

let body = '';
res.on('data', (chunk) => {
body += chunk;
});

res.on('end', () => {
expect(JSON.parse(body)).toEqual({ status: 'ok' });
done();
});
});
}, 100);
});
});
});
23 changes: 23 additions & 0 deletions containers/example-app/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Placeholder HTTP server for AppRunner. Replace with real application code.
import http from 'http';

export const createRequestHandler = () => {
return (_req: http.IncomingMessage, res: http.ServerResponse) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok' }));
};
};

export const startServer = (port: number = Number(process.env.PORT ?? 8080)) => {
const server = http.createServer(createRequestHandler());
server.listen(port, () => {
console.log(`Placeholder app listening on port ${port}`);
});
return server;
};

/* istanbul ignore next */
// Only start server on local/direct run
if (require.main === module) {
startServer();
}
7 changes: 7 additions & 0 deletions containers/example-app/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@tsconfig/node22/tsconfig.json",
"include": [
"src/**/*",
"jest.config.ts"
]
}
2 changes: 1 addition & 1 deletion infrastructure/terraform/bin/terraform.sh
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ fi;
# Run pre.sh
if [ -f "pre.sh" ]; then
PROJECT="${project}" REGION="${region}" COMPONENT="${component}" AWS_ACCOUNT_ID="${aws_account_id}" ENVIRONMENT="${environment}" ACTION="${action}" \
source pre.sh || error_and_die "Component pre script execution failed with exit code ${?}";
source pre.sh || error_and_die "Component pre script execution failed with exit code ${?}";
fi;

# Pull down secret TFVAR file from S3
Expand Down
13 changes: 2 additions & 11 deletions scripts/config/pre-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ repos:
rev: v5.0.0 # Use the ref you want to point at
hooks:
- id: trailing-whitespace
exclude: |
(?x)^(
frontend/src/__tests__/.*\.tsx\.snap |
frontend/src/__tests__/utils/markdownit/fixtures/index\.ts
)$
- id: detect-aws-credentials
args: [--allow-missing-credentials]
- id: check-added-large-files
Expand All @@ -17,12 +12,8 @@ repos:
- id: forbid-new-submodules
- id: mixed-line-ending
- id: pretty-format-json
exclude: |
(?x)^(
.*/?package-lock.json |
packages/event-schemas/schemas/[^/]+/[^/]+\.json
)$
args: ['--autofix']
exclude: '(^|/)package(-lock)?\.json$'
# - id: ...
- repo: local
hooks:
Expand All @@ -42,7 +33,7 @@ repos:
hooks:
- id: check-file-format
name: Check file format
entry: /usr/bin/env check=branch exclude=frontend/src/__tests__/utils/markdownit/fixtures/index.ts ./scripts/githooks/check-file-format.sh
entry: /usr/bin/env check=branch ./scripts/githooks/check-file-format.sh
language: script
pass_filenames: false
- repo: local
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[A-Z]+s
Bitwarden
bot
Cognito
Expand Down
2 changes: 1 addition & 1 deletion scripts/docker/docker.lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ function docker-push-container() {
function docker-calculate-image-name() {
local dir=${dir:-$PWD}
local container_name="${CONTAINER_NAME:-$(basename "$dir")}"
local ecr_repo="${ECR_REPO:-nhs-notify-main-acct}"
local ecr_repo="${ECR_REPO:-nhs-main-acct-admail}"
local image_suffix="${CONTAINER_IMAGE_SUFFIX:-$(docker-get-git-version-suffix)}"
local image_tag="${CONTAINER_IMAGE_PREFIX}-${container_name}"
local ecr_repo_uri="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ecr_repo}"
Expand Down
4 changes: 1 addition & 3 deletions scripts/docker/docker.mk
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,10 @@ docker-build-and-push: # Build and push container in one workflow - required: ba
AWS_ACCOUNT_ID="$${AWS_ACCOUNT_ID}" \
AWS_REGION="$${AWS_REGION}" \
ECR_REPO="$${ECR_REPO:-${ecr_repo}}" \
GITHUB_TOKEN="$${GITHUB_TOKEN:-}" \
CONTAINER_NAME="$${CONTAINER_NAME:-${container_name}}" \
dir="$${dir}" \
docker-calculate-image-name); \
echo "Building and pushing: $${DOCKER_IMAGE}"; \
${MAKE} docker-ghcr-login; \
${MAKE} docker-ecr-login; \
${MAKE} docker-build base_image=${base_image} dir="$${dir}" DOCKER_IMAGE="$${DOCKER_IMAGE}"; \
${MAKE} docker-push DOCKER_IMAGE="$${DOCKER_IMAGE}"
Expand All @@ -42,7 +40,7 @@ docker-ecr-login: # Authenticate Docker with AWS ECR - required: AWS_ACCOUNT_ID,

docker-ghcr-login: # Authenticate Docker with GitHub Container Registry - required: GITHUB_TOKEN @Development
source scripts/docker/docker.lib.sh; \
GITHUB_TOKEN="$${GITHUB_TOKEN:-}" \
GITHUB_TOKEN="$${GITHUB_TOKEN}" \
docker-ghcr-login

clean:: # Remove container image and resources - required: DOCKER_IMAGE @Development
Expand Down
2 changes: 0 additions & 2 deletions scripts/githooks/check-file-format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,8 @@ function main() {
esac

if command -v editorconfig-checker > /dev/null 2>&1 && ! is-arg-true "${FORCE_USE_DOCKER:-false}"; then
echo "Running editorconfig-checker natively"
filter="$filter" dry_run_opt="${dry_run_opt:-}" run-editorconfig-natively
else
echo "Running editorconfig-checker in Docker"
filter="$filter" dry_run_opt="${dry_run_opt:-}" run-editorconfig-in-docker
fi
}
Expand Down
Loading
Loading