Cookbook
Complete workflows for common setups. The README covers the
basic pull-request check; these recipes assemble the other pieces — scheduled
runs, fork-safe triggers, merge gating, and consuming updates-json.
Weekly report as a tracking issue
On runs without a pull request context, open-tracking-issue: true keeps a
single issue updated with the report and closes it automatically once
everything is up to date — no PR required.
name: Weekly SPM dependency report
on:
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:
permissions:
contents: read
issues: write
jobs:
spm-updates:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hbmartin/github-action-spm_version_updates@v1
with:
package-manifest-paths: Modules/Package.swift
open-tracking-issue: true
The issue number and URL are available as the tracking-issue-number and
tracking-issue-url outputs.
Checking pull requests from forks
Fork PRs get a read-only token, so commenting requires pull_request_target
— which means the manifests under check are untrusted input. Pin the
checkout to the PR head, drop credentials, and restrict lookup hosts:
on:
pull_request_target:
permissions:
contents: read
pull-requests: write
jobs:
spm-version-updates:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- uses: hbmartin/github-action-spm_version_updates@v1
with:
package-manifest-paths: Modules/Package.swift
allow-hosts: github.com
Read the security guide before enabling this — it explains what a malicious fork PR could otherwise do.
Blocking merges on major updates
fail-on: major fails the job when a major semantic-version update exists —
after the outputs, step summary, annotations, and PR comment have all been
written, so the failure is explained where reviewers look. Make the check
required in branch protection to actually block the merge.
- uses: hbmartin/github-action-spm_version_updates@v1
with:
xcode-project-path: MyApp.xcodeproj
fail-on: major # 'minor' also fails on minor; 'patch' fails on any semantic update
Posting updates to Slack
updates-json is a machine-readable mirror of the report. Pipe it through
jq into anything — here, a Slack incoming webhook:
- id: spm
uses: hbmartin/github-action-spm_version_updates@v1
with:
package-manifest-paths: Modules/Package.swift
- name: Notify Slack
if: steps.spm.outputs.updates-found != '0'
env:
UPDATES_JSON: ${{ steps.spm.outputs.updates-json }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
payload="$(jq -n --argjson updates "$UPDATES_JSON" \
'{text: ("📦 SPM updates available:\n" + ([$updates[]
| "• " + (if .package and .current_version and .available_version
then "\(.package): \(.current_version) → \(.available_version)"
else .message end)
] | join("\n")))}')"
curl -sS -X POST -H 'Content-type: application/json' --data "$payload" "$SLACK_WEBHOOK_URL"
Every update object has a message; the structured fields are present "when
available", so the recipe falls back to message for anything unusual.
Opening an automatic bump PR
In manifest mode, each update carries a ready-to-run suggested_command
(swift package update <identity>) and a source naming the manifest it
applies to. A scheduled job on a macOS runner (for the Swift toolchain) can
apply every in-constraint update and open one PR with the result.
Updates that also carry suggested_requirement need a manifest edit first
(the new version is outside the declared constraint), so the script below
skips them — they keep being reported until you bump the constraint.
name: SPM auto-bump
on:
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
bump:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- id: spm
uses: hbmartin/github-action-spm_version_updates@v1
with:
package-manifest-paths: Modules/Package.swift
- name: Apply in-constraint updates
if: steps.spm.outputs.updates-found != '0'
env:
UPDATES_JSON: ${{ steps.spm.outputs.updates-json }}
run: |
echo "$UPDATES_JSON" |
jq -r '.[]
| select(.suggested_command != null and .suggested_requirement == null)
| [.source, .suggested_command] | @tsv' |
sort -u |
while IFS="$(printf '\t')" read -r source command; do
(cd "$(dirname "$source")" && $command)
done
- uses: peter-evans/create-pull-request@v7
with:
branch: spm-version-bumps
title: Bump Swift package dependencies
commit-message: Bump Swift package dependencies
Different settings per manifest in one job
Invoking the action twice lets each manifest group use its own options. Leave
setup-ruby enabled on the first invocation and disable it on later ones —
the runtime is already installed. Both invocations would otherwise manage the
same single PR comment, so disable commenting on all but one:
- uses: hbmartin/github-action-spm_version_updates@v1
with:
package-manifest-paths: Modules/Package.swift
- uses: hbmartin/github-action-spm_version_updates@v1
with:
package-manifest-paths: BuildTools/Package.swift
report-above-maximum: true
comment: false # the first invocation owns the PR comment
setup-ruby: false # runtime already installed by the first invocation
If both groups should share one comment and one set of options, pass both paths to a single invocation instead — that's the default multi-manifest setup from the README.