Skip to content

Commit 5a4050c

Browse files
committed
first commit!
1 parent 8c5c0c1 commit 5a4050c

File tree

3 files changed

+202
-1
lines changed

3 files changed

+202
-1
lines changed

.github/workflows/cicd.yml

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
name: release
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
7+
jobs:
8+
release:
9+
runs-on: ubuntu-latest
10+
concurrency: release
11+
env:
12+
FORCE_PATCH_IF_NO_COMMIT_TOKEN: false
13+
IGNORE_PATHS: |
14+
resources/**
15+
assets/**
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
22+
- uses: actions/setup-python@v5
23+
with:
24+
python-version: '3.11'
25+
26+
- name: Compute bumped version
27+
id: version
28+
env:
29+
FORCE_PATCH_IF_NO_COMMIT_TOKEN: ${{ env.FORCE_PATCH_IF_NO_COMMIT_TOKEN }}
30+
IGNORE_PATHS: ${{ env.IGNORE_PATHS }}
31+
run: |
32+
set -euo pipefail
33+
echo "now: $(date -u +"%Y-%m-%dT%H:%M:%S.%3N")"
34+
35+
# get the latest semantic-version style tag
36+
_latest_semver_tag=$(git tag --sort=-creatordate | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || true)
37+
echo "Latest semantic-version style tag: $_latest_semver_tag"
38+
if [ -z "$_latest_semver_tag" ]; then
39+
echo "No previous semver tag found — using 0.0.0"
40+
echo "version=0.0.0" >> "$GITHUB_OUTPUT"
41+
exit 0
42+
else
43+
LATEST_SEMVER_TAG_NO_V="${_latest_semver_tag#v}"
44+
fi
45+
46+
# get info on what's changed since that tag
47+
_that_commit=$(git rev-list -n 1 "$_latest_semver_tag")
48+
echo "Looking at what's changed since $_latest_semver_tag / $_that_commit"
49+
#
50+
CHANGED_FILES=$(git diff --name-only "$_that_commit"..HEAD)
51+
echo "Changed files:"
52+
echo "$CHANGED_FILES"
53+
#
54+
COMMIT_LOG=$(git log "$_that_commit"..HEAD --pretty=%B)
55+
echo "Commit log(s) since then:"
56+
echo "$COMMIT_LOG"
57+
58+
# get next version
59+
export LATEST_SEMVER_TAG_NO_V
60+
export CHANGED_FILES
61+
export COMMIT_LOG
62+
VERSION=$(python compute_next_version.py)
63+
64+
# handle output
65+
if [ -z "$VERSION" ]; then
66+
echo "No release needed — skipping"
67+
echo "version=" >> "$GITHUB_OUTPUT"
68+
else
69+
echo "Bumped version: $VERSION"
70+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
71+
fi
72+
73+
- name: Install build tools
74+
if: steps.version.outputs.version != ''
75+
run: |
76+
set -euo pipefail
77+
echo "now: $(date -u +"%Y-%m-%dT%H:%M:%S.%3N")"
78+
79+
exit 1 # TODO: REMOVE
80+
81+
python -m pip install --upgrade pip
82+
pip install build twine setuptools setuptools-scm
83+
84+
- name: Build package
85+
if: steps.version.outputs.version != ''
86+
run: |
87+
set -euo pipefail
88+
echo "now: $(date -u +"%Y-%m-%dT%H:%M:%S.%3N")"
89+
# Validate setuptools-scm usage
90+
if ! grep -q 'setuptools-scm' pyproject.toml; then
91+
echo "❌ setuptools-scm not found in pyproject.toml — aborting"
92+
exit 1
93+
fi
94+
95+
if grep -A 5 '^\[project\]' pyproject.toml | grep -q '^version\s*='; then
96+
echo "❌ [project] version is explicitly set — must be managed by setuptools-scm"
97+
exit 1
98+
fi
99+
100+
echo "✅ setuptools-scm is configured properly"
101+
102+
SETUPTOOLS_SCM_PRETEND_VERSION=${{ steps.version.outputs.version }} python -m build
103+
104+
- name: Create GitHub Release via API
105+
if: steps.version.outputs.version != ''
106+
env:
107+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
108+
VERSION: ${{ steps.version.outputs.version }}
109+
run: |
110+
set -euo pipefail
111+
echo "now: $(date -u +"%Y-%m-%dT%H:%M:%S.%3N")"
112+
API_URL="https://api.github.com/repos/${{ github.repository }}/releases"
113+
TAG="release-${VERSION}"
114+
PAYLOAD=$(jq -n \
115+
--arg tag_name "$TAG" \
116+
--arg target_commitish "${{ github.sha }}" \
117+
--arg name "Release $VERSION" \
118+
--arg body "Automated release for commit ${{ github.sha }}" \
119+
'{ tag_name: $tag_name, target_commitish: $target_commitish, name: $name, body: $body, draft: false, prerelease: false }')
120+
curl -sSf -X POST -H "Authorization: Bearer $GH_TOKEN" \
121+
-H "Content-Type: application/json" \
122+
-d "$PAYLOAD" "$API_URL"
123+
124+
- name: Upload to PyPI
125+
if: steps.version.outputs.version != ''
126+
env:
127+
TWINE_USERNAME: __token__
128+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
129+
run: |
130+
set -euo pipefail
131+
echo "now: $(date -u +"%Y-%m-%dT%H:%M:%S.%3N")"
132+
twine upload dist/*

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ cython_debug/
165165
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166166
# and can be added to the global gitignore or merged into this file. For a more nuclear
167167
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
168-
#.idea/
168+
.idea/
169169

170170
# Ruff stuff:
171171
.ruff_cache/

compute_next_version.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""A script for determining the next version of a package."""
2+
3+
import enum
4+
import os
5+
import sys
6+
from fnmatch import fnmatch
7+
8+
9+
class BumpType(enum.StrEnum):
10+
MAJOR = enum.auto()
11+
MINOR = enum.auto()
12+
PATCH = enum.auto()
13+
14+
15+
def main(
16+
tag: str,
17+
changed_files: list[str],
18+
commit_log: str,
19+
ignore_patterns: list[str],
20+
force_patch: bool,
21+
):
22+
"""Print the next version of a package; if there's no print, then's no new version."""
23+
24+
# is a release needed?
25+
if not changed_files:
26+
raise ValueError("No changes detected")
27+
if all(any(fnmatch(f, pat) for pat in ignore_patterns) for f in changed_files):
28+
raise ValueError("None of the changed files require a release.")
29+
30+
# detect bump
31+
if "[major]" in commit_log:
32+
bump = BumpType.MAJOR
33+
elif "[minor]" in commit_log:
34+
bump = BumpType.MINOR
35+
elif ("[patch]" in commit_log) or ("[fix]" in commit_log):
36+
bump = BumpType.PATCH
37+
elif force_patch:
38+
bump = BumpType.PATCH
39+
else:
40+
return sys.exit(0) # no release needed!
41+
42+
# increment
43+
major, minor, patch = map(int, tag.split("."))
44+
match bump:
45+
case BumpType.MAJOR:
46+
major += 1
47+
minor = patch = 0
48+
case BumpType.MINOR:
49+
minor += 1
50+
patch = 0
51+
case BumpType.PATCH:
52+
patch += 1
53+
case _:
54+
raise ValueError(f"Bump type not supported: {bump}")
55+
56+
# print the next version
57+
print(f"{major}.{minor}.{patch}")
58+
59+
60+
if __name__ == "__main__":
61+
main(
62+
tag=os.environ["LATEST_SEMVER_TAG_NO_V"],
63+
changed_files=os.environ["CHANGED_FILES"].splitlines(),
64+
commit_log=os.environ["COMMIT_LOG"].lower(),
65+
ignore_patterns=os.environ.get("IGNORE_PATHS", "").strip().splitlines(),
66+
force_patch=(
67+
os.environ.get("FORCE_PATCH_IF_NO_COMMIT_TOKEN", "false").lower() == "true"
68+
),
69+
)

0 commit comments

Comments
 (0)