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
56 changes: 56 additions & 0 deletions .github/actions/check-overrides/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: "Check pnpm Overrides"
description: "Discover and optionally remove stale pnpm overrides from pnpm-workspace.yaml"

inputs:
project-dir:
description: "Path to the project root containing pnpm-workspace.yaml"
required: false
default: "."
apply:
description: "Whether to apply removals and raise a PR (true/false)"
required: false
default: "false"
node-version:
description: "Node.js version to use"
required: false
default: "22"

runs:
using: "composite"
steps:
- name: "Set up Node.js"
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ inputs.node-version }}

- name: "Install pnpm"
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
with:
version: 10

- name: "Run check-overrides"
shell: bash
env:
PROJECT_DIR: ${{ inputs.project-dir }}
APPLY: ${{ inputs.apply }}
TOOL_DIR: ${{ github.action_path }}/../../../tools/check-overrides
run: ${{ github.action_path }}/check-overrides.sh

- name: "Create or update pull request"
if: ${{ !env.ACT && inputs.apply == 'true' }}
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
with:
token: ${{ github.token }}
commit-message: "remove stale pnpm overrides"
branch: feature/remove-stale-pnpm-overrides
delete-branch: true
title: "remove stale pnpm overrides"
body-path: ${{ inputs.project-dir }}/.tmp/pr-body.md
add-paths: |
pnpm-workspace.yaml
pnpm-lock.yaml
sign-commits: true
labels: |
dependencies
automation
draft: false
32 changes: 32 additions & 0 deletions .github/actions/check-overrides/check-overrides.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash

set -euo pipefail

function main() {
if [[ -z "${TOOL_DIR:-}" ]]; then
echo "ERROR: TOOL_DIR is not set" >&2
exit 1
fi

if [[ ! -d "${TOOL_DIR}" ]]; then
echo "ERROR: Tool directory does not exist: ${TOOL_DIR}" >&2
exit 1
fi

local project_dir="${PROJECT_DIR:-.}"
local apply="${APPLY:-false}"

echo "Installing check-overrides dependencies..."
(cd "${TOOL_DIR}" && pnpm install --no-frozen-lockfile)

local args=("--project-dir" "${project_dir}")

if [[ "${apply}" == "true" ]]; then
args+=("--apply")
fi

echo "Running check-overrides..."
(cd "${TOOL_DIR}" && pnpm run check "${args[@]}")
}

main "$@"
1 change: 1 addition & 0 deletions docs/github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This repository provides reusable composite actions for NHS Notify projects.
- [Check English Usage](github-actions/check-english-usage.html) - Validates writing style using Vale
- [Check File Format](github-actions/check-file-format.html) - Validates file formatting standards
- [Check Markdown Format](github-actions/check-markdown-format.html) - Checks Markdown files with markdownlint
- [Check pnpm Overrides](github-actions/check-overrides.html) - Discovers and removes stale pnpm overrides
- [Check PR Title Format](github-actions/check-pr-title-format.html) - Validates PR titles against regex
- [Check TODO Usage](github-actions/check-todo-usage.html) - Validates TODO comment format
- [Create Lines of Code Report](github-actions/create-lines-of-code-report.html) - Counts lines of code
Expand Down
69 changes: 69 additions & 0 deletions docs/github-actions/check-overrides.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
layout: default
title: Check pnpm overrides
parent: GitHub Actions
grand_parent: Home
nav_order: 17
---

<!-- vale off -->

## Check pnpm overrides

Discovers and optionally removes stale `pnpm.overrides` entries from `pnpm-workspace.yaml`.

### Description

This composite action scans the `overrides` section of `pnpm-workspace.yaml` to identify entries that are no longer required to mitigate transitive dependency vulnerabilities. For each override (or chain of linked overrides), it:

1. Temporarily removes the override from `pnpm-workspace.yaml`
2. Regenerates the lockfile using `pnpm update --lockfile-only`
3. Checks whether the dependency still resolves to a version that satisfies the original minimum version constraint
4. Reports which overrides are removable, simplifiable, or still required

When run in apply mode, it also modifies `pnpm-workspace.yaml` and raises a pull request with the changes.

### Prerequisites

