Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f245923
Version the docs site with mike: stable release as public default, pr…
Mmoncadaisla Aug 19, 2026
a8ad9a1
Point README docs links at the latest/ alias; the site root redirect …
Mmoncadaisla Aug 19, 2026
b4408c0
Skip Pages publish until a default docs version exists, so the pre-ve…
Mmoncadaisla Aug 19, 2026
cce14a3
Serialize only the Pages publish and retry the gh-pages push: a queue…
Mmoncadaisla Aug 19, 2026
25500e6
Validate release tags and dispatch versions before handing them to mi…
Mmoncadaisla Aug 19, 2026
5816a38
Pin the mike fork to a commit SHA; it is installed from a mutable bra…
Mmoncadaisla Aug 19, 2026
8106da5
Drop the redundant version default; the theme already falls back to t…
Mmoncadaisla Aug 19, 2026
e63fdab
Document how to roll back the docs deployment of a bad release.
Mmoncadaisla Aug 19, 2026
56c9bb5
Deploy pre-release docs to a rolling 'rc' version: a patch-level RC m…
Mmoncadaisla Aug 19, 2026
9fdb8f2
Take the docs source ref as a dispatch input: dispatching from an old…
Mmoncadaisla Aug 19, 2026
86715b7
Never move 'latest' backward: a maintenance release for an older line…
Mmoncadaisla Aug 19, 2026
973f0a5
Rollback docs: use the pinned mike fork for deletion and republish th…
Mmoncadaisla Aug 19, 2026
f5214c8
Evaluate the latest-alias guard against freshly fetched gh-pages on e…
Mmoncadaisla Aug 19, 2026
bb468c4
Newest-wins concurrency for rolling docs builds so an overlapping old…
Mmoncadaisla Aug 19, 2026
8678e6a
Allow manual redeploys of the rolling rc docs version, but never as t…
Mmoncadaisla Aug 19, 2026
1665abd
Manual rc rebuilds join the rc concurrency group and keep their tag-d…
Mmoncadaisla Aug 19, 2026
299702b
Queue stable-release docs builds so releases of the same line deploy …
Mmoncadaisla Aug 19, 2026
a320d49
Tighten workflow comments.
Mmoncadaisla Aug 19, 2026
795fa4b
Treat a tag pre-release suffix as pre-release even without the releas…
Mmoncadaisla Aug 19, 2026
5b872a6
Fail closed when the current latest target cannot be verified; only a…
Mmoncadaisla Aug 19, 2026
8626b34
Detect pre-releases by the suffix after the patch number, not by any …
Mmoncadaisla Aug 19, 2026
cf719da
Reset the local gh-pages branch to the remote between push retries; a…
Mmoncadaisla Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 199 additions & 15 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ on:
push:
branches:
- main

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: true
release:
types:
- published
workflow_dispatch:
inputs:
ref:
description: "Git ref (tag) to build the docs from; empty builds the ref the workflow runs on"
required: false
type: string
default: ""
version:
description: "Docs version to deploy (e.g. 0.3, or rc for the rolling pre-release docs)"
required: true
type: string
latest:
description: "Point the 'latest' alias (public default) at this version"
required: false
type: boolean
default: false

defaults:
run:
Expand All @@ -21,30 +31,204 @@ defaults:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
# Rolling targets (dev, rc) are newest-wins; stable releases queue in
# publish order. The groups must stay separate: cancel-in-progress is
# evaluated per incoming run, so a shared group would let an arriving
# pre-release cancel a running stable build
concurrency:
group: docs-build-${{ github.event_name == 'push' && 'dev' || ((github.event_name == 'release' && (github.event.release.prerelease || (contains(github.event.release.tag_name, '-') && !contains(github.event.release.tag_name, '+')))) || (github.event_name == 'workflow_dispatch' && inputs.version == 'rc')) && 'rc' || github.event_name == 'release' && 'stable' || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'push' || (github.event_name == 'release' && (github.event.release.prerelease || (contains(github.event.release.tag_name, '-') && !contains(github.event.release.tag_name, '+')))) || (github.event_name == 'workflow_dispatch' && inputs.version == 'rc') }}
Comment thread
Mmoncadaisla marked this conversation as resolved.

