Nightly Release #54
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Nightly Release | |
| on: | |
| schedule: | |
| # Run at 02:00 UTC (approximately 3–4 AM CET/CEST depending on DST) | |
| # Using a fixed 02:00 UTC time to be safe across CET/CEST transitions | |
| - cron: '0 2 * * *' | |
| workflow_dispatch: # Allow manual trigger for testing | |
| permissions: | |
| issues: write | |
| contents: write | |
| pull-requests: read | |
| jobs: | |
| check-for-changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| has_changes: ${{ steps.check.outputs.has_changes }} | |
| last_tag: ${{ steps.check.outputs.last_tag }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Fetch all history for proper comparison | |
| - name: Check for commits since last release | |
| id: check | |
| run: | | |
| # Get the last release tag | |
| LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| echo "last_tag=$LAST_TAG" >> $GITHUB_OUTPUT | |
| if [ -z "$LAST_TAG" ]; then | |
| echo "No previous tags found, will create first release" | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Check if there are commits since last tag | |
| COMMITS_SINCE=$(git rev-list ${LAST_TAG}..HEAD --count) | |
| echo "Commits since $LAST_TAG: $COMMITS_SINCE" | |
| if [ "$COMMITS_SINCE" -gt 0 ]; then | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| echo "✅ Found $COMMITS_SINCE new commits since last release" | |
| else | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "⏭️ No new commits since last release, skipping" | |
| fi | |
| run-tests-v11: | |
| needs: check-for-changes | |
| if: needs.check-for-changes.outputs.has_changes == 'true' | |
| runs-on: ubuntu-latest | |
| environment: tm1-11-cloud | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install dependencies | |
| run: | | |
| pip install -e .[pandas,dev] | |
| - name: Retrieve TM1 Connection Details | |
| run: echo "Retrieving TM1 connection details" | |
| env: | |
| TM1_CONNECTION: ${{ vars.TM1_CONNECTION }} | |
| TM1_CONNECTION_SECRET: ${{ secrets.TM1_CONNECTION_SECRET }} | |
| - name: Generate config.ini | |
| run: | | |
| python Tests/resources/generate_config.py | |
| env: | |
| TM1_CONNECTION: ${{ vars.TM1_CONNECTION }} | |
| TM1_CONNECTION_SECRET: ${{ secrets.TM1_CONNECTION_SECRET }} | |
| - name: Run integration tests | |
| run: pytest Tests/ | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-results-tm1-11-cloud | |
| path: Tests/test-reports/ | |
| retention-days: 7 | |
| run-tests-v12: | |
| needs: check-for-changes | |
| if: needs.check-for-changes.outputs.has_changes == 'true' | |
| runs-on: ubuntu-latest | |
| environment: tm1-12-cloud | |
| continue-on-error: true | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install dependencies | |
| run: | | |
| pip install -e .[pandas,dev] | |
| - name: Retrieve TM1 Connection Details | |
| run: echo "Retrieving TM1 connection details" | |
| env: | |
| TM1_CONNECTION: ${{ vars.TM1_CONNECTION }} | |
| TM1_CONNECTION_SECRET: ${{ secrets.TM1_CONNECTION_SECRET }} | |
| - name: Generate config.ini | |
| run: | | |
| python Tests/resources/generate_config.py | |
| env: | |
| TM1_CONNECTION: ${{ vars.TM1_CONNECTION }} | |
| TM1_CONNECTION_SECRET: ${{ secrets.TM1_CONNECTION_SECRET }} | |
| - name: Run integration tests | |
| run: pytest Tests/ | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-results-tm1-12-cloud | |
| path: Tests/test-reports/ | |
| retention-days: 7 | |
| determine-version: | |
| needs: [check-for-changes, run-tests-v11] | |
| runs-on: ubuntu-latest | |
| outputs: | |
| new_version: ${{ steps.version.outputs.new_version }} | |
| version_type: ${{ steps.version.outputs.version_type }} | |
| changelog: ${{ steps.changelog.outputs.changelog }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Determine version bump type | |
| id: version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| LAST_TAG="${{ needs.check-for-changes.outputs.last_tag }}" | |
| # Parse current version (default to 2.2.0 if no tags) | |
| if [ -z "$LAST_TAG" ]; then | |
| CURRENT_VERSION="2.2.0" | |
| else | |
| CURRENT_VERSION="${LAST_TAG#v}" # Remove 'v' prefix if present | |
| fi | |
| echo "Current version: $CURRENT_VERSION" | |
| # Split version into parts | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" | |
| # Get all merged PRs since last tag | |
| if [ -z "$LAST_TAG" ]; then | |
| COMMITS=$(git log --format="%H" HEAD) | |
| else | |
| COMMITS=$(git log --format="%H" ${LAST_TAG}..HEAD) | |
| fi | |
| # Determine version bump type by checking PR labels | |
| VERSION_TYPE="skip" # Default to skip (no release unless explicitly labeled) | |
| FOUND_RELEASE_LABEL=false | |
| for COMMIT in $COMMITS; do | |
| # Get PR number(s) associated with this commit (works with rebase & merge) | |
| PR_NUM=$(gh api \ | |
| -H "Accept: application/vnd.github+json" \ | |
| repos/${{ github.repository }}/commits/$COMMIT/pulls \ | |
| --jq '.[0].number' 2>/dev/null || echo "") | |
| if [ -n "$PR_NUM" ]; then | |
| echo "Checking PR #$PR_NUM for release labels..." | |
| # Get PR labels using GitHub CLI | |
| LABELS=$(gh pr view $PR_NUM --json labels --jq '.labels[].name' 2>/dev/null || echo "") | |
| if echo "$LABELS" | grep -q "release:major"; then | |
| VERSION_TYPE="major" | |
| FOUND_RELEASE_LABEL=true | |
| echo "Found release:major label in PR #$PR_NUM" | |
| break # Major takes precedence | |
| elif echo "$LABELS" | grep -q "release:minor"; then | |
| VERSION_TYPE="minor" | |
| FOUND_RELEASE_LABEL=true | |
| echo "Found release:minor label in PR #$PR_NUM" | |
| # Don't break, continue checking for major | |
| elif echo "$LABELS" | grep -q "release:patch"; then | |
| # Only set to patch if we haven't found minor yet | |
| if [ "$VERSION_TYPE" != "minor" ]; then | |
| VERSION_TYPE="patch" | |
| FOUND_RELEASE_LABEL=true | |
| fi | |
| echo "Found release:patch label in PR #$PR_NUM" | |
| elif echo "$LABELS" | grep -q "skip-release"; then | |
| echo "Found skip-release label in PR #$PR_NUM" | |
| # Explicit skip, don't change VERSION_TYPE | |
| fi | |
| fi | |
| done | |
| # If no release labels found, skip the release | |
| if [ "$FOUND_RELEASE_LABEL" = false ]; then | |
| echo "⏭️ No release labels found on any merged PRs, skipping release" | |
| VERSION_TYPE="skip" | |
| fi | |
| # Calculate new version | |
| case $VERSION_TYPE in | |
| major) | |
| NEW_VERSION="$((MAJOR + 1)).0.0" | |
| ;; | |
| minor) | |
| NEW_VERSION="${MAJOR}.$((MINOR + 1)).0" | |
| ;; | |
| patch) | |
| NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" | |
| ;; | |
| skip) | |
| NEW_VERSION="" # No version bump | |
| echo "⏭️ Skipping release - no release labels found" | |
| ;; | |
| esac | |
| echo "Version bump type: $VERSION_TYPE" | |
| echo "New version: $NEW_VERSION" | |
| echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT | |
| echo "version_type=$VERSION_TYPE" >> $GITHUB_OUTPUT | |
| - name: Generate changelog | |
| id: changelog | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| LAST_TAG="${{ needs.check-for-changes.outputs.last_tag }}" | |
| echo "Generating changelog..." | |
| # Get commits since last tag | |
| if [ -z "$LAST_TAG" ]; then | |
| COMMITS=$(git log --format="- %s (%h)" HEAD) | |
| else | |
| COMMITS=$(git log --format="- %s (%h)" ${LAST_TAG}..HEAD) | |
| fi | |
| # Create changelog | |
| CHANGELOG="## What's Changed"$'\n\n'"$COMMITS"$'\n\n'"**Full Changelog**: https://github.com/${{ github.repository }}/compare/${LAST_TAG}...${{ steps.version.outputs.new_version }}" | |
| # Save to output (handle multiline) | |
| { | |
| echo 'changelog<<EOF' | |
| echo "$CHANGELOG" | |
| echo EOF | |
| } >> $GITHUB_OUTPUT | |
| create-release: | |
| needs: [determine-version] | |
| if: needs.determine-version.outputs.version_type != 'skip' && needs.determine-version.outputs.new_version != '' | |
| runs-on: ubuntu-latest | |
| environment: release-approval # Requires manual approval | |
| outputs: | |
| upload_url: ${{ steps.create_release.outputs.upload_url }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Update version in pyproject.toml | |
| run: | | |
| NEW_VERSION="${{ needs.determine-version.outputs.new_version }}" | |
| sed -i "s/^version = .*/version = \"$NEW_VERSION\"/" pyproject.toml | |
| sed -i "s|tarball/.*\"|tarball/$NEW_VERSION\"|" pyproject.toml | |
| - name: Commit version bump | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add pyproject.toml | |
| git commit -m "chore: bump version to ${{ needs.determine-version.outputs.new_version }}" | |
| git push | |
| - name: Create and push tag | |
| run: | | |
| git fetch origin --tags | |
| git tag ${{ needs.determine-version.outputs.new_version }} | |
| git push origin ${{ needs.determine-version.outputs.new_version }} | |
| - name: Create GitHub Release | |
| id: create_release | |
| uses: softprops/action-gh-release@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tag_name: ${{ needs.determine-version.outputs.new_version }} | |
| name: Release ${{ needs.determine-version.outputs.new_version }} | |
| body: | | |
| ${{ needs.determine-version.outputs.changelog }} | |
| --- | |
| 🤖 This release was automatically created by the nightly release workflow. | |
| Install via pip: | |
| ```bash | |
| pip install --upgrade TM1py | |
| ``` | |
| draft: false | |
| prerelease: false | |
| publish-to-pypi: | |
| needs: [determine-version, create-release] | |
| runs-on: ubuntu-latest | |
| environment: pypi-publish # Requires manual approval (configure in GitHub repo settings) | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.determine-version.outputs.new_version }} | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install build tools | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install build twine | |
| - name: Build package | |
| run: python -m build | |
| - name: Check package | |
| run: twine check dist/* | |
| - name: Publish to PyPI | |
| env: | |
| TWINE_USERNAME: __token__ | |
| TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} | |
| run: twine upload dist/* | |
| - name: Success notification | |
| run: | | |
| echo "✅ Successfully published TM1py ${{ needs.determine-version.outputs.new_version }} to PyPI!" | |
| echo "Users can now install it with: pip install --upgrade TM1py" | |
| notify-on-skip: | |
| needs: [determine-version] | |
| if: needs.determine-version.outputs.version_type == 'skip' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Notify skip | |
| run: | | |
| echo "⏭️ Release skipped - no release labels found on merged PRs" | |
| echo "" | |
| echo "To trigger a release, add one of these labels to PRs before merging:" | |
| echo " - release:patch (for bug fixes)" | |
| echo " - release:minor (for new features)" | |
| echo " - release:major (for breaking changes)" | |
| echo "" | |
| echo "Or use 'skip-release' label to explicitly skip a PR" | |
| notify-on-failure: | |
| needs: [run-tests-v11, determine-version] | |
| if: ${{ always() && failure() }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Create issue on failure | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const issue = await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: '🚨 Nightly Release Failed', | |
| body: `The nightly release workflow failed. Please check the [workflow run](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) for details.`, | |
| labels: ['release', 'bug', 'automation'] | |
| }); | |
| console.log('Created issue:', issue.data.number); |