The consuming repository must have a `pnpm-workspace.yaml` with an `overrides` section. The action requires `contents: write` and `pull-requests: write` permissions to create pull requests.

### Inputs

| Input | Required | Default | Description |
| -------------- | -------- | ------- | --------------------------------------------------------- |
| `project-dir` | No | `.` | Path to the project root containing `pnpm-workspace.yaml` |
| `apply` | No | `false` | Whether to apply removals and raise a pull request |
| `node-version` | No | `22` | Node.js version to use |

### Usage

```yaml
name: Check pnpm overrides

on:
schedule:
- cron: "0 8 * * 1-5"
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
check-overrides:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<sha> # v4

- name: Check and remove stale overrides
uses: NHSDigital/nhs-notify-shared-modules/.github/actions/check-overrides@main
with:
apply: "true"
```

### Behaviour

- In **check mode** (default), the action reports findings to the workflow log without modifying any files.
- In **apply mode** (`apply: "true"`), the action modifies `pnpm-workspace.yaml` and `pnpm-lock.yaml`, then raises a pull request targeting `feature/remove-stale-pnpm-overrides`. If a pull request for that branch already exists, it is updated.
- The pull request body contains a full report listing which overrides were removed or simplified and why.
- The pull request creation step is skipped when running locally with [`act`](https://github.com/nektos/act).
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[A-Z]+s
[Bb]undler
Bitwarden
bot
Expand All @@ -21,17 +20,20 @@ Gitleaks
Grype
idempotence
Jira
lockfile
markdownlint
npm
OAuth
Octokit
onboarding
Podman
[Pp]npm
Python
rawContent
relative_url
[Rr]epo
sed
simplifiable
Syft
toolchain
Trivy
Expand Down
60 changes: 60 additions & 0 deletions tools/check-overrides/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import js from "@eslint/js";
import jest from "eslint-plugin-jest";
import security from "eslint-plugin-security";
import sonarjs from "eslint-plugin-sonarjs";
import unicorn from "eslint-plugin-unicorn";
import globals from "globals";
import tseslint from "typescript-eslint";

export default tseslint.config(
{
ignores: [
"**/coverage/**",
"**/.build/**",
"**/node_modules/**",
"**/dist/**",
],
},
{
files: ["**/*.{js,mjs,cjs,ts}"],
extends: [js.configs.recommended, ...tseslint.configs.recommended],
languageOptions: {
globals: globals.node,
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: import.meta.dirname,
},
},
},
{
files: ["**/*.{js,mjs,cjs,ts}"],
extends: [security.configs.recommended],
},
{
files: ["**/*.{js,mjs,cjs,ts}"],
plugins: { sonarjs },
rules: sonarjs.configs.recommended.rules,
},
{
files: ["**/*.{js,mjs,cjs,ts}"],
extends: [unicorn.configs.recommended],
rules: {
"unicorn/prevent-abbreviations": "off",
"unicorn/no-null": "off",
"unicorn/no-useless-undefined": "off",
"unicorn/prefer-module": "off",
},
},
{
files: ["**/__tests__/**", "**/*.test.*", "**/*.spec.*"],
extends: [jest.configs["flat/recommended"]],
rules: {
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-explicit-any": "off",
},
},
);
28 changes: 28 additions & 0 deletions tools/check-overrides/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: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
};

export default jestConfig;
36 changes: 36 additions & 0 deletions tools/check-overrides/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"engines": {
"node": ">=22.0.0"
},
"name": "check-overrides",
"version": "0.0.1",
"private": true,
"scripts": {
"check": "tsx ./src/index.ts",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"test:unit": "jest",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"semver": "^7.7.0",
"yaml": "^2.7.0"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.5",
"@types/jest": "^30.0.0",
"@types/node": "^25.9.1",
"@types/semver": "^7.5.8",
"eslint": "^10.4.0",
"eslint-plugin-jest": "^29.15.2",
"eslint-plugin-security": "^4.0.0",
"eslint-plugin-sonarjs": "^4.0.3",
"eslint-plugin-unicorn": "^64.0.0",
"globals": "^17.6.0",
"jest": "^30.4.2",
"ts-jest": "^29.4.11",
"tsx": "^4.22.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.60.1"
}
}
Loading