Skip to content
Open
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
6 changes: 3 additions & 3 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# NHS Notify Code Owners

# Notify default owners
* @NHSDigital/nhs-notify-repository-template
* @NHSDigital/nhs-notify-admail

/.github/ @NHSDigital/nhs-notify-repository-template-admins
*.code-workspace @NHSDigital/nhs-notify-repository-template-admins
/.github/ @NHSDigital/nhs-notify-admail-admins
*.code-workspace @NHSDigital/nhs-notify-admail-admins
/infrastructure/terraform/ @NHSDigital/nhs-notify-platform

# Root level AGENTS.md owned by platform.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
## This workflow is DISABLED.
## To enable, rename from .disabled to .yaml and replace any references as per the comments.
name: PR Closed

on:
Expand Down Expand Up @@ -46,7 +44,7 @@ jobs:
strategy:
max-parallel: 1
matrix:
component: [acct, app]
component: [admail]

steps:
- name: Checkout repository
Expand All @@ -59,8 +57,8 @@ jobs:
run: |
bash .github/scripts/dispatch_internal_repo_workflow.sh \
--releaseVersion "main" \
--targetWorkflow "dispatch-deploy-static-notify-bounded-context-env.yaml" ## Replace with correct targetWorkflow \
--targetWorkflow "dispatch-deploy-static-notify-admail-env.yaml" \
--targetEnvironment "main" \
--targetAccountGroup "nhs-notify-bounded-context-dev" ## Replace with correct targetAccountGroup \
--targetAccountGroup "nhs-notify-admail-dev" \
--targetComponent "${{ matrix.component }}" \
--terraformAction "apply"
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
## This workflow is DISABLED.
## To enable, rename from .disabled to .yaml and replace any references as per the comments.
name: Github Release Created

on:
Expand All @@ -22,7 +20,7 @@ jobs:
strategy:
max-parallel: 1
matrix:
component: [component1, component2] ## Replace with correct components
component: [admail]

steps:
- name: Checkout repository
Expand All @@ -35,8 +33,8 @@ jobs:
run: |
bash .github/scripts/dispatch_internal_repo_workflow.sh \
--releaseVersion "${{ github.event.release.tag_name }}" \
--targetWorkflow "dispatch-deploy-static-notify-bounded-context-env.yaml" ## Replace with correct targetWorkflow \
--targetWorkflow "dispatch-deploy-static-notify-admail-env.yaml" \
--targetEnvironment "main" \
--targetAccountGroup "nhs-notify-bounded-context-nonprod" ## Replace with correct targetAccountGroup \
--targetAccountGroup "nhs-notify-admail-nonprod" \
--targetComponent "${{ matrix.component }}" \
--terraformAction "apply"
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ gitleaks 8.24.0
jq 1.6
nodejs 22.11.0
pre-commit 3.6.0
terraform 1.10.1
terraform-docs 0.19.0
terraform 1.14.3
terraform-docs 0.21.0
trivy 0.61.0
vale 3.6.0
python 3.13.2
Expand Down
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. `lambda-build`).
- Scripts: Provides top-level commands that fan out across workspaces using `--workspaces` (lint, typecheck, unit tests) and project-specific runners (e.g. `build:container`).
- 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`, `lambda-build`) 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:container`) 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 lambda-build`).
- No regression in container build tooling (`npm run build:container`).

## 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;
23 changes: 23 additions & 0 deletions containers/example-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"devDependencies": {
"@tsconfig/node22": "^22.0.2",
"@types/jest": "^29.5.14",
"@types/node": "^22.0.0",
"jest": "^29.7.0",
"jest-mock-extended": "^3.0.7",
"typescript": "^5.8.2"
},
"name": "nhs-notify-admail-example-app",
"private": true,
"unused-scripts": {
"build:archive": "rm -rf dist && npx esbuild --bundle --minify --sourcemap --target=es2020 --platform=node --loader:.node=file --entry-names=[name] --outdir=dist src/index.ts"
},
"scripts": {
"build:container": "cd ../.. && make docker-build-and-push base_image=node:22-alpine dir=containers/example-app",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"test:unit": "jest",
"typecheck": "tsc --noEmit"
},
"version": "0.0.1"
}
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"
]
}
Loading
Loading