Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# pnpm/update

Updates the dependencies of your project with `pnpm update`, optionally bumps
the pinned pnpm (`packageManager` / `devEngines.packageManager`) and Node.js
(`devEngines.runtime`) versions, and opens a pull request with the result.

Unlike external dependency bots, this action runs pnpm itself, so it supports
every feature of your workspace: catalogs, patched dependencies, config
dependencies, overrides, and anything pnpm learns in the future.

The action expects pnpm (and a runtime, if your project needs one for
verification) to already be set up — pair it with [`pnpm/setup`].

## Usage

```yaml
name: Update Dependencies

on:
schedule:
- cron: '0 0 * * 1' # Every Monday at midnight UTC
workflow_dispatch: {}

permissions:
contents: write
pull-requests: write

concurrency:
group: update-dependencies
cancel-in-progress: false

jobs:
update-dependencies:
if: github.repository == 'your-org/your-repo' # Don't run on forks
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
# Installs the pnpm version from `packageManager` and the runtime
# from `devEngines.runtime`.
- uses: pnpm/setup@v1
- uses: pnpm/update@v0
with:
node: 24
verify: |
pnpm build
pnpm test
```

[`pnpm/setup`]: https://github.com/pnpm/setup

## Inputs

| Input | Default | Description |
|---|---|---|
| `token` | `github.token` | Token used to push the branch and create the PR. PRs created with the default `GITHUB_TOKEN` don't trigger other workflows; pass a GitHub App token or PAT if you want CI to run on the PR. |
| `branch` | `chore/update-dependencies` | Branch the updates are pushed to (force-pushed on every run, so at most one update PR stays open). |
| `base` | repository default branch | Branch the updates are based on and the pull request targets. |
| `latest` | `true` | Update to the latest versions, ignoring `package.json` ranges. Set to `false` to update within ranges. |
| `exclude` | — | Whitespace-separated package name patterns that should not be updated, e.g. `typescript @types/*`. |
| `update-pnpm` | `latest` | Bump pnpm itself via `pnpm self-update`. A dist-tag or exact version, or `false` to skip. |
| `node` | — | Bump the Node.js version pinned in `devEngines.runtime` to the latest release of this major, e.g. `24`. Empty to skip. |
| `verify` | — | Shell commands run after updating (build, tests). If they fail, no PR is created. |
| `commit-message` | `chore: update dependencies` | Message of the update commit. |
| `pr-title` | `chore: update dependencies` | Title of the pull request. |
| `pr-body` | Automated dependency updates… | Body of the pull request. |
145 changes: 145 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: 'pnpm update'
description: 'Update dependencies (and optionally pnpm and the runtime) with pnpm, then open a pull request'
branding:
icon: 'refresh-cw'
color: 'orange'
inputs:
token:
description: >-
Token used to push the update branch and create the pull request.
Pull requests created with the default GITHUB_TOKEN do not trigger other
workflows; pass a GitHub App token or PAT if you want CI to run on the PR.
default: ${{ github.token }}
branch:
description: >-
Branch the updates are pushed to. It is force-pushed on every run, so at
most one update PR stays open at a time.
default: 'chore/update-dependencies'
base:
description: 'Branch the updates are based on and the pull request targets.'
default: ${{ github.event.repository.default_branch }}
latest:
description: >-
Update dependencies to their latest versions, ignoring the ranges
declared in package.json. Set to "false" to update within ranges.
default: 'true'
exclude:
description: >-
Whitespace-separated package name patterns that should not be updated.
Example: "typescript @types/*"
default: ''
update-pnpm:
description: >-
Update pnpm itself (packageManager and devEngines.packageManager) with
`pnpm self-update`. Set to a dist-tag or exact version, or "false" to skip.
default: 'latest'
node:
description: >-
Update the Node.js version pinned in devEngines.runtime to the latest
release of this major version, e.g. "24". Empty to skip.
default: ''
verify:
description: >-
Shell commands run after updating (e.g. build and tests). If they fail,
no pull request is created.
default: ''
commit-message:
description: 'Message of the update commit.'
default: 'chore: update dependencies'
pr-title:
description: 'Title of the pull request.'
default: 'chore: update dependencies'
pr-body:
description: 'Body of the pull request.'
default: 'Automated dependency updates generated with `pnpm update`.'
runs:
using: 'composite'
steps:
- name: Prepare the update branch
shell: bash
env:
BRANCH: ${{ inputs.branch }}
BASE: ${{ inputs.base }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Base the update on the latest base branch, even when the workflow
# was dispatched from another ref or the checkout is shallow.
git fetch origin "$BASE"
git checkout -B "$BRANCH" FETCH_HEAD

- name: Update dependencies
shell: bash
env:
LATEST: ${{ inputs.latest }}
EXCLUDE: ${{ inputs.exclude }}
UPDATE_PNPM: ${{ inputs.update-pnpm }}
NODE_MAJOR: ${{ inputs.node }}
run: |
set -euo pipefail
# Keep patterns like "@types/*" from glob-expanding against the repo.
set -f

args=(--recursive)
if [ "$LATEST" = "true" ]; then
args+=(--latest)
fi
for pattern in $EXCLUDE; do
args+=("!$pattern")
done
pnpm update "${args[@]}"

if [ -n "$NODE_MAJOR" ]; then
pnpm runtime set node "$NODE_MAJOR"
fi

# Last, so every earlier step runs on the pnpm the workflow installed.
if [ "$UPDATE_PNPM" != "false" ]; then
pnpm self-update "$UPDATE_PNPM"
fi

- name: Verify the updated project
if: ${{ inputs.verify != '' }}
shell: bash
run: ${{ inputs.verify }}

- name: Commit, push, and create the pull request
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
BRANCH: ${{ inputs.branch }}
BASE: ${{ inputs.base }}
COMMIT_MESSAGE: ${{ inputs.commit-message }}
PR_TITLE: ${{ inputs.pr-title }}
PR_BODY: ${{ inputs.pr-body }}
run: |
set -euo pipefail

if [ -z "$(git status --porcelain)" ]; then
echo "Everything is up to date."
exit 0
fi

git add -A
git commit -m "$COMMIT_MESSAGE"

# Remove any credentials persisted by actions/checkout: they would
# take precedence over the token this action was given, silently
# downgrading a user-supplied PAT or App token to GITHUB_TOKEN.
git config --local --unset-all http.https://github.com/.extraheader || true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
# Supply the token through a credential helper (it reads GH_TOKEN
# from the environment) so it never appears on a command line.
git -c credential.helper= \
-c credential.helper='!f() { echo username=x-access-token; echo "password=${GH_TOKEN}"; }; f' \
push --force origin "$BRANCH"

# A PR left open by a previous run already points at the branch we
# just force-pushed, so there is nothing more to do.
if [ -z "$(gh pr list --head "$BRANCH" --state open --json number --jq '.[].number')" ]; then
gh pr create \
--title "$PR_TITLE" \
--body "$PR_BODY" \
--base "$BASE" \
--head "$BRANCH"
fi