diff --git a/.github/scripts/dependency-update-commit-and-push.sh b/.github/scripts/dependency-update-commit-and-push.sh new file mode 100755 index 000000000..86c935280 --- /dev/null +++ b/.github/scripts/dependency-update-commit-and-push.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Commit generated dependency changes and update the reusable PR branch. + +set -euo pipefail + +: "${GITHUB_OUTPUT:?GITHUB_OUTPUT is required}" +: "${HAS_OPEN_PR:?HAS_OPEN_PR is required}" + +if [[ "${HAS_OPEN_PR}" != "true" && "${HAS_OPEN_PR}" != "false" ]]; then + echo "HAS_OPEN_PR must be either true or false" >&2 + exit 1 +fi + +changes_detected=false +if [[ -z "$(git status --porcelain)" ]]; then + echo "No changes detected" +else + echo "Changes detected" + git add --all + git commit -m "Update dependencies ($(date -u +%Y-%m-%d))" + changes_detected=true +fi + +branch_pushed=false +if [[ "${HAS_OPEN_PR}" == "true" ]]; then + commits_to_push="$(git rev-list --count origin/update-dependencies..HEAD)" + if [[ "${commits_to_push}" -gt 0 ]]; then + git push origin HEAD:update-dependencies + branch_pushed=true + fi +elif [[ "${changes_detected}" == "true" ]]; then + # No open PR owns this branch, so replacing a stale remote branch cannot + # rewrite active review history. + git push --force-with-lease origin HEAD:update-dependencies + branch_pushed=true +fi + +echo "branch_pushed=${branch_pushed}" >> "${GITHUB_OUTPUT}" diff --git a/.github/scripts/dependency-update-create-pr.sh b/.github/scripts/dependency-update-create-pr.sh new file mode 100755 index 000000000..1084c03e8 --- /dev/null +++ b/.github/scripts/dependency-update-create-pr.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Open a dependency-update PR after a new update branch has been pushed. + +set -euo pipefail + +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}" + +gh pr create \ + --repo "${GITHUB_REPOSITORY}" \ + --title "Update dependencies" \ + --body "Automated daily dependency updates. Subsequent runs append commits to this branch while the pull request remains open." \ + --base main \ + --head update-dependencies diff --git a/.github/scripts/dependency-update-prepare-branch.sh b/.github/scripts/dependency-update-prepare-branch.sh new file mode 100755 index 000000000..ea63f00b0 --- /dev/null +++ b/.github/scripts/dependency-update-prepare-branch.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# Reuse the branch for an open dependency-update PR, or start a new branch +# from current main when no update PR exists. + +set -euo pipefail + +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}" +: "${GITHUB_OUTPUT:?GITHUB_OUTPUT is required}" + +git config user.name "policyengine-auto" +git config user.email "policyengine-auto@users.noreply.github.com" + +open_pr="$( + gh pr list \ + --repo "${GITHUB_REPOSITORY}" \ + --base main \ + --head update-dependencies \ + --state open \ + --json number \ + --jq '.[0].number // empty' +)" + +if [[ -n "${open_pr}" ]]; then + echo "has_open_pr=true" >> "${GITHUB_OUTPUT}" + git fetch origin update-dependencies + git checkout -B update-dependencies origin/update-dependencies + + # Preserve the open PR's commits while incorporating current main. Prefer + # main's version of conflicts because later steps regenerate clients and + # recreate dependency lock changes. + git merge --no-edit -X theirs origin/main +else + echo "has_open_pr=false" >> "${GITHUB_OUTPUT}" + git checkout -B update-dependencies origin/main +fi diff --git a/.github/workflows/update-dependencies.yml b/.github/workflows/update-dependencies.yml index 0538560ed..caf7b0142 100644 --- a/.github/workflows/update-dependencies.yml +++ b/.github/workflows/update-dependencies.yml @@ -2,10 +2,15 @@ name: Update dependencies on: schedule: - # Run every 15 minutes - - cron: '*/15 * * * *' + # Run daily at 3:00 PM US Eastern time. + - cron: '0 15 * * *' + timezone: 'America/New_York' workflow_dispatch: # Allow manual triggering +concurrency: + group: update-dependencies + cancel-in-progress: false + jobs: update-dependencies: name: File update PR @@ -25,9 +30,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - # Checkout main branch with: ref: main + fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} - name: Install uv @@ -37,47 +42,27 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.13" + + - name: Prepare update branch + id: branch + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: .github/scripts/dependency-update-prepare-branch.sh - name: Generate API clients - run: | - # Generate clients with correct naming before updating dependencies - ./scripts/generate-clients.sh + run: ./scripts/generate-clients.sh - name: Update dependencies - id: update - run: | - # Run the update command - make update - - # Check if there are changes - if [[ -z $(git status --porcelain) ]]; then - echo "No changes detected" - echo "changes_detected=false" >> $GITHUB_OUTPUT - else - echo "Changes detected" - echo "changes_detected=true" >> $GITHUB_OUTPUT - fi - - - name: Create pull request - if: steps.update.outputs.changes_detected == 'true' - run: | - git config --global user.name "policyengine-auto" - git config --global user.email "policyengine-auto@users.noreply.github.com" - - git checkout -b update-dependencies - git add . - git commit -m "Update dependencies" - git push origin update-dependencies --force - - # Try to create PR, ignore if it already exists - gh pr create \ - --title "Update dependencies" \ - --body "Automated dependency updates + run: make update - This PR was automatically created by the dependency update workflow. + - name: Commit and push update + id: push + env: + HAS_OPEN_PR: ${{ steps.branch.outputs.has_open_pr }} + run: .github/scripts/dependency-update-commit-and-push.sh - Last updated: $(date)" \ - --base main \ - --head update-dependencies || echo "PR may already exist, continuing..." + - name: Create pull request + if: steps.branch.outputs.has_open_pr == 'false' && steps.push.outputs.branch_pushed == 'true' env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} \ No newline at end of file + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: .github/scripts/dependency-update-create-pr.sh