Skip to content
Draft
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
84 changes: 77 additions & 7 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ concurrency:

permissions:
contents: write
pull-requests: write

on:
push:
Expand All @@ -19,9 +20,13 @@ on:
jobs:
format:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push' && !contains(github.event.head_commit.message, '[skip ci]')

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Python
uses: actions/setup-python@v5
Expand All @@ -45,17 +50,43 @@ jobs:
run: |
isort src tests

- name: Check for formatting changes
id: check_changes
run: |
if [[ -n $(git status --porcelain) ]]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "Formatting changes detected"
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No formatting changes"
fi

- name: Commit formatting changes
if: steps.check_changes.outputs.has_changes == 'true'
run: |
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: auto-format code [skip ci]"
git push

test:
name: ${{ matrix.platform }} (${{ matrix.python-version }})
runs-on: ${{ matrix.platform }}
needs: format
if: always() && (needs.format.result == 'success' || needs.format.result == 'skipped')
strategy:
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
with:
ref: main
- name: Pull latest changes if on main
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: git pull origin main
Comment on lines +87 to +89
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

[nitpick] The git pull can introduce inconsistency in what code is being tested. After the format job pushes changes, this pull will fetch those formatting commits. However, if testing should validate the exact commit that triggered the workflow, this pull defeats that purpose.

Consider whether you want to test the original commit (remove the pull) or always test the latest state of main (acceptable but may not reflect the actual commit being released).

Suggested change
- name: Pull latest changes if on main
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: git pull origin main

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

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

@copilot Won't the newest version (post-formatting) be tested AND released?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, exactly. The workflow ensures that the formatted version is both tested AND released. The format job commits formatting changes, then the test job pulls and tests those changes, and finally the tag-release job tags the tested (formatted) code. This maintains consistency - what gets tested is what gets released.

The git pull in the test job specifically ensures that tests run on the formatted code, not the original unformatted version.

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
Expand Down Expand Up @@ -86,24 +117,63 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version from CITATION.cff
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"



- name: Generate new version and update CITATION.cff
id: get_tag
run: |
tag=$(grep '^version:' CITATION.cff | sed 's/version: //')
# Generate PEP440 compliant date-based version tag
tag=$(python -c "from datetime import datetime, timezone; now = datetime.now(timezone.utc); print(f'{now.year}.{now.month:02d}.{now.day:02d}.{now.hour:02d}{now.minute:02d}')")
echo "tag=$tag" >> $GITHUB_OUTPUT
echo "Found version: $tag"
echo "Generated version: $tag"

# Update version in CITATION.cff
sed -i "s/^version: .*/version: $tag/" CITATION.cff

# Update date-released in CITATION.cff
today=$(date -u +%Y-%m-%d)
sed -i "s/^date-released: .*/date-released: $today/" CITATION.cff

echo "Updated CITATION.cff with version $tag and date $today"

- name: Check if tag already exists
run: |
if git rev-parse "${{ steps.get_tag.outputs.tag }}" >/dev/null 2>&1; then
if [[ -n $(git tag -l "${{ steps.get_tag.outputs.tag }}") ]]; then
echo "Tag ${{ steps.get_tag.outputs.tag }} already exists, skipping release"
exit 1
fi
- name: Create and push tag

- name: Create and push tag (before version commit)
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git rev-parse "${{ steps.get_tag.outputs.tag }}" >/dev/null 2>&1; then
echo "Tag ${{ steps.get_tag.outputs.tag }} already exists, skipping tag creation and push"
else
# Tag the current commit (the actual code) before we add the version bump commit
git tag ${{ steps.get_tag.outputs.tag }}
git push origin ${{ steps.get_tag.outputs.tag }}
fi

- name: Commit version update
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag ${{ steps.get_tag.outputs.tag }}
git push origin ${{ steps.get_tag.outputs.tag }}
if [[ -n $(git status --porcelain) ]]; then
git add CITATION.cff
git commit -m "chore: bump version to ${{ steps.get_tag.outputs.tag }} [skip ci]"
git push origin main
else
echo "No changes to commit"
fi

- name: Create GitHub Release with Notes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down