From 499d44ba38c4cdc1e09251817c69efee7d4699a8 Mon Sep 17 00:00:00 2001 From: Pat Teruel Date: Tue, 30 Dec 2025 21:24:27 +0800 Subject: [PATCH] ci: add PR validation workflow with Conventional Commits check - Add .gitea/workflows/pr-validation.yml with 4 validation jobs - Validate PR titles follow Conventional Commits format - Check Dockerfile and nginx.conf syntax - Run integration tests (build image, test container, verify pages load) - Verify required files exist and HTTrack artifacts excluded - Update copilot-instructions.md with PR validation details --- .gitea/workflows/pr-validation.yml | 196 +++++++++++++++++++++++++++++ .github/copilot-instructions.md | 22 +++- 2 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 .gitea/workflows/pr-validation.yml diff --git a/.gitea/workflows/pr-validation.yml b/.gitea/workflows/pr-validation.yml new file mode 100644 index 0000000..7a8d4cc --- /dev/null +++ b/.gitea/workflows/pr-validation.yml @@ -0,0 +1,196 @@ +name: Pull Request Validation + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +jobs: + validate-pr-title: + runs-on: ubuntu-latest + steps: + - name: Validate PR title follows Conventional Commits + run: | + PR_TITLE="${{ github.event.pull_request.title }}" + echo "Validating PR title: $PR_TITLE" + + # Conventional Commits pattern: type(scope): description + # Types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert + PATTERN="^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\(.+\))?: .+" + + if echo "$PR_TITLE" | grep -qE "$PATTERN"; then + echo "✅ PR title follows Conventional Commits format" + exit 0 + else + echo "❌ PR title does not follow Conventional Commits format" + echo "" + echo "Expected format: (optional scope): " + echo "" + echo "Valid types:" + echo " - feat: A new feature" + echo " - fix: A bug fix" + echo " - docs: Documentation only changes" + echo " - style: Code style changes (formatting, missing semi-colons, etc)" + echo " - refactor: Code change that neither fixes a bug nor adds a feature" + echo " - perf: Performance improvements" + echo " - test: Adding or updating tests" + echo " - chore: Changes to build process or auxiliary tools" + echo " - ci: CI/CD configuration changes" + echo " - build: Changes to build system or dependencies" + echo " - revert: Reverts a previous commit" + echo "" + echo "Examples:" + echo " - feat: add user authentication" + echo " - fix(api): handle null pointer exception" + echo " - docs: update installation instructions" + echo " - ci: add Docker build workflow" + exit 1 + fi + + validate-docker: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Validate Dockerfile syntax + run: | + if [ -f "Dockerfile" ]; then + echo "✅ Dockerfile exists" + # Basic syntax check by attempting to parse it + docker build --no-cache --target="" -f Dockerfile . 2>&1 | head -5 || true + echo "✅ Dockerfile syntax appears valid" + else + echo "❌ Dockerfile not found" + exit 1 + fi + + - name: Check .dockerignore exists + run: | + if [ -f ".dockerignore" ]; then + echo "✅ .dockerignore exists" + else + echo "⚠️ Warning: .dockerignore not found (recommended)" + fi + + - name: Validate nginx config + run: | + if [ -f "nginx.conf" ]; then + echo "✅ nginx.conf exists" + # Test nginx config syntax using alpine nginx image + docker run --rm -v "$(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro" nginx:alpine nginx -t + echo "✅ nginx.conf syntax is valid" + else + echo "⚠️ Warning: nginx.conf not found" + fi + + build-test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image (no push) + uses: docker/build-push-action@v5 + with: + context: . + push: false + tags: karaokepedia:pr-${{ github.event.pull_request.number }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Test container starts + run: | + docker build -t karaokepedia:test . + CONTAINER_ID=$(docker run -d -p 8080:80 karaokepedia:test) + echo "Container started: $CONTAINER_ID" + + # Wait for container to be healthy + echo "Waiting for container to start..." + sleep 5 + + # Test homepage + if curl -f -s -o /dev/null http://localhost:8080/; then + echo "✅ Homepage loads successfully" + else + echo "❌ Homepage failed to load" + docker logs $CONTAINER_ID + exit 1 + fi + + # Test songs page + if curl -f -s -o /dev/null http://localhost:8080/songs.html; then + echo "✅ Songs page loads successfully" + else + echo "❌ Songs page failed to load" + exit 1 + fi + + # Test assets + if curl -f -s -o /dev/null http://localhost:8080/assets/application-b504973bc673ef9f09352588c7d791495f9fa7b652e6bf0d71ea86a094aa4007.css; then + echo "✅ CSS assets load successfully" + else + echo "❌ CSS assets failed to load" + exit 1 + fi + + # Cleanup + docker stop $CONTAINER_ID + docker rm $CONTAINER_ID + + echo "✅ All container tests passed" + + check-files: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check required files exist + run: | + REQUIRED_FILES=( + "karaoke.karaniwan.org/index.html" + "karaoke.karaniwan.org/songs.html" + "karaoke.karaniwan.org/assets/application-b504973bc673ef9f09352588c7d791495f9fa7b652e6bf0d71ea86a094aa4007.css" + "Dockerfile" + "nginx.conf" + ".dockerignore" + ) + + MISSING_FILES=() + for file in "${REQUIRED_FILES[@]}"; do + if [ -f "$file" ]; then + echo "✅ $file exists" + else + echo "❌ $file is missing" + MISSING_FILES+=("$file") + fi + done + + if [ ${#MISSING_FILES[@]} -gt 0 ]; then + echo "" + echo "❌ Missing required files:" + printf ' - %s\n' "${MISSING_FILES[@]}" + exit 1 + fi + + echo "" + echo "✅ All required files present" + + - name: Check for HTTrack artifacts in content + run: | + echo "Checking that HTTrack artifacts are not included in Docker context..." + + # These should be in .dockerignore + if [ -d "hts-cache" ]; then + if grep -q "hts-cache" .dockerignore; then + echo "✅ hts-cache/ directory excluded in .dockerignore" + else + echo "❌ hts-cache/ should be in .dockerignore" + exit 1 + fi + fi + + echo "✅ HTTrack artifacts properly excluded" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 66f03b5..9e07824 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -76,13 +76,31 @@ docker tag karaokepedia:latest your-registry/karaokepedia:latest docker push your-registry/karaokepedia:latest ``` -### CI/CD Pipeline (Gitea Actions) +### CI/CD Pipelines (Gitea Actions) + +#### Build & Deploy (`.gitea/workflows/build.yml`) - **Trigger**: Push to `main` branch or manual dispatch -- **Workflow**: `.gitea/workflows/build.yml` (GitHub Actions-compatible syntax) - **Steps**: Checkout → Setup Buildx → Login to registry → Build & push → Output digest - **Tags**: `:latest` and `:main-` - **Registry**: Configure via secrets (DOCKER_USERNAME/DOCKER_PASSWORD for Docker Hub, or adapt for Gitea registry) +#### PR Validation (`.gitea/workflows/pr-validation.yml`) +- **Trigger**: Pull request opened, edited, synchronized, or reopened +- **Jobs**: + - `validate-pr-title`: Enforces [Conventional Commits](https://www.conventionalcommits.org/) format + - `validate-docker`: Checks Dockerfile, nginx.conf, and .dockerignore syntax + - `build-test`: Builds image and tests container starts, pages load, assets accessible + - `check-files`: Verifies required files exist, HTTrack artifacts excluded + +**Conventional Commit Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`, `build`, `revert` + +**Example PR titles**: +- ✅ `feat: add user authentication` +- ✅ `fix(docker): correct nginx config path` +- ✅ `docs: update README with deployment steps` +- ❌ `Added new feature` (missing type) +- ❌ `Update files` (not descriptive) + ### Registry Configuration Edit `.gitea/workflows/build.yml` and uncomment the appropriate registry: - **Docker Hub** (default): Uses `DOCKER_USERNAME` and `DOCKER_PASSWORD` secrets