From edfefc08363e6e6347394d4398f6310884541f3c Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:02:39 +0100 Subject: [PATCH 1/7] ci: add release-please, dependabot, and PR title validation Replace manual tag-based release workflow with release-please for automated changelog generation and version bumps via conventional commits. Add Dependabot for weekly dependency scanning with auto-merge for minor/patch updates. Add PR title validation to enforce conventional commit format. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/dependabot.yml | 10 ++ .github/workflows/dependabot-auto-merge.yaml | 23 ++++ .github/workflows/pr-title.yaml | 27 ++++ .github/workflows/release.yaml | 129 +++++++------------ .release-please-config.json | 14 ++ .release-please-manifest.json | 3 + CITATION.cff | 2 +- 7 files changed, 125 insertions(+), 83 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dependabot-auto-merge.yaml create mode 100644 .github/workflows/pr-title.yaml create mode 100644 .release-please-config.json create mode 100644 .release-please-manifest.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..abd2e5876 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + - package-ecosystem: pip + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/dependabot-auto-merge.yaml b/.github/workflows/dependabot-auto-merge.yaml new file mode 100644 index 000000000..5c40c2cf8 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yaml @@ -0,0 +1,23 @@ +name: Dependabot auto-merge + +on: + pull_request: + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + name: Auto-merge minor/patch + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-24.04 + steps: + - uses: dependabot/fetch-metadata@v2 + id: metadata + + - if: steps.metadata.outputs.update-type != 'version-update:semver-major' + run: gh pr merge "$PR" --auto --squash + env: + PR: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/pr-title.yaml b/.github/workflows/pr-title.yaml new file mode 100644 index 000000000..a6f996a44 --- /dev/null +++ b/.github/workflows/pr-title.yaml @@ -0,0 +1,27 @@ +name: PR Title + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +jobs: + validate: + name: Validate conventional commit + runs-on: ubuntu-24.04 + permissions: + pull-requests: read + steps: + - uses: amannn/action-semantic-pull-request@v6 + with: + types: | + feat + fix + refactor + test + docs + chore + ci + build + perf + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index dedad171a..f573f41a7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,41 +2,23 @@ name: Release on: push: - tags: - - v*.*.* + branches: [main] + +permissions: + contents: write + pull-requests: write env: PYTHON_VERSION: "3.11" - PREPARATION_COMMIT: '[ci] prepare release ${{ github.ref_name }}' jobs: - check-preparation: - name: Check if release is prepared + release-please: + name: Release Please runs-on: ubuntu-24.04 outputs: - prepared: ${{ steps.validate.outputs.prepared }} - steps: - - uses: actions/checkout@v6 - - - name: Validate commit message - id: validate - run: | - COMMIT_MESSAGE=$(git log -1 --pretty=%B) - echo "Expected: '${{ env.PREPARATION_COMMIT }}'" - echo "Received: '$COMMIT_MESSAGE'" - - prepared="false" - if [[ "$COMMIT_MESSAGE" == "${{ env.PREPARATION_COMMIT }}" ]]; then - prepared="true" - fi - - echo "prepared=$prepared" >> $GITHUB_OUTPUT - - prepare-release: - name: Prepare release - needs: [check-preparation] - if: needs.check-preparation.outputs.prepared == 'false' - runs-on: ubuntu-24.04 + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + version: ${{ steps.release.outputs.version }} steps: - name: Generate token for Release Bot id: generate-token @@ -45,50 +27,28 @@ jobs: app-id: ${{ vars.RELEASE_BOT_APP_ID }} private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} - - uses: actions/checkout@v6 + - uses: googleapis/release-please-action@v4 + id: release with: - fetch-depth: 0 - ref: main token: ${{ steps.generate-token.outputs.token }} - - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Update CITATION.cff - run: | - VERSION=${GITHUB_REF#refs/tags/v} - DATE=$(date +%Y-%m-%d) - sed -i "s/^version: .*/version: $VERSION/" CITATION.cff - sed -i "s/^date-released: .*/date-released: $DATE/" CITATION.cff - - - name: Remove previous tag - run: | - git tag -d ${{ github.ref_name }} - git push origin --delete ${{ github.ref_name }} - - - name: Commit and re-tag - run: | - git add CITATION.cff - git commit -m "${{ env.PREPARATION_COMMIT }}" - git push origin main - git tag -a ${{ github.ref_name }} -m "${{ github.ref_name }}" - git push origin ${{ github.ref_name }} + config-file: .release-please-config.json + manifest-file: .release-please-manifest.json test: name: Run tests - needs: [check-preparation] - if: needs.check-preparation.outputs.prepared == 'true' + needs: release-please + if: needs.release-please.outputs.release_created == 'true' uses: ./.github/workflows/tests.yaml build: name: Build package - needs: [check-preparation, test] - if: needs.check-preparation.outputs.prepared == 'true' + needs: [release-please, test] + if: needs.release-please.outputs.release_created == 'true' runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 + with: + ref: ${{ needs.release-please.outputs.tag_name }} - uses: astral-sh/setup-uv@v7 with: @@ -110,7 +70,7 @@ jobs: publish-pypi: name: Publish to PyPI - needs: [build] + needs: build runs-on: ubuntu-24.04 environment: name: pypi @@ -130,7 +90,7 @@ jobs: verify-pypi: name: Verify PyPI installation - needs: [publish-pypi] + needs: [release-please, publish-pypi] runs-on: ubuntu-24.04 steps: - uses: astral-sh/setup-uv@v7 @@ -143,7 +103,7 @@ jobs: - name: Verify installation run: | - VERSION=${GITHUB_REF#refs/tags/v} + VERSION=${{ needs.release-please.outputs.version }} for delay in 10 20 40 60 90 120 180 300; do sleep $delay @@ -159,35 +119,40 @@ jobs: echo "Failed to verify PyPI installation" exit 1 - create-release: - name: Create GitHub release - needs: [verify-pypi] + update-citation-date: + name: Update CITATION.cff date + needs: [release-please, publish-pypi] + if: needs.release-please.outputs.release_created == 'true' runs-on: ubuntu-24.04 - permissions: - contents: write steps: - - uses: actions/checkout@v6 + - name: Generate token for Release Bot + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.RELEASE_BOT_APP_ID }} + private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} - - uses: actions/setup-python@v6 + - uses: actions/checkout@v6 with: - python-version: ${{ env.PYTHON_VERSION }} + ref: main + token: ${{ steps.generate-token.outputs.token }} - - name: Extract release notes + - name: Update date-released run: | - VERSION=${GITHUB_REF#refs/tags/v} - python scripts/extract_release_notes.py $VERSION > current_release_notes.md + DATE=$(date +%Y-%m-%d) + sed -i "s/^date-released: .*/date-released: $DATE/" CITATION.cff - - uses: softprops/action-gh-release@v2 - with: - body_path: current_release_notes.md - draft: false - prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }} - generate_release_notes: true + - name: Commit and push + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add CITATION.cff + git diff --staged --quiet || git commit -m "chore: update CITATION.cff date-released" && git push origin main deploy-docs: name: Deploy documentation - needs: [create-release] + needs: [release-please, verify-pypi] uses: ./.github/workflows/docs.yaml with: deploy: true - version: ${{ github.ref_name }} + version: ${{ needs.release-please.outputs.tag_name }} diff --git a/.release-please-config.json b/.release-please-config.json new file mode 100644 index 000000000..550d09609 --- /dev/null +++ b/.release-please-config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "include-component-in-tag": false, + "packages": { + ".": { + "release-type": "python", + "package-name": "flixopt", + "changelog-path": "CHANGELOG.md", + "extra-files": [ + "CITATION.cff" + ] + } + } +} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 000000000..8ace91523 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "6.1.0" +} diff --git a/CITATION.cff b/CITATION.cff index 889cffa04..44bfdd7ef 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -2,7 +2,7 @@ cff-version: 1.2.0 message: "If you use this software, please cite it as below and consider citing the related publication." type: software title: "flixopt" -version: 6.2.0rc0 +version: 6.2.0rc0 # x-release-please-version date-released: 2026-03-23 url: "https://github.com/flixOpt/flixopt" repository-code: "https://github.com/flixOpt/flixopt" From 899067cfeec9dfd3d5d9ffe2756bd7ef42a981c3 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:07:46 +0100 Subject: [PATCH 2/7] chore: remove Renovate in favor of Dependabot Co-Authored-By: Claude Opus 4.6 (1M context) --- MANIFEST.in | 1 - pyproject.toml | 2 +- renovate.json | 51 -------------------------------------------------- 3 files changed, 1 insertion(+), 53 deletions(-) delete mode 100644 renovate.json diff --git a/MANIFEST.in b/MANIFEST.in index f6611cdcb..6b55b8523 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -20,6 +20,5 @@ prune .venv prune venv exclude .gitignore exclude .pre-commit-config.yaml -exclude renovate.json exclude mkdocs.yml exclude test_package.sh diff --git a/pyproject.toml b/pyproject.toml index afec83e45..fec7eee48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,7 @@ exclude = ["tests*", "docs*"] include-package-data = true [tool.setuptools.exclude-package-data] -"*" = ["*.md", ".git*", "*.ipynb", "renovate.json"] +"*" = ["*.md", ".git*", "*.ipynb"] [tool.setuptools_scm] version_scheme = "post-release" diff --git a/renovate.json b/renovate.json deleted file mode 100644 index ded1fbf17..000000000 --- a/renovate.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "extends": [ - ":dependencyDashboard", - ":semanticPrefixFixDepsChoreOthers", - ":ignoreModulesAndTests", - ":semanticCommits", - "group:monorepos", - "group:recommended", - "mergeConfidence:age-confidence-badges", - "replacements:all", - "workarounds:all", - "schedule:earlyMondays" - ], - "automerge": false, - "labels": ["dependencies"], - "rangeStrategy": "widen", - "minimumReleaseAge": "7 days", - "packageRules": [ - { - "description": "Group and automerge dev and docs dependencies", - "matchDepTypes": ["dev", "docs"], - "groupName": "dev dependencies", - "rangeStrategy": "pin", - "minimumReleaseAge": "14 days", - "automerge": true, - "automergeType": "pr", - "separateMinorPatch": false - }, - { - "matchUpdateTypes": ["patch"], - "matchCurrentVersion": "!/^0/", - "automerge": true, - "automergeType": "pr" - }, - { - "description": "CalVer packages (xarray, dask) can have breaking changes in any release - never automerge, longer release age", - "matchPackageNames": ["xarray", "dask"], - "minimumReleaseAge": "14 days", - "schedule": ["* * * * *"], - "labels": ["calver", "breaking-change-risk", "dependencies"], - "prPriority": 10 - }, - { - "description": "Automerge ruff patches despite 0.x version", - "matchPackageNames": ["ruff"], - "matchUpdateTypes": ["patch"], - "automerge": true, - "automergeType": "pr" - } - ] -} From 0f75733ba227bc4726d5abad56a46e6c124c412f Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:03:43 +0100 Subject: [PATCH 3/7] fix: address blueprint validation gaps - pr-title.yaml: add release-please branch trigger so the check passes on release-please PRs (required if "PR Title" is a required check) - release-please-manifest: seed at 6.2.0rc0 (latest tag, not 6.1.0) - release.yaml: fix shell precedence bug in update-citation-date job - Remove dead scripts/extract_release_notes.py (no longer used) Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/pr-title.yaml | 3 ++ .github/workflows/release.yaml | 2 +- .release-please-manifest.json | 2 +- scripts/extract_release_notes.py | 55 -------------------------------- 4 files changed, 5 insertions(+), 57 deletions(-) delete mode 100644 scripts/extract_release_notes.py diff --git a/.github/workflows/pr-title.yaml b/.github/workflows/pr-title.yaml index a6f996a44..781740e5b 100644 --- a/.github/workflows/pr-title.yaml +++ b/.github/workflows/pr-title.yaml @@ -1,12 +1,15 @@ name: PR Title on: + push: + branches: ["release-please--**"] pull_request: types: [opened, edited, synchronize, reopened] jobs: validate: name: Validate conventional commit + if: github.event_name == 'pull_request' runs-on: ubuntu-24.04 permissions: pull-requests: read diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f573f41a7..b0480cf1d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -147,7 +147,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add CITATION.cff - git diff --staged --quiet || git commit -m "chore: update CITATION.cff date-released" && git push origin main + git diff --staged --quiet || (git commit -m "chore: update CITATION.cff date-released" && git push origin main) deploy-docs: name: Deploy documentation diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8ace91523..dc98dff27 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "6.1.0" + ".": "6.2.0rc0" } diff --git a/scripts/extract_release_notes.py b/scripts/extract_release_notes.py deleted file mode 100644 index 9c31e18d1..000000000 --- a/scripts/extract_release_notes.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -""" -Extract release notes from CHANGELOG.md for a specific version. -Usage: python extract_release_notes.py -""" - -import re -import sys -from pathlib import Path - - -def extract_release_notes(version: str) -> str: - """Extract release notes for a specific version from CHANGELOG.md. - - For pre-release versions (rc, alpha, beta), falls back to base version notes. - E.g., 6.0.0rc5 will use notes from 6.0.0 if no specific rc5 entry exists. - """ - changelog_path = Path('CHANGELOG.md') - - if not changelog_path.exists(): - print('❌ Error: CHANGELOG.md not found', file=sys.stderr) - sys.exit(1) - - content = changelog_path.read_text(encoding='utf-8') - - # Try exact version first, then fall back to base version for pre-releases - versions_to_try = [version] - base_version = re.sub(r'(rc|alpha|beta)\d*$', '', version) - if base_version != version: - versions_to_try.append(base_version) - - for v in versions_to_try: - # Pattern to match version section: ## [2.1.2] - 2025-06-14 or ## [6.0.0] - Upcoming - pattern = rf'## \[{re.escape(v)}\] - [^\n]+\n(.*?)(?=\n## \[|\n\[Unreleased\]|\Z)' - match = re.search(pattern, content, re.DOTALL) - if match: - return match.group(1).strip() - - print(f"❌ Error: No release notes found for version '{version}'", file=sys.stderr) - sys.exit(1) - - -def main(): - if len(sys.argv) != 2: - print('Usage: python extract_release_notes.py ') - print('Example: python extract_release_notes.py 2.1.2') - sys.exit(1) - - version = sys.argv[1] - release_notes = extract_release_notes(version) - print(release_notes) - - -if __name__ == '__main__': - main() From 63a37a55f8f3f4769456143ac9928dbc64bc782a Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:04:40 +0100 Subject: [PATCH 4/7] revert release please base version --- .release-please-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index dc98dff27..8ace91523 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "6.2.0rc0" + ".": "6.1.0" } From 29d99d0048f1492c27f0b3dcb67e22c7e8f598fd Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:26:14 +0100 Subject: [PATCH 5/7] fix: add tag-triggered prerelease support, switch to simple release type - Add `v*.*.*` tag trigger so manually pushed tags run the full build/publish pipeline and create a GitHub prerelease - Introduce `release-info` hub job that unifies release-please and tag-push paths for downstream jobs - Switch release-type from "python" to "simple" (setuptools_scm derives version from git tags, no static version files to bump) - Skip docs deploy and citation update for prereleases - pr-title.yaml: drop redundant `synchronize` trigger, add `revert` and `style` types Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/pr-title.yaml | 4 +- .github/workflows/release.yaml | 74 ++++++++++++++++++++++++++++----- .release-please-config.json | 2 +- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pr-title.yaml b/.github/workflows/pr-title.yaml index 781740e5b..3a358dc31 100644 --- a/.github/workflows/pr-title.yaml +++ b/.github/workflows/pr-title.yaml @@ -4,7 +4,7 @@ on: push: branches: ["release-please--**"] pull_request: - types: [opened, edited, synchronize, reopened] + types: [opened, edited, reopened] jobs: validate: @@ -26,5 +26,7 @@ jobs: ci build perf + revert + style env: GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b0480cf1d..3b2cfbab6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,6 +3,8 @@ name: Release on: push: branches: [main] + tags: + - "v*.*.*" permissions: contents: write @@ -14,6 +16,7 @@ env: jobs: release-please: name: Release Please + if: github.ref_type == 'branch' runs-on: ubuntu-24.04 outputs: release_created: ${{ steps.release.outputs.release_created }} @@ -34,21 +37,55 @@ jobs: config-file: .release-please-config.json manifest-file: .release-please-manifest.json + release-info: + name: Release info + runs-on: ubuntu-24.04 + needs: release-please + if: always() + outputs: + should_release: ${{ steps.info.outputs.should_release }} + tag_name: ${{ steps.info.outputs.tag_name }} + version: ${{ steps.info.outputs.version }} + is_prerelease: ${{ steps.info.outputs.is_prerelease }} + steps: + - name: Determine release info + id: info + run: | + if [[ "${{ github.ref_type }}" == "tag" ]]; then + TAG="${{ github.ref_name }}" + VERSION="${TAG#v}" + echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "tag_name=$TAG" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + if [[ "$VERSION" =~ (rc|alpha|beta) ]]; then + echo "is_prerelease=true" >> "$GITHUB_OUTPUT" + else + echo "is_prerelease=false" >> "$GITHUB_OUTPUT" + fi + elif [[ "${{ needs.release-please.outputs.release_created }}" == "true" ]]; then + echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "tag_name=${{ needs.release-please.outputs.tag_name }}" >> "$GITHUB_OUTPUT" + echo "version=${{ needs.release-please.outputs.version }}" >> "$GITHUB_OUTPUT" + echo "is_prerelease=false" >> "$GITHUB_OUTPUT" + else + echo "should_release=false" >> "$GITHUB_OUTPUT" + fi + test: name: Run tests - needs: release-please - if: needs.release-please.outputs.release_created == 'true' + needs: release-info + if: needs.release-info.outputs.should_release == 'true' uses: ./.github/workflows/tests.yaml build: name: Build package - needs: [release-please, test] - if: needs.release-please.outputs.release_created == 'true' + needs: [release-info, test] + if: needs.release-info.outputs.should_release == 'true' runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 with: - ref: ${{ needs.release-please.outputs.tag_name }} + ref: ${{ needs.release-info.outputs.tag_name }} - uses: astral-sh/setup-uv@v7 with: @@ -90,7 +127,7 @@ jobs: verify-pypi: name: Verify PyPI installation - needs: [release-please, publish-pypi] + needs: [release-info, publish-pypi] runs-on: ubuntu-24.04 steps: - uses: astral-sh/setup-uv@v7 @@ -103,7 +140,7 @@ jobs: - name: Verify installation run: | - VERSION=${{ needs.release-please.outputs.version }} + VERSION=${{ needs.release-info.outputs.version }} for delay in 10 20 40 60 90 120 180 300; do sleep $delay @@ -119,10 +156,24 @@ jobs: echo "Failed to verify PyPI installation" exit 1 + create-prerelease: + name: Create GitHub prerelease + needs: [release-info, verify-pypi] + if: needs.release-info.outputs.is_prerelease == 'true' + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + + - uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.release-info.outputs.tag_name }} + prerelease: true + generate_release_notes: true + update-citation-date: name: Update CITATION.cff date - needs: [release-please, publish-pypi] - if: needs.release-please.outputs.release_created == 'true' + needs: [release-info, publish-pypi] + if: needs.release-info.outputs.is_prerelease == 'false' runs-on: ubuntu-24.04 steps: - name: Generate token for Release Bot @@ -151,8 +202,9 @@ jobs: deploy-docs: name: Deploy documentation - needs: [release-please, verify-pypi] + needs: [release-info, verify-pypi] + if: needs.release-info.outputs.is_prerelease == 'false' uses: ./.github/workflows/docs.yaml with: deploy: true - version: ${{ needs.release-please.outputs.tag_name }} + version: ${{ needs.release-info.outputs.tag_name }} diff --git a/.release-please-config.json b/.release-please-config.json index 550d09609..ed357ff3a 100644 --- a/.release-please-config.json +++ b/.release-please-config.json @@ -3,7 +3,7 @@ "include-component-in-tag": false, "packages": { ".": { - "release-type": "python", + "release-type": "simple", "package-name": "flixopt", "changelog-path": "CHANGELOG.md", "extra-files": [ From edacd0cbf80ae483d111959dc6d58406050a3ffe Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:31:40 +0100 Subject: [PATCH 6/7] refactor: split release into release.yaml + publish.yaml Adopt the blueprint pattern: release.yaml handles release-please on main, publish.yaml is a reusable workflow with two triggers: - workflow_call: called by release-please for stable releases - push tags v*: manual tag push for prereleases The github-release job only runs on tag push (release-please creates its own release). Prerelease detection uses PEP 440 patterns (rc/alpha/beta) instead of semver hyphens. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/publish.yaml | 120 ++++++++++++++++++++++++++ .github/workflows/release.yaml | 151 +++------------------------------ 2 files changed, 130 insertions(+), 141 deletions(-) create mode 100644 .github/workflows/publish.yaml diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 000000000..1155d04a8 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,120 @@ +name: Publish + +on: + push: + tags: ["v*"] + workflow_call: + inputs: + tag: + required: true + type: string + +env: + PYTHON_VERSION: "3.11" + +jobs: + test: + name: Run tests + uses: ./.github/workflows/tests.yaml + + build: + name: Build package + needs: test + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag || github.ref_name }} + fetch-depth: 0 + + - uses: astral-sh/setup-uv@v7 + with: + version: "0.10.9" + enable-cache: true + + - uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Build package + run: uv build + + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + retention-days: 7 + + publish-pypi: + name: Publish to PyPI + needs: build + runs-on: ubuntu-24.04 + environment: + name: pypi + url: https://pypi.org/p/flixopt + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true + + verify-pypi: + name: Verify PyPI installation + needs: publish-pypi + runs-on: ubuntu-24.04 + steps: + - uses: astral-sh/setup-uv@v7 + with: + version: "0.10.9" + + - uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Verify installation + run: | + VERSION="${TAG#v}" + + for delay in 10 20 40 60 90 120 180 300; do + sleep $delay + echo "Attempting installation (waited ${delay}s)..." + + if uv pip install --system --index-url https://pypi.org/simple/ "flixopt==$VERSION" && \ + python -c "from importlib.metadata import version; assert version('flixopt') == '$VERSION'"; then + echo "PyPI installation successful!" + exit 0 + fi + done + + echo "Failed to verify PyPI installation" + exit 1 + env: + TAG: ${{ inputs.tag || github.ref_name }} + + github-release: + name: Create GitHub release + if: github.event_name == 'push' + needs: verify-pypi + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + + - name: Create GitHub release + run: | + if [[ "$TAG" =~ (rc|alpha|beta) ]]; then + gh release create "$TAG" --generate-notes --prerelease + else + gh release create "$TAG" --generate-notes + fi + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.ref_name }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3b2cfbab6..c73df4988 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,25 +3,18 @@ name: Release on: push: branches: [main] - tags: - - "v*.*.*" permissions: contents: write pull-requests: write -env: - PYTHON_VERSION: "3.11" - jobs: release-please: name: Release Please - if: github.ref_type == 'branch' runs-on: ubuntu-24.04 outputs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} - version: ${{ steps.release.outputs.version }} steps: - name: Generate token for Release Bot id: generate-token @@ -37,143 +30,20 @@ jobs: config-file: .release-please-config.json manifest-file: .release-please-manifest.json - release-info: - name: Release info - runs-on: ubuntu-24.04 + publish: + name: Publish needs: release-please - if: always() - outputs: - should_release: ${{ steps.info.outputs.should_release }} - tag_name: ${{ steps.info.outputs.tag_name }} - version: ${{ steps.info.outputs.version }} - is_prerelease: ${{ steps.info.outputs.is_prerelease }} - steps: - - name: Determine release info - id: info - run: | - if [[ "${{ github.ref_type }}" == "tag" ]]; then - TAG="${{ github.ref_name }}" - VERSION="${TAG#v}" - echo "should_release=true" >> "$GITHUB_OUTPUT" - echo "tag_name=$TAG" >> "$GITHUB_OUTPUT" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - if [[ "$VERSION" =~ (rc|alpha|beta) ]]; then - echo "is_prerelease=true" >> "$GITHUB_OUTPUT" - else - echo "is_prerelease=false" >> "$GITHUB_OUTPUT" - fi - elif [[ "${{ needs.release-please.outputs.release_created }}" == "true" ]]; then - echo "should_release=true" >> "$GITHUB_OUTPUT" - echo "tag_name=${{ needs.release-please.outputs.tag_name }}" >> "$GITHUB_OUTPUT" - echo "version=${{ needs.release-please.outputs.version }}" >> "$GITHUB_OUTPUT" - echo "is_prerelease=false" >> "$GITHUB_OUTPUT" - else - echo "should_release=false" >> "$GITHUB_OUTPUT" - fi - - test: - name: Run tests - needs: release-info - if: needs.release-info.outputs.should_release == 'true' - uses: ./.github/workflows/tests.yaml - - build: - name: Build package - needs: [release-info, test] - if: needs.release-info.outputs.should_release == 'true' - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - with: - ref: ${{ needs.release-info.outputs.tag_name }} - - - uses: astral-sh/setup-uv@v7 - with: - version: "0.10.9" - enable-cache: true - - - uses: actions/setup-python@v6 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Build package - run: uv build - - - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/ - retention-days: 7 - - publish-pypi: - name: Publish to PyPI - needs: build - runs-on: ubuntu-24.04 - environment: - name: pypi - url: https://pypi.org/p/flixopt + if: needs.release-please.outputs.release_created permissions: id-token: write - steps: - - uses: actions/download-artifact@v4 - with: - name: dist - path: dist/ - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - skip-existing: true - - verify-pypi: - name: Verify PyPI installation - needs: [release-info, publish-pypi] - runs-on: ubuntu-24.04 - steps: - - uses: astral-sh/setup-uv@v7 - with: - version: "0.10.9" - - - uses: actions/setup-python@v6 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Verify installation - run: | - VERSION=${{ needs.release-info.outputs.version }} - - for delay in 10 20 40 60 90 120 180 300; do - sleep $delay - echo "Attempting installation (waited ${delay}s)..." - - if uv pip install --system --index-url https://pypi.org/simple/ "flixopt==$VERSION" && \ - python -c "from importlib.metadata import version; assert version('flixopt') == '$VERSION'"; then - echo "PyPI installation successful!" - exit 0 - fi - done - - echo "Failed to verify PyPI installation" - exit 1 - - create-prerelease: - name: Create GitHub prerelease - needs: [release-info, verify-pypi] - if: needs.release-info.outputs.is_prerelease == 'true' - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ needs.release-info.outputs.tag_name }} - prerelease: true - generate_release_notes: true + contents: write + uses: ./.github/workflows/publish.yaml + with: + tag: ${{ needs.release-please.outputs.tag_name }} update-citation-date: name: Update CITATION.cff date - needs: [release-info, publish-pypi] - if: needs.release-info.outputs.is_prerelease == 'false' + needs: publish runs-on: ubuntu-24.04 steps: - name: Generate token for Release Bot @@ -202,9 +72,8 @@ jobs: deploy-docs: name: Deploy documentation - needs: [release-info, verify-pypi] - if: needs.release-info.outputs.is_prerelease == 'false' + needs: [release-please, publish] uses: ./.github/workflows/docs.yaml with: deploy: true - version: ${{ needs.release-info.outputs.tag_name }} + version: ${{ needs.release-please.outputs.tag_name }} From 60a37ca4fdee607fdaa618a8a2d17fa1468e5582 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:37:29 +0100 Subject: [PATCH 7/7] chore: address review feedback on publish.yaml - Start verify-pypi retry at 30s instead of 10s (PyPI propagation) - Add clarifying comment on github-release condition Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/publish.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 1155d04a8..fc44659ab 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -82,7 +82,7 @@ jobs: run: | VERSION="${TAG#v}" - for delay in 10 20 40 60 90 120 180 300; do + for delay in 30 60 90 120 180 300; do sleep $delay echo "Attempting installation (waited ${delay}s)..." @@ -100,6 +100,7 @@ jobs: github-release: name: Create GitHub release + # Only on tag push — release-please creates its own release via workflow_call if: github.event_name == 'push' needs: verify-pypi runs-on: ubuntu-24.04