# VibePod CI/CD & GitHub Actions

---

## Workflows Overview

| Workflow | Trigger | Purpose |
|----------|---------|---------|
| `test.yml` | Push, PR | Run tests and linting |
| `release.yml` | Tag `v*` | Build and publish to PyPI + binaries |
| `docker.yml` | Push to main, Tag | Build Datasette image |

---

## Test Workflow

### `.github/workflows/test.yml`

```yaml
name: Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.10", "3.11", "3.12"]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[dev]"
      
      - name: Lint with ruff
        run: |
          ruff check src tests
          ruff format --check src tests
      
      - name: Type check with mypy
        run: mypy src
      
      - name: Test with pytest
        run: pytest --cov=vibepod --cov-report=xml
      
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
        with:
          files: ./coverage.xml
          fail_ci_if_error: false

  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      
      - name: Install dependencies
        run: pip install ruff
      
      - name: Run ruff
        run: ruff check --output-format=github src tests
```

---

## Release Workflow

### `.github/workflows/release.yml`

```yaml
name: Release

on:
  push:
    tags:
      - "v*"

permissions:
  contents: write

jobs:
  # Build and test first
  test:
    uses: ./.github/workflows/test.yml

  # Build Python package
  build-package:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      
      - name: Install build tools
        run: pip install build twine
      
      - name: Build package
        run: python -m build
      
      - name: Check package
        run: twine check dist/*
      
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  # Build standalone binaries
  build-binaries:
    needs: test
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            target: linux-amd64
            asset_name: vp-linux-amd64
          - os: macos-latest
            target: darwin-amd64
            asset_name: vp-darwin-amd64
          - os: macos-14
            target: darwin-arm64
            asset_name: vp-darwin-arm64
          - os: windows-latest
            target: windows-amd64
            asset_name: vp-windows-amd64.exe
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      
      - name: Install dependencies
        run: |
          pip install -e ".[build]"
      
      - name: Build binary with PyInstaller
        run: |
          pyinstaller --onefile --name ${{ matrix.asset_name }} src/vibepod/cli.py
      
      - name: Upload binary
        uses: actions/upload-artifact@v4
        with:
          name: binary-${{ matrix.target }}
          path: dist/${{ matrix.asset_name }}

  # Publish to PyPI
  publish-pypi:
    needs: [build-package]
    runs-on: ubuntu-latest
    environment: release
    permissions:
      id-token: write  # For trusted publishing
    
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        # Uses trusted publishing - no token needed

  # Create GitHub Release
  create-release:
    needs: [build-package, build-binaries]
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts/
      
      - name: Prepare release files
        run: |
          mkdir release
          cp artifacts/dist/* release/
          cp artifacts/binary-*/* release/
          ls -la release/
      
      - name: Extract changelog
        id: changelog
        run: |
          # Extract version from tag
          VERSION=${GITHUB_REF#refs/tags/v}
          
          # Extract changelog for this version
          sed -n "/^## \[${VERSION}\]/,/^## \[/p" CHANGELOG.md | head -n -1 > release_notes.md
          
          # Fallback if no changelog entry
          if [ ! -s release_notes.md ]; then
            echo "Release ${VERSION}" > release_notes.md
          fi
      
      - name: Create Release
        uses: softprops/action-gh-release@v1
        with:
          body_path: release_notes.md
          files: release/*
          generate_release_notes: true

  # Update Homebrew tap
  update-homebrew:
    needs: create-release
    runs-on: ubuntu-latest
    steps:
      - name: Update Homebrew Formula
        uses: mislav/bump-homebrew-formula-action@v3
        with:
          formula-name: vp
          homebrew-tap: VibePod/homebrew-tap
          download-url: https://github.com/VibePod/vibepod/releases/download/${{ github.ref_name }}/vp-darwin-arm64
        env:
          COMMITTER_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
```

---

## Docker Workflow

### `.github/workflows/docker.yml`

```yaml
name: Docker

on:
  push:
    branches: [main]
    tags: ["v*"]
    paths:
      - "datasette/**"
      - ".github/workflows/docker.yml"

env:
  IMAGE_NAME: vibepod/datasette

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.IMAGE_NAME }}
          tags: |
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=sha-
      
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: ./datasette
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64
```

