Skip to content

Update lockfiles

Update lockfiles #11

name: Update lockfiles
# Generates one committed, checksummed lockfile per test-matrix cell so that CI
# installs are fully pinned (supply-chain hardening). Run manually to create the
# initial lockfiles, and on a schedule to refresh them deliberately.
#
# Under MISE_ENV=ci the mise-action step installs every Ruby declared in
# .mise.ci.toml; bin/relock then sweeps every gem's test-matrix.json — the
# single source of truth, shared with the *_test.yml workflows — resolving each
# cell against its matching Ruby via `mise exec`. One invocation regenerates the
# whole lock set.
on:
workflow_dispatch:
schedule:
# Weekly: refresh pins so we keep getting security patches.
- cron: "0 4 * * 1"
permissions:
contents: write
jobs:
update:
runs-on: ubuntu-latest
# Resolves every cell across all gems serially, each under its own Ruby.
timeout-minutes: 120
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Install every Ruby in .mise.ci.toml and restore the shared cache; bin/relock
# then resolves each cell against its matching Ruby. Same setup the *_test
# workflows use, so they all share one mise tool cache.
- uses: ./.github/actions/setup-mise
- name: Regenerate lockfiles
run: ruby bin/relock
- name: Configure git
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
- name: Create branch
id: create-branch
run: |
# Stage first, then diff the index against HEAD. `git diff` alone only
# sees tracked files, so newly generated (untracked) locks — i.e. the
# bootstrap run and any filled-in missing cell — would otherwise look
# like "no change" and never get pushed.
git add '**/gemfiles/*.gemfile.lock'
if git diff --cached --quiet; then
echo "No lockfile changes; nothing to do."
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
COMMIT_TITLE="ci: 🤖 Update pinned CI lockfiles"
BRANCH_NAME="lockfiles/update-$(date +%m-%d)"
# Which gems changed: the top-level dir of each touched lock.
GEMS=$(git diff --cached --name-only -- '**/gemfiles/*.gemfile.lock' | cut -d/ -f1 | sort -u)
# Aggregate dependency version changes across every touched lock. Pair
# the removed (-) and added (+) "name (version)" spec lines per gem so
# the summary reads "gem: old → new" (deduped across cells).
DEPS=$(git diff --cached --text -U0 -- '**/gemfiles/*.gemfile.lock' \
| { grep -E '^[+-] +[A-Za-z0-9_.-]+ \([0-9]' || true; } \
| sed -E 's/^([+-]) +([A-Za-z0-9_.-]+) \(([^)]+)\).*/\1 \2 \3/' \
| awk '{ if ($1 == "-") old[$2] = $3; else neu[$2] = $3 }
END {
for (g in neu) if (neu[g] != old[g]) printf "- `%s`: %s → %s\n", g, (g in old ? old[g] : "new"), neu[g]
for (g in old) if (!(g in neu)) printf "- `%s`: %s → removed\n", g, old[g]
}' \
| sort -u)
git checkout -B "$BRANCH_NAME"
git commit -m "$COMMIT_TITLE"
git push origin "$BRANCH_NAME" --force
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
echo "commit_title=$COMMIT_TITLE" >> "$GITHUB_OUTPUT"
{
echo "gems<<EOF"; echo "$GEMS"; echo "EOF"
echo "deps<<EOF"; echo "$DEPS"; echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Create pull request
if: steps.create-branch.outputs.changed == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
BRANCH_NAME: ${{ steps.create-branch.outputs.branch_name }}
COMMIT_TITLE: ${{ steps.create-branch.outputs.commit_title }}
GEMS: ${{ steps.create-branch.outputs.gems }}
DEPS: ${{ steps.create-branch.outputs.deps }}
with:
script: |
const branchName = process.env.BRANCH_NAME;
const commitTitle = process.env.COMMIT_TITLE;
const gems = (process.env.GEMS || '').trim().split('\n').filter(Boolean);
const deps = (process.env.DEPS || '').trim();
const gemsList = gems.length ? gems.map(g => `\`${g}\``).join(', ') : '_none_';
const depsBlock = deps || '_No dependency version changes (checksum/metadata only)._';
const prBody = `Automated regeneration of the per-matrix lockfiles used to pin CI dependencies (supply-chain hardening).
#skip-changelog
## Gems updated
${gemsList}
## Dependency changes
${depsBlock}
## Action required
- If CI passes on this PR, it's safe to approve and merge: the refreshed pins resolve and the suite is green.
- If CI fails, a dependency update broke something — investigate before merging.
_🤖 This PR was automatically created by [.github/workflows/update_lockfiles.yml](https://github.com/getsentry/sentry-ruby/blob/master/.github/workflows/update_lockfiles.yml)._`.replace(/^ {12}/gm, '');
// Close stale lockfile PRs — they're now obsolete.
const existingPRs = await github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
});
for (const pr of existingPRs) {
if (pr.head.ref.startsWith('lockfiles/')) {
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
state: 'closed',
});
}
}
await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: commitTitle,
head: branchName,
base: 'master',
body: prBody,
});