This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Docs build: manual only, or on PR to main with manual approval. | |
| # Configure environment "approval-required" in repo Settings > Environments with required reviewers. | |
| name: Build Documentation | |
| on: | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - 'docs/**' | |
| - 'dev/mkdocs.yml' | |
| - '.readthedocs.yaml' | |
| - 'dev/requirements-rtd.txt' | |
| - 'ccbt/**' | |
| workflow_dispatch: | |
| concurrency: | |
| group: docs-build-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| check-validation: | |
| name: check-validation | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' | |
| permissions: | |
| contents: read | |
| actions: read | |
| pull-requests: read | |
| steps: | |
| - name: Check if validation workflows passed | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| // For PRs, check if ci.yml and test.yml have passed | |
| if (context.eventName === 'pull_request') { | |
| const { data: checks } = await github.rest.checks.listForRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: context.payload.pull_request.head.sha, | |
| }); | |
| const requiredChecks = ['CI/CD Pipeline', 'Test']; | |
| const passedChecks = checks.check_runs.filter( | |
| check => requiredChecks.includes(check.name) && check.conclusion === 'success' | |
| ); | |
| if (passedChecks.length < requiredChecks.length) { | |
| core.setFailed('Required validation workflows must pass first'); | |
| } | |
| } | |
| // For workflow_dispatch, allow manual override | |
| build-docs: | |
| name: build-docs | |
| needs: check-validation | |
| runs-on: ubuntu-latest | |
| environment: approval-required | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'pull_request' && needs.check-validation.result == 'success') | |
| permissions: | |
| contents: read | |
| actions: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install UV | |
| uses: astral-sh/setup-uv@v4 | |
| with: | |
| version: "latest" | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Cache Python dependencies | |
| uses: actions/cache@v3 | |
| with: | |
| path: | | |
| ~/.cache/uv | |
| .venv | |
| key: ubuntu-python-3.11-${{ hashFiles('uv.lock') }} | |
| restore-keys: | | |
| ubuntu-python-3.11- | |
| - name: Cache MkDocs cache | |
| uses: actions/cache@v3 | |
| with: | |
| path: .cache | |
| key: mkdocs-${{ github.sha }} | |
| restore-keys: | | |
| mkdocs- | |
| - name: Cache pytest cache (for coverage report) | |
| uses: actions/cache@v3 | |
| with: | |
| path: .pytest_cache | |
| key: pytest-docs-${{ github.sha }} | |
| restore-keys: | | |
| pytest-docs- | |
| - name: Install dependencies | |
| run: | | |
| uv sync --dev | |
| - name: Check for port conflicts | |
| run: | | |
| # Check for common test ports that might be in use | |
| # This helps detect lingering processes from previous test runs | |
| echo "Checking for port conflicts..." | |
| if command -v lsof &> /dev/null; then | |
| # Unix-like systems (Linux, macOS) | |
| PORTS=(6881 6882 6883 5001 8080 8081 8082) | |
| for port in "${PORTS[@]}"; do | |
| if lsof -i :$port &> /dev/null; then | |
| echo "⚠️ Warning: Port $port is in use" | |
| lsof -i :$port || true | |
| fi | |
| done | |
| elif command -v netstat &> /dev/null; then | |
| # Windows or older Unix systems | |
| PORTS=(6881 6882 6883 5001 8080 8081 8082) | |
| for port in "${PORTS[@]}"; do | |
| if netstat -an | grep -q ":$port "; then | |
| echo "⚠️ Warning: Port $port is in use" | |
| netstat -an | grep ":$port " || true | |
| fi | |
| done | |
| else | |
| echo "⚠️ Port conflict detection tools not available, skipping check" | |
| fi | |
| echo "Port conflict check complete" | |
| continue-on-error: true | |
| - name: Ensure report directories exist | |
| run: | | |
| mkdir -p site/reports/htmlcov | |
| mkdir -p docs/reports/bandit | |
| mkdir -p docs/en/reports/bandit | |
| - name: Generate coverage report | |
| run: | | |
| uv run pytest -c dev/pytest.ini tests/ --cov=ccbt --cov-report=html:site/reports/htmlcov | |
| continue-on-error: true | |
| - name: Generate Bandit report | |
| run: | | |
| uv run python tests/scripts/ensure_bandit_dir.py | |
| uv run bandit -r ccbt/ -f json -o docs/reports/bandit/bandit-report.json --severity-level medium -x tests,benchmarks,dev,dist,docs,htmlcov,site,.venv,.pre-commit-cache,.pre-commit-home,.pytest_cache,.ruff_cache,.hypothesis,.github,.ccbt,.cursor,.benchmarks | |
| continue-on-error: true | |
| - name: Ensure report files exist in documentation location | |
| run: | | |
| # Ensure coverage directory has at least an index file (even if empty) | |
| mkdir -p site/reports/htmlcov | |
| if [ ! -f site/reports/htmlcov/index.html ]; then | |
| echo '<html><body><h1>Coverage Report</h1><p>Coverage report not available. Run tests to generate coverage data.</p></body></html>' > site/reports/htmlcov/index.html | |
| fi | |
| # Ensure bandit reports exist in documentation location (docs/en/reports/bandit/) | |
| mkdir -p docs/en/reports/bandit | |
| # Copy or create bandit-report.json | |
| if [ -f docs/reports/bandit/bandit-report.json ]; then | |
| cp docs/reports/bandit/bandit-report.json docs/en/reports/bandit/bandit-report.json | |
| else | |
| echo '{"generated_at": "N/A", "metrics": {}, "results": []}' > docs/en/reports/bandit/bandit-report.json | |
| fi | |
| # Copy or create bandit-report-all.json | |
| if [ -f docs/reports/bandit/bandit-report-all.json ]; then | |
| cp docs/reports/bandit/bandit-report-all.json docs/en/reports/bandit/bandit-report-all.json | |
| else | |
| echo '{"generated_at": "N/A", "metrics": {}, "results": []}' > docs/en/reports/bandit/bandit-report-all.json | |
| fi | |
| # Create placeholder for upnp-check if it doesn't exist | |
| if [ ! -f docs/en/reports/bandit/bandit-upnp-check.json ]; then | |
| echo '{"generated_at": "N/A", "metrics": {}, "results": []}' > docs/en/reports/bandit/bandit-upnp-check.json | |
| fi | |
| echo "✅ All report files ensured in documentation location" | |
| - name: Build documentation | |
| run: | | |
| # Ensure coverage directory exists right before build (in case it was cleaned) | |
| mkdir -p site/reports/htmlcov | |
| if [ ! -f site/reports/htmlcov/index.html ]; then | |
| echo '<html><body><h1>Coverage Report</h1><p>Coverage report not available. Run tests to generate coverage data.</p></body></html>' > site/reports/htmlcov/index.html | |
| fi | |
| # Use the patched build script which includes all necessary patches: | |
| # - i18n plugin fixes (alternates attribute, Locale validation for 'arc') | |
| # - git-revision-date-localized plugin fix for 'arc' locale | |
| # - Autorefs plugin patch to suppress multiple primary URLs warnings | |
| # - Coverage plugin patch to suppress missing directory warnings | |
| # - All patches are applied before mkdocs is imported | |
| # Set MKDOCS_STRICT=true to enable strict mode in CI | |
| MKDOCS_STRICT=true uv run python dev/build_docs_patched_clean.py | |
| - name: Upload documentation artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: documentation | |
| path: site/ | |
| retention-days: 7 | |
| - name: Trigger Read the Docs build | |
| if: env.RTD_API_TOKEN != '' | |
| env: | |
| RTD_API_TOKEN: ${{ secrets.RTD_API_TOKEN }} | |
| RTD_PROJECT_SLUG: ${{ secrets.RTD_PROJECT_SLUG || 'ccbittorrent' }} | |
| BRANCH_NAME: ${{ github.ref_name }} | |
| run: | | |
| echo "Triggering Read the Docs build for branch: $BRANCH_NAME" | |
| curl -X POST \ | |
| -H "Authorization: Token $RTD_API_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| "https://readthedocs.org/api/v3/projects/$RTD_PROJECT_SLUG/versions/$BRANCH_NAME/builds/" \ | |
| -d "{}" || echo "⚠️ Failed to trigger Read the Docs build. This may be expected if the branch is not configured in Read the Docs." | |
| continue-on-error: true | |
| - name: Read the Docs build info | |
| if: env.RTD_API_TOKEN == '' | |
| run: | | |
| echo "ℹ️ Read the Docs API token not configured." | |
| echo " To enable automatic Read the Docs builds from any branch:" | |
| echo " 1. Get your Read the Docs API token from https://readthedocs.org/accounts/token/" | |
| echo " 2. Add it as a GitHub secret named RTD_API_TOKEN" | |
| echo " 3. Optionally set RTD_PROJECT_SLUG secret (defaults to 'ccbittorrent')" | |
| echo "" | |
| echo " Note: Read the Docs will only build branches configured in your project settings." | |
| echo " By default, only 'main' and 'dev' branches are built automatically." | |
| # Note: Documentation is automatically published to Read the Docs | |
| # when changes are pushed to the repository for configured branches (main/dev by default). | |
| # To build other branches, configure them in Read the Docs project settings or use the API trigger above. | |