Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a2fc9dd
CCM-12869: add config -> DynamoDB publisher tooling + release-bundled…
m-houston Feb 27, 2026
fc3a7f4
Refactor Node.js setup in CI workflows and enhance dependency management
m-houston Feb 27, 2026
7b82e85
Ensure workspace packages are generated before bundling in release sc…
m-houston Feb 27, 2026
74fd573
Enhance bundling process in release script to resolve workspace packa…
m-houston Feb 27, 2026
79879b0
Use absolute path for alias in bundling to ensure correct resolution …
m-houston Feb 27, 2026
2d41988
Update dependencies in package.json and package-lock.json for improve…
m-houston Feb 27, 2026
ff73805
Improve debug logging and update bundling configuration for workspace…
m-houston Feb 27, 2026
c2f003c
Add ddb-publish bundling action and update release workflows
m-houston Feb 27, 2026
9234b39
Add workflow artifact upload and improve ddb-publish bundle resolution
m-houston Mar 3, 2026
b30ec6f
Refactor ddb-publish action to use action repository for artifact dow…
m-houston Mar 3, 2026
70ff42e
Enhance ddb-publish action to search multiple workflows for successfu…
m-houston Mar 3, 2026
daecddc
Enhance runPublisher to log detailed steps and summarize loaded records
m-houston Mar 3, 2026
1f2a4bd
Add integration tests for DynamoDB Local and support custom endpoint …
m-houston Mar 3, 2026
6d3216e
Remove redundant console log eslint disables in bundle-release.mjs
m-houston Mar 3, 2026
365fe60
Use testcontainers for ddb-local container management
m-houston Mar 3, 2026
288f9b2
Refactor pre-commit hooks to remove redundant local repo entries
m-houston Mar 3, 2026
b15b3ab
feat(ddb-publisher): document local DynamoDB usage and add sample con…
m-houston Mar 11, 2026
67e9305
feat(tests): enhance integration tests for DynamoDB Local and improve…
m-houston Mar 12, 2026
53ff4e0
refactor(tests): replace 'kind' fields with 'id' in config store vali…
m-houston Mar 12, 2026
eed3b67
feat(tests): add test for explicit AWS env values in local DynamoDB c…
m-houston Mar 12, 2026
423ba1d
refactor(publish): remove 'entity' and 'env' fields from records and …
m-houston Mar 12, 2026
5a82841
test(run): manage AWS environment variables during DynamoDB local tests
m-houston Mar 12, 2026
6054b00
chore(template): add note for code generated by a coding agent in PR …
m-houston Mar 12, 2026
2052c7a
feat(lint): add linting and type checking scripts; update package.jso…
m-houston Mar 13, 2026
07296d3
fix(bundle): use fileURLToPath for packageRoot resolution; update ver…
m-houston Mar 13, 2026
2f9126b
feat(package): add pretypecheck script and update dependencies
m-houston Mar 13, 2026
ac83225
Merge branch 'main' into CCM-12869-dynamodb-publish-action
m-houston Apr 2, 2026
3a3d0bb
Merge branch 'main' into CCM-12869-dynamodb-publish-action
m-houston Apr 7, 2026
6911798
Fix linting and integration test setup
m-houston Apr 7, 2026
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
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
- [ ] 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. TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549 -->
- [ ] This PR includes code generated by a coding agent

---

Expand Down
65 changes: 65 additions & 0 deletions .github/actions/bundle-ddb-publish/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: "Bundle ddb-publish"
description: "Build and package the ddb-publish release bundle (ddb-publish-bundle.tgz) from the workspace sources."

inputs:
node-version:
description: "Node.js version to use"
required: true
run-typecheck:
description: "Run workspace typecheck before bundling"
required: false
default: "true"

outputs:
tarball-path:
description: "Path to the generated tarball"
value: ${{ steps.bundle.outputs.tarball_path }}

runs:
using: "composite"
steps:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "${{ inputs.node-version }}"
cache: npm

- name: Install dependencies
shell: bash
run: |
set -euo pipefail
npm ci

- name: Generate dependencies
shell: bash
run: |
set -euo pipefail
npm run generate-dependencies --workspaces --if-present

