# GitHub Actions: PR Preview Environments

Give every pull request its own Zuplo environment. Reviewers can test changes
against a live API, and environments clean up automatically when PRs close.

```yaml title=".github/workflows/pr-workflow.yaml"
name: PR Workflow

on:
  pull_request:
    types: [opened, synchronize, reopened, closed]

jobs:
  deploy-and-test:
    # Run on PR open/update, not on close
    if: github.event.action != 'closed'
    runs-on: ubuntu-latest
    env:
      ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm install

      - name: Deploy to Zuplo
        id: deploy
        shell: bash
        run: |
          OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" 2>&1)
          echo "$OUTPUT"
          DEPLOYMENT_URL=$(echo "$OUTPUT" | grep -oP 'Deployed to \K(https://[^ ]+)')
          echo "url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT

      - name: Run tests
        run: npx zuplo test --endpoint "${{ steps.deploy.outputs.url }}"

      - name: Comment PR with deployment URL
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `🚀 Deployed to: ${{ steps.deploy.outputs.url }}`
            })

  cleanup:
    # Only run when PR is closed (merged or not)
    if: github.event.action == 'closed'
    runs-on: ubuntu-latest
    env:
      ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm install

      - name: Delete environment
        run: |
          # Environment name is based on branch name
          BRANCH_NAME="${{ github.head_ref }}"
          # Convert slashes to hyphens (Zuplo convention)
          ENV_NAME="${BRANCH_NAME//\//-}"

          npx zuplo delete \
            --environment "$ENV_NAME" \
            --api-key "$ZUPLO_API_KEY" \
            --wait
```

This workflow:

1. **On PR open/update**: Deploys to an environment named after the branch, runs
   tests, and comments the URL on the PR
2. **On PR close**: Deletes the preview environment

## How It Works

- The environment name comes from the branch name (`feature/auth` becomes
  `feature-auth`)
- Each push to the PR updates the same environment
- Closing the PR (merge or abandon) triggers cleanup
- The PR comment lets reviewers quickly access the preview

## Next Steps

- Add [automatic cleanup on branch delete](./cleanup-on-branch-delete.mdx) as a
  backup
- Implement [multi-stage deployment](./multi-stage-deployment.mdx) for
  production releases
