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
21 changes: 18 additions & 3 deletions .github/scripts/extract_changelog.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
#!/usr/bin/env python3
import re
import sys

package, version = sys.argv[1], sys.argv[2]
sections = open(f"packages/{package}/RELEASE_NOTES.md").read().split("## ")
match = next((s for s in sections if s.startswith(version)), None)
content = open(f"packages/{package}/RELEASE_NOTES.md").read()
# Split on level-2 headers only ("## " at line start), not "### " subheaders.
sections = re.split(r"^## ", content, flags=re.MULTILINE)


def matches(section: str) -> bool:
# Header is commitizen-generated ("gen-v7.3.0 (2026-09-07)"); tolerate a bare
# version too ("7.3.0") for any hand-written entries.
head = section.split(None, 1)[0] if section.split() else ""
return head == version or head.endswith(f"-v{version}")


match = next((s for s in sections if matches(s)), None)

if not match:
sys.exit(f"No changelog entry found for version {version}")
print(match[len(version):].strip())

# Drop the header line, keep the body.
body = match.split("\n", 1)[1] if "\n" in match else ""
print(body.strip())
19 changes: 16 additions & 3 deletions .github/scripts/update_changelog.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
#!/usr/bin/env python3
import re
import sys

package, version, body = sys.argv[1], sys.argv[2], sys.argv[3]
path = f"packages/{package}/RELEASE_NOTES.md"
content = open(path).read()
sections = content.split("## ")
match = next((s for s in sections if s.startswith(version)), None)
# Split on level-2 headers only ("## " at line start), not "### " subheaders.
sections = re.split(r"^## ", content, flags=re.MULTILINE)


def matches(section: str) -> bool:
# Header is commitizen-generated ("gen-v7.3.0 (2026-09-07)"); tolerate a bare
# version too ("7.3.0") for any hand-written entries.
head = section.split(None, 1)[0] if section.split() else ""
return head == version or head.endswith(f"-v{version}")


match = next((s for s in sections if matches(s)), None)

if not match:
sys.exit(f"No changelog entry found for version {version}")

open(path, "w").write(content.replace(match, f"{version}\n\n{body.strip()}\n\n"))
# Keep the generated header line, replace only the body with the published notes.
header = match.split("\n", 1)[0]
open(path, "w").write(content.replace(f"## {match}", f"## {header}\n\n{body.strip()}\n\n"))
30 changes: 30 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Releasing

This monorepo publishes three independent PyPI packages, each with its own version, tag prefix, and changelog. Publish in dependency order when several changed together: **base → core → gen**.

| Package | PyPI name | Tag prefix | Changelog |
| --- | --- | --- | --- |
| `base` | `sap-ai-sdk-base` | `base-v*` | `packages/base/RELEASE_NOTES.md` |
| `core` | `sap-ai-sdk-core` | `core-v*` | `packages/core/RELEASE_NOTES.md` |
| `gen` | `sap-ai-sdk-gen` | `gen-v*` | `packages/gen/RELEASE_NOTES.md` |

## Flow


0. Commit with the package's scope → feat(base|core|gen): ... / fix(...): ...
1. Run the "bump" workflow → Actions ▸ bump ▸ Run workflow ▸ pick the package
2. draft-release (automatic) → notes auto-generated from commits, on the new tag
3. Review the draft release, then Publish → edits sync back to RELEASE_NOTES.md
4. publish-pypi (automatic) → package lands on PyPI

Also on tag push: **api-doc.yml** (any package) regenerates the API docs and opens a PR in `SAP/ai-sdk`; **documentation.yml** (`gen` only) builds the Sphinx HTML.

## Commit scopes

Versions and changelog entries come **from commit messages**, not the diff — each package counts only commits matching its own scope (from `[tool.commitizen.customize]` in its `pyproject.toml`). `feat` → minor bump, `fix` → patch bump. Any other scope (`chore`, `test`, none) is **silently ignored**.

| Package | `bump_pattern` |
| --- | --- |
| `base` | `^(feat\|fix)\(base\)` |
| `core` | `^(feat\|fix)\(core\)` |
| `gen` | `^(feat\|fix)\((gen\|prompt_registry\|evaluations)\)` |