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
54 changes: 15 additions & 39 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,52 +17,27 @@ permissions:
packages: write

jobs:
detect-version-change:
# ── Read version from pyproject.toml (release builds only) ─────────────────
read-version:
if: github.ref == 'refs/heads/release'
runs-on: ubuntu-latest
outputs:
version_changed: ${{ steps.check.outputs.changed }}
version: ${{ steps.check.outputs.version }}
version: ${{ steps.pkg.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/checkout@v4

- name: Check for version bump
id: check
- name: Read version
id: pkg
run: |
CURRENT=$(python3 -c "
VERSION=$(python3 -c "
import re, pathlib
text = pathlib.Path('pyproject.toml').read_text()
m = re.search(r'^version\s*=\s*\"([^\"]+)\"', text, re.M)
print(m.group(1))
")
echo "version=$VERSION" >> $GITHUB_OUTPUT

git show HEAD~1:pyproject.toml > /tmp/prev_pyproject.toml 2>/dev/null || true
if [ -f /tmp/prev_pyproject.toml ]; then
PREVIOUS=$(python3 -c "
import re, pathlib
text = pathlib.Path('/tmp/prev_pyproject.toml').read_text()
m = re.search(r'^version\s*=\s*\"([^\"]+)\"', text, re.M)
print(m.group(1) if m else '')
")
else
PREVIOUS=""
fi

echo "current=$CURRENT"
echo "previous=$PREVIOUS"

if [ "$CURRENT" != "$PREVIOUS" ] && [ -n "$CURRENT" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "version=$CURRENT" >> "$GITHUB_OUTPUT"
echo "Version changed: $PREVIOUS -> $CURRENT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No version change detected"
fi

# ── Dev builds (main branch) ──────────────────────────────────────────────
build-dev:
if: github.ref == 'refs/heads/main'
strategy:
Expand Down Expand Up @@ -145,9 +120,10 @@ jobs:
-t "${{ env.IMAGE }}:dev-${SHORT_SHA}" \
$(printf '${{ env.IMAGE }}@sha256:%s ' *)

# ── Release builds (release branch) ───────────────────────────────────────
build-release:
needs: detect-version-change
if: github.ref == 'refs/heads/release' && needs.detect-version-change.outputs.version_changed == 'true'
needs: read-version
if: github.ref == 'refs/heads/release'
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -198,8 +174,8 @@ jobs:
retention-days: 1

merge-release:
needs: [detect-version-change, build-release]
if: github.ref == 'refs/heads/release' && needs.detect-version-change.outputs.version_changed == 'true'
needs: [read-version, build-release]
if: github.ref == 'refs/heads/release'
runs-on: ubuntu-latest
steps:
- name: Download digests
Expand All @@ -222,7 +198,7 @@ jobs:
- name: Create release multi-arch manifest
working-directory: /tmp/digests
run: |
VERSION="${{ needs.detect-version-change.outputs.version }}"
VERSION="${{ needs.read-version.outputs.version }}"
MAJOR_MINOR="${VERSION%.*}"
docker buildx imagetools create \
-t "${{ env.IMAGE }}:latest" \
Expand Down
76 changes: 29 additions & 47 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
@@ -1,106 +1,88 @@
name: Publish to PyPI

on:
pull_request:
types: [closed]
push:
branches: [release]

concurrency:
group: pypi-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

jobs:
detect-version-change:
if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'main'
build:
runs-on: ubuntu-latest
outputs:
version_changed: ${{ steps.check.outputs.changed }}
version: ${{ steps.check.outputs.version }}
steps:
- name: Checkout current commit
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2 # need parent to diff against

- name: Check for version bump
id: check
- name: Read version
id: pkg
run: |
# Extract version from current commit
CURRENT=$(python3 -c "
VERSION=$(python3 -c "
import re, pathlib
text = pathlib.Path('pyproject.toml').read_text()
m = re.search(r'^version\s*=\s*\"([^\"]+)\"', text, re.M)
print(m.group(1))
")
echo "version=$VERSION" >> $GITHUB_OUTPUT

# Extract version from previous commit
git show HEAD~1:pyproject.toml > /tmp/prev_pyproject.toml 2>/dev/null || true
if [ -f /tmp/prev_pyproject.toml ]; then
PREVIOUS=$(python3 -c "
import re, pathlib
text = pathlib.Path('/tmp/prev_pyproject.toml').read_text()
m = re.search(r'^version\s*=\s*\"([^\"]+)\"', text, re.M)
print(m.group(1) if m else '')
")
else
PREVIOUS=""
fi

echo "current=$CURRENT"
echo "previous=$PREVIOUS"

if [ "$CURRENT" != "$PREVIOUS" ] && [ -n "$CURRENT" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "version=$CURRENT" >> "$GITHUB_OUTPUT"
echo "Version changed: $PREVIOUS -> $CURRENT"
- name: Check if version already on PyPI
id: check
run: |
VERSION="${{ steps.pkg.outputs.version }}"
if pip index versions cptr 2>/dev/null | grep -q "$VERSION"; then
echo "skip=true" >> $GITHUB_OUTPUT
echo "Version $VERSION already on PyPI — skipping"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No version change detected"
echo "skip=false" >> $GITHUB_OUTPUT
fi

build:
needs: detect-version-change
if: needs.detect-version-change.outputs.version_changed == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node.js
if: steps.check.outputs.skip != 'true'
uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: cptr/frontend/package-lock.json

- name: Build frontend
if: steps.check.outputs.skip != 'true'
working-directory: cptr/frontend
run: |
npm ci
npm run build

- name: Set up Python
if: steps.check.outputs.skip != 'true'
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install build tools
if: steps.check.outputs.skip != 'true'
run: pip install hatchling build

- name: Build package
if: steps.check.outputs.skip != 'true'
run: python -m build

- name: Upload dist artifacts
if: steps.check.outputs.skip != 'true'
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
needs: [detect-version-change, build]
needs: build
if: needs.build.outputs.skip != 'true'
runs-on: ubuntu-latest
environment: pypi # GitHub environment for deployment protection
environment: pypi
permissions:
id-token: write # required for trusted publishing (OIDC)
id-token: write
steps:
- name: Download dist artifacts
uses: actions/download-artifact@v4
Expand Down
89 changes: 19 additions & 70 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,71 +1,20 @@
# ─────────────────────────────────────────────────────────────────────────────
# Release — Create GitHub release from CHANGELOG, trigger Docker builds
# Runs only when main is merged into the release branch and pyproject.toml
# version changes.
# Runs on pushes to the release branch when version changes.
# ─────────────────────────────────────────────────────────────────────────────
name: Release

on:
pull_request:
types: [closed]
push:
branches: [release]

concurrency:
group: release-${{ github.event.pull_request.base.ref || github.ref }}
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
# ── Detect version bump in pyproject.toml ─────────────────────────────────
detect-version-change:
if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'main'
runs-on: ubuntu-latest
outputs:
version_changed: ${{ steps.check.outputs.changed }}
version: ${{ steps.check.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2

- name: Check for version bump
id: check
run: |
CURRENT=$(python3 -c "
import re, pathlib
text = pathlib.Path('pyproject.toml').read_text()
m = re.search(r'^version\s*=\s*\"([^\"]+)\"', text, re.M)
print(m.group(1))
")

git show HEAD~1:pyproject.toml > /tmp/prev_pyproject.toml 2>/dev/null || true
if [ -f /tmp/prev_pyproject.toml ]; then
PREVIOUS=$(python3 -c "
import re, pathlib
text = pathlib.Path('/tmp/prev_pyproject.toml').read_text()
m = re.search(r'^version\s*=\s*\"([^\"]+)\"', text, re.M)
print(m.group(1) if m else '')
")
else
PREVIOUS=""
fi

echo "current=$CURRENT"
echo "previous=$PREVIOUS"

if [ "$CURRENT" != "$PREVIOUS" ] && [ -n "$CURRENT" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "version=$CURRENT" >> "$GITHUB_OUTPUT"
echo "Version changed: $PREVIOUS -> $CURRENT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No version change detected"
fi

# ── Create release and trigger downstream workflows ──────────────────────
publish:
needs: detect-version-change
if: needs.detect-version-change.outputs.version_changed == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
Expand All @@ -74,31 +23,31 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Read version
id: pkg
run: |
VERSION=$(python3 -c "
import re, pathlib
text = pathlib.Path('pyproject.toml').read_text()
m = re.search(r'^version\s*=\s*\"([^\"]+)\"', text, re.M)
print(m.group(1))
")
echo "version=$VERSION" >> $GITHUB_OUTPUT

- name: Extract release notes from CHANGELOG
run: |
VER="${{ needs.detect-version-change.outputs.version }}"
VER="${{ steps.pkg.outputs.version }}"
awk "/^## \[${VER}\]/{found=1; next} /^## \[/{if(found) exit} found{print}" \
CHANGELOG.md > /tmp/release-notes.md

- name: Publish GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "v${{ needs.detect-version-change.outputs.version }}" &>/dev/null; then
echo "Release v${{ needs.detect-version-change.outputs.version }} already exists — skipping creation"
if gh release view "v${{ steps.pkg.outputs.version }}" &>/dev/null; then
echo "Release v${{ steps.pkg.outputs.version }} already exists — skipping creation"
else
gh release create "v${{ needs.detect-version-change.outputs.version }}" \
--title "v${{ needs.detect-version-change.outputs.version }}" \
gh release create "v${{ steps.pkg.outputs.version }}" \
--title "v${{ steps.pkg.outputs.version }}" \
--notes-file /tmp/release-notes.md
fi

- name: Trigger Docker build
uses: actions/github-script@v7
with:
script: |
github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'docker.yml',
ref: 'release',
})
Loading