diff --git a/Jenkinsfile b/.archive/archived-23-07-2026-Jenkinsfile similarity index 100% rename from Jenkinsfile rename to .archive/archived-23-07-2026-Jenkinsfile diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 000000000..03d73e0fe --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,165 @@ +name: CI/CD + +on: + push: + branches: + - prod + - main + - dev + + pull_request: + branches: + - '**' + +jobs: + test: + name: Run tests + runs-on: [self-hosted, gh-runner-common] + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests, first attempt + id: first_run + continue-on-error: true + run: npm test -- --coverage --json --outputFile=test-results.json + + - name: Identify failed test suites + if: steps.first_run.outcome == 'failure' + id: failed_tests + run: | + if [ ! -f test-results.json ]; then + echo "No test results file found, treating as full failure" + exit 1 + fi + FAILED_SUITES=$(node -e " + const results = require('./test-results.json'); + const failed = results.testResults + .filter(suite => suite.status === 'failed') + .map(suite => suite.name) + .join('\n'); + console.log(failed); + ") + if [ -z "$FAILED_SUITES" ]; then + echo "No failed suites identified despite failure exit code" + exit 1 + fi + echo "Failed test suites:" + echo "$FAILED_SUITES" + echo "$FAILED_SUITES" > failed-suites.txt + - name: Re-run failed test suites individually + if: steps.first_run.outcome == 'failure' + run: | + OVERALL_EXIT=0 + while IFS= read -r suite; do + if [ -n "$suite" ]; then + echo "" + echo "=== Re-running: $suite ===" + if ! npx jest "$suite"; then + echo "Still failing: $suite" + OVERALL_EXIT=1 + else + echo "Passed on retry: $suite" + fi + fi + done < failed-suites.txt + exit $OVERALL_EXIT + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v7 + with: + token: ${{ secrets.CODECOV_TOKEN }} + url: ${{ secrets.CODECOV_URL }} + slug: Alt-Org/Altzone-Server + files: ./coverage/coverage-final.json + + build-and-push: + name: Build and Push Docker Image + needs: test + if: github.ref_name == 'main' || github.ref_name == 'dev' || github.ref_name == 'prod' + runs-on: [self-hosted, gh-runner-common] + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Log in to Docker Hub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v7 + env: + DOCKER_BUILD_RECORD_UPLOAD: false + with: + context: . + push: true + load: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:${{ github.ref_name }}-${{ github.run_number }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:${{ github.ref_name }}-latest + + - name: Scan image for vulnerabilities + uses: aquasecurity/trivy-action@v0.36.0 + with: + image-ref: ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:${{ github.ref_name }}-${{ github.run_number }} + format: json + ignore-unfixed: true + exit-code: '0' + output: trivy-results.json + + - name: Upload vulnerabilities to summary tab + if: always() + run: | + { + echo "## Vulnerability scan results" + echo "" + echo "| Target | Package | CVE | Severity | Installed | Fixed |" + echo "|---|---|---|---|---|---|" + jq -r ' + .Results[]? + | select(.Vulnerabilities != null) + | .Target as $t + | .Vulnerabilities[] + | "| \($t) | \(.PkgName) | \(.VulnerabilityID) | \(.Severity) | \(.InstalledVersion) | \(.FixedVersion // "-") |" + ' trivy-results.json + } >> "$GITHUB_STEP_SUMMARY" + + - name: Convert scan results to SARIF format + if: always() + run: trivy convert --format sarif --output trivy-results.sarif trivy-results.json + + - name: Upload vulnerabilities to Security tab + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: trivy-results.sarif + + notify: + name: Notify server about new image build + needs: build-and-push + if: github.ref_name == 'main' || github.ref_name == 'dev' || github.ref_name == 'prod' + runs-on: [self-hosted, gh-runner-common] + steps: + - name: Notify server + env: + WEBHOOK_SECRET: ${{ secrets.SERVER_WEBHOOK_NOTIFY_SECRET }} + WEBHOOK_URL: ${{ secrets.SERVER_WEBHOOK_NOTIFY_URL }} + PAYLOAD: '{"name": "api", "tag": "${{ github.ref_name }}"}' + run: | + SIGNATURE=$(echo "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | sed 's/^.* //') + + curl -s -o /dev/null -X POST "$WEBHOOK_URL" \ + -H "Content-Type: application/json" \ + -H "X-Hub-Signature: sha256=$SIGNATURE" \ + -d "$PAYLOAD" \ + --insecure \ No newline at end of file diff --git a/README.md b/README.md index a8a5e144b..e18ad1594 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![codecov](https://yrfbcpxonsco.mikhail.com.de/gh/Alt-Org/Altzone-Server/graph/badge.svg?token=DF96AVM86K)](https://yrfbcpxonsco.mikhail.com.de/gh/Alt-Org/Altzone-Server) + # Altzone-Server This is a REST API for the Altzone game. For more detailed API description and additional instructions see the [wiki pages](https://github.com/Alt-Org/Altzone-Server/wiki) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..63fe9641a --- /dev/null +++ b/codecov.yml @@ -0,0 +1,11 @@ +coverage: + status: + project: + default: + target: 63 + patch: + default: + target: 50 + +codecov: + branch: dev \ No newline at end of file diff --git a/jest.config.ts b/jest.config.ts index 6dad81131..59b9b7a7e 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -17,7 +17,7 @@ const config: Config = { testPathIgnorePatterns: [`${testDir}/test_utils`], collectCoverageFrom: [`${codeDir}/**/*.ts`], coverageDirectory: './coverage', - coverageReporters: ['cobertura'], + coverageReporters: ['json', 'json-summary', 'text-summary'], reporters: ['default', 'jest-junit'], setupFiles: [