🔄 Weekly Dependency Update #5
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: 🔄 Weekly Dependency Update | |
| on: | |
| schedule: | |
| # Run weekly on Monday at 9 AM UTC | |
| - cron: '0 9 * * 1' | |
| workflow_dispatch: | |
| inputs: | |
| dry-run: | |
| description: 'Check for updates without creating PR' | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| check-updates: | |
| name: Check for dependency updates | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| has-updates: ${{ steps.check.outputs.has-updates }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: SocketDev/socket-registry/.github/actions/setup-and-install@ea1986b8019fedee5fb38b485690b13ad8e0217f # main | |
| with: | |
| checkout: 'false' | |
| - name: Check for npm updates | |
| id: check | |
| shell: bash | |
| run: | | |
| echo "Checking for npm package updates..." | |
| HAS_UPDATES=false | |
| NPM_UPDATES=$(pnpm outdated 2>/dev/null || true) | |
| if [ -n "$NPM_UPDATES" ] && ! echo "$NPM_UPDATES" | grep -q "No outdated"; then | |
| echo "npm packages have updates available" | |
| HAS_UPDATES=true | |
| fi | |
| echo "has-updates=$HAS_UPDATES" >> $GITHUB_OUTPUT | |
| apply-updates: | |
| name: Apply updates with Claude Code | |
| needs: check-updates | |
| if: needs.check-updates.outputs.has-updates == 'true' && inputs.dry-run != true | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: write # Trigger CI workflow via workflow_dispatch | |
| contents: write # Push update branch | |
| pull-requests: write # Create PR | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: SocketDev/socket-registry/.github/actions/setup-and-install@ea1986b8019fedee5fb38b485690b13ad8e0217f # main | |
| with: | |
| checkout: 'false' | |
| - name: Create update branch | |
| id: branch | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GITHUB_REPO: ${{ github.repository }} | |
| run: | | |
| BRANCH_NAME="weekly-update-$(date +%Y%m%d)" | |
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPO}.git" | |
| # Branch from HEAD~1 so the PR is behind main, making the | |
| # "Update branch" button available to trigger enterprise checks. | |
| git checkout -b "$BRANCH_NAME" HEAD~1 | |
| echo "branch=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
| - uses: SocketDev/socket-registry/.github/actions/setup-git-signing@ea1986b8019fedee5fb38b485690b13ad8e0217f # main | |
| with: | |
| gpg-private-key: ${{ secrets.BOT_GPG_PRIVATE_KEY }} | |
| - name: Update dependencies (haiku — fast, cheap) | |
| id: update | |
| timeout-minutes: 10 | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| GITHUB_ACTIONS: 'true' | |
| run: | | |
| if [ -z "$ANTHROPIC_API_KEY" ]; then | |
| echo "ANTHROPIC_API_KEY not set - skipping automated update" | |
| echo "success=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| set +e | |
| pnpm exec claude --print \ | |
| --allowedTools "Bash(pnpm:*)" "Bash(git add:*)" "Bash(git commit:*)" "Bash(git status:*)" "Bash(git diff:*)" "Bash(git log:*)" "Bash(git rev-parse:*)" "Read" "Write" "Edit" "Glob" "Grep" \ | |
| --model haiku \ | |
| --max-turns 15 \ | |
| "$(cat <<'PROMPT' | |
| /updating | |
| <context> | |
| You are an automated CI agent in a weekly dependency update workflow. | |
| Git is configured with GPG signing. A branch has been created for you. | |
| </context> | |
| <instructions> | |
| Update all dependencies to their latest versions. | |
| Create one atomic commit per dependency update with a conventional commit message. | |
| Leave all changes local — the workflow handles pushing and PR creation. | |
| Do not run builds or tests — the next step handles that. | |
| </instructions> | |
| <success_criteria> | |
| Each updated dependency has its own commit. | |
| The lockfile is consistent with package.json changes. | |
| No uncommitted changes remain in the working tree. | |
| </success_criteria> | |
| PROMPT | |
| )" \ | |
| 2>&1 | tee claude-update.log | |
| CLAUDE_EXIT=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$CLAUDE_EXIT" -eq 0 ]; then | |
| echo "success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "success=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Run tests | |
| id: tests | |
| if: steps.update.outputs.success == 'true' | |
| run: | | |
| set +e | |
| pnpm build 2>&1 | tee build.log | |
| BUILD_EXIT=${PIPESTATUS[0]} | |
| pnpm test 2>&1 | tee test.log | |
| TEST_EXIT=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$BUILD_EXIT" -eq 0 ] && [ "$TEST_EXIT" -eq 0 ]; then | |
| echo "tests-passed=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "tests-passed=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Fix test failures (sonnet — smarter, escalated) | |
| id: claude | |
| if: steps.update.outputs.success == 'true' && steps.tests.outputs.tests-passed == 'false' | |
| timeout-minutes: 15 | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| GITHUB_ACTIONS: 'true' | |
| run: | | |
| FAILURE_LOG="$(cat build.log test.log 2>/dev/null)" | |
| set +e | |
| pnpm exec claude --print \ | |
| --allowedTools "Bash(pnpm:*)" "Bash(git add:*)" "Bash(git commit:*)" "Bash(git status:*)" "Bash(git diff:*)" "Bash(git log:*)" "Bash(git rev-parse:*)" "Read" "Write" "Edit" "Glob" "Grep" \ | |
| --model sonnet \ | |
| --max-turns 25 \ | |
| "$(cat <<PROMPT | |
| <context> | |
| You are an automated CI agent in a weekly dependency update workflow. | |
| Git is configured with GPG signing. A branch has been created for you. | |
| Dependencies were updated in the previous step but build/tests failed. | |
| </context> | |
| <failure_log> | |
| $FAILURE_LOG | |
| </failure_log> | |
| <instructions> | |
| The dependency updates above caused build or test failures. | |
| Diagnose the failures from the logs and fix the code so it builds and tests pass. | |
| Create one atomic commit per fix with a conventional commit message. | |
| Run pnpm build && pnpm test to verify your fixes. | |
| Leave all changes local — the workflow handles pushing and PR creation. | |
| </instructions> | |
| <success_criteria> | |
| pnpm build succeeds. | |
| pnpm test succeeds. | |
| Each fix has its own commit. | |
| No uncommitted changes remain in the working tree. | |
| </success_criteria> | |
| PROMPT | |
| )" \ | |
| 2>&1 | tee claude-fix.log | |
| CLAUDE_EXIT=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$CLAUDE_EXIT" -eq 0 ]; then | |
| echo "success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "success=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Set final status | |
| id: final | |
| if: always() | |
| env: | |
| UPDATE_SUCCESS: ${{ steps.update.outputs.success }} | |
| TESTS_PASSED: ${{ steps.tests.outputs.tests-passed }} | |
| FIX_SUCCESS: ${{ steps.claude.outputs.success }} | |
| run: | | |
| if [ "$UPDATE_SUCCESS" = "true" ] && [ "$TESTS_PASSED" = "true" ]; then | |
| echo "success=true" >> $GITHUB_OUTPUT | |
| elif [ "$UPDATE_SUCCESS" = "true" ] && [ "$FIX_SUCCESS" = "true" ]; then | |
| echo "success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "success=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Validate changes | |
| id: validate | |
| if: steps.final.outputs.success == 'true' | |
| run: | | |
| UNEXPECTED="" | |
| for file in $(git diff --name-only origin/main..HEAD); do | |
| case "$file" in | |
| package.json|*/package.json|pnpm-lock.yaml|*/pnpm-lock.yaml|.npmrc|pnpm-workspace.yaml) ;; | |
| src/*|test/*) ;; | |
| *.ts|*.mts|*.js|*.mjs) ;; | |
| *) UNEXPECTED="$UNEXPECTED $file" ;; | |
| esac | |
| done | |
| if [ -n "$UNEXPECTED" ]; then | |
| echo "::error::Unexpected files modified by Claude:$UNEXPECTED" | |
| echo "valid=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "valid=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Check for changes | |
| id: changes | |
| run: | | |
| if [ -n "$(git status --porcelain)" ] || [ "$(git rev-list --count HEAD ^origin/main)" -gt 0 ]; then | |
| echo "has-changes=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has-changes=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Push branch | |
| if: steps.final.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true' | |
| env: | |
| BRANCH_NAME: ${{ steps.branch.outputs.branch }} | |
| run: git push origin "$BRANCH_NAME" | |
| - name: Create Pull Request | |
| if: steps.final.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| BRANCH_NAME: ${{ steps.branch.outputs.branch }} | |
| run: | | |
| COMMITS=$(git log --oneline origin/main..HEAD) | |
| COMMIT_COUNT=$(git rev-list --count origin/main..HEAD) | |
| PR_BODY="## Weekly Dependency Update"$'\n\n' | |
| PR_BODY+="Automated weekly update of npm packages."$'\n\n' | |
| PR_BODY+="---"$'\n\n' | |
| PR_BODY+="### Commits (${COMMIT_COUNT})"$'\n\n' | |
| PR_BODY+="<details>"$'\n' | |
| PR_BODY+="<summary>View commit history</summary>"$'\n\n' | |
| PR_BODY+="\`\`\`"$'\n' | |
| PR_BODY+="${COMMITS}"$'\n' | |
| PR_BODY+="\`\`\`"$'\n\n' | |
| PR_BODY+="</details>"$'\n\n' | |
| PR_BODY+="---"$'\n\n' | |
| PR_BODY+="<sub>Generated by [weekly-update.yml](.github/workflows/weekly-update.yml)</sub>" | |
| gh pr create \ | |
| --title "chore(deps): weekly dependency update ($(date +%Y-%m-%d))" \ | |
| --body "$PR_BODY" \ | |
| --draft \ | |
| --head "$BRANCH_NAME" \ | |
| --base main | |
| # Pushes made with GITHUB_TOKEN don't trigger other workflows. | |
| # Use workflow_dispatch to directly trigger CI on the PR branch. | |
| - name: Trigger CI checks | |
| if: steps.final.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| BRANCH_NAME: ${{ steps.branch.outputs.branch }} | |
| run: gh workflow run ci.yml --ref "$BRANCH_NAME" | |
| - name: Add job summary | |
| if: steps.final.outputs.success == 'true' && steps.validate.outputs.valid == 'true' && steps.changes.outputs.has-changes == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| BRANCH_NAME: ${{ steps.branch.outputs.branch }} | |
| run: | | |
| COMMIT_COUNT=$(git rev-list --count origin/main..HEAD) | |
| pr_number=$(gh pr list --head "$BRANCH_NAME" --json number --jq '.[0].number' || echo "") | |
| pr_url="https://github.com/${{ github.repository }}/pull/${pr_number}" | |
| cat >> "$GITHUB_STEP_SUMMARY" <<EOF | |
| ## Weekly Update Complete | |
| **PR:** [#${pr_number}](${pr_url}) | |
| **Branch:** \`${BRANCH_NAME}\` | |
| **Commits:** ${COMMIT_COUNT} | |
| > **Note:** Enterprise required workflows (e.g. Audit GHA Workflows) won't trigger | |
| > automatically on bot PRs. Click **"Update branch"** on the PR to trigger them, | |
| > or push an empty commit to the branch: | |
| > | |
| > \`\`\`sh | |
| > git fetch origin ${BRANCH_NAME} && git checkout ${BRANCH_NAME} | |
| > git commit --allow-empty -m "chore: trigger enterprise checks" | |
| > git push origin ${BRANCH_NAME} | |
| > \`\`\` | |
| EOF | |
| - name: Upload Claude output | |
| if: always() | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: claude-output-${{ github.run_id }} | |
| path: | | |
| claude-update.log | |
| claude-fix.log | |
| build.log | |
| test.log | |
| retention-days: 7 | |
| - uses: SocketDev/socket-registry/.github/actions/cleanup-git-signing@ea1986b8019fedee5fb38b485690b13ad8e0217f # main | |
| if: always() | |
| notify: | |
| name: Notify results | |
| needs: [check-updates, apply-updates] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Report status | |
| env: | |
| HAS_UPDATES: ${{ needs.check-updates.outputs.has-updates }} | |
| DRY_RUN: ${{ inputs.dry-run }} | |
| run: | | |
| if [ "$HAS_UPDATES" = "true" ]; then | |
| if [ "$DRY_RUN" = "true" ]; then | |
| echo "Updates available (dry-run mode - no PR created)" | |
| else | |
| echo "Weekly update workflow completed" | |
| echo "Check the PRs tab for the automated update PR" | |
| fi | |
| else | |
| echo "All dependencies are up to date - no action needed!" | |
| fi |