Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .github/workflows/build-and-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ jobs:
;;
esac

- name: Validate committed release notes
if: steps.validate_tag.outputs.should_publish == 'true'
run: |
poetry run python -c "from corneto.release import _ensure_release_notes; _ensure_release_notes('${{ steps.validate_tag.outputs.version }}')"

# 7. Build source & wheel (only if should publish)
- name: Build package
if: steps.validate_tag.outputs.should_publish == 'true'
Expand Down
94 changes: 44 additions & 50 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ on:
workflows: ["Publish Python Package"]
types: [completed]

permissions:
contents: write

concurrency:
group: docs-pages
cancel-in-progress: false

jobs:
build-and-deploy:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -83,67 +90,54 @@ jobs:
- name: Sync GitHub Releases to Docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
sleep 30
python scripts/sync_releases.py --repo saezlab/corneto --docs-dir docs
run: python scripts/sync_releases.py --repo saezlab/corneto --docs-dir docs

- name: Build Docs
env:
SPHINX_VERSION_MATCH: ${{ steps.set_version.outputs.version_folder }}
DOCS_BASE_URL: https://corneto.org
run: poetry run sphinx-build -W --keep-going -b html docs docs/_build/html

- name: Deploy “stable” docs
if: ${{ steps.set_version.outputs.version_folder == 'stable' }}
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/_build/html
destination_dir: stable
keep_files: true

- name: Deploy versioned docs for release tag
if: ${{ steps.set_version.outputs.version_folder != 'stable' }}
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/_build/html
destination_dir: ${{ steps.set_version.outputs.version_folder }}
keep_files: true

- name: Prepare root redirect
- name: Check out gh-pages
run: |
mkdir -p temp_root
touch temp_root/.nojekyll
cp docs/custom-index.html temp_root/index.html
- name: Generate switcher.json
set -euo pipefail
git fetch origin gh-pages:refs/remotes/origin/gh-pages
git worktree add --detach gh-pages-worktree refs/remotes/origin/gh-pages

- name: Prepare Pages tree
env:
DOCS_BASE_URL: https://corneto.org
run: |
python scripts/generate_switcher.py --output temp_root/switcher.json

- name: Deploy root redirect
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: temp_root
destination_dir: ./
keep_files: true

- name: Patch switcher URL in published HTML
set -euo pipefail
root_args=()
if [ "${{ steps.set_version.outputs.version_folder }}" = "stable" ]; then
root_args+=(--update-root)
fi
poetry run python scripts/prepare_docs_pages.py \
--pages-dir gh-pages-worktree \
--build-dir docs/_build/html \
--landing-dir docs/landing \
--version-folder "${{ steps.set_version.outputs.version_folder }}" \
--base-url "${DOCS_BASE_URL}" \
"${root_args[@]}"

- name: Commit and deploy Pages tree
env:
VERSION_FOLDER: ${{ steps.set_version.outputs.version_folder }}
run: |
git fetch origin gh-pages
git worktree add gh-pages-worktree gh-pages
python scripts/patch_switcher_urls.py \
--root gh-pages-worktree \
--new-url https://corneto.org/switcher.json
set -euo pipefail
cd gh-pages-worktree
if [ -n "$(git status --porcelain)" ]; then
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "chore: update switcher json url"
git push origin gh-pages
else
echo "No switcher URL changes detected."
if [ -z "$(git status --porcelain)" ]; then
echo "No Pages changes to deploy."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "docs: deploy ${VERSION_FOLDER}"
git fetch origin gh-pages
if ! git merge --ff-only origin/gh-pages; then
echo "gh-pages advanced during deployment; refusing to overwrite it." >&2
exit 1
fi
git push origin HEAD:gh-pages
35 changes: 34 additions & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,40 @@ poetry run release v1.2.3
Pre-release tags use the same flow, for example `v1.2.3-alpha.0`,
`v1.2.3-beta.0`, or `v1.2.3-rc.0`. The helper confirms that the tree is clean,
the checkout is on and matches the selected remote's `main`, and the tag does
not already exist.
not already exist. It also validates the committed release page before a tag
can be created.

## Preparing release notes

Add `docs/releases/<tag>.md` and include it in `docs/releases/index.md` before
opening the public release pull request. Use the tag exactly as written; do not
rewrite `v1.0.0-rc.4` as `CORNETO 1.0.0 RC4`. Pre-releases include the same
badge used by the documentation release synchronizer:

```markdown
# Release v1.0.0-rc.4 {bdg-warning}`Pre-release`

One or two sentences describing the purpose of the release.

## Highlights

- The most important user-facing change.
- Another important change.

## Additional context

Optional installation, migration, compatibility, or method-specific details.
```

For a stable release, omit the pre-release badge:

```markdown
# Release v1.0.0
```

The release helper and tag publication workflow both enforce the canonical
title and the `## Highlights` section. This keeps manually prepared pages
consistent with pages produced by `scripts/sync_releases.py`.

Useful options:

Expand Down
23 changes: 23 additions & 0 deletions corneto/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import re
import subprocess
import sys
from pathlib import Path
from typing import Sequence

VERSION_RE = re.compile(r"^v\d+\.\d+\.\d+(?:-(?:alpha|beta|rc)\.\d+)?$")
Expand Down Expand Up @@ -72,6 +73,27 @@ def _ensure_tag_does_not_exist(version: str, remote: str) -> None:
raise ReleaseError(f"Tag already exists on {remote}: {version}")


def _expected_release_heading(version: str) -> str:
heading = f"# Release {version}"
if re.search(r"-(?:alpha|beta|rc)\.\d+$", version):
heading += " {bdg-warning}`Pre-release`"
return heading


def _ensure_release_notes(version: str, *, root: Path = Path(".")) -> None:
notes_path = root / "docs" / "releases" / f"{version}.md"
if not notes_path.is_file():
raise ReleaseError(f"Missing release notes: {notes_path}")

content = notes_path.read_text(encoding="utf-8")
first_line = next((line.strip() for line in content.splitlines() if line.strip()), "")
expected_heading = _expected_release_heading(version)
if first_line != expected_heading:
raise ReleaseError(f"Release notes must start with {expected_heading!r}; found {first_line!r} in {notes_path}.")
if not re.search(r"^## Highlights\s*$", content, flags=re.MULTILINE):
raise ReleaseError(f"Release notes must contain a '## Highlights' section: {notes_path}")


def _create_and_push_tag(version: str, remote: str) -> None:
_run(["git", "tag", "-a", version, "-m", version], check=True)
_run(["git", "push", remote, version], check=True)
Expand Down Expand Up @@ -114,6 +136,7 @@ def main(argv: Sequence[str] | None = None) -> int:
_ensure_on_main()
_ensure_up_to_date_with_remote_main(args.remote)
_ensure_tag_does_not_exist(version, args.remote)
_ensure_release_notes(version)

if args.dry_run:
print(f"[dry-run] All checks passed. Would create and push tag: {version}")
Expand Down
Loading
Loading