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
2 changes: 1 addition & 1 deletion .github/dependabot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ updates:
schedule:
interval: "daily"

- package-ecosystem: "npm"
- package-ecosystem: "pnpm"
directory: "/"
schedule:
interval: "daily"
Expand Down
52 changes: 44 additions & 8 deletions .github/workflows/stage-2-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,21 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: "Setup pnpm"
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
version: 10.33.0
- name: "Use Node.js"
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.tool-versions'
cache: 'pnpm'
- name: "Repo setup"
run: |
npm ci
pnpm install --frozen-lockfile
- name: "Generate dependencies"
run: |
npm run generate-dependencies --workspaces --if-present
pnpm run generate-dependencies
git diff --exit-code
test-unit:
name: "Unit tests"
Expand All @@ -62,12 +71,21 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: "Setup pnpm"
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
version: 10.33.0
- name: "Use Node.js"
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.tool-versions'
cache: 'pnpm'
- name: "Repo setup"
run: |
npm ci
pnpm install --frozen-lockfile
- name: "Generate dependencies"
run: |
npm run generate-dependencies --workspaces --if-present
pnpm run generate-dependencies
- name: "Run unit test suite"
run: |
make test-unit
Expand All @@ -90,12 +108,21 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: "Setup pnpm"
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
version: 10.33.0
- name: "Use Node.js"
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.tool-versions'
cache: 'pnpm'
- name: "Repo setup"
run: |
npm ci
pnpm install --frozen-lockfile
- name: "Generate dependencies"
run: |
npm run generate-dependencies --workspaces --if-present
pnpm run generate-dependencies
- name: "Run linting"
run: |
make test-lint
Expand All @@ -106,12 +133,21 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: "Setup pnpm"
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
version: 10.33.0
- name: "Use Node.js"
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.tool-versions'
cache: 'pnpm'
- name: "Repo setup"
run: |
npm ci
pnpm install --frozen-lockfile
- name: "Generate dependencies"
run: |
npm run generate-dependencies --workspaces --if-present
pnpm run generate-dependencies
- name: "Run typecheck"
run: |
make test-typecheck
Expand Down
3 changes: 2 additions & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
act 0.2.64
gitleaks 8.24.0
jq 1.6
nodejs 22.11.0
nodejs 24.14.1
pnpm 10.33.0
pre-commit 3.6.0
terraform 1.14.3
terraform-docs 0.21.0
Expand Down
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@ The root `package.json` is the orchestration manifestgit co for this repo. It do

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.
- Before adding or removing a workspace, update the root `workspaces` array in `pnpm-workspace.yaml` and ensure CI scripts still succeed with `pnpm run lint`, `pnpm run typecheck`, and `pnpm 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 `pnpm -r run` 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`).
- `pnpm run lint`, `pnpm run typecheck`, and `pnpm run test:unit` pass at the repo root.
- Workspace discovery is correct (new projects appear under `pnpm run typecheck`).
- No regression in lambda build tooling (`pnpm run build:archive`).

## What Agents Can / Can’t Do

Expand Down
26 changes: 13 additions & 13 deletions containers/example-app/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Config } from 'jest';
import type { Config } from "jest";

const config: Config = {
preset: 'ts-jest',
preset: "ts-jest",

// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true,
Expand All @@ -10,10 +10,10 @@ const config: Config = {
collectCoverage: true,

// The directory where Jest should output its coverage files
coverageDirectory: './.reports/unit/coverage',
coverageDirectory: "./.reports/unit/coverage",

// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'babel',
coverageProvider: "babel",

coverageThreshold: {
global: {
Expand All @@ -24,26 +24,26 @@ const config: Config = {
},
},

coveragePathIgnorePatterns: ['/__tests__/'],
transform: { '^.+\\.ts$': 'ts-jest' },
testPathIgnorePatterns: ['.build'],
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
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',
"default",
[
'jest-html-reporter',
"jest-html-reporter",
{
pageTitle: 'Test Report',
outputPath: './.reports/unit/test-report.html',
pageTitle: "Test Report",
outputPath: "./.reports/unit/test-report.html",
includeFailureMsg: true,
},
],
],

// The test environment that will be used for testing
testEnvironment: 'node',
testEnvironment: "node",
};

export default config;
19 changes: 11 additions & 8 deletions containers/example-app/package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
{
"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"
"@tsconfig/node22": "catalog:tools",
"@types/jest": "catalog:test",
"@types/node": "catalog:tools",
"jest": "catalog:test",
"jest-mock-extended": "catalog:test",
"typescript": "catalog:tools"
},
"engines": {
"node": ">=24.14.1"
},
"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"
"build:archive": "rm -rf dist && pnpm exec 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",
"build:container": "cd ../.. && make docker-build-and-push base_image=node:24-alpine dir=containers/example-app",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"test:unit": "jest",
Expand Down
78 changes: 45 additions & 33 deletions containers/example-app/src/__tests__/server.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import http from 'http';
import { createRequestHandler, startServer } from '../server';
import http from "node:http";
import { createRequestHandler, startServer } from "../server";

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

it('responds with 200 status and JSON body', (done) => {
it("responds with 200 status and JSON body", () => {
const handler = createRequestHandler();
const mockReq = {} as http.IncomingMessage;
const mockRes = {
Expand All @@ -18,44 +18,56 @@ describe('example-app server', () => {

handler(mockReq, mockRes);

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

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

afterEach((done) => {
if (server) {
server.close(done);
} else {
done();
}
afterEach(async () => {
await new Promise<void>((resolve) => {
if (server) {
server.close(() => {
resolve();
});
} else {
resolve();
}
});
});

it('starts server on specified port and responds correctly', (done) => {
it("starts server on specified port and responds correctly", async () => {
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');
await new Promise<void>((resolve, reject) => {
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;
});
let body = "";
res.on("data", (chunk: Buffer) => {
body += chunk.toString();
});

res.on('end', () => {
expect(JSON.parse(body)).toEqual({ status: 'ok' });
done();
});
});
}, 100);
res.on("end", () => {
expect(JSON.parse(body)).toEqual({ status: "ok" });
resolve();
});

res.on("error", reject);
})
.on("error", reject);
}, 100);
});
});
});
});
18 changes: 10 additions & 8 deletions containers/example-app/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
// Placeholder HTTP server for AppRunner. Replace with real application code.
import http from 'http';
import http from "node:http";

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

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