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
85 changes: 85 additions & 0 deletions .github/workflows/publish-release-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Publish release-check package

on:
release:
types: ["published"]
workflow_dispatch:

jobs:
check-release-check-version-change:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
outputs:
version_changed: ${{ steps.check-version.outputs.version_changed }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Read tool versions
id: versions
shell: bash
run: |
echo "nodejs_version=$(grep "^nodejs\s" .tool-versions | cut -f2 -d' ')" >> "$GITHUB_OUTPUT"

- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: ${{ steps.versions.outputs.nodejs_version }}
registry-url: 'https://npm.pkg.github.com'

- name: Check if local version differs from latest published version
id: check-version
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
published_version=$(npm view @nhsdigital/nhs-notify-release-check --json 2>/dev/null | jq -r '.["dist-tags"].latest // "null"')
echo "Published version: $published_version"

local_version=$(jq -r '.version' tools/release-check/package.json)
echo "Local version: $local_version"

if [[ "$local_version" = "$published_version" ]]; then
echo "Local version matches the latest published version - skipping publish"
echo "version_changed=false" >> "$GITHUB_OUTPUT"
else
echo "Local version differs from the latest published version - publishing new version"
echo "version_changed=true" >> "$GITHUB_OUTPUT"
fi

publish-release-check:
needs: check-release-check-version-change
if: needs.check-release-check-version-change.outputs.version_changed == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Read tool versions
id: versions
shell: bash
run: |
echo "nodejs_version=$(grep "^nodejs\s" .tool-versions | cut -f2 -d' ')" >> "$GITHUB_OUTPUT"
echo "pnpm_version=$(grep "^pnpm\s" .tool-versions | cut -f2 -d' ')" >> "$GITHUB_OUTPUT"

- name: Node install and setup
uses: ./.github/actions/node-install
with:
node-version: ${{ steps.versions.outputs.nodejs_version }}
pnpm-version: ${{ steps.versions.outputs.pnpm_version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Validate package
run: pnpm --filter @nhsdigital/nhs-notify-release-check run typecheck && pnpm --filter @nhsdigital/nhs-notify-release-check run test:unit

- name: Publish package
run: pnpm --filter @nhsdigital/nhs-notify-release-check publish --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59 changes: 59 additions & 0 deletions tools/release-check/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# release-check

Compares a local repository release tag against a Jira release version and reports mismatches across:

- git commit history since the previous tag
- Jira issues assigned to the release version
- release notes, using the GitHub release body when available

## Usage

From the shared-modules repository root:

```bash
pnpm release-check -- --repo ../nhs-notify-client-config --git-tag 0.1.0 --jira-version 71260
```

Or directly:

```bash
pnpm --filter @nhsdigital/nhs-notify-release-check run check -- --repo ../nhs-notify-client-config --git-tag 0.1.0 --jira-version 71260
```

## Required environment

- `JIRA_API_TOKEN` or `JIRA_PERSONAL_TOKEN` or `JIRA_TOKEN`

## Optional environment

- `GITHUB_TOKEN` or `GH_TOKEN` for fetching GitHub release notes from private repositories

## Notes

- The tool auto-detects the previous tag using `git describe --tags --abbrev=0 <tag>^`.
- When GitHub release notes are unavailable, auto mode falls back to annotated tag notes if the tag is annotated.
- Reports default to `.tmp/release-check/<repo>-<tag>.txt` in the current working directory.

## Publishing

The package is configured for GitHub Packages as `@nhsdigital/nhs-notify-release-check`.

```bash
pnpm --filter @nhsdigital/nhs-notify-release-check pack
pnpm --filter @nhsdigital/nhs-notify-release-check publish --no-git-checks
```

## Consuming from another repository

Add this to the consuming repository's `.npmrc`:

```ini
@nhsdigital:registry=https://npm.pkg.github.com
```

Then install and use the CLI:

```bash
pnpm add -D @nhsdigital/nhs-notify-release-check
pnpm release-check --repo ../nhs-notify-client-config --git-tag 0.1.0 --jira-version 71260
```
121 changes: 121 additions & 0 deletions tools/release-check/src/__tests__/compare.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { compareRelease } from '../compare';

import type { GitCommit, JiraIssue } from '../types';

const issues: JiraIssue[] = [
{
clinicalLead: '',
clinicalReviewStatus: 'Review required',
key: 'CCM-100',
medicalClinicalSafetyCategory: '',
summary: 'First ticket',
status: 'Done',
components: ['Platform'],
},
{
clinicalLead: 'Dr Test',
clinicalReviewStatus: 'In review',
key: 'CCM-101',
medicalClinicalSafetyCategory: 'Cat 1',
summary: 'Exact summary fallback',
status: 'In Progress',
components: ['Platform'],
},
{
clinicalLead: '',
clinicalReviewStatus: 'Review not needed',
key: 'CCM-102',
medicalClinicalSafetyCategory: '',
summary: 'Release only ticket',
status: 'Done',
components: ['Platform'],
},
];

const commits: GitCommit[] = [
{
hash: 'a'.repeat(40),
shortHash: 'aaaaaaaa',
subject: 'CCM-100: implement first ticket',
body: '',
explicitIssueKeys: ['CCM-100'],
},
{
hash: 'b'.repeat(40),
shortHash: 'bbbbbbbb',
subject: 'Exact summary fallback (#123)',
body: '',
explicitIssueKeys: [],
},
{
hash: 'c'.repeat(40),
shortHash: 'cccccccc',
subject: 'Untracked maintenance change',
body: '',
explicitIssueKeys: [],
},
{
hash: 'd'.repeat(40),
shortHash: 'dddddddd',
subject: 'CCM-999: outside release',
body: '',
explicitIssueKeys: ['CCM-999'],
},
];

describe('compareRelease', () => {
it('compares git and release-note issue references against the Jira release', () => {
const result = compareRelease(commits, issues, ['CCM-100', 'CCM-200']);

expect(result.gitReferencedIssueKeys).toEqual([
'CCM-100',
'CCM-101',
'CCM-999',
]);
expect(result.notesReferencedIssueKeys).toEqual(['CCM-100', 'CCM-200']);
expect(result.jiraIssuesMissingFromGit.map((issue) => issue.key)).toEqual([
'CCM-102',
]);
expect(
result.jiraIssuesMissingFromReleaseNotes.map((issue) => issue.key),
).toEqual(['CCM-101', 'CCM-102']);
expect(
result.releaseReferencedIssuesNotDone.map((issue) => issue.key),
).toEqual(['CCM-101']);
expect(
result.jiraIssuesMissingClinicalSafetyCategory.map((issue) => issue.key),
).toEqual(['CCM-100']);
expect(
result.jiraIssuesMissingClinicalLead.map((issue) => issue.key),
).toEqual(['CCM-100']);
expect(result.releaseNotesIssueKeysOutsideRelease).toEqual(['CCM-200']);
expect(
result.commitsWithIssueKeysOutsideRelease.map(
({ missingKeys }) => missingKeys,
),
).toEqual([['CCM-999']]);
expect(
result.commitsWithoutMatches.map((commit) => commit.shortHash),
).toEqual(['cccccccc']);
});

it('treats punctuation-only subjects as unmatched when no Jira key is present', () => {
const result = compareRelease(
[
{
hash: 'e'.repeat(40),
shortHash: 'eeeeeeee',
subject: ' (#123) ',
body: '',
explicitIssueKeys: [],
},
],
issues,
[],
);

expect(
result.commitsWithoutMatches.map((commit) => commit.shortHash),
).toEqual(['eeeeeeee']);
});
});
Loading
Loading