diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 00000000000..ddbcb764dcf --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,516 @@ +# Reusable workflow: build + stage the documentation for a single branch. +# +# Called by: +# - sphinxbuild.yml (PR / push CI — builds the current ref) +# - deploy-docs.yml (scheduled deploy — builds every version via a matrix) +# +# It builds HTML / ePub / PDF for all manuals, stages them into a per-version +# folder and uploads a single `staged-docs-` artifact. It does NOT +# touch gh-pages — deployment lives in deploy-docs.yml. +name: "Build documentation (reusable)" + +on: + workflow_call: + inputs: + branch: + description: "Logical branch used for version detection and deploy folder mapping (e.g. master, stable34)." + required: true + type: string + ref: + description: "Git ref to check out and build. Empty = default checkout (the caller's event ref, e.g. a PR merge)." + required: false + type: string + default: "" + outputs: + branch_name: + description: "Primary deploy folder name (latest / stable / )." + value: ${{ jobs.stage-and-check.outputs.branch_name }} + additional_deployment: + description: "Extra deploy folder for the highest stable (), else empty." + value: ${{ jobs.stage-and-check.outputs.additional_deployment }} + artifact_name: + description: "Name of the uploaded staged-docs artifact." + value: ${{ jobs.stage-and-check.outputs.artifact_name }} + +permissions: + contents: read + packages: write + +jobs: + # ============================================================================ + # BUILD HTML + # ============================================================================ + build-html: + name: Building ${{ matrix.manual.name }} HTML + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + manual: + - name: "user_manual" + directory: "user_manual" + make_target: "html" + build_path: "_build/html" + publish: true + + - name: "user_manual-en" + directory: "user_manual" + make_target: "html-lang-en" + build_path: "_build/html" + publish: false + + - name: "developer_manual" + directory: "developer_manual" + make_target: "html" + build_path: "_build/html/com" + publish: true + + - name: "admin_manual" + directory: "admin_manual" + make_target: "html" + build_path: "_build/html/com" + publish: true + + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.ref }} + + - name: Get stable branches + id: stable_branches + run: | + branches=$(git ls-remote --heads origin "heads/stable[0-9][0-9]" \ + | awk '{gsub(/^refs\/heads\/stable/, "", $2); print $2}' \ + | sort -n -r | tr '\n' ' ') + echo "branches=$branches" >> $GITHUB_OUTPUT + + - name: Setup PHP for version detection + uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # v2.40.0 + + - name: Detect and export version constants + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Detect current highest released stable and lowest supported stable via + # GitHub API (same logic as build/build-index.php and generate-top-index.yml). + # Exports DOCS_VERSION_STABLE and DOCS_VERSION_START so conf.py always has + # accurate values without manual updates to any branch. + eval $(php build/detect-versions.php ${{ steps.stable_branches.outputs.branches }}) + echo "DOCS_VERSION_STABLE=$highest_stable" >> $GITHUB_ENV + echo "DOCS_VERSION_START=$lowest_stable" >> $GITHUB_ENV + + # Compute display version: branch number for stableNN builds, dev version for master. + branch="${{ inputs.branch }}" + if [[ "$branch" =~ ^stable([0-9]+)$ ]]; then + echo "DOCS_DISPLAY_VERSION=${BASH_REMATCH[1]}" >> $GITHUB_ENV + else + echo "DOCS_DISPLAY_VERSION=$((highest_stable + 1))" >> $GITHUB_ENV + fi + + - name: Validate conf.py fallbacks are current + if: inputs.branch == 'master' + run: | + conf_stable=$(grep -m1 "os.environ.get('DOCS_VERSION_STABLE'" conf.py | grep -o '[0-9]\+') + conf_start=$(grep -m1 "os.environ.get('DOCS_VERSION_START'" conf.py | grep -o '[0-9]\+') + err=0 + if [ "$DOCS_VERSION_STABLE" != "$conf_stable" ]; then + echo "::error::conf.py fallback version_stable ($conf_stable) != current highest stable ($DOCS_VERSION_STABLE). Update the fallback in conf.py." + err=1 + fi + if [ "$DOCS_VERSION_START" != "$conf_start" ]; then + echo "::error::conf.py fallback version_start ($conf_start) != current lowest stable ($DOCS_VERSION_START). Update the fallback in conf.py." + err=1 + fi + exit $err + + - name: Resolve Python version + id: pyver + run: | + # Read the version from the checked-out branch (ref input) so each + # version builds with its own Python; fall back to 3.13 if absent. + if [ -f .python-version ]; then + echo "version=$(tr -d '[:space:]' < .python-version)" >> "$GITHUB_OUTPUT" + else + echo "version=3.13" >> "$GITHUB_OUTPUT" + fi + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ steps.pyver.outputs.version }} + cache: "pip" + + - name: Install pip dependencies + run: python -m pip install -r requirements.txt + + - name: Build html documentation + run: cd ${{ matrix.manual.directory }} && make ${{ matrix.manual.make_target }} + + - name: Upload static documentation + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + if: ${{ matrix.manual.publish }} + with: + # Namespaced by branch so several versions can build in one run (the + # deploy matrix) without artifact-name collisions. Prefix is stripped + # again in the stage-and-check "organize" step. + name: ${{ inputs.branch }}__${{ matrix.manual.name }} + path: ${{ matrix.manual.directory }}/${{ matrix.manual.build_path }} + + # ============================================================================ + # BUILD EPUB + # ============================================================================ + build-epub: + name: Building ${{ matrix.manual.name }} ePub + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + manual: + - name: "user_manual" + directory: "user_manual" + build_epub_path: "_build/epub/Nextcloud_User_Manual.epub" + + - name: "admin_manual" + directory: "admin_manual" + build_epub_path: "_build/epub/Nextcloud_Server_Administration_Manual.epub" + + - name: "developer_manual" + directory: "developer_manual" + build_epub_path: "_build/epub/Nextcloud_Developer_Manual.epub" + + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.ref }} + + - name: Resolve Python version + id: pyver + run: | + if [ -f .python-version ]; then + echo "version=$(tr -d '[:space:]' < .python-version)" >> "$GITHUB_OUTPUT" + else + echo "version=3.13" >> "$GITHUB_OUTPUT" + fi + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ steps.pyver.outputs.version }} + cache: "pip" + + - name: Install pip dependencies + run: python -m pip install -r requirements.txt + + - name: Build epub documentation + run: | + set -e + # HTML sets DOCS_DISPLAY_VERSION via detect-versions and PDF via DOCS_RELEASE; + # the epub job needs it too so stable branches render their real version. + if [[ "${{ inputs.branch }}" =~ ^stable([0-9]+)$ ]]; then + export DOCS_DISPLAY_VERSION="${BASH_REMATCH[1]}" + fi + cd ${{ matrix.manual.directory }} + make epub + ls -la ${{ matrix.manual.build_epub_path }} + + - name: Upload ePub documentation + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: ${{ inputs.branch }}__${{ matrix.manual.name }}-epub + path: ${{ matrix.manual.directory }}/${{ matrix.manual.build_epub_path }} + + # ============================================================================ + # PREPARE PDF IMAGE + # ============================================================================ + prepare-pdf-image: + name: Prepare PDF build image + runs-on: ubuntu-latest + + outputs: + image: ${{ steps.result.outputs.image }} + + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.ref }} + fetch-depth: 2 + + - name: Check whether Dockerfile changed + id: changed + run: | + if git diff --name-only HEAD^ HEAD -- .devcontainer/Dockerfile 2>/dev/null | grep -q .; then + echo "dockerfile_changed=true" >> $GITHUB_OUTPUT + else + echo "dockerfile_changed=false" >> $GITHUB_OUTPUT + fi + + - name: Set up Docker Buildx + if: steps.changed.outputs.dockerfile_changed == 'true' + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - name: Login to GitHub Container Registry + if: steps.changed.outputs.dockerfile_changed == 'true' + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image with SHA tag + if: steps.changed.outputs.dockerfile_changed == 'true' + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: .devcontainer + push: true + tags: ghcr.io/${{ github.repository }}/sphinx-latex:sha-${{ github.sha }} + cache-from: type=registry,ref=ghcr.io/${{ github.repository }}/sphinx-latex:latest + cache-to: type=inline + + - name: Output image reference + id: result + run: | + if [ "${{ steps.changed.outputs.dockerfile_changed }}" = "true" ]; then + echo "image=ghcr.io/${{ github.repository }}/sphinx-latex:sha-${{ github.sha }}" >> $GITHUB_OUTPUT + else + echo "image=ghcr.io/${{ github.repository }}/sphinx-latex:latest" >> $GITHUB_OUTPUT + fi + + # ============================================================================ + # BUILD PDF + # ============================================================================ + build-pdf: + name: Building ${{ matrix.manual.name }} PDF + runs-on: ubuntu-latest + needs: prepare-pdf-image + container: ${{ needs.prepare-pdf-image.outputs.image }} + + strategy: + fail-fast: false + matrix: + manual: + - name: "user_manual" + directory: "user_manual" + build_pdf_path: "_build/latex/Nextcloud_User_Manual.pdf" + + - name: "admin_manual" + directory: "admin_manual" + build_pdf_path: "_build/latex/Nextcloud_Server_Administration_Manual.pdf" + + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.ref }} + + - name: Resolve Python version + id: pyver + run: | + if [ -f .python-version ]; then + echo "version=$(tr -d '[:space:]' < .python-version)" >> "$GITHUB_OUTPUT" + else + echo "version=3.13" >> "$GITHUB_OUTPUT" + fi + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ steps.pyver.outputs.version }} + # pip cache is not compatible with the Docker container + # cache: "pip" + + - name: Install pip dependencies + run: python -m pip install -r requirements.txt + + - name: Compute PDF release version + id: pdf_version + shell: bash + run: | + branch="${{ inputs.branch }}" + if [[ "$branch" =~ ^stable([0-9]+)$ ]]; then + echo "release=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT + else + # master: derive the dev version from conf.py. + version_stable=$(grep -m1 '^\s*version_stable\s*=' conf.py | grep -o '[0-9]\+') + echo "release=$((version_stable + 1))" >> $GITHUB_OUTPUT + fi + + - name: Build pdf documentation + env: + DOCS_RELEASE: ${{ steps.pdf_version.outputs.release }} + run: | + set -e + cd ${{ matrix.manual.directory }} + make latexpdf + ls -la ${{ matrix.manual.build_pdf_path }} + + - name: Upload PDF documentation + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: ${{ inputs.branch }}__${{ matrix.manual.name }}-pdf + path: ${{ matrix.manual.directory }}/${{ matrix.manual.build_pdf_path }} + + # ============================================================================ + # STAGE AND VALIDATE + # ============================================================================ + # Organizes build artifacts into a per-version folder and uploads a single + # staged-docs- artifact for the deploy job. Does NOT modify gh-pages. + # ============================================================================ + stage-and-check: + name: Stage and check documentation + needs: [build-html, build-pdf, build-epub] + runs-on: ubuntu-latest + + outputs: + branch_name: ${{ steps.branch.outputs.branch_name }} + additional_deployment: ${{ steps.branch.outputs.additional_deployment }} + artifact_name: ${{ steps.branch.outputs.artifact_name }} + + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.ref }} + + - name: Download this branch's build artifacts + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + # Only this version's artifacts — other matrix legs use a different prefix. + pattern: ${{ inputs.branch }}__* + path: artifacts/ + + # ======================================================================== + # DETERMINE DEPLOYMENT TARGETS (branch_name and version_name) + # ======================================================================== + # master → branch_name=latest (no version_name) + # stable where N is highest → branch_name=stable, additional= + # stable where N is not highest → branch_name= + # Any other branch → branch_name= + # ======================================================================== + - name: Determine deployment targets (branch_name and version_name) + id: branch + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Logical branch to deploy is passed in explicitly by the caller. + current_branch="${{ inputs.branch }}" + + # Find the highest numbered stable branch that has a v{N}.0.0 release. + # Mirrors the is_version_released() check in build/build-index.php: + # a branch whose first release doesn't exist yet (e.g. RC-only) is skipped. + highest_stable="" + for n in $(git ls-remote --heads origin | sed -n 's?.*refs/heads/stable\([0-9]\{2\}\)$?\1?p' | sort -n -r); do + # Nextcloud moved to nextcloud-releases/server starting with v32 + if [ "$n" -ge 32 ]; then + repo="nextcloud-releases/server" + else + repo="nextcloud/server" + fi + http_status=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + "https://api.github.com/repos/${repo}/releases/tags/v${n}.0.0") + if [ "$http_status" = "200" ]; then + highest_stable="$n" + echo "stable${n}: v${n}.0.0 released — using as highest stable" + break + else + echo "stable${n}: v${n}.0.0 not released yet (HTTP ${http_status}) — skipping" + fi + done + + if [ -z "$highest_stable" ]; then + echo "ERROR: No released stable branch found" + exit 1 + fi + highest_stable_branch="stable${highest_stable}" + + echo "Current branch: $current_branch" + echo "Highest released stable branch: $highest_stable_branch" + + # Map branch to deployment folder names + case "$current_branch" in + "master") + echo "branch_name=latest" >> $GITHUB_OUTPUT + ;; + "$highest_stable_branch") + echo "branch_name=stable" >> $GITHUB_OUTPUT + echo "additional_deployment=${highest_stable}" >> $GITHUB_OUTPUT + ;; + *) + branch_for_deploy="${current_branch#stable}" + echo "branch_name=$branch_for_deploy" >> $GITHUB_OUTPUT + ;; + esac + + # Artifact name is unique per version so the deploy job can download + # every version's staged output together (pattern staged-docs-*). + echo "artifact_name=staged-docs-${current_branch}" >> $GITHUB_OUTPUT + + - name: Log deployment targets + run: | + echo "Deployment target folder: ${{ steps.branch.outputs.branch_name }}" + echo "Additional deployment folder (if applicable): ${{ steps.branch.outputs.additional_deployment }}" + echo "Artifact name: ${{ steps.branch.outputs.artifact_name }}" + + # ======================================================================== + # ORGANIZE ARTIFACTS FOR DEPLOYMENT + # ======================================================================== + # Stage into stage//. When this is the highest stable branch, + # ALSO stage into stage// so a single artifact carries both the + # server/stable/ and server// deployments and the deploy job can stay + # version-agnostic (it just applies every folder it finds). + # ======================================================================== + - name: Organize artifacts for deployment + id: organize + run: | + branch="${{ steps.branch.outputs.branch_name }}" + additional="${{ steps.branch.outputs.additional_deployment }}" + + mkdir -p "stage/${branch}" + + # Artifacts are named "__[-epub|-pdf]"; strip that + # prefix so the deployed folders are just user_manual/admin_manual/etc. + prefix="${{ inputs.branch }}__" + + # Copy artifacts preserving their manual folder structure + for artifact in artifacts/*; do + if [ -d "$artifact" ]; then + manual_name="$(basename "$artifact")" + manual_name="${manual_name#$prefix}" + mkdir -p "stage/${branch}/${manual_name}" + cp -r "$artifact/"* "stage/${branch}/${manual_name}/" + fi + done + + # Move PDF and ePub files to the root of the branch folder + echo "Looking for PDF and ePub files to move..." + find "stage/${branch}/" -maxdepth 2 \( -name "*.pdf" -o -name "*.epub" \) -type f + for f in "stage/${branch}"/*/*.pdf "stage/${branch}"/*/*.epub; do + if [ -f "$f" ]; then + echo "Moving: $f" + mv "$f" "stage/${branch}/" + fi + done + + # Duplicate into the additional versioned folder for the highest stable. + if [ -n "${additional}" ]; then + rm -rf "stage/${additional}" + cp -r "stage/${branch}" "stage/${additional}" + fi + + # Clean up empty directories + find stage -type d -empty -delete + + echo "Staged artifacts:" + find stage -type f | head -20 + + - name: Upload staged artifacts + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: ${{ steps.branch.outputs.artifact_name }} + path: stage/ + retention-days: 1 diff --git a/.github/workflows/schedule-builds.yml b/.github/workflows/schedule-builds.yml deleted file mode 100644 index 572a5d305a7..00000000000 --- a/.github/workflows/schedule-builds.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: "Schedule documentation builds" - -on: - schedule: - - cron: '0 2 * * *' # 02:00 UTC daily — triggers sphinxbuild on master + all stable* branches - workflow_dispatch: - -permissions: - actions: write - -jobs: - dispatch: - name: Dispatch sphinxbuild on all branches - runs-on: ubuntu-latest - - steps: - - name: Dispatch on master and stable branches - env: - GH_TOKEN: ${{ github.token }} - run: | - set -e - for branch in master $(gh api "repos/${{ github.repository }}/branches" --paginate \ - --jq '.[].name | select(startswith("stable"))'); do - echo "Dispatching sphinxbuild.yml on ${branch}" - gh workflow run sphinxbuild.yml --repo "${{ github.repository }}" --ref "${branch}" - done diff --git a/.github/workflows/sphinxbuild.yml b/.github/workflows/sphinxbuild.yml index 62d1a21946c..c5f29012599 100644 --- a/.github/workflows/sphinxbuild.yml +++ b/.github/workflows/sphinxbuild.yml @@ -6,514 +6,49 @@ on: branches: - master - stable* - workflow_dispatch: - -permissions: - contents: read - packages: write concurrency: group: build-documentation-${{ github.head_ref || github.ref }} cancel-in-progress: true -jobs: - # ============================================================================ - # BUILD HTML - # ============================================================================ - # Builds the HTML documentation for all manuals. No LaTeX required. - # Starts immediately without waiting for any setup job. - # ============================================================================ - build-html: - name: Building ${{ matrix.manual.name }} HTML - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - manual: - - name: "user_manual" - directory: "user_manual" - make_target: "html" - build_path: "_build/html" - publish: true - - - name: "user_manual-en" - directory: "user_manual" - make_target: "html-lang-en" - build_path: "_build/html" - publish: false - - - name: "developer_manual" - directory: "developer_manual" - make_target: "html" - build_path: "_build/html/com" - publish: true - - - name: "admin_manual" - directory: "admin_manual" - make_target: "html" - build_path: "_build/html/com" - publish: true - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Validate version constants in conf.py - if: github.ref == 'refs/heads/master' || github.base_ref == 'master' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # version_stable: highest stableNN branch whose vN.0.0 release actually exists. - # Mirrors the is_version_released() check in build/build-index.php and stage-and-check. - # A branch that exists but has no release yet (e.g. RC phase) must be skipped. - highest_stable="" - for n in $(git ls-remote --heads origin \ - | sed -n 's?.*refs/heads/stable\([0-9]\{2\}\)$?\1?p' | sort -n -r); do - repo=$( [ "$n" -ge 32 ] && echo "nextcloud-releases/server" || echo "nextcloud/server" ) - status=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: token ${GITHUB_TOKEN}" \ - "https://api.github.com/repos/${repo}/releases/tags/v${n}.0.0") - if [ "$status" = "200" ]; then - highest_stable="$n" - break - fi - done - - # version_start: lowest stableNN branch that still exists (branch existence = still documented). - lowest_stable=$(git ls-remote --heads origin \ - | sed -n 's?.*refs/heads/stable\([0-9]\{2\}\)$?\1?p' \ - | sort -n | head -1) - - conf_stable=$(grep -m1 '^\s*version_stable\s*=' conf.py | grep -o '[0-9]\+') - conf_start=$(grep -m1 '^\s*version_start\s*=' conf.py | grep -o '[0-9]\+') - - err=0 - if [ -z "$highest_stable" ]; then - echo "::error::Could not detect any released stable branch." - exit 1 - fi - if [ "$highest_stable" != "$conf_stable" ]; then - echo "::error::version_stable in conf.py ($conf_stable) != highest released stable ($highest_stable). Update conf.py." - err=1 - fi - if [ "$lowest_stable" != "$conf_start" ]; then - echo "::error::version_start in conf.py ($conf_start) != lowest existing stable branch ($lowest_stable). Update conf.py." - err=1 - fi - exit $err - - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.13" - cache: "pip" - - - name: Install pip dependencies - run: python -m pip install -r requirements.txt - - - name: Build html documentation - run: cd ${{ matrix.manual.directory }} && make ${{ matrix.manual.make_target }} - - - name: Upload static documentation - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: ${{ matrix.manual.publish }} - with: - name: ${{ matrix.manual.name }} - path: ${{ matrix.manual.directory }}/${{ matrix.manual.build_path }} - - # ============================================================================ - # BUILD EPUB - # ============================================================================ - # Builds epub documentation for all three manuals. No LaTeX required — - # runs in the same plain Python environment as build-html. - # ============================================================================ - build-epub: - name: Building ${{ matrix.manual.name }} ePub - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - manual: - - name: "user_manual" - directory: "user_manual" - build_epub_path: "_build/epub/Nextcloud_User_Manual.epub" - - - name: "admin_manual" - directory: "admin_manual" - build_epub_path: "_build/epub/Nextcloud_Server_Administration_Manual.epub" - - - name: "developer_manual" - directory: "developer_manual" - build_epub_path: "_build/epub/Nextcloud_Developer_Manual.epub" - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.13" - cache: "pip" - - - name: Install pip dependencies - run: python -m pip install -r requirements.txt - - - name: Build epub documentation - run: | - set -e - cd ${{ matrix.manual.directory }} - make epub - ls -la ${{ matrix.manual.build_epub_path }} - - - name: Upload ePub documentation - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: ${{ matrix.manual.name }}-epub - path: ${{ matrix.manual.directory }}/${{ matrix.manual.build_epub_path }} - - # ============================================================================ - # PREPARE PDF IMAGE - # ============================================================================ - # Detects whether .devcontainer/Dockerfile changed in this run. - # - If it changed: builds a fresh image, pushes it to GHCR with a SHA tag, - # and outputs that tag so build-pdf uses the new image immediately. - # - If it did not change: outputs the stable ghcr.io/.../sphinx-latex:latest - # tag so build-pdf uses the already-published image. - # ============================================================================ - prepare-pdf-image: - name: Prepare PDF build image - runs-on: ubuntu-latest - - outputs: - image: ${{ steps.result.outputs.image }} - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 2 - - - name: Check whether Dockerfile changed - id: changed - run: | - if git diff --name-only HEAD^ HEAD -- .devcontainer/Dockerfile 2>/dev/null | grep -q .; then - echo "dockerfile_changed=true" >> $GITHUB_OUTPUT - else - echo "dockerfile_changed=false" >> $GITHUB_OUTPUT - fi - - - name: Set up Docker Buildx - if: steps.changed.outputs.dockerfile_changed == 'true' - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - - - name: Login to GitHub Container Registry - if: steps.changed.outputs.dockerfile_changed == 'true' - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push image with SHA tag - if: steps.changed.outputs.dockerfile_changed == 'true' - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 - with: - context: .devcontainer - push: true - tags: ghcr.io/${{ github.repository }}/sphinx-latex:sha-${{ github.sha }} - cache-from: type=registry,ref=ghcr.io/${{ github.repository }}/sphinx-latex:latest - cache-to: type=inline - - - name: Output image reference - id: result - run: | - if [ "${{ steps.changed.outputs.dockerfile_changed }}" = "true" ]; then - echo "image=ghcr.io/${{ github.repository }}/sphinx-latex:sha-${{ github.sha }}" >> $GITHUB_OUTPUT - else - echo "image=ghcr.io/${{ github.repository }}/sphinx-latex:latest" >> $GITHUB_OUTPUT - fi - - # ============================================================================ - # BUILD PDF - # ============================================================================ - # Builds the PDF documentation using the pre-built sphinx-latex Docker image. - # The image already contains all LaTeX packages, so no apt install is needed. - # Uses the image prepared by the prepare-pdf-image job: either the stable - # ghcr.io/.../sphinx-latex:latest image or a freshly built SHA-tagged image - # if .devcontainer/Dockerfile changed in this run. - # ============================================================================ - build-pdf: - name: Building ${{ matrix.manual.name }} PDF - runs-on: ubuntu-latest - needs: prepare-pdf-image - # Use the image prepared by prepare-pdf-image: either the stable ghcr image - # or a freshly built SHA-tagged image if .devcontainer/Dockerfile changed. - container: ${{ needs.prepare-pdf-image.outputs.image }} - - strategy: - fail-fast: false - matrix: - manual: - - name: "user_manual" - directory: "user_manual" - build_pdf_path: "_build/latex/Nextcloud_User_Manual.pdf" - - - name: "admin_manual" - directory: "admin_manual" - build_pdf_path: "_build/latex/Nextcloud_Server_Administration_Manual.pdf" - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.13" - # pip cache is not compatible with the Docker container - # cache: "pip" - - - name: Install pip dependencies - run: python -m pip install -r requirements.txt - - - name: Compute PDF release version - id: pdf_version - shell: bash - run: | - # For PRs use the target branch; for pushes use the current branch. - # This handles both stable33 direct pushes and backport/*/stable33 PRs. - branch="${GITHUB_BASE_REF:-${GITHUB_REF#refs/heads/}}" - if [[ "$branch" =~ ^stable([0-9]+)$ ]]; then - echo "release=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT - else - # master: derive the dev version from conf.py. - version_stable=$(grep -m1 '^\s*version_stable\s*=' conf.py | grep -o '[0-9]\+') - echo "release=$((version_stable + 1))" >> $GITHUB_OUTPUT - fi - - - name: Build pdf documentation - env: - DOCS_RELEASE: ${{ steps.pdf_version.outputs.release }} - run: | - set -e - cd ${{ matrix.manual.directory }} - make latexpdf - ls -la ${{ matrix.manual.build_pdf_path }} - - - name: Upload PDF documentation - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: ${{ matrix.manual.name }}-pdf - path: ${{ matrix.manual.directory }}/${{ matrix.manual.build_pdf_path }} +permissions: + contents: read + packages: write +jobs: # ============================================================================ - # STAGE AND VALIDATE + # BUILD + STAGE # ============================================================================ - # This job is responsible for: - # 1. Determining deployment target folder names (branch_name/version_name) - # 2. Organizing build artifacts into a clean structure - # 3. Validating the documentation (link checking) - # 4. Uploading a minimal staging artifact for the deploy job - # - # IMPORTANT: This job does NOT modify gh-pages. It only prepares and validates - # the artifacts that will be deployed. The actual deployment happens in the - # deploy job. + # All build logic lives in the reusable build-docs.yml workflow. For a PR we + # build against the target (base) branch mapping; for a push we use the pushed + # branch. The checkout ref is left empty so the default event ref (PR merge or + # pushed commit) is built. # ============================================================================ - stage-and-check: - name: Stage and check documentation - needs: [build-html, build-pdf, build-epub] - runs-on: ubuntu-latest - - outputs: - # branch_name: The primary deployment folder name for this branch - # - master → "latest" - # - stable (if highest) → "stable" - # - stable (if not highest) → "" (numeric version) - branch_name: ${{ steps.branch.outputs.branch_name }} - - # additional_deployment: ONLY set if deploying the highest stable branch - # - If this IS the highest stable → "" (numeric version, e.g. "32") - # - Otherwise → "" (empty string) - # - # This allows the highest stable to be deployed to TWO locations: - # server/stable/ (via branch_name) - # server// (via additional_deployment) - additional_deployment: ${{ steps.branch.outputs.additional_deployment }} - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Download all artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - path: artifacts/ - - # ======================================================================== - # DETERMINE DEPLOYMENT TARGETS (branch_name and version_name) - # ======================================================================== - # Logic: - # 1. Determine current_branch: use GITHUB_REF if push, GITHUB_BASE_REF if PR - # 2. Find the highest numbered stable branch from git remotes - # 3. Map the current branch to deployment folder names: - # - # master → branch_name=latest (no version_name) - # - # stable where N is highest → branch_name=stable, version_name= - # (deployed to both server/stable/ and server//) - # - # stable where N is not highest → branch_name= (no version_name) - # (deployed only to server//) - # - # Any other branch → branch_name= (no version_name) - # ======================================================================== - - name: Determine deployment targets (branch_name and version_name) - id: branch - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # Determine which branch we're building from - current_branch=${GITHUB_REF#refs/heads/} - if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - current_branch=${GITHUB_BASE_REF} - fi - - # Find the highest numbered stable branch that has a v{N}.0.0 release. - # Mirrors the is_version_released() check in build/build-index.php: - # a branch whose first release doesn't exist yet (e.g. RC-only) is skipped. - highest_stable="" - for n in $(git ls-remote --heads origin | sed -n 's?.*refs/heads/stable\([0-9]\{2\}\)$?\1?p' | sort -n -r); do - # Nextcloud moved to nextcloud-releases/server starting with v32 - if [ "$n" -ge 32 ]; then - repo="nextcloud-releases/server" - else - repo="nextcloud/server" - fi - http_status=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: token ${GITHUB_TOKEN}" \ - "https://api.github.com/repos/${repo}/releases/tags/v${n}.0.0") - if [ "$http_status" = "200" ]; then - highest_stable="$n" - echo "stable${n}: v${n}.0.0 released — using as highest stable" - break - else - echo "stable${n}: v${n}.0.0 not released yet (HTTP ${http_status}) — skipping" - fi - done - - if [ -z "$highest_stable" ]; then - echo "ERROR: No released stable branch found" - exit 1 - fi - highest_stable_branch="stable${highest_stable}" - - echo "Current branch: $current_branch" - echo "Highest released stable branch: $highest_stable_branch" - - # Map branch to deployment folder names - case "$current_branch" in - "master") - # master always deploys to "latest" - echo "branch_name=latest" >> $GITHUB_OUTPUT - ;; - "$highest_stable_branch") - # Highest stable gets TWO locations: both "stable" and "" - echo "branch_name=stable" >> $GITHUB_OUTPUT - echo "additional_deployment=${highest_stable}" >> $GITHUB_OUTPUT - ;; - *) - # Other branches (including older stable branches) get their branch name - # For stable where N is not highest: strip "stable" prefix to get just "" - branch_for_deploy="${current_branch#stable}" - echo "branch_name=$branch_for_deploy" >> $GITHUB_OUTPUT - ;; - esac - - - name: Log deployment targets - run: | - echo "Deployment target folder: ${{ steps.branch.outputs.branch_name }}" - echo "Additional deployment folder (if applicable): ${{ steps.branch.outputs.additional_deployment }}" - - # ======================================================================== - # ORGANIZE ARTIFACTS FOR DEPLOYMENT - # ======================================================================== - # Create a clean, minimal staging structure: - # - Deploy only the NEW artifacts for this branch - # - No need to include existing versions (we'll merge them during deploy) - # ======================================================================== - - name: Organize artifacts for deployment - id: organize - run: | - branch="${{ steps.branch.outputs.branch_name }}" - - # Create the branch folder directly - mkdir -p "stage/${branch}" - - # Copy artifacts preserving their manual folder structure - # Each artifact (user_manual, admin_manual, developer_manual) contains - # the build output that should be placed in a folder named after the artifact - for artifact in artifacts/*; do - if [ -d "$artifact" ]; then - manual_name="$(basename "$artifact")" - # Create the manual-specific folder - mkdir -p "stage/${branch}/${manual_name}" - # Copy artifact contents into the manual folder - cp -r "$artifact/"* "stage/${branch}/${manual_name}/" - fi - done - - # Move PDF and ePub files to the root of the branch folder for cleaner structure - echo "Looking for PDF and ePub files to move..." - find "stage/${branch}/" -maxdepth 2 \( -name "*.pdf" -o -name "*.epub" \) -type f - for f in "stage/${branch}"/*/*.pdf "stage/${branch}"/*/*.epub; do - if [ -f "$f" ]; then - echo "Moving: $f" - mv "$f" "stage/${branch}/" - fi - done - - # Clean up empty directories - find stage -type d -empty -delete - - echo "Staged artifacts for ${branch}:" - find stage -type f | head -20 - - # ======================================================================== - # UPLOAD STAGING ARTIFACTS - # ======================================================================== - # Upload the staging folder for use in both the deploy and link-check jobs. - # ======================================================================== - - name: Upload staged artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: staged-docs - path: stage/ - retention-days: 1 + build: + name: Build and stage + uses: ./.github/workflows/build-docs.yml + with: + branch: ${{ github.base_ref || github.ref_name }} + secrets: inherit # ============================================================================ # LINK CHECK # ============================================================================ - # Runs in parallel with deploy. Downloads the staged artifacts, strips - # canonical links, then runs lychee against the new content only. - # ============================================================================ link-check: name: Check for broken links - needs: stage-and-check + needs: build runs-on: ubuntu-latest steps: - name: Download staged artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: - name: staged-docs + name: ${{ needs.build.outputs.artifact_name }} path: stage/ - name: Strip canonical links from validation HTML run: | - find "stage/${{ needs.stage-and-check.outputs.branch_name }}" -name '*.html' -print0 | while IFS= read -r -d '' f; do + find "stage/${{ needs.build.outputs.branch_name }}" -name '*.html' -print0 | while IFS= read -r -d '' f; do perl -0pi -e 's{^\s*\n}{}m' "$f" done ls -la stage/* @@ -523,8 +58,13 @@ jobs: # - mailto: links: These are not valid URLs and will always fail # - 404.html: This is not necessary # - latest/stable/xx links from the version selector + # - links to a specific numbered version (e.g. docs.nextcloud.com/server/34/...): this + # job only ever builds and stages the branch under test (e.g. "latest" for master), so + # any other version's folder under stage/ doesn't exist here and can never be verified + # locally. Content referencing an older/newer release intentionally links to a pinned + # version and is checked against the live site instead, not this offline build. - name: Check for broken links with lychee - uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0 + uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 with: fail: true token: ${{ secrets.GITHUB_TOKEN }} @@ -532,186 +72,20 @@ jobs: args: | --root-dir "$(pwd)/stage" --offline --no-progress - --remap "https://docs.nextcloud.com/server/latest/ file://$(pwd)/stage/${{ needs.stage-and-check.outputs.branch_name }}/" + --remap "https://docs.nextcloud.com/server/latest/ file://$(pwd)/stage/${{ needs.build.outputs.branch_name }}/" --remap "https://docs.nextcloud.com/server/ file://$(pwd)/stage/" --exclude 'go\.php' --exclude 'mailto:' --exclude-path '.*/404\.html' --exclude-path '.*/_static/.*' --exclude "/user_manual/" --include "/user_manual/en/" - --exclude '^file://.*/stage/(latest|stable|[0-9]+)/(developer_manual|admin_manual|user_manual)/?$' - 'stage/${{ needs.stage-and-check.outputs.branch_name }}/user_manual/en/**/*.html' - 'stage/${{ needs.stage-and-check.outputs.branch_name }}/admin_manual/**/*.html' - 'stage/${{ needs.stage-and-check.outputs.branch_name }}/developer_manual/**/*.html' - - # ============================================================================ - # DEPLOY - # ============================================================================ - # This job is responsible for: - # 1. Downloading the staged artifacts from stage-and-check - # 2. Applying them to the gh-pages branch - # 3. Creating a pull request for the deployment - # - # This job ONLY runs on pushes (not on pull requests), since we only want - # to deploy when code is merged to master or a stable branch. - # ============================================================================ - deploy: - name: Deploy documentation for gh-pages - needs: stage-and-check - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - - permissions: - contents: write - pull-requests: write - - steps: - - name: Checkout gh-pages branch - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: gh-pages - fetch-depth: 1 - persist-credentials: false - - - name: Download staged artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: staged-docs - path: stage/ - - # ======================================================================== - # APPLY STAGED ARTIFACTS TO GH-PAGES - # ======================================================================== - # Strategy: - # - Copy from stage// to server// - # - If version_name is set, ALSO copy to server// - # - This allows the highest stable to live in both locations - # ======================================================================== - - name: Apply staged artifacts to gh-pages - id: apply - run: | - branch="${{ needs.stage-and-check.outputs.branch_name }}" - additional="${{ needs.stage-and-check.outputs.additional_deployment }}" - - # Copy built documentation into server folder - mkdir -p server/${branch} - for artifact in stage/${branch}/*; do - if [ -d "$artifact" ]; then - manual_name="$(basename "$artifact")" - rm -rf "server/${branch}/${manual_name}" - cp -r "$artifact" "server/${branch}/${manual_name}" - fi - done - - # Copy PDF and ePub files to the root of the branch folder - echo "Looking for PDF and ePub files to copy..." - find stage/${branch}/ -maxdepth 1 \( -name "*.pdf" -o -name "*.epub" \) -type f - for f in stage/${branch}/*.pdf stage/${branch}/*.epub; do - if [ -f "$f" ]; then - echo "Copying: $f" - cp "$f" server/${branch}/ - fi - done - - # If this is the highest stable branch, also deploy to its versioned folder - if [ -n "${additional}" ]; then - rm -rf server/${additional} - cp -r server/${branch} server/${additional} - fi - - # Cleanup empty directories - find . -type d -empty -delete - - # Check for meaningful changes, ignoring: - # - lastupdated date lines in HTML (Sphinx build-time timestamps) - # - epub/pdf binaries (they regenerate automatically alongside HTML) - meaningful_html=$(git diff HEAD -- '*.html' | grep -E '^[+-]' | grep -v '^[+-]{3}' | grep -ivE 'lastupdated|Last updated on' | wc -l) - other_changes=$(git diff --name-only HEAD | grep -cvE '\.(html|epub|pdf)$' || true) - - if [ "$meaningful_html" -gt 0 ] || [ "$other_changes" -gt 0 ]; then - echo "has_changes=true" >> $GITHUB_OUTPUT - else - echo "has_changes=false" >> $GITHUB_OUTPUT - echo "::notice::Skipping deploy PR: only lastupdated timestamps or epub/pdf binaries changed" - fi - - - name: Strip noindex from stable docs - run: | - # Strip noindex from stable docs – these are the canonical pages we want indexed - if [ -d server/stable ]; then - find server/stable -name '*.html' -print0 | \ - xargs -0 perl -pi -e 's{\n?}{}g' - fi - - - name: Write robots.txt - run: | - cat > robots.txt << 'EOF' - User-agent: * - # Only the stable version should be indexed - Allow: /server/stable/ - Disallow: /server/ - EOF - - # Remove the stage/ directory BEFORE creating the PR so it doesn't get committed - - name: Clean up staging cache before commit - run: rm -rf stage/ - - # ======================================================================== - # ADD REDIRECT FILES - # ======================================================================== - - name: Add various redirects for go.php and user_manual english version - run: | - branch="${{ needs.stage-and-check.outputs.branch_name }}" - additional="${{ needs.stage-and-check.outputs.additional_deployment }}" - - git fetch origin ${{ github.event.repository.default_branch }} ${{ github.ref_name }} - - # Add go.php redirect from main branch - git checkout origin/${{ github.event.repository.default_branch }} -- go.php/index.html - mkdir -p server/${branch}/go.php - mv go.php/index.html server/${branch}/go.php/index.html - - # Add user_manual english redirect - git checkout origin/${{ github.ref_name }} -- user_manual/index.html - mkdir -p server/${branch}/user_manual - mv user_manual/index.html server/${branch}/user_manual/index.html - - # Also copy to versioned folder if applicable - if [ -n "${additional}" ]; then - mkdir -p server/${additional}/go.php server/${additional}/user_manual - cp server/${branch}/go.php/index.html server/${additional}/go.php/ - cp server/${branch}/user_manual/index.html server/${additional}/user_manual/ - fi - - - name: Create Pull Request for documentation deployment - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 - id: cpr - if: steps.apply.outputs.has_changes == 'true' - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - commit-message: "chore: update documentation for `${{ needs.stage-and-check.outputs.branch_name }}`" - committer: nextcloud-command - author: nextcloud-command - signoff: true - branch: "automated/deploy/documentation-${{ needs.stage-and-check.outputs.branch_name }}" - base: gh-pages - title: "Documentation update for `${{ needs.stage-and-check.outputs.branch_name }}`" - body: | - This PR was automatically generated by the CI workflow and - includes the latest changes for the `${{ needs.stage-and-check.outputs.branch_name }}` branch. - - delete-branch: true - labels: "automated, 3. to review" - - - name: Enable Pull Request Automerge - run: gh pr merge --merge --auto "${{ steps.cpr.outputs.pull-request-number }}" - if: steps.cpr.outputs.pull-request-number != '' - env: - GH_TOKEN: ${{ secrets.COMMAND_BOT_PAT }} + --exclude '^file://.*/stage/(latest|stable)/(developer_manual|admin_manual|user_manual)/?$' + --exclude '^file://.*/stage/[0-9]+/' + 'stage/${{ needs.build.outputs.branch_name }}/user_manual/en/**/*.html' + 'stage/${{ needs.build.outputs.branch_name }}/admin_manual/**/*.html' + 'stage/${{ needs.build.outputs.branch_name }}/developer_manual/**/*.html' # ============================================================================ # NETLIFY PREVIEW # ============================================================================ # Runs only on pull requests, in parallel with link-check. - # Downloads the staged-docs artifact (already produced by stage-and-check) - # and deploys a preview to Netlify under a stable per-PR alias. # # Required repository secrets: # NETLIFY_AUTH_TOKEN – personal-access token for the Netlify account @@ -722,7 +96,7 @@ jobs: # ============================================================================ netlify-preview: name: Deploy Netlify preview - needs: stage-and-check + needs: build if: github.event_name == 'pull_request' runs-on: ubuntu-latest @@ -732,14 +106,14 @@ jobs: steps: - name: Download staged artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: - name: staged-docs + name: ${{ needs.build.outputs.artifact_name }} path: stage/ - name: Assemble Netlify deploy directory run: | - branch="${{ needs.stage-and-check.outputs.branch_name }}" + branch="${{ needs.build.outputs.branch_name }}" mkdir -p netlify-deploy # Copy each manual directory into the deploy directory @@ -895,7 +269,7 @@ jobs: } summary: - needs: [stage-and-check, link-check, deploy, netlify-preview] + needs: [build, link-check, netlify-preview] runs-on: ubuntu-latest-low if: always() @@ -909,13 +283,9 @@ jobs: run: | if ${{ github.event_name == 'pull_request' }} then - echo "This workflow ran for a pull request. We need stage-and-check and link-check to succeed, deploy must be skipped, and netlify-preview must succeed or fail (non-fatal on forks)" - if ${{ needs.stage-and-check.result != 'success' || needs.link-check.result != 'success' || needs.deploy.result != 'skipped' || (needs.netlify-preview.result != 'success' && needs.netlify-preview.result != 'failure') }}; then exit 1; fi - elif ${{ github.event_name == 'push' }} - then - echo "This workflow ran for a push. We need stage-and-check and link-check to succeed; deploy and netlify-preview must be skipped" - if ${{ needs.stage-and-check.result != 'success' || needs.link-check.result != 'success' || needs.deploy.result != 'skipped' || needs.netlify-preview.result != 'skipped' }}; then exit 1; fi + echo "Pull request: build and link-check must succeed; netlify-preview must succeed or fail (non-fatal on forks)." + if ${{ needs.build.result != 'success' || needs.link-check.result != 'success' || (needs.netlify-preview.result != 'success' && needs.netlify-preview.result != 'failure') }}; then exit 1; fi else - echo "This workflow ran on schedule or workflow_dispatch. We need stage-and-check, link-check, and deploy to succeed; netlify-preview must be skipped" - if ${{ needs.stage-and-check.result != 'success' || needs.link-check.result != 'success' || needs.deploy.result != 'success' || needs.netlify-preview.result != 'skipped' }}; then exit 1; fi + echo "Push: build and link-check must succeed; netlify-preview must be skipped." + if ${{ needs.build.result != 'success' || needs.link-check.result != 'success' || needs.netlify-preview.result != 'skipped' }}; then exit 1; fi fi