chore(deps): monthly dependency maintenance #23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Dependabot | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| schedule: | |
| # Monthly lock file maintenance on first Monday at 6 AM | |
| - cron: '0 6 1 * 1' | |
| workflow_dispatch: | |
| concurrency: | |
| group: "dependency-updates-${{ github.ref }}" | |
| cancel-in-progress: true | |
| jobs: | |
| config: | |
| name: Get Configuration | |
| runs-on: ubuntu-latest | |
| outputs: | |
| default-python-version: ${{ steps.config.outputs.default-python-version }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Get project configuration | |
| id: config | |
| uses: ./.github/actions/get-config | |
| setup-cache: | |
| name: Setup Cache | |
| needs: config | |
| uses: ./.github/workflows/cache-management.yml | |
| with: | |
| cache-type: dependencies | |
| cache-key-base: dependabot | |
| python-version: ${{ needs.config.outputs.default-python-version }} | |
| update-uv-lock: | |
| name: Update UV Lock File | |
| if: github.actor == 'dependabot[bot]' && github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| needs: [config, setup-cache] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| # On a Dependabot pull_request the default checkout target is the | |
| # synthesised merge ref, which lands as a detached HEAD. The | |
| # subsequent commit succeeds but the push errors with | |
| # `fatal: You are not currently on a branch`. Pin to the PR head | |
| # branch by name so push has an upstream to write to. | |
| ref: ${{ github.head_ref }} | |
| - name: Setup Python and UV | |
| uses: ./.github/actions/setup-uv-cached | |
| with: | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| fail-on-cache-miss: false | |
| - name: Update UV lock file | |
| run: | | |
| uv lock | |
| - name: Commit updated lock file | |
| env: | |
| GIT_AUTHOR_NAME: github-actions[bot] | |
| GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com | |
| GIT_COMMITTER_NAME: github-actions[bot] | |
| GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com | |
| run: | | |
| if [[ -n $(git status --porcelain) ]]; then | |
| git add uv.lock | |
| git commit -m "chore(deps): update uv.lock after dependabot changes" | |
| git push | |
| else | |
| echo "No changes to commit" | |
| fi | |
| auto-merge: | |
| name: Auto-merge | |
| if: github.actor == 'dependabot[bot]' && github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| needs: [update-uv-lock] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| checks: read | |
| steps: | |
| - name: Check if PR is ready for auto-merge | |
| id: check-automerge | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 | |
| with: | |
| script: | | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number | |
| }); | |
| // SECURITY: Sanitize inputs to prevent injection | |
| const title = pr.title.toLowerCase().replace(/[^a-z0-9\-\s]/g, ''); | |
| const allowedLabels = ['patch', 'minor', 'major', 'dependencies', 'dev-dependencies', 'security']; | |
| const labels = pr.labels | |
| .map(label => label.name) | |
| .filter(name => allowedLabels.includes(name)); | |
| // Auto-merge conditions | |
| const isPatch = title.includes('patch') || labels.includes('patch'); | |
| const isMinor = title.includes('minor') || labels.includes('minor'); | |
| const isDev = title.includes('dev-dependencies') || labels.includes('dependencies'); | |
| const shouldAutoMerge = isPatch || (isMinor && isDev); | |
| console.log(`Sanitized title: ${title}`); | |
| console.log(`Filtered labels: ${labels.join(', ')}`); | |
| console.log(`Should auto-merge: ${shouldAutoMerge}`); | |
| return shouldAutoMerge; | |
| - name: Wait for checks to complete | |
| if: steps.check-automerge.outputs.result == 'true' | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 | |
| with: | |
| script: | | |
| const maxWaitTime = 10 * 60 * 1000; // 10 minutes | |
| const checkInterval = 30 * 1000; // 30 seconds | |
| const startTime = Date.now(); | |
| while (Date.now() - startTime < maxWaitTime) { | |
| const { data: checks } = await github.rest.checks.listForRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: context.payload.pull_request.head.sha | |
| }); | |
| const relevantChecks = checks.check_runs.filter(check => | |
| !check.name.includes('auto-merge') && | |
| !check.name.includes('dependabot') | |
| ); | |
| const allCompleted = relevantChecks.every(check => | |
| check.status === 'completed' | |
| ); | |
| const allSuccessful = relevantChecks.every(check => | |
| check.conclusion === 'success' || check.conclusion === 'neutral' | |
| ); | |
| if (allCompleted && allSuccessful) { | |
| console.log('All checks passed!'); | |
| return true; | |
| } | |
| if (allCompleted && !allSuccessful) { | |
| console.log('Some checks failed, not auto-merging'); | |
| return false; | |
| } | |
| console.log('Waiting for checks to complete...'); | |
| await new Promise(resolve => setTimeout(resolve, checkInterval)); | |
| } | |
| console.log('Timeout waiting for checks'); | |
| return false; | |
| - name: Auto-merge PR | |
| if: steps.check-automerge.outputs.result == 'true' | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 | |
| with: | |
| script: | | |
| await github.rest.pulls.merge({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number, | |
| merge_method: 'squash', | |
| commit_title: `${context.payload.pull_request.title} (#${context.issue.number})`, | |
| commit_message: 'Auto-merged by Dependabot workflow' | |
| }); | |
| monthly-maintenance: | |
| name: Monthly Maintenance | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| needs: [config, setup-cache] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Setup Python and UV | |
| uses: ./.github/actions/setup-uv-cached | |
| with: | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| fail-on-cache-miss: false | |
| - name: Update all dependencies | |
| run: | | |
| echo "Updating all dependencies..." | |
| uv lock --upgrade | |
| - name: Create Pull Request | |
| uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| commit-message: "chore(deps): monthly dependency maintenance" | |
| title: "chore(deps): monthly dependency maintenance" | |
| body: | | |
| ## Monthly Dependency Maintenance | |
| This PR updates all dependencies to their latest compatible versions. | |
| ### Changes | |
| - Updated `uv.lock` with latest dependency versions | |
| - Automated monthly maintenance run | |
| ### Review Notes | |
| - Check for any breaking changes in updated dependencies | |
| - Verify all tests pass before merging | |
| - Review security advisories for updated packages | |
| branch: chore/monthly-dependency-maintenance | |
| delete-branch: true | |
| labels: | | |
| dependencies | |
| maintenance | |
| automated |