---

## Container Build Workflows

For the container repos (nezhar/*-container), each has its own workflow:

### Example: `nezhar/claude-container/.github/workflows/build.yml`

```yaml
name: Build and Push

on:
  push:
    branches: [main]
    tags: ["v*"]
  schedule:
    - cron: "0 0 * * 0"  # Weekly rebuild
  workflow_dispatch:

env:
  IMAGE_NAME: vibepod/claude

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.IMAGE_NAME }}
          tags: |
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
            type=semver,pattern={{version}}
            type=sha,prefix=sha-
            type=raw,value={{date 'YYYY.MM.DD'}},enable=${{ github.ref == 'refs/heads/main' }}
      
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
```

---

## Repository Secrets

### VibePod/vibepod

| Secret | Purpose |
|--------|---------|
| `DOCKERHUB_USERNAME` | Docker Hub login |
| `DOCKERHUB_TOKEN` | Docker Hub access token |
| `HOMEBREW_TAP_TOKEN` | PAT for Homebrew tap updates |

Note: PyPI uses trusted publishing, no token needed.

### nezhar/*-container

| Secret | Purpose |
|--------|---------|
| `DOCKERHUB_USERNAME` | Docker Hub login |
| `DOCKERHUB_TOKEN` | Docker Hub access token |

---

## Pre-commit Configuration

### `.pre-commit-config.yaml`

```yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
      - id: check-merge-conflict
  
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.3.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format
  
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.8.0
    hooks:
      - id: mypy
        additional_dependencies:
          - typer
          - types-PyYAML
```

---

## Dependabot Configuration

### `.github/dependabot.yml`

```yaml
version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"
    groups:
      python-dependencies:
        patterns:
          - "*"
  
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    groups:
      github-actions:
        patterns:
          - "*"
```

---

## Release Checklist

1. Update version in `src/vibepod/__init__.py`
2. Update `CHANGELOG.md`
3. Commit: `git commit -m "Release v1.x.x"`
4. Tag: `git tag v1.x.x`
5. Push: `git push origin main --tags`
6. GitHub Actions will:
   - Run tests
   - Build Python package
   - Build binaries (Linux, macOS, Windows)
   - Publish to PyPI
   - Create GitHub Release
   - Update Homebrew tap

---

## Version Bumping

```bash
# Patch (1.0.0 -> 1.0.1)
make release-patch

# Minor (1.0.0 -> 1.1.0)
make release-minor

# Major (1.0.0 -> 2.0.0)
make release-major
```

### Makefile targets

```makefile
VERSION := $(shell grep -m 1 '__version__' src/vibepod/__init__.py | cut -d'"' -f2)

release-patch:
	@echo "Current version: $(VERSION)"
	@NEW_VERSION=$$(echo $(VERSION) | awk -F. '{print $$1"."$$2"."$$3+1}') && \
	sed -i '' "s/__version__ = \".*\"/__version__ = \"$$NEW_VERSION\"/" src/vibepod/__init__.py && \
	git add src/vibepod/__init__.py && \
	git commit -m "Bump version to $$NEW_VERSION" && \
	git tag "v$$NEW_VERSION" && \
	echo "Tagged v$$NEW_VERSION"

release-minor:
	@NEW_VERSION=$$(echo $(VERSION) | awk -F. '{print $$1"."$$2+1".0"}') && \
	sed -i '' "s/__version__ = \".*\"/__version__ = \"$$NEW_VERSION\"/" src/vibepod/__init__.py && \
	git add src/vibepod/__init__.py && \
	git commit -m "Bump version to $$NEW_VERSION" && \
	git tag "v$$NEW_VERSION"

release-major:
	@NEW_VERSION=$$(echo $(VERSION) | awk -F. '{print $$1+1".0.0"}') && \
	sed -i '' "s/__version__ = \".*\"/__version__ = \"$$NEW_VERSION\"/" src/vibepod/__init__.py && \
	git add src/vibepod/__init__.py && \
	git commit -m "Bump version to $$NEW_VERSION" && \
	git tag "v$$NEW_VERSION"
```
