🔄 Weekly Dependency Update #10
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 | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| check-updates: | |
| name: Check for dependency updates | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| outputs: | |
| has-updates: ${{ steps.check.outputs.has-updates }} | |
| steps: | |
| - uses: SocketDev/socket-registry/.github/actions/setup-and-install@3362af95fadd1e325cb48e9ad6daff21c112bd72 # main | |
| - 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 | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: SocketDev/socket-registry/.github/actions/setup-and-install@3362af95fadd1e325cb48e9ad6daff21c112bd72 # main | |
| - name: Create update branch | |
| id: branch | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| BRANCH_NAME="weekly-update-$(date +%Y%m%d)" | |
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" | |
| git checkout -b "$BRANCH_NAME" | |
| echo "branch=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
| - uses: SocketDev/socket-registry/.github/actions/setup-git-signing@3362af95fadd1e325cb48e9ad6daff21c112bd72 # main | |
| with: | |
| gpg-private-key: ${{ secrets.BOT_GPG_PRIVATE_KEY }} | |
| - name: Update dependencies (haiku — fast, cheap) | |
| id: update | |
| timeout-minutes: 10 | |
| shell: bash | |
| 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 | |
| claude --print \ | |
| --model haiku \ | |
| --max-turns 15 \ | |
| --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" \ | |
| "$(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-output.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' | |
| shell: bash | |
| run: | | |
| set +e | |
| pnpm run build 2>&1 | tee build-output.log | |
| BUILD_EXIT=${PIPESTATUS[0]} | |
| if [ "$BUILD_EXIT" -ne 0 ]; then | |
| echo "tests-passed=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| pnpm test 2>&1 | tee test-output.log | |
| TEST_EXIT=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$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 | |
| shell: bash | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| GITHUB_ACTIONS: 'true' | |
| run: | | |
| # Collect failure context for the agent. | |
| FAILURE_LOG="" | |
| if [ -f build-output.log ]; then | |
| FAILURE_LOG+="Build output (last 50 lines):"$'\n' | |
| FAILURE_LOG+="$(tail -50 build-output.log)"$'\n\n' | |
| fi | |
| if [ -f test-output.log ]; then | |
| FAILURE_LOG+="Test output (last 100 lines):"$'\n' | |
| FAILURE_LOG+="$(tail -100 test-output.log)"$'\n' | |
| fi | |
| # Write prompt to file to avoid shell expansion of FAILURE_LOG. | |
| PROMPT_FILE=$(mktemp) | |
| { | |
| printf '%s\n' 'Build or tests failed after dependency updates. Fix them.' | |
| printf '\n<failure_output>\n' | |
| printf '%s' "$FAILURE_LOG" | |
| printf '\n</failure_output>\n\n' | |
| printf '%s\n' '<instructions>' | |
| printf '%s\n' 'Diagnose the failures from the output above.' | |
| printf '%s\n' 'Fix the code — do not revert the dependency updates.' | |
| printf '%s\n' 'Run the tests again to verify your fix works.' | |
| printf '%s\n' 'Create a commit for each fix with a conventional commit message.' | |
| printf '%s\n' '</instructions>' | |
| } > "$PROMPT_FILE" | |
| set +e | |
| claude --print \ | |
| --model sonnet \ | |
| --max-turns 25 \ | |
| --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" \ | |
| "$(cat "$PROMPT_FILE")" \ | |
| 2>&1 | tee -a claude-output.log | |
| CLAUDE_EXIT=${PIPESTATUS[0]} | |
| set -e | |
| rm -f "$PROMPT_FILE" | |
| 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" ] || [ "$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: | | |
| # Allow dependency files + source/test fixes from the fix step. | |
| 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:$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 | |
| - name: Add job summary | |
| 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: | | |
| COMMIT_COUNT=$(git rev-list --count origin/main..HEAD) | |
| echo "## Weekly Update Complete" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Branch:** \`${BRANCH_NAME}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "**Commits:** ${COMMIT_COUNT}" >> $GITHUB_STEP_SUMMARY | |
| - name: Upload logs | |
| if: always() | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: weekly-update-logs-${{ github.run_id }} | |
| path: | | |
| claude-output.log | |
| build-output.log | |
| test-output.log | |
| retention-days: 7 | |
| - uses: SocketDev/socket-registry/.github/actions/cleanup-git-signing@3362af95fadd1e325cb48e9ad6daff21c112bd72 # 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 |