Skip to content
Closed
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 }}
7 changes: 7 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ export default defineConfig([
'no-relative-import-paths/no-relative-import-paths': 0,
},
},
{
files: ['tools/release-check/**'],
rules: {
'no-relative-import-paths/no-relative-import-paths': 0,
'import-x/no-relative-packages': 0,
},
},
{
files: ['scripts/**'],
rules: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"clean": "pnpm -r run --if-present clean",
"lint": "turbo run lint",
"lint:fix": "turbo run lint:fix",
"release-check": "pnpm --filter @nhsdigital/nhs-notify-release-check run check",
"test:unit": "turbo run test:unit",
"typecheck": "turbo run typecheck"
},
Expand Down
31 changes: 31 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ packages:
- "src/lambdas/apim-key-generator"
- "src/utils"
- "tools/check-overrides"
- "tools/release-check"
- "infrastructure/terraform/modules/eventpub/lambda/eventpub"

allowBuilds:
Expand Down
75 changes: 75 additions & 0 deletions tools/release-check/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# release-check design

## Purpose

`release-check` compares git release history, Jira release membership, and release
notes for a repository so that release managers can spot:

- Jira tickets that are in a release but not represented in git
- git-referenced tickets that are outside the selected Jira release scope
- tickets that are referenced but not done
- clinical review metadata gaps

It also supports carefully scoped Jira fix-up actions for a single release pair.

## High-level structure

The package is split into a small set of focused modules:

- [`src/args.ts`](./src/args.ts) parses and validates the CLI contract
- [`src/git.ts`](./src/git.ts) resolves tags and collects commit history
- [`src/jira.ts`](./src/jira.ts) resolves Jira versions, fetches issues, and applies Jira updates
- [`src/github-release.ts`](./src/github-release.ts) reads GitHub or annotated-tag release notes
- [`src/compare.ts`](./src/compare.ts) computes the comparison model from commits, issues, and notes
- [`src/report.ts`](./src/report.ts) renders Markdown reports and fix proposal summaries
- [`src/index.ts`](./src/index.ts) orchestrates the end-to-end flow
- [`src/cli.ts`](./src/cli.ts) is the thin executable entrypoint

## Execution flow

1. Parse CLI options.
2. Resolve the target repository and selected git tags.
3. Collect commits across the selected release ranges.
4. Resolve the selected Jira versions and fetch the issues assigned to them.
5. Read release notes from GitHub releases or annotated tags.
6. Compare commits, Jira issues, and release-note references.
7. Enrich any outside-release issue keys with Jira lookup results where possible.
8. Render a Markdown report.
9. Optionally show a confirmation summary and apply scoped Jira updates.

## Reporting model

The report is Markdown-first so it works well in editor preview panes.

Issue-based sections are rendered as tables rather than nested bullets:

- `Issue` column: Jira key, components, title, and status
- `Commit` column: the first representative commit plus the total commit count

For issue keys that cannot be resolved in Jira, the report keeps the commit
evidence and labels the issue as `not found in Jira`.

## Fix workflows

Two fix actions are currently supported:

- `fix-version`
- `clinical-review-not-needed`

Both actions are intentionally constrained:

- component-scoped via `--fix-component`
- single release pair only
- confirmation shown before changes are applied
- `--yes` required for non-interactive automation

This keeps the first version conservative and easy to audit.

## Extension points

Likely future enhancements:

- richer filtering beyond component-only matching
- multi-release fix inference
- additional Markdown sections or machine-readable exports
- safer dry-run or diff views for Jira mutations
125 changes: 125 additions & 0 deletions tools/release-check/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# release-check

Compares a local repository release tag, or a selected set of release tags, against one or more Jira release versions and reports mismatches across:

- git commit history for the selected release ranges
- Jira issues assigned to the selected release versions
- 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
```

## Multi-release usage

Explicit list selection:

```bash
pnpm release-check -- \
--repo ../nhs-notify-client-config \
--git-tags 0.1.0,v0.2.0,v0.3.0,v0.3.1 \
--jira-versions client-config-0.1.0,client-config-0.2.0,client-config-0.3.0,client-config-0.3.1
```

Wildcard selection against tag and Jira version names:

```bash
pnpm release-check -- \
--repo ../nhs-notify-client-config \
--git-tags '0.1.0,v0.2.*,v0.3.*' \
--jira-versions 'client-config-0.1.0,client-config-0.2.*,client-config-0.3.*'
```

Notes for multi-release mode:

- `--git-tags` and `--jira-versions` accept comma-separated selectors.
- Selectors can be exact values or glob-style patterns using `*` and `?`.
- Multiple selected git tags are expanded in repository tag order.
- Commit history is aggregated by collecting each selected release range and de-duplicating overlapping commits.
- Multiple selected Jira versions are aggregated into one issue set before comparison.

## 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

## Fix workflows

The CLI can also prepare and optionally apply targeted Jira updates for a single
resolved release pair.

### Add the selected Jira fix version to git-referenced issues outside the release

```bash
pnpm release-check -- \
--repo ../nhs-notify-client-config \
--git-tag v0.2.0 \
--jira-version client-config-0.2.0 \
--fix fix-version \
--fix-component onboarding-journey-improvements
```

### Mark clinical review as not needed for a component-scoped subset

```bash
pnpm release-check -- \
--repo ../nhs-notify-client-config \
--git-tag v0.3.0 \
--jira-version client-config-0.3.0 \
--fix clinical-review-not-needed \
--fix-component onboarding-journey-improvements
```

Notes for fix mode:

- `--fix` accepts `fix-version` or `clinical-review-not-needed`.
- `--fix-component` is required and scopes the proposed Jira updates.
- Fix mode currently requires exactly one resolved git tag and one resolved Jira version.
- The CLI prints the full issue and representative commit list before applying updates.
- By default the CLI asks for confirmation before changing Jira.
- Use `--yes` to skip the confirmation prompt in non-interactive automation.

## 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>.md` for single-release checks.
- Multi-release reports default to `.tmp/release-check/<repo>-<first-tag>-to-<last-tag>-<count>-tags.md`.
- Reports are emitted as Markdown so they can be inspected in a Markdown preview.

## 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
```
28 changes: 28 additions & 0 deletions tools/release-check/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Config } from 'jest';

const jestConfig: Config = {
preset: 'ts-jest',
clearMocks: true,
silent: true,
collectCoverage: true,
coverageDirectory: './.reports/unit/coverage',
coverageProvider: 'v8',
coveragePathIgnorePatterns: ['/__tests__/', '/node_modules/'],
transform: { '^.+\\.ts$': 'ts-jest' },
testPathIgnorePatterns: ['.build'],
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
testEnvironment: 'node',
moduleNameMapper: {
'^src/(.*)$': '<rootDir>/src/$1',
},
coverageThreshold: {
global: {
branches: 95,
functions: 100,
lines: 99,
statements: 99,
},
},
};

export default jestConfig;
Loading
Loading