Skip to content
Merged
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
92 changes: 69 additions & 23 deletions .github/scripts/update-policyengine-package.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
#!/usr/bin/env bash
#
# Check whether PolicyEngine .py has a newer release on PyPI. If so, update the
# policyengine[models] pin, refresh uv.lock, derive bundled package versions,
# create a changelog fragment, and open a PR.
# Update the policyengine[models] pin to a released PolicyEngine .py version,
# refresh uv.lock, derive bundled package versions, create a changelog
# fragment, and open a single-version PR on branch
# auto/update-policyengine-bundle-<version>.
#
# Environment:
# GH_TOKEN must be set for gh.
# LATEST_OVERRIDE may be set for testing a specific version.
# LATEST_OVERRIDE may be set to target an exact version (the
# repository_dispatch trigger passes the just-released version here);
# otherwise the latest version on PyPI is used.
# FORCE=1 allows targeting a version that is not newer than the current pin.
set -euo pipefail

DRY_RUN=0
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=1
fi

create_pr_body() {
cat <<EOF
## Summary

Update PolicyEngine .py bundle from ${CURRENT} to ${LATEST}.

## Bundled versions

- policyengine: ${POLICYENGINE_VERSION:-resolved during update}
- policyengine-core: ${POLICYENGINE_CORE_VERSION:-resolved during update}
- policyengine-us: ${US_VERSION:-resolved during update}
- policyengine-uk: ${UK_VERSION:-resolved during update}

---
Generated automatically by GitHub Actions
EOF
}

CURRENT=$(python3 -c '
import re
from pathlib import Path
Expand Down Expand Up @@ -45,18 +67,44 @@ if [[ "$CURRENT" == "$LATEST" ]]; then
exit 0
fi

BRANCH="auto/update-policyengine-bundle-${LATEST}"
if git ls-remote --exit-code --heads origin "$BRANCH" &>/dev/null; then
echo "Branch '${BRANCH}' already exists on remote. Skipping."
if [[ "$(printf '%s\n%s\n' "$CURRENT" "$LATEST" | sort -V | tail -n1)" != "$LATEST" && "${FORCE:-0}" != "1" ]]; then
echo "Requested ${LATEST} is not newer than current ${CURRENT}. Skipping (set FORCE=1 to override)."
exit 0
fi

BRANCH="auto/update-policyengine-bundle-${LATEST}"

if [[ "$DRY_RUN" == "1" ]]; then
if git ls-remote --exit-code --heads origin "$BRANCH" &>/dev/null; then
echo "Dry run: remote branch '${BRANCH}' already exists; would ensure a PR exists for it."
exit 0
fi
echo "Dry run complete. Would update PolicyEngine .py bundle from ${CURRENT} to ${LATEST}."
echo "Would update pyproject.toml, refresh uv.lock, create a changelog fragment, and open branch '${BRANCH}'."
exit 0
fi

EXISTING_PR=$(gh pr list \
--head "$BRANCH" \
--state open \
--json number \
--jq '.[0].number' 2>/dev/null || true)
if [[ -n "$EXISTING_PR" ]]; then
echo "PR #${EXISTING_PR} already exists for ${BRANCH}. Skipping."
exit 0
fi

if git ls-remote --exit-code --heads origin "$BRANCH" &>/dev/null; then
echo "Remote branch '${BRANCH}' already exists without an open PR. Creating PR."
gh pr create \
--base master \
--head "$BRANCH" \
--title "Update PolicyEngine bundle to ${LATEST}" \
--body "$(create_pr_body)"
echo "PR created for existing branch ${BRANCH}"
exit 0
fi

python3 -c '
import re
import sys
Expand All @@ -76,28 +124,26 @@ if updated == text:
path.write_text(updated)
' "$CURRENT" "$LATEST"

uv lock --upgrade-package policyengine
# The PyPI Simple index (which uv resolves from) can lag the JSON API right
# after a release, so retry the lock a few times.
for attempt in 1 2 3; do
if uv lock --upgrade-package policyengine; then
break
fi
if [[ "$attempt" == "3" ]]; then
echo "ERROR: uv lock failed after ${attempt} attempts." >&2
exit 1
fi
echo "uv lock attempt ${attempt} failed; retrying in 30s..."
sleep 30
done

VERSIONS_OUTPUT=$(uv run python .github/find-api-model-versions.py --shell)
eval "$VERSIONS_OUTPUT"

FRAGMENT="changelog.d/update-policyengine-bundle-${LATEST}.changed.md"
echo "Update the PolicyEngine bundle to ${LATEST}." > "$FRAGMENT"

PR_BODY="## Summary

Update PolicyEngine .py bundle from ${CURRENT} to ${LATEST}.

## Bundled versions

- policyengine: ${POLICYENGINE_VERSION}
- policyengine-core: ${POLICYENGINE_CORE_VERSION}
- policyengine-us: ${US_VERSION}
- policyengine-uk: ${UK_VERSION}

---
Generated automatically by GitHub Actions"

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

Expand All @@ -110,6 +156,6 @@ git push -u origin "$BRANCH"
gh pr create \
--base master \
--title "Update PolicyEngine bundle to ${LATEST}" \
--body "$PR_BODY"
--body "$(create_pr_body)"

echo "PR created: PolicyEngine bundle ${CURRENT} -> ${LATEST}"
18 changes: 13 additions & 5 deletions .github/workflows/update-policyengine-bundle.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
name: Update PolicyEngine bundle

on:
schedule:
- cron: "*/30 * * * *" # Every 30 minutes
repository_dispatch:
types: [policyengine-release]
workflow_dispatch:
inputs:
version:
description: "policyengine version to update to (defaults to latest on PyPI)"
required: false
type: string

concurrency:
group: update-policyengine-bundle-${{ github.event.client_payload.version || inputs.version || github.event_name }}
cancel-in-progress: false

jobs:
update:
Expand Down Expand Up @@ -35,6 +44,5 @@ jobs:
- name: Check for update and open PR
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
chmod +x .github/scripts/update-policyengine-package.sh
.github/scripts/update-policyengine-package.sh
LATEST_OVERRIDE: ${{ github.event.client_payload.version || inputs.version }}
run: bash .github/scripts/update-policyengine-package.sh
1 change: 1 addition & 0 deletions changelog.d/policyengine-release-dispatch.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Trigger PolicyEngine bundle update PRs from a `policyengine-release` repository dispatch sent by policyengine.py's release pipeline instead of polling PyPI every 30 minutes.
Loading