steps:
# Dispatch builds a tag via the ref input; dispatching from a tag
# would use that tag's workflow definition, which lacks this trigger
- uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ inputs.ref }}
Comment thread
Mmoncadaisla marked this conversation as resolved.

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Build documentation
run: uvx --with "mkdocstrings[python]" zensical build
# Tags predating the versioning setup lack the provider key, and
# their docs would build without the version selector
- name: Ensure version provider on backfill builds
if: github.event_name == 'workflow_dispatch' && inputs.ref != ''
run: |
if ! grep -q 'provider = "mike"' zensical.toml; then
printf '\n[project.extra.version]\nprovider = "mike"\n' >> zensical.toml
fi

- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: ./site
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Resolve docs version
id: docs
env:
EVENT_NAME: ${{ github.event_name }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_PRERELEASE: ${{ github.event.release.prerelease }}
INPUT_VERSION: ${{ inputs.version }}
INPUT_LATEST: ${{ inputs.latest }}
INPUT_REF: ${{ inputs.ref }}
run: |
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
# Only MAJOR.MINOR or the rolling 'rc' target; in particular
# this rejects the 'latest' alias and the auto-managed 'dev'
if [[ ! "$INPUT_VERSION" =~ ^([0-9]+\.[0-9]+|rc)$ ]]; then
echo "Invalid docs version '$INPUT_VERSION'; expected MAJOR.MINOR (e.g. 0.3) or rc" >&2
exit 1
fi
if [[ "$INPUT_VERSION" == "rc" && "$INPUT_LATEST" == "true" ]]; then
echo "The public default cannot point at the rolling rc version" >&2
exit 1
fi
version="$INPUT_VERSION"
latest="$INPUT_LATEST"
# An rc rebuild keeps its tag-derived title instead of 'rc'
if [[ "$INPUT_VERSION" == "rc" && -n "$INPUT_REF" ]]; then
title="${INPUT_REF#v}"
fi
elif [[ "$EVENT_NAME" == "release" ]]; then
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([-+].*)?$ ]]; then
echo "Tag '$RELEASE_TAG' is not vMAJOR.MINOR.PATCH; refusing to deploy docs" >&2
exit 1
fi
# The tag's own pre-release suffix counts even when the GitHub
# pre-release checkbox was forgotten: a '-' right after the
# patch number, not a hyphen inside '+' build metadata
if [[ "$RELEASE_PRERELEASE" == "true" || "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+- ]]; then
# Pre-releases go to a rolling 'rc' version so they can never
# overwrite the stable X.Y that 'latest' may already point to
version="rc"
title="${RELEASE_TAG#v}"
latest="false"
else
# v0.4.0 -> 0.4
version="$(echo "${RELEASE_TAG#v}" | cut -d. -f1,2)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent older patches from replacing a stable line

When an older patch release is published or republished after a newer patch in the same minor line—for example, v0.3.3 after v0.3.4—both tags collapse to the 0.3 target, so the later event overwrites 0.3/ with the older documentation and the latest guard sees equal line values and permits it. Unlike the prior overlap finding, this occurs even when the runs are fully sequential, so the concurrency group does not prevent it; preserve and compare the full release version before replacing an existing stable line.

Useful? React with 👍 / 👎.

latest="true"
Comment thread
Mmoncadaisla marked this conversation as resolved.
fi
else
version="dev"
latest="false"
fi
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "title=${title:-${version}}" >> "$GITHUB_OUTPUT"
Comment thread
Mmoncadaisla marked this conversation as resolved.
echo "latest=${latest}" >> "$GITHUB_OUTPUT"

- name: Deploy documentation version
env:
VERSION: ${{ steps.docs.outputs.version }}
TITLE: ${{ steps.docs.outputs.title }}
LATEST: ${{ steps.docs.outputs.latest }}
GUARD_LATEST: ${{ github.event_name == 'release' }}
run: |
# The zensical fork of mike is GitHub-only (no PyPI releases), so
# pin it to a commit; this job holds contents: write
mike() {
uvx --from "git+https://github.com/squidfunk/mike.git@2d4ad799442f4592db8ad53b179bfb33db8c69ac" \
--with "mkdocstrings[python]" mike "$@"
}
# A maintenance release for an older line (e.g. v0.3.4 after
# v0.4.0) must not move 'latest' backward; re-checked against
# fresh gh-pages state on every attempt since release builds are
# not serialized. Dispatches honor the explicit input.
# Returns 0 = may move, 1 = older line, 2 = could not verify —
# fail closed, except for a confirmed-absent gh-pages (bootstrap)
may_move_latest() {
[[ "$GUARD_LATEST" == "true" ]] || return 0
if ! git fetch --quiet origin gh-pages 2>/dev/null; then
rc=0
git ls-remote --exit-code origin refs/heads/gh-pages >/dev/null 2>&1 || rc=$?
[[ "$rc" -eq 2 ]] && return 0
echo "Cannot verify the current latest target; failing this attempt" >&2
return 2
fi
current="$(git show FETCH_HEAD:versions.json 2>/dev/null \
| jq -r '.[] | select(.aliases | index("latest")) | .version')" || {
echo "Cannot read versions.json from gh-pages; failing this attempt" >&2
return 2
}
if [[ -n "$current" ]] && \
[[ "$(printf '%s\n%s\n' "$current" "$VERSION" | sort -V | tail -1)" != "$VERSION" ]]; then
echo "Line ${VERSION} is older than current latest (${current}); not moving the alias" >&2
return 1
fi
}
deploy() {
if [[ "$LATEST" != "true" ]]; then
mike deploy --push --title "$TITLE" "$VERSION"
Comment thread
Mmoncadaisla marked this conversation as resolved.
return
fi
rc=0
may_move_latest || rc=$?
case "$rc" in
0) mike deploy --push --update-aliases --title "$TITLE" "$VERSION" latest &&
mike set-default --push latest ;;
1) mike deploy --push --title "$TITLE" "$VERSION" ;;
*) return 2 ;;
esac
}
# Concurrent runs race on the gh-pages push; the local branch must
# be reset to the remote before rerunning, or every retry replays
# the same non-fast-forward rejection
retry() {
for attempt in 1 2; do
"$@" && return 0
echo "attempt ${attempt} failed; retrying" >&2
sleep 15
git fetch --force origin gh-pages:gh-pages 2>/dev/null || true
done
"$@"
}
retry deploy

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
pages: write
id-token: write
# Serialize Pages publishes only; every build job must run so no mike
# deploy is lost. gh-pages state is cumulative, so a pending deploy
# superseded in this queue is covered by the newer one that replaced it
concurrency:
group: "pages"
cancel-in-progress: false

