From 07fed4847f9bfe657b30b0f65dd0fee73a97de0c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Oct 2026 19:35:22 +0000 Subject: [PATCH 1/2] Initial plan From 1c2cdae46fb989b401f6067eb9c2703dc52d1d21 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Oct 2026 19:36:23 +0000 Subject: [PATCH 2/2] Enable docs previews for fork PRs via workflow_run Co-authored-by: MLopez-Ibanez <2620021+MLopez-Ibanez@users.noreply.github.com> --- .github/workflows/R.yml | 50 ++++--------- .github/workflows/docs-preview-cleanup.yml | 9 +-- .github/workflows/docs-preview-deploy.yml | 82 ++++++++++++++++++++++ .github/workflows/python.yml | 50 ++++--------- 4 files changed, 111 insertions(+), 80 deletions(-) create mode 100644 .github/workflows/docs-preview-deploy.yml diff --git a/.github/workflows/R.yml b/.github/workflows/R.yml index 9cff1629e..4ed11f475 100644 --- a/.github/workflows/R.yml +++ b/.github/workflows/R.yml @@ -190,7 +190,6 @@ jobs: needs: R-CMD-check permissions: contents: write # github-pages-deploy-action - pull-requests: write # post PR preview comment concurrency: # Recommended if you intend to make multiple deployments in quick succession. group: web-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} runs-on: ${{ matrix.os }} @@ -234,45 +233,20 @@ jobs: single-commit: true clean: true - - name: Deploy PR preview šŸš€ - if: success() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository - uses: JamesIves/github-pages-deploy-action@v4 - with: - folder: r/docs - target-folder: previews/pr-${{ github.event.pull_request.number }}/r - single-commit: true - clean: true + - name: Stage PR preview + if: github.event_name == 'pull_request' + run: | + mkdir -p preview + cp -r r/docs preview/site + echo "${{ github.event.pull_request.number }}" > preview/pr-number - - name: Comment PR preview link - if: success() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository - uses: actions/github-script@v9 + - name: Upload PR preview + if: github.event_name == 'pull_request' + uses: actions/upload-artifact@v6 with: - script: | - const marker = ''; - const prNumber = context.issue.number; - const url = `https://multi-objective.github.io/moocore/previews/pr-${prNumber}/r/`; - const body = `${marker}\nšŸ“š [**R docs preview** for this PR](${url})\n\n`; - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - }); - const existing = comments.data.find(c => c.body.includes(marker)); - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body, - }); - } + name: docs-preview-r + path: preview/ + retention-days: 3 rchk: needs: pkgdown diff --git a/.github/workflows/docs-preview-cleanup.yml b/.github/workflows/docs-preview-cleanup.yml index 5ad60598d..9c9dc002a 100644 --- a/.github/workflows/docs-preview-cleanup.yml +++ b/.github/workflows/docs-preview-cleanup.yml @@ -1,7 +1,9 @@ name: Cleanup docs preview on: - pull_request: + # pull_request_target runs from the base repo with a write token, also for + # fork PRs. It is safe here because no PR code is checked out or executed. + pull_request_target: types: [closed] permissions: @@ -11,16 +13,15 @@ jobs: cleanup: name: Remove docs preview for closed PR runs-on: ubuntu-latest - # Only run for same-repo PRs; fork PRs never get a preview deployed - if: github.event.pull_request.head.repo.full_name == github.repository steps: - uses: actions/checkout@v6 with: ref: gh-pages - name: Remove preview folder + env: + PR_NUMBER: ${{ github.event.pull_request.number }} run: | - PR_NUMBER="${{ github.event.pull_request.number }}" PREVIEW_DIR="previews/pr-${PR_NUMBER}" if [ -d "${PREVIEW_DIR}" ]; then git rm -rf "${PREVIEW_DIR}" diff --git a/.github/workflows/docs-preview-deploy.yml b/.github/workflows/docs-preview-deploy.yml new file mode 100644 index 000000000..c00ee86e3 --- /dev/null +++ b/.github/workflows/docs-preview-deploy.yml @@ -0,0 +1,82 @@ +name: Deploy docs preview + +# Runs from the default branch with a write token. It only handles the static +# artifact built by the unprivileged Python/R workflows and never checks out +# or executes PR code. +on: + workflow_run: + workflows: [Python, R] + types: [completed] + +permissions: {} + +jobs: + deploy: + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write + actions: read # download artifact from the triggering run + concurrency: + group: gh-pages-deploy + cancel-in-progress: false + steps: + - name: Select docs + id: docs + env: + WORKFLOW: ${{ github.event.workflow_run.name }} + run: | + case "${WORKFLOW}" in + Python) echo "sub=python" >> "$GITHUB_OUTPUT" ;; + R) echo "sub=r" >> "$GITHUB_OUTPUT" ;; + *) exit 1 ;; + esac + + - uses: actions/download-artifact@v8 + with: + name: docs-preview-${{ steps.docs.outputs.sub }} + path: artifact + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Validate artifact + id: pr + run: | + n=$(head -c 20 artifact/pr-number | tr -d '[:space:]') + [[ "$n" =~ ^[0-9]+$ ]] || { echo "Invalid PR number"; exit 1; } + [ -f artifact/site/index.html ] || { echo "Missing index.html"; exit 1; } + if find artifact/site -type l | grep -q .; then echo "Symlinks not allowed"; exit 1; fi + rm -rf artifact/site/.git artifact/site/.github + echo "number=$n" >> "$GITHUB_OUTPUT" + + - name: Deploy PR preview šŸš€ + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: artifact/site + target-folder: previews/pr-${{ steps.pr.outputs.number }}/${{ steps.docs.outputs.sub }} + single-commit: true + clean: true + + - name: Comment PR preview link + uses: actions/github-script@v9 + env: + PR_NUMBER: ${{ steps.pr.outputs.number }} + SUB: ${{ steps.docs.outputs.sub }} + with: + script: | + const prNumber = Number(process.env.PR_NUMBER); + const sub = process.env.SUB; + const label = sub === 'r' ? 'R' : 'Python'; + const marker = ``; + const url = `https://multi-objective.github.io/moocore/previews/pr-${prNumber}/${sub}/`; + const body = `${marker}\nšŸ“š [**${label} docs preview** for this PR](${url})\n\n`; + const {owner, repo} = context.repo; + const comments = await github.paginate(github.rest.issues.listComments, {owner, repo, issue_number: prNumber}); + const existing = comments.find(c => c.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({owner, repo, comment_id: existing.id, body}); + } else { + await github.rest.issues.createComment({owner, repo, issue_number: prNumber, body}); + } diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index e377a3372..009448c1c 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -116,7 +116,6 @@ jobs: if: "! contains(github.event.head_commit.message, '[skip ci]')" permissions: contents: write - pull-requests: write # post PR preview comment concurrency: # Recommended if you intend to make multiple deployments in quick succession. group: web-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} timeout-minutes: 10 @@ -165,45 +164,20 @@ jobs: single-commit: true clean: true - - name: Deploy PR preview šŸš€ - if: success() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository - uses: JamesIves/github-pages-deploy-action@v4 - with: - folder: python/doc/_build/html/ - target-folder: previews/pr-${{ github.event.pull_request.number }}/python - single-commit: true - clean: true + - name: Stage PR preview + if: github.event_name == 'pull_request' + run: | + mkdir -p preview + cp -r python/doc/_build/html preview/site + echo "${{ github.event.pull_request.number }}" > preview/pr-number - - name: Comment PR preview link - if: success() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository - uses: actions/github-script@v9 + - name: Upload PR preview + if: github.event_name == 'pull_request' + uses: actions/upload-artifact@v6 with: - script: | - const marker = ''; - const prNumber = context.issue.number; - const url = `https://multi-objective.github.io/moocore/previews/pr-${prNumber}/python/`; - const body = `${marker}\nšŸ“š [**Python docs preview** for this PR](${url})\n\n`; - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - }); - const existing = comments.data.find(c => c.body.includes(marker)); - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body, - }); - } + name: docs-preview-python + path: preview/ + retention-days: 3 coverage: timeout-minutes: 15