name: CI on: push: branches: ['release/*'] pull_request: branches: [main, 'release/*'] jobs: ci: runs-on: docker container: image: forge.lvl0.xyz/lvl0/incr-ci:php8.3-2 services: db: # Fully qualified, matching docker/dev/docker-compose.yml: the runner is # podman-backed and a bare name depends on unqualified-search-registries. image: docker.io/library/mysql:8.0 env: MYSQL_DATABASE: testing MYSQL_USER: incr_user MYSQL_PASSWORD: incr_password MYSQL_ROOT_PASSWORD: root_password steps: - uses: https://data.forgejo.org/actions/checkout@v4 - name: Cache Composer dependencies uses: https://data.forgejo.org/actions/cache@v4 with: path: ~/.cache/composer key: composer-${{ hashFiles('composer.lock') }} restore-keys: composer- - name: Install PHP dependencies run: composer install --no-interaction --prefer-dist --no-progress - name: Prepare environment run: cp .env.testing .env # Temporary: db resolves but refuses :3306. Capture what the service # container is actually doing before assuming a cause. - name: Probe the db service continue-on-error: true run: | echo "--- ports responding on db ---" for p in 3306 33060 80; do php -r "\$f=@fsockopen('db',$p,\$n,\$e,2); echo '$p: '.(\$f?'open':\$e).PHP_EOL;" done echo "--- can we reach ourselves? (sanity) ---" php -r '$f=@fsockopen("127.0.0.1",1,$n,$e,1); echo "localhost:1 -> $e".PHP_EOL;' - name: Wait for MySQL run: | for i in $(seq 1 30); do if php -r 'exit(@fsockopen("db", 3306) ? 0 : 1);'; then echo "MySQL is up after ${i} attempt(s)" exit 0 fi echo "Waiting for MySQL... ($i/30)" sleep 2 done echo "MySQL never became reachable on db:3306" >&2 exit 1 - name: Run migrations run: php artisan migrate --force - name: Lint run: vendor/bin/pint --test - name: Static analysis run: vendor/bin/phpstan analyse --memory-limit=1G --no-progress --error-format=github - name: Tests run: php artisan test --coverage-clover coverage.xml --coverage-text - name: Parse coverage if: github.event_name == 'pull_request' id: coverage run: | COVERAGE=$(php -r ' $xml = simplexml_load_file("coverage.xml"); if ($xml === false || !isset($xml->project->metrics)) { echo "0"; exit; } $metrics = $xml->project->metrics; $statements = (int) $metrics["statements"]; $covered = (int) $metrics["coveredstatements"]; echo $statements > 0 ? round(($covered / $statements) * 100, 2) : 0; ') echo "percentage=$COVERAGE" >> "$GITHUB_OUTPUT" - name: Comment coverage on PR if: github.event_name == 'pull_request' continue-on-error: true env: FORGEJO_TOKEN: ${{ secrets.FORGEJO_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} COVERAGE: ${{ steps.coverage.outputs.percentage }} REPO: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} COMMIT_SHA: ${{ github.sha }} run: | API_URL="${SERVER_URL}/api/v1/repos/${REPO}/issues/${PR_NUMBER}/comments" MARKER="" BODY="${MARKER} ## Code Coverage Report | Metric | Value | |--------|-------| | **Line Coverage** | ${COVERAGE}% | _Updated by CI — commit ${COMMIT_SHA}_" EXISTING=$(curl -sf -H "Authorization: token ${FORGEJO_TOKEN}" \ "${API_URL}?limit=50" | \ php -r ' $comments = json_decode(file_get_contents("php://stdin"), true); if (!is_array($comments)) exit; foreach ($comments as $c) { if (str_contains($c["body"], "")) { echo $c["id"]; exit; } } ' || true) if [ -n "$EXISTING" ]; then curl -sf -X PATCH \ -H "Authorization: token ${FORGEJO_TOKEN}" \ -H "Content-Type: application/json" \ -d "$(php -r 'echo json_encode(["body" => $argv[1]]);' "$BODY")" \ "${SERVER_URL}/api/v1/repos/${REPO}/issues/comments/${EXISTING}" > /dev/null else curl -sf -X POST \ -H "Authorization: token ${FORGEJO_TOKEN}" \ -H "Content-Type: application/json" \ -d "$(php -r 'echo json_encode(["body" => $argv[1]]);' "$BODY")" \ "${API_URL}" > /dev/null fi build: runs-on: docker container: image: node:22-bookworm-slim steps: - uses: https://data.forgejo.org/actions/checkout@v4 - name: Install JS dependencies run: npm ci - name: Build assets run: npm run build