diff --git a/.github/workflows/docs-dependencies.yml b/.github/workflows/docs-dependencies.yml new file mode 100644 index 0000000000..b769bfb1fc --- /dev/null +++ b/.github/workflows/docs-dependencies.yml @@ -0,0 +1,190 @@ +name: "Docs / Update dependencies" + +on: + schedule: + - cron: "00 8 * * 1" # Every Monday at 08:00 UTC. + workflow_dispatch: + +concurrency: + group: docs-update-dependencies + cancel-in-progress: false + +permissions: + contents: read + +jobs: + update: + # Manual runs must use the default branch, just like scheduled runs. + if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.lockfile.outputs.changed }} + lockfile: ${{ steps.lockfile.outputs.path }} + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.sha }} + persist-credentials: false + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.14' + + - name: Install uv + uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 + + - name: Update docs dependencies to latest compatible versions + run: make -C docs update + + - name: Test docs + run: make -C docs test + + - name: Check for lockfile changes + id: lockfile + run: | + if [ -f docs/uv.lock ]; then + lockfile=docs/uv.lock + elif [ -f uv.lock ]; then + lockfile=uv.lock + else + echo "::error::No uv.lock found in docs/ or the repository root." + exit 1 + fi + + echo "path=$lockfile" >> "$GITHUB_OUTPUT" + if [ -z "$(git status --porcelain -- "$lockfile")" ]; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Prepare lockfile artifact + if: steps.lockfile.outputs.changed == 'true' + env: + LOCKFILE: ${{ steps.lockfile.outputs.path }} + run: | + mkdir -p "$RUNNER_TEMP/docs-dependencies" + cp -- "$LOCKFILE" "$RUNNER_TEMP/docs-dependencies/uv.lock" + + - name: Upload updated lockfile + if: steps.lockfile.outputs.changed == 'true' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: docs-dependencies-lockfile + path: ${{ runner.temp }}/docs-dependencies/uv.lock + if-no-files-found: error + retention-days: 1 + + pull-request: + needs: update + if: needs.update.outputs.changed == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout tested commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.sha }} + persist-credentials: false + fetch-depth: 0 + + - name: Download updated lockfile + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: docs-dependencies-lockfile + path: ${{ runner.temp }}/docs-dependencies + + # Treat the artifact and outputs of the dependency job as untrusted data. + - name: Validate and copy lockfile + id: validated-lockfile + env: + LOCKFILE: ${{ needs.update.outputs.lockfile }} + run: | + python3 - <<'PY' + import os + import pathlib + import shutil + import stat + import tomllib + + lockfile = os.environ["LOCKFILE"] + if lockfile not in {"uv.lock", "docs/uv.lock"}: + raise SystemExit("Unexpected lockfile path") + + artifact = pathlib.Path(os.environ["RUNNER_TEMP"]) / "docs-dependencies" + source = artifact / "uv.lock" + if list(artifact.iterdir()) != [source] or not stat.S_ISREG(source.lstat().st_mode): + raise SystemExit("Artifact must contain only a regular uv.lock file") + + # Parse as data; never install or execute packages in this job. + tomllib.loads(source.read_text(encoding="utf-8")) + destination = pathlib.Path.cwd() / lockfile + if destination.resolve() != destination: + raise SystemExit("Lockfile destination must not contain symlinks") + shutil.copyfile(source, destination) + PY + + echo "path=$LOCKFILE" >> "$GITHUB_OUTPUT" + if [ -z "$(git status --porcelain -- "$LOCKFILE")" ]; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + # Requires "Allow GitHub Actions to create and approve pull requests" + # in the repository's Actions settings. + - name: Close previous update PR and create a new one + if: steps.validated-lockfile.outputs.changed == 'true' + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + BASE_BRANCH: ${{ github.event.repository.default_branch }} + LOCKFILE: ${{ steps.validated-lockfile.outputs.path }} + PR_BRANCH: automation/docs-update-dependencies + PR_TITLE: "chore(docs): update dependencies to latest compatible versions" + PR_BODY: | + Updates the Python dependencies used to build the docs to the latest + versions allowed by the project's `pyproject.toml`. + + Review the updated dependencies in the lockfile before merging, including + version changes, added or removed packages, and relevant release notes. + + ### How to test + + Check out this PR and run the following from the `docs` folder: + + ```sh + make preview + ``` + + Make sure the docs build completes without errors and the site renders + correctly at http://localhost:5500. + run: | + gh auth setup-git + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Only replace the automation branch if it has not changed since checkout. + expected_head=$(git rev-parse --verify "refs/remotes/origin/$PR_BRANCH" 2>/dev/null || true) + git switch -c "$PR_BRANCH" + git add -- "$LOCKFILE" + git commit -m "chore(docs): update dependencies" + git push --force-with-lease="refs/heads/$PR_BRANCH:$expected_head" \ + origin "HEAD:refs/heads/$PR_BRANCH" + + body_file="$RUNNER_TEMP/docs-update-dependencies-pr.md" + printf '%s\n' "$PR_BODY" > "$body_file" + pr_number=$(gh pr list --head "$PR_BRANCH" --base "$BASE_BRANCH" \ + --state open --json number --jq '.[0].number // empty') + + if [ -n "$pr_number" ]; then + gh pr close "$pr_number" + fi + + gh pr create --head "$PR_BRANCH" --base "$BASE_BRANCH" \ + --title "$PR_TITLE" --body-file "$body_file" diff --git a/.github/workflows/docs-pr-report.yml b/.github/workflows/docs-pr-report.yml new file mode 100644 index 0000000000..aa56704aee --- /dev/null +++ b/.github/workflows/docs-pr-report.yml @@ -0,0 +1,23 @@ +name: "Docs / Report PR" + +on: + workflow_run: + workflows: ["Docs / Build PR"] + types: [completed] + +permissions: + contents: read + actions: read + pull-requests: write + +concurrency: + group: docs-report-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: false + +jobs: + report: + if: github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Update docs build comment + uses: scylladb/sphinx-scylladb-theme/.github/actions/docs-report@0d821fc4038ee9ad6363362c02035132489bf3f6 diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 17c7a19629..d9a1401223 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -11,7 +11,7 @@ on: - master paths: - "docs/**" - - ".github/workflows/docs-pr.yml" + - ".github/workflows/docs-*.yml" - "cassandra/**" - "pyproject.toml" - "setup.py" @@ -19,7 +19,7 @@ on: pull_request: paths: - "docs/**" - - ".github/workflows/docs-pr.yml" + - ".github/workflows/docs-*.yml" - "cassandra/**" - "pyproject.toml" - "setup.py" @@ -39,6 +39,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false + fetch-depth: 0 - name: Install uv uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 @@ -47,4 +48,14 @@ jobs: enable-cache: true - name: Build docs - run: make -C docs test + id: docs-build + shell: bash + run: | + make -C docs test SPHINXOPTS="-j auto -w $RUNNER_TEMP/docs-warnings.log" \ + 2>&1 | tee "$RUNNER_TEMP/docs-build.log" + + - name: Summarize docs diagnostics + if: always() + uses: scylladb/sphinx-scylladb-theme/.github/actions/docs-report@0d821fc4038ee9ad6363362c02035132489bf3f6 + with: + build-outcome: ${{ steps.docs-build.outcome }} diff --git a/docs/pyproject.toml b/docs/pyproject.toml index ddcf1182c0..d62ae11beb 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -12,9 +12,9 @@ dependencies = [ "redirects_cli~=0.1.3", "sphinx-autobuild>=2025.0.0,<2026.0.0", "sphinx-sitemap>=2.8.0,<3.0.0", - "sphinx-scylladb-theme>=1.9.3", - "sphinx-multiversion-scylla>=0.3.2,<1.0.0", - "sphinx>=9.0", + "sphinx-scylladb-theme>=1.10.0,<1.11", + "sphinx-multiversion-scylla>=0.3.9,<1.0.0", + "sphinx>=9.1.1,<10", "six>=1.9", "tornado>=6.5,<7.0", ] diff --git a/docs/uv.lock b/docs/uv.lock index 88239baca3..4e4fc4d848 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -343,10 +343,10 @@ requires-dist = [ { name = "pygments", specifier = ">=2.19.2,<3.0.0" }, { name = "redirects-cli", specifier = "~=0.1.3" }, { name = "six", specifier = ">=1.9" }, - { name = "sphinx", specifier = ">=9.0" }, + { name = "sphinx", specifier = ">=9.1.1,<10" }, { name = "sphinx-autobuild", specifier = ">=2025.0.0,<2026.0.0" }, - { name = "sphinx-multiversion-scylla", specifier = ">=0.3.2,<1.0.0" }, - { name = "sphinx-scylladb-theme", specifier = ">=1.9.3" }, + { name = "sphinx-multiversion-scylla", specifier = ">=0.3.9,<1.0.0" }, + { name = "sphinx-scylladb-theme", specifier = ">=1.10.0,<1.11" }, { name = "sphinx-sitemap", specifier = ">=2.8.0,<3.0.0" }, { name = "tornado", specifier = ">=6.5,<7.0" }, ] @@ -477,7 +477,7 @@ wheels = [ [[package]] name = "sphinx" -version = "9.1.0" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alabaster" }, @@ -498,9 +498,9 @@ dependencies = [ { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/73/567727fc8c8d85bc3adc88a246e12937cfda8d565cf1d80e85bceaa61fe5/sphinx-9.1.1.tar.gz", hash = "sha256:48b627f02908f22c601b3ba9c7b17265b4f315dd05c8bc64cd4da41183f44fbc", size = 8725638, upload-time = "2026-10-20T00:41:13.96Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, + { url = "https://files.pythonhosted.org/packages/47/18/3c66ea76da7de47e716b181c35bdbfafbd5e9af0bb247b14578e9de348e9/sphinx-9.1.1-py3-none-any.whl", hash = "sha256:f8b60013ce25017be41a509e5c2d265bc1918410061315c36b809c7f335646c0", size = 3923559, upload-time = "2026-10-20T00:41:11.288Z" }, ] [[package]] @@ -585,14 +585,14 @@ wheels = [ [[package]] name = "sphinx-multiversion-scylla" -version = "0.3.8" +version = "0.3.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/b7/ca070ac96cbca8b91c499827790ca4816a259b9259a961108a3fd9fa470c/sphinx_multiversion_scylla-0.3.8.tar.gz", hash = "sha256:418b563afd3c75c40f096b614cb4c595928692fb0b340762e5fd19c876567040", size = 12749, upload-time = "2026-04-13T16:45:16.119Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/52/8ef02f8288b6fa3eb72fa82c1bd106a6a60f5be0a5930e822bbaae0d7448/sphinx_multiversion_scylla-0.3.9.tar.gz", hash = "sha256:f829d38f31627663e40f8b20be472a229458026d8f868c7c31420bd38265db4c", size = 13171, upload-time = "2026-10-22T10:41:17.405Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/1d/c4dea80220e2cab5dc326da2369ecac60b5fc71cb0643df698ef76bb14bb/sphinx_multiversion_scylla-0.3.8-py3-none-any.whl", hash = "sha256:a3a16724eb5ec76563f12dd66efb2e7c5d9aa2ddf5e8bb755e817f9973b44ffd", size = 12675, upload-time = "2026-04-13T16:45:14.859Z" }, + { url = "https://files.pythonhosted.org/packages/64/86/6f23f25e6226e8fef99bfd8a04b78200be0b49788ec972beeebad61de20c/sphinx_multiversion_scylla-0.3.9-py3-none-any.whl", hash = "sha256:7d7f791c19715e7d94f9333f234e131aad1d4ae9adfd64c3bffcff21cfaf3c18", size = 12765, upload-time = "2026-10-22T10:41:16.447Z" }, ] [[package]] @@ -609,7 +609,7 @@ wheels = [ [[package]] name = "sphinx-scylladb-theme" -version = "1.9.3" +version = "1.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -623,9 +623,9 @@ dependencies = [ { name = "sphinx-tabs" }, { name = "sphinxcontrib-mermaid" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/43/2c6ca729655d99c69a7970efe56345d4bf345a345511ce4dc247aa5380df/sphinx_scylladb_theme-1.9.3.tar.gz", hash = "sha256:2a80252d6a5bb1ef8b61b6af47db4b7cbdec9c056b339ad4bc2cee97604603ad", size = 1691127, upload-time = "2026-07-16T16:44:51.404Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/a3/d63abd59e0f2c2f388a588f1dca2ec336f5172641c63a3e6e09bcdd18ca1/sphinx_scylladb_theme-1.10.0.tar.gz", hash = "sha256:784244323e3c6ac150dbe92b7f4bd3ce0912287785ff4eabc073a63a07db513d", size = 1716694, upload-time = "2026-10-22T10:36:44.125Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/0e/b79e97434339b4b935535709b5b20491451e5c5de5d4b90c8cafbdcc7c2f/sphinx_scylladb_theme-1.9.3-py3-none-any.whl", hash = "sha256:2b9eb999421711deb7ca0fbd5c287c668cdaee7bd41d19abf75b140e8e6982d4", size = 1674682, upload-time = "2026-07-16T16:44:49.644Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ab/f61875676c7ec5b0e24ad308294b2cac4eed1a858d2723a6d14fbcf11ce1/sphinx_scylladb_theme-1.10.0-py3-none-any.whl", hash = "sha256:a63c9c6432841159ec5f56cb594ae33e32275941b9c7eccca5a1abb915fbb771", size = 1683263, upload-time = "2026-10-22T10:36:41.783Z" }, ] [[package]]