steps:
- uses: actions/checkout@v6
with:
ref: gh-pages

- name: Check a default version exists
id: bootstrapped
run: |
# The root redirect only exists once a stable version has been
# deployed and set as default; until then, keep serving the
# previously published site instead of a broken root
if [[ -f index.html ]]; then
echo "ok=true" >> "$GITHUB_OUTPUT"
else
echo "ok=false" >> "$GITHUB_OUTPUT"
echo "No default docs version set yet; skipping Pages publish" >&2
fi

- name: Upload artifact
if: steps.bootstrapped.outputs.ok == 'true'
uses: actions/upload-pages-artifact@v4
with:
path: .

- name: Deploy to GitHub Pages
if: steps.bootstrapped.outputs.ok == 'true'
id: deployment
uses: actions/deploy-pages@v5
37 changes: 37 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,43 @@ To create a release, please do the following:
7. Click "Publish Release". This will kick of a GitHub action to build the project and push the binaries + wheels to PyPI.
8. Celebrate a successful release!

### Documentation versions

Publishing a release also deploys the documentation, versioned with
[mike](https://zensical.org/docs/setup/versioning/) on the `gh-pages` branch:

- A stable release deploys its docs under `XX.YY/` and moves the `latest`
alias (the version the site root redirects to) to it.
- A pre-release (e.g. `vXX.YY.0-rc.1`) deploys under a rolling `rc/` version
(titled with the full tag) and leaves `latest` untouched, so the public
default stays on the latest stable release.
- Every push to `main` refreshes the `dev` version.

Docs for any version can be (re)deployed manually from the
[docs workflow](https://github.com/xqlsystems/xarray-sql/actions/workflows/docs.yml)
via "Run workflow": run it on `main` (dispatch uses the workflow definition
at the selected ref, and tags predating it have no manual trigger), put the
git tag to build from in the "ref" input, set the docs version (e.g. `0.3`),
and tick "latest" only if the site root should point there.

When undoing a bad release (see below), note that deleting the release and
tag does not undo its docs deployment. To roll the docs back:

1. If the bad version should disappear entirely, delete it from `gh-pages`
with the same pinned mike fork the workflow uses (the `mike` on PyPI is
not Zensical-compatible):

```shell
uvx --from "git+https://github.com/squidfunk/mike.git@2d4ad799442f4592db8ad53b179bfb33db8c69ac" \
--with "mkdocstrings[python]" mike delete --push XX.YY
```

2. Re-run the docs workflow on `main` with "ref" set to the last good tag,
its docs version, and "latest" ticked. Pages only updates through the
workflow — never from `gh-pages` pushes alone — so this single run moves
the public default back and republishes the site without the deleted
version.

## Undoing a bad release

We all mess up sometimes. For example, I have often forgotten to do one of the steps (often, step 1) in the above process, and it leads to a failed release (i.e. an unsuccessful push to PyPI.)
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ rel = con.sql('SELECT time, AVG("air") AS air FROM air GROUP BY time ORDER BY ti
xql.to_dataset(rel, template=ds) # any engine's Arrow result round-trips
```

See [Engines](https://xqlsystems.github.io/xarray-sql/engines/) for the support matrix, DuckDB/Polars details,
See [Engines](https://xqlsystems.github.io/xarray-sql/latest/engines/) for the support matrix, DuckDB/Polars details,
and the lazy chunked round-trip.

## A bigger example: ARCO-ERA5
Expand Down Expand Up @@ -245,7 +245,7 @@ Every case matches its array reference. The headline finding: these operations
are not really "array" operations at all — they are `GROUP BY`, `JOIN`, window
functions, and `CASE` in disguise, and a query engine runs them at scale. See
[`benchmarks/geospatial/`](https://github.com/xqlsystems/xarray-sql/tree/main/benchmarks/geospatial/) and the write-up,
[Geospatial operations are relational operations](https://xqlsystems.github.io/xarray-sql/geospatial/).
[Geospatial operations are relational operations](https://xqlsystems.github.io/xarray-sql/latest/geospatial/).

## Why does this work?

Expand All @@ -260,10 +260,10 @@ anything that speaks Arrow).
## What are the current limitations?

The sharp edges we know about — per engine and fundamental — are cataloged in
[Known issues & limitations](https://xqlsystems.github.io/xarray-sql/limitations/). Currently, we're looking for
[Known issues & limitations](https://xqlsystems.github.io/xarray-sql/latest/limitations/). Currently, we're looking for
early users – "tire kickers", if you will. We'd love your input to shape the direction of this
project! Please, give this a try and [file issues](https://github.com/xqlsystems/xarray-sql/issues) as
you see fit. Check out our [contributing guide](https://xqlsystems.github.io/xarray-sql/contributing/), too 😉.
you see fit. Check out our [contributing guide](https://xqlsystems.github.io/xarray-sql/latest/contributing/), too 😉.

## What would a deeper integration look like?

Expand Down
6 changes: 5 additions & 1 deletion zensical.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
site_name = "xarray-sql"
site_description = "Query Xarray with SQL"
site_author = "Alexander Merose"
site_url = "https://xqlsystems.github.io/xarray-sql"
site_url = "https://xqlsystems.github.io/xarray-sql/"
repo_url = "https://github.com/xqlsystems/xarray-sql"
repo_name = "xqlsystems/xarray-sql"
edit_uri = "edit/main/docs/"
Expand Down Expand Up @@ -129,6 +129,10 @@ filters = ["!^_"]
# Extra configuration
[project.extra]

# Versioned docs via mike (Zensical fork); versions live on the gh-pages branch
[project.extra.version]
provider = "mike"

[[project.extra.social]]
icon = "fontawesome/brands/github"
link = "https://github.com/xqlsystems/xarray-sql"
Expand Down
Loading