- name: Typecheck
if: inputs.run-typecheck == 'true'
shell: bash
run: |
set -euo pipefail
npm run typecheck --workspaces --if-present

- name: Build ddb-publish bundle
shell: bash
run: |
set -euo pipefail
npm run bundle:release --workspace @supplier-config/ddb-publisher

- name: Smoke-test bundle
shell: bash
run: |
set -euo pipefail
node packages/ddb-publisher/artifacts/ddb-publish/index.cjs --help > /dev/null

- name: Package tarball
id: bundle
shell: bash
run: |
set -euo pipefail
tarball="ddb-publish-bundle.tgz"
tar -czf "$tarball" -C packages/ddb-publisher/artifacts/ddb-publish .
echo "tarball_path=$tarball" >> "$GITHUB_OUTPUT"
46 changes: 46 additions & 0 deletions .github/workflows/release-ddb-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Release ddb-publish bundle

on:
release:
types: [published]
workflow_dispatch:

permissions:
contents: write

jobs:
bundle-and-upload:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Read Node version
id: versions
shell: bash
run: |
set -euo pipefail
echo "node=$(grep '^nodejs\s' .tool-versions | awk '{print $2}')" >> "$GITHUB_OUTPUT"

- name: Bundle ddb-publish
id: bundle
uses: ./.github/actions/bundle-ddb-publish
with:
node-version: "${{ steps.versions.outputs.node }}"
run-typecheck: "true"

- name: Upload release asset
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
gh release upload "${{ github.event.release.tag_name }}" "${{ steps.bundle.outputs.tarball-path }}" --clobber

- name: Upload bundle as workflow artifact
if: github.event_name != 'release'
uses: actions/upload-artifact@v4
with:
name: ddb-publish-bundle
path: "${{ steps.bundle.outputs.tarball-path }}"
16 changes: 16 additions & 0 deletions .github/workflows/stage-2-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,19 @@ jobs:
sonar_organisation_key: "${{ vars.SONAR_ORGANISATION_KEY }}"
sonar_project_key: "${{ vars.SONAR_PROJECT_KEY }}"
sonar_token: "${{ secrets.SONAR_TOKEN }}"
test-integration:
name: "Integration tests"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: "Checkout code"
uses: actions/checkout@v4
- name: "Repo setup"
run: |
npm ci
- name: "Generate dependencies"
run: |
npm run generate-dependencies --workspaces --if-present
- name: "Run integration tests"
run: |
npm run test:integration
22 changes: 22 additions & 0 deletions .github/workflows/stage-3-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,28 @@ jobs:
uses: ./.github/actions/build-docs
with:
version: "${{ inputs.version }}"

ddb-publish-bundle:
name: "Bundle ddb-publish CLI"
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- name: "Checkout code"
uses: actions/checkout@v4

- name: "Bundle ddb-publish"
id: bundle
uses: ./.github/actions/bundle-ddb-publish
with:
node-version: "${{ inputs.nodejs_version }}"
run-typecheck: "false"

- name: "Upload bundle as workflow artifact"
uses: actions/upload-artifact@v4
with:
name: ddb-publish-bundle
path: "${{ steps.bundle.outputs.tarball-path }}"

artefact-1:
name: "Artefact 1"
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
act 0.2.64
gitleaks 8.24.0
jq 1.6
nodejs 22.11.0
nodejs 24.14.0
pre-commit 3.6.0
terraform 1.9.2
terraform-docs 0.19.0
Expand Down
7 changes: 6 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ When proposing a change, agents should:

to catch formatting and basic lint issues. Domain specific checks will be defined in appropriate nested AGENTS.md files.

- When editing Markdown files, also run `./scripts/githooks/check-markdown-format.sh <file>` (or `check=all ./scripts/githooks/check-markdown-format.sh <file>` when needed) to catch markdownlint issues before review.
- Suggest at least one extra validation step (for example `npm test` in a lambda, or triggering a specific workflow).
- Any required follow up activites which fall outside of the current task's scope should be clearly marked with a 'TODO: CCM-12345' comment. The human user should be prompted to create and provide a JIRA ticket ID to be added to the comment.

Expand All @@ -96,12 +97,16 @@ If you are blocked by an unavailable secret, unclear architectural constraint, m

## `nhs-notify-supplier-config` repo-specific notes

- `nhs-notify-repository-template/` is a checked-in reference copy of the template repo. Do not make normal feature changes there; only edit it when intentionally comparing with or syncing the template.
- Template sync workflows and helper scripts may use `nhs-notify-repository-template/` as a temporary upstream checkout path. Treat it as sync/reference material rather than normal product code if it appears during maintenance work.
- The main application and domain work in this repo is concentrated under `packages/`. Use local `README` files, manifests, and tests there to understand package-specific behaviour before making non-trivial changes.
- Schema and event payload work is concentrated in `packages/events/`, with CloudEvent construction in `packages/event-builder/`. When changing schemas under `packages/events/src/domain/` or `packages/events/src/events/`, update any affected builders and Jest tests in both workspaces.
- Supplier-config schemas use Zod 4 `.meta()` metadata for field titles/descriptions and generated artefacts. Preserve or update that metadata when changing schema fields, and rerun the relevant `packages/events/` generators when outputs change.
- Important repo data inputs live at the top level and under `packages/ui/data/`, especially `specifications.json`, `specifications.xlsx`, and `letterVariants.csv`. Treat these as source material for supplier configuration work and preserve their formats unless the task explicitly changes them.
- `make build` currently builds the Jekyll docs site (`docs/`); it is not a full monorepo build for the supplier-config packages.
- CI currently still includes some template-era scaffolding. In particular, root workspace scripts and some `make test-*` paths do not yet cover every directory under `packages/`, so validate the specific package or area you changed directly as well as running the standard repo hooks.
- Suggested extra validation by area:
- `packages/events/`: run focused scripts such as `npm run test:unit --workspace=@nhsdigital/nhs-notify-event-schemas-supplier-config`, plus generators when schema outputs or derived artefacts are affected.
- `packages/event-builder/`: run `npm run test:unit --workspace=@supplier-config/event-builder` and `npm run typecheck --workspace=@supplier-config/event-builder` when changing event builder logic.
- `packages/`: run the changed package's local scripts (for example tests, typecheck, or generators) in addition to repo-level hooks.
- `docs/`: run `(cd docs && make build)` when changing Jekyll content or templates.
- root config/docs guidance: run `pre-commit run --config scripts/config/pre-commit.yaml` from the repo root.
Expand Down
140 changes: 140 additions & 0 deletions actions/ddb-publish/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
name: "Publish supplier config to DynamoDB"
description: "Reads config from a file store and publishes records to a DynamoDB table."
author: "NHS Notify"

inputs:
source-path:
description: "Path to the config store root directory"
required: true
target-env:
description: "Target environment label (e.g. draft/int/prod)"
required: true
table-name:
description: "DynamoDB table name"
required: true
force:
description: "Bypass audit safety checks"
required: false
default: "false"
dry-run:
description: "Validate only (no AWS calls). Does not require credentials."
required: false
default: "false"

runs:
using: "composite"
steps:
- name: Resolve and download ddb-publish bundle
shell: bash
env:
GH_TOKEN: ${{ github.token }}
CALLER_REPO: ${{ github.repository }}
ACTION_REPO: ${{ github.action_repository }}
ACTION_REF: ${{ github.action_ref }}
RELEASE_ASSET_NAME: ddb-publish-bundle.tgz
WORKFLOW_ARTIFACT_NAME: ddb-publish-bundle
run: |
set -euo pipefail

download_dir="${RUNNER_TEMP}/ddb-publish"
unpack_dir="${download_dir}/unpacked"

mkdir -p "${download_dir}" "${unpack_dir}"

echo "[ddb-publish] caller_repo=${CALLER_REPO}"
echo "[ddb-publish] action_repo=${ACTION_REPO}"
echo "[ddb-publish] action_ref=${ACTION_REF:-<empty>}"

if [[ -z "${ACTION_REF}" ]]; then
echo "ERROR: github.action_ref is empty; unable to locate release asset or branch artifact." >&2
exit 1
fi

if [[ -z "${ACTION_REPO}" ]]; then
echo "ERROR: github.action_repository is empty; unable to determine where to fetch bundle artifacts." >&2
exit 1
fi

# Prefer release assets when the action ref is a tag with a matching release in the action repo.
if gh release view "${ACTION_REF}" --repo "${ACTION_REPO}" >/dev/null 2>&1; then
echo "[ddb-publish] Found release for ref '${ACTION_REF}' in '${ACTION_REPO}'. Downloading '${RELEASE_ASSET_NAME}'."
gh release download "${ACTION_REF}" --repo "${ACTION_REPO}" --pattern "${RELEASE_ASSET_NAME}" --dir "${download_dir}"
tar -xzf "${download_dir}/${RELEASE_ASSET_NAME}" -C "${unpack_dir}"
echo "[ddb-publish] Bundle extracted from release asset."
exit 0
fi

# Otherwise treat the ref as a branch-like ref and fetch the latest successful CI artifact from the action repo.
branch="${ACTION_REF#refs/heads/}"
echo "[ddb-publish] No release found for ref '${ACTION_REF}'. Falling back to latest workflow artifact on branch '${branch}' from '${ACTION_REPO}'."

run_id=""
workflow_used=""

for workflow_file in "stage-3-build.yaml" "cicd-1-pull-request.yaml"; do
echo "[ddb-publish] Looking for successful runs in workflow '${workflow_file}'."

set +e
run_id_candidate="$(gh run list \
--repo "${ACTION_REPO}" \
--workflow "${workflow_file}" \
--branch "${branch}" \
--status success \
--json databaseId \
--jq '.[0].databaseId' 2>/tmp/ddb_publish_run_list_err.log)"
rc=$?
set -e

if [[ $rc -ne 0 ]]; then
if grep -q "HTTP 404" /tmp/ddb_publish_run_list_err.log; then
echo "[ddb-publish] Workflow '${workflow_file}' not found on default branch; trying next workflow candidate."
continue
fi

echo "ERROR: Failed to query workflow runs from '${ACTION_REPO}' for workflow '${workflow_file}'." >&2
cat /tmp/ddb_publish_run_list_err.log >&2 || true
echo "HINT: ensure the token has read access to '${ACTION_REPO}' and workflow permissions allow actions read." >&2
exit 1
fi

if [[ -n "${run_id_candidate}" && "${run_id_candidate}" != "null" ]]; then
run_id="${run_id_candidate}"
workflow_used="${workflow_file}"
break
fi
done

if [[ -z "${run_id}" || "${run_id}" == "null" ]]; then
echo "ERROR: Could not find a successful run on branch '${branch}' in '${ACTION_REPO}' containing artifact '${WORKFLOW_ARTIFACT_NAME}'. Checked workflows: stage-3-build.yaml, cicd-1-pull-request.yaml." >&2
exit 1
fi

echo "[ddb-publish] Downloading artifact '${WORKFLOW_ARTIFACT_NAME}' from workflow run ${run_id} (${workflow_used}) in '${ACTION_REPO}'."
gh run download "${run_id}" --repo "${ACTION_REPO}" --name "${WORKFLOW_ARTIFACT_NAME}" --dir "${download_dir}"

tarball_path="${download_dir}/${RELEASE_ASSET_NAME}"
if [[ ! -f "${tarball_path}" ]]; then
echo "ERROR: Downloaded artifact did not contain expected tarball '${RELEASE_ASSET_NAME}'." >&2
ls -la "${download_dir}" >&2
exit 1
fi

tar -xzf "${tarball_path}" -C "${unpack_dir}"
echo "[ddb-publish] Bundle extracted from workflow artifact."

- name: Run publisher
shell: bash
run: |
set -euo pipefail

echo "[ddb-publish] Starting publish run"
echo "[ddb-publish] source='${{ inputs.source-path }}' env='${{ inputs.target-env }}' table='${{ inputs.table-name }}' force='${{ inputs.force }}' dry-run='${{ inputs.dry-run }}'"

node "${RUNNER_TEMP}/ddb-publish/unpacked/index.cjs" \
--source "${{ inputs.source-path }}" \
--env "${{ inputs.target-env }}" \
--table "${{ inputs.table-name }}" \
$([[ "${{ inputs.force }}" == "true" ]] && echo "--force") \
$([[ "${{ inputs.dry-run }}" == "true" ]] && echo "--dry-run")

echo "[ddb-publish] Publish run completed"
Loading
Loading