name: Refresh Lockfile

on:
  pull_request:
    branches:
      - master
    types:
      - opened
      - synchronize
      - reopened
      - ready_for_review

concurrency:
  group: refresh-lockfile-pr-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  refresh:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: write
      pull-requests: read

    steps:
      - name: Detect dependency manifest changes
        id: changes
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          changed="$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --paginate --jq '.[].filename')"
          manifest_pattern='(^|/)package\.json$|^pnpm-workspace\.yaml$|^\.npmrc$|^pnpmfile\.(cjs|js|mjs)$'

          if printf '%s\n' "$changed" | grep -Eq "$manifest_pattern"; then
            echo "manifest_changed=true" >> "$GITHUB_OUTPUT"
          else
            echo "manifest_changed=false" >> "$GITHUB_OUTPUT"
          fi

          if [ "${{ github.event.pull_request.head.repo.full_name }}" = "${{ github.repository }}" ]; then
            echo "same_repo=true" >> "$GITHUB_OUTPUT"
          else
            echo "same_repo=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Checkout pull request head
        if: steps.changes.outputs.manifest_changed == 'true'
        uses: actions/checkout@v4
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}
          fetch-depth: 0

      - name: Setup pnpm
        if: steps.changes.outputs.manifest_changed == 'true'
        uses: pnpm/action-setup@v4
        with:
          version: 9.15.4
          run_install: false

      - name: Setup Node.js
        if: steps.changes.outputs.manifest_changed == 'true'
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm

      - name: Refresh pnpm lockfile
        if: steps.changes.outputs.manifest_changed == 'true'
        run: pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile

      - name: Fail on unexpected file changes
        if: steps.changes.outputs.manifest_changed == 'true'
        run: |
          changed="$(git status --porcelain)"
          if [ -z "$changed" ]; then
            echo "Lockfile is already up to date."
            exit 0
          fi
          if printf '%s\n' "$changed" | grep -Fvq ' pnpm-lock.yaml'; then
            echo "Unexpected files changed during lockfile refresh:"
            echo "$changed"
            exit 1
          fi

      - name: Commit refreshed lockfile to same-repo PR branch
        if: steps.changes.outputs.manifest_changed == 'true' && steps.changes.outputs.same_repo == 'true'
        run: |
          if git diff --quiet -- pnpm-lock.yaml; then
            echo "Lockfile unchanged, nothing to do."
            exit 0
          fi

          git config user.name "lockfile-bot"
          git config user.email "lockfile-bot@users.noreply.github.com"
          git add pnpm-lock.yaml
          git commit -m "chore(lockfile): refresh pnpm-lock.yaml"
          git push origin "HEAD:${{ github.event.pull_request.head.ref }}"

      - name: Fail fork PRs that need a lockfile refresh
        if: steps.changes.outputs.manifest_changed == 'true' && steps.changes.outputs.same_repo != 'true'
        run: |
          if git diff --quiet -- pnpm-lock.yaml; then
            echo "Lockfile unchanged, nothing to do."
            exit 0
          fi

          echo "This fork PR changes dependency manifests and requires a refreshed pnpm-lock.yaml." >&2
          echo "Run: pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile" >&2
          echo "Then commit pnpm-lock.yaml to the PR branch." >&2
          exit 1
