diff --git a/.github/actions/deploy-versioned-pages/action.yml b/.github/actions/deploy-versioned-pages/action.yml deleted file mode 100644 index a595f272015..00000000000 --- a/.github/actions/deploy-versioned-pages/action.yml +++ /dev/null @@ -1,144 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: Deploy Versioned Pages -description: Will push the documentation to the gh-pages branch, possibly with a versioned URL. When the PR is closed, the documentation will be deleted. -# Note: this has some shortcomings, like race conditions when multiple PRs are opened at the same time, -# problems with overwriting existing files, not deleting deleted files, etc. -# We'll address these when we transform this action into a truly reusable independent action. -# But for now, let's get it working! - -inputs: - source_folder: - description: "Path to the html files to deploy in current working directory" - required: true - versions_file: - description: "Path to the versions file on gh-pages branch" - default: "versions.json" - create_comment: - description: "Create a comment on the PR with the URL to the documentation" # in case of PR - default: "true" - -outputs: - target_folder: - description: "The target folder for the documentation" - value: ${{ steps.calc.outputs.target_folder }} - -runs: - using: "composite" - steps: - - name: Determine target_folder - id: calc - shell: bash - run: | - if [[ "${{ github.event_name }}" == 'pull_request_target' || "${{ github.event_name }}" == 'pull_request' ]]; then - target_folder="pr-${{ github.event.pull_request.number }}" - else - target_folder="${{github.ref_name}}" - fi - echo "target_folder=$target_folder" >> $GITHUB_OUTPUT - - - name: Prepare the deploy folder - shell: bash - run: | - # Prepare the deploy folder - mkdir -p deploy_root - mkdir -p version_root - # Move the files to the deploy folder - mv ${{ inputs.source_folder }}/* deploy_root/ - # Ensure that the folder is not treated as a Jekyll site - touch deploy_root/.nojekyll - - # Add the target folder to the versions file - BASE_REPO="https://github.com/${{ github.repository }}.git" - - echo "Fetching gh-pages from BASE_REPO: $BASE_REPO" - git remote add base "$BASE_REPO" || git remote set-url base "$BASE_REPO" - git fetch base gh-pages --depth 1 - - # Checkout only the versions file from gh-pages branch of the base repo - git checkout base/gh-pages -- "${{ inputs.versions_file }}" - - target="${{ steps.calc.outputs.target_folder }}" - new_version="${{ steps.calc.outputs.target_folder }}" - - - if jq -e --arg version "$new_version" 'map(select(.version == $version)) | length > 0' "${{ inputs.versions_file }}" > /dev/null; then - echo "Version '$new_version' already exists in ${{ inputs.versions_file }}" - else - REPO_NAME=$(basename ${{ github.repository }}) - USER_NAME=$(echo ${{ github.repository }} | cut -d'/' -f1) - GITHUB_PAGES_URL="https://${USER_NAME}.github.io/${REPO_NAME}" - if [ "$target" = "/" ]; then - new_url="${GITHUB_PAGES_URL}/" - else - new_url="${GITHUB_PAGES_URL}/$target/" - fi - - jq --arg version "$new_version" --arg url "$new_url" '. + [{"version": $version, "url": $url}]' "${{ inputs.versions_file }}" > tmp_versions.json - mv tmp_versions.json "${{ inputs.versions_file }}" - fi - mv "${{ inputs.versions_file }}" version_root/ - ls -al . - ls -al deploy_root - ls -al version_root - cat version_root/"${{ inputs.versions_file }}" - - - name: Deploy Documentation - uses: JamesIves/github-pages-deploy-action@v4 - with: - folder: deploy_root - target-folder: ${{ steps.calc.outputs.target_folder }} - clean: true - clean-exclude: .nojekyll - - - name: Deploy version file 🚀 - uses: JamesIves/github-pages-deploy-action@v4 - with: - folder: version_root - clean: false - - - name: Warn of gh-pages size - id: ghpages_size - if: ${{ github.event_name == 'pull_request_target' }} - shell: bash - run: | - git fetch --depth 1 origin gh-pages || true - size_mb=$(git ls-tree -r -l origin/gh-pages | awk '{sum+=$4} END{printf("%d\n", sum/1024/1024)}') - - # Trigger a warning if the gh-pages branch is approximately 950MB or larger - if [ "$size_mb" -gt 950 ]; then - warn_text=":warning: The gh-pages branch is approximately '${size_mb}'MB. Published docs might look incorrect." - else - warn_text="" - fi - echo "warn_text=$warn_text" >> "$GITHUB_OUTPUT" - - - name: Find Comment - if: ${{ github.event_name == 'pull_request_target' }} - uses: peter-evans/find-comment@v3 - id: fc - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: The created documentation from the pull request - - - name: Comment on PR with docs URL - if: ${{ github.event_name == 'pull_request_target' && inputs.create_comment == 'true' && steps.fc.outputs.comment-id == '' }} - uses: peter-evans/create-or-update-comment@v4 - with: - issue-number: ${{github.event.pull_request.number}} - body: | - The created documentation from the pull request is available at: [docu-html](https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/${{steps.calc.outputs.target_folder}}/) - ${{ steps.ghpages_size.outputs.warn_text }} - reactions: rocket diff --git a/.github/scripts/check_doc_tool_version.py b/.github/scripts/check_doc_tool_version.py deleted file mode 100644 index 77681ce4e17..00000000000 --- a/.github/scripts/check_doc_tool_version.py +++ /dev/null @@ -1,79 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -#!/usr/bin/env python3 -"""Docs-as-Code version consistency checker.""" - -import argparse -import re -import sys -from pathlib import Path - -def main(): - parser = argparse.ArgumentParser(description="Check Doc-as-Code version consistency") - parser.add_argument( - "--doc", - type=Path, - help="Path to the documentation file (default: docs/score_tools/doc_as_code.rst)" - ) - parser.add_argument( - "--dac-module-name", - default="score_docs_as_code", - help="Module name to search for in MODULE.bazel (default: score_docs_as_code)" - ) - args = parser.parse_args() - - ROOT = Path(__file__).resolve().parents[2] - MODULE = ROOT / "MODULE.bazel" - DOC = args.doc if args.doc else ROOT / "docs/score_tools/doc_as_code.rst" - - if not DOC.exists() or not MODULE.exists(): - raise SystemExit(f"Missing {DOC} or {MODULE}. Nothing to compare.") - - # Parse MODULE.bazel - module_bazel = MODULE.read_text(encoding="utf-8") - module_bazel_match = re.search( - rf'bazel_dep\(\s*name\s*=\s*"{re.escape(args.dac_module_name)}",\s*version\s*=\s*"([^"\s]+)"', - module_bazel, - ) - module_bazel_version = module_bazel_match.group(1) if module_bazel_match else "" - - # Parse doc_as_code.rst - doc_as_code_rst = DOC.read_text(encoding="utf-8") - doc_match = re.search(r':version:\s*(\S+)', doc_as_code_rst) - doc_version_raw = doc_match.group(1) if doc_match else "" - doc_version = doc_version_raw.lstrip("vV") if doc_version_raw else "" - - # Compare versions - mismatch = not module_bazel_version or not doc_version or module_bazel_version != doc_version - - comment = "" - if mismatch: - comment = "\n".join( - [ - "Warning: Doc-as-Code version mismatch detected.", - "", - f"- MODULE.bazel version: {module_bazel_version or '(not found)'}", - f"- doc_as_code.rst :version:: {doc_version_raw or '(not found)'}", - "", - "Please align the documentation with the Bazel dependency.", - ] - ) - - if comment: - print(comment) - - sys.exit(1 if mismatch else 0) - -if __name__ == "__main__": - main() diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml new file mode 100644 index 00000000000..4903153bfed --- /dev/null +++ b/.github/workflows/daily.yml @@ -0,0 +1,40 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +name: Daily Maintenance + +permissions: + contents: write + issues: write + pull-requests: write + pages: write + id-token: write + +on: + # Runs every day at midnight UTC + schedule: + - cron: '0 0 * * *' + + # On changes to this workflow file + pull_request: + branches: + - main + paths: + - '.github/workflows/daily.yml' + + # Manually trigger the workflow from the GitHub UI + workflow_dispatch: {} + +jobs: + maintenance: + uses: eclipse-score/cicd-workflows/.github/workflows/daily.yml@829b3e11ccbf924a5782f7bfed647cb1619fdf78 # v0.0.1 diff --git a/.github/workflows/docs-cleanup.yml b/.github/workflows/docs-cleanup.yml deleted file mode 100644 index 0567955285c..00000000000 --- a/.github/workflows/docs-cleanup.yml +++ /dev/null @@ -1,129 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: Daily Documentation Cleanup -on: - schedule: - - cron: "0 0 * * *" # Runs every day at midnight UTC - workflow_dispatch: # Allows manual trigger - -jobs: - docs-cleanup: - name: Cleanup old documentation - runs-on: ubuntu-latest - permissions: - pages: write - contents: write - pull-requests: write - issues: write # Allow label creation - steps: - - name: Close abandoned feature-request PRs - uses: actions/stale@v10 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - only-pr-labels: feature_request - days-before-issue-stale: -1 # Override days-before-stale for issues only - days-before-issue-close: -1 # Override days-before-close for issues only - days-before-pr-stale: 90 # 30 days idle → stale - days-before-pr-close: 10 # close countdown - stale-pr-message: > - This feature request has been idle for 90 days. We'll auto-close it in 10 days unless there's new activity. - close-pr-label: autoclosed - operations-per-run: 90 - close-pr-message: "Auto-closing: no activity for 90 days. If this is still relevant, reopen or create a fresh PR." - - - name: Close abandoned PRs - uses: actions/stale@v10 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - exempt-pr-labels: feature_request - - # days-before-stale Idle number of days before marking issues/PRs stale (default: 60) - # days-before-close Idle number of days before closing stale issues/PRs (default: 7) - - # Don't handle issues - # If set to a negative number like -1, the issues or the pull requests will never be closed automatically. - days-before-issue-stale: -1 # Override days-before-stale for issues only - days-before-issue-close: -1 # Override days-before-close for issues only - - # Handle PRs - # Mark as stale after 30 days of inactivity and then for stale PRs close after 10 days - days-before-pr-stale: 30 # Override days-before-stale for PRs only - days-before-pr-close: 10 # Override days-before-close for PRs only - - # Add message to the PR when it is stale - stale-pr-message: > - This PR is stale because it has been open for 30 days with no activity. It will be closed in 10 days if no further activity occurs. #magic___^_^___line - # Label the PR as autoclosed - close-pr-label: autoclosed - operations-per-run: 90 # Increase the number of operations per run - - # Add comment to the PR when it is closed - close-pr-message: "Auto-closing: no activity for 30 days. If this is still relevant, reopen or create a fresh PR." - - - name: Checkout gh-pages branch - uses: actions/checkout@v6 - with: - repository: ${{ github.repository }} - ref: gh-pages - fetch-depth: 0 - - - name: Cleanup old documentation - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh auth status # Verify authentication - - # Fetch list of active branches - ACTIVE_BRANCHES=$(gh api --paginate repos/${{ github.repository }}/branches --jq '.[].name') - - # Fetch list of open PRs - OPEN_PRS=$(gh api --paginate repos/${{ github.repository }}/pulls --jq '.[].number' | sed 's/^/pr-/') - - # Combine active branches and PRs into one list - VALID_ENTRIES=$(echo -e "$ACTIVE_BRANCHES\n$OPEN_PRS") - - # Get current folder names, excluding hidden folders - CURRENT_FOLDERS=$(find . -maxdepth 1 -type d -not -name '.' -not -path './.*' -exec basename {} \;) - - # Read versions.json - if [[ -f versions.json ]]; then - jq '.' versions.json > versions_tmp.json - else - echo "[]" > versions_tmp.json - fi - - # Remove outdated folders and update versions.json - for FOLDER in $CURRENT_FOLDERS; do - if ! echo "$VALID_ENTRIES" | grep -Fxq "$FOLDER"; then - echo "Removing $FOLDER" - rm -rf "$FOLDER" - jq --arg ver "$FOLDER" 'map(select(.version != $ver))' versions_tmp.json > tmp.json && mv tmp.json versions_tmp.json - fi - done - - # Remove versions.json entries without corresponding folders - jq '[.[] | select((.version | IN($folders[])))]' --argjson folders "$(ls -1 | jq -R -s -c 'split("\n")[:-1]')" versions_tmp.json > versions_tmp_clean.json - - # Ensure "main" is the first entry and others sorted alphabetically - jq '[.[] | select(.version != "main")] | sort_by(.version) | [{"version": "main", "url": "https://eclipse-score.github.io/score/main/"}] + .' versions_tmp_clean.json > versions.json - - # Clean up temp files - rm versions_tmp.json versions_tmp_clean.json - - - name: Commit and Push Changes - uses: JamesIves/github-pages-deploy-action@v4 - with: - branch: gh-pages - folder: . - commit-message: "Daily cleanup of outdated documentation" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8e003563434..3b9bc299ba5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -12,6 +12,13 @@ # ******************************************************************************* name: Documentation + +permissions: + contents: write + pages: write + pull-requests: write + id-token: write + on: pull_request_target: types: [opened, reopened, synchronize] # Handles forked PRs @@ -25,127 +32,12 @@ on: jobs: docs-build: - name: Build documentation - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - # ------------------------------------------------------------------------------ - # Checkout the correct branch safely in all scenarios (PRs, forks, merges) - # ------------------------------------------------------------------------------ - # | Condition | Event Type | Checked Out Branch | - # |----------------------------------------|--------------------|-----------------------| - # | github.head_ref | pull_request_target | PR branch (source branch) | - # | github.event.pull_request.head.ref | pull_request | PR branch (source branch) | - # | github.ref | push, merge_group | The branch being pushed/merged | - # ------------------------------------------------------------------------------ - # ------------------------------------------------------------------------------ - # Checkout the correct repository safely in all scenarios (PRs, forks, merges) - # ------------------------------------------------------------------------------ - # | Condition | Event Type | Checked Out Repository | - # |------------------------------------------------|--------------------|----------------------------------| - # | github.event.pull_request.head.repo.full_name | pull_request | Forked repository (if PR is from a fork) | - # | github.repository | push, merge_group | Default repository (same repo PRs, merges, pushes) | - - name: Checkout repository (Handle all events) - uses: actions/checkout@v6 - with: - ref: ${{ github.head_ref || github.event.pull_request.head.ref || github.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - - - name: Verify Doc-as-Code version - if: ${{ github.event_name == 'pull_request_target' }} - id: doc_version - run: | - if python3 .github/scripts/check_doc_tool_version.py \ - --doc docs/score_tools/doc_as_code.rst \ - --dac-module-name score_docs_as_code - then - echo "Doc-as-Code version matching. Everything is fine." - echo "mismatch=False" >> "$GITHUB_OUTPUT" - else - echo "mismatch=True" >> "$GITHUB_OUTPUT" - fi - - - name: Find Comment - if: ${{ github.event_name == 'pull_request_target' }} - uses: peter-evans/find-comment@v4 - id: fc - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: Docs-as-Code version mismatch detected - - - name: Warn in PR if docs-as-code version mismatch - if: ${{ github.event_name == 'pull_request_target' && steps.doc_version.outputs.mismatch == 'True' && steps.fc.outputs.comment-id == '' }} - uses: peter-evans/create-or-update-comment@v5 - with: - issue-number: ${{github.event.pull_request.number}} - body: | - ⚠️ **Docs-as-Code version mismatch detected** - Please check the CI build logs for details and align the documentation version with the Bazel dependency. - - - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.19.0 - - name: Install Graphviz - run: sudo apt update && sudo apt install -y graphviz - - name: Build documentation - run: | - bazel run //:docs -- --github_user=${{ github.repository_owner }} --github_repo=${{ github.event.repository.name }} - tar -cf github-pages.tar _build - # ------------------------------------------------------------------------------ - # Generate a unique artifact name to ensure proper tracking in all scenarios - # ------------------------------------------------------------------------------ - # | Condition | Event Type | Artifact Name Value | - # |-----------------------------------------------|------------------------|----------------------------------------------| - # | github.event.pull_request.head.sha | pull_request | PR commit SHA (ensures uniqueness per PR) | - # | github.event.pull_request.head.sha | pull_request_target | PR commit SHA (ensures uniqueness per PR) | - # | github.sha | push, merge_group | Current commit SHA (used for main branch) | - # ------------------------------------------------------------------------------ - - name: Upload artifact for job analysis - uses: actions/upload-artifact@v7.0.1 - with: - name: github-pages-${{ github.event.pull_request.head.sha || github.sha }} - path: github-pages.tar - retention-days: 1 - if-no-files-found: error - - docs-deploy: - name: Deploy documentation to GitHub Pages + uses: eclipse-score/cicd-workflows/.github/workflows/docs.yml@c1c90b1a82a1fab0fc202979dde6686b2162d5a8 # v0.0.0 permissions: - pages: write - id-token: write contents: write + pages: write pull-requests: write - runs-on: ubuntu-latest - needs: docs-build - steps: - # ------------------------------------------------------------------------------ - # Always checks out the BASE repository since pull_request_target is used. - # This ensures that the workflow runs with trusted code from the base repo, - # even when triggered by a pull request from a fork. - # ------------------------------------------------------------------------------ - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Download documentation artifact - uses: actions/download-artifact@v8.0.1 - # ------------------------------------------------------------------------------ - # Generate a unique artifact name to ensure proper tracking in all scenarios - # ------------------------------------------------------------------------------ - # | Condition | Event Type | Artifact Name Value | - # |-----------------------------------------------|------------------------|----------------------------------------------| - # | github.event.pull_request.head.sha | pull_request | PR commit SHA (ensures uniqueness per PR) | - # | github.event.pull_request.head.sha | pull_request_target | PR commit SHA (ensures uniqueness per PR) | - # | github.sha | push, merge_group | Current commit SHA (used for main branch) | - # ------------------------------------------------------------------------------ - with: - name: github-pages-${{ github.event.pull_request.head.sha || github.sha }} - - - name: Untar documentation artifact - run: mkdir -p extracted_docs && tar -xf github-pages.tar -C extracted_docs - - - name: Deploy 🚀 - id: pages-deployment - uses: ./.github/actions/deploy-versioned-pages - with: - source_folder: extracted_docs/_build + id-token: write + with: + bazel-target: "//:docs -- --github_user=${{ github.repository_owner }} --github_repo=${{ github.event.repository.name }}" + retention-days: 3