Release #29
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: Release | |
| # MANUAL TRIGGER ONLY - Creates a new release with NuGet package and Gallery apps | |
| # Go to Actions → Release → Run workflow → Enter version (e.g., 1.0.6) | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to release (e.g., 1.0.6) - without "v" prefix' | |
| required: true | |
| type: string | |
| skip_nuget: | |
| description: 'Skip NuGet publish (useful for re-running failed releases)' | |
| required: false | |
| type: boolean | |
| default: false | |
| skip_tag: | |
| description: 'Skip tag creation (assumes you already pushed the tag manually)' | |
| required: false | |
| type: boolean | |
| default: true | |
| jobs: | |
| validate: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.validate.outputs.version }} | |
| tag: ${{ steps.validate.outputs.tag }} | |
| steps: | |
| - name: Validate version format | |
| id: validate | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "::error::Invalid version format. Use semantic versioning (e.g., 1.0.6)" | |
| exit 1 | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=v$VERSION" >> $GITHUB_OUTPUT | |
| echo "✅ Version: $VERSION, Tag: v$VERSION" | |
| build-library: | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| - name: Verify localization RESX keys | |
| run: python Utils/check_resx_keys.py Flowery.NET/Localization/FloweryStrings.resx Flowery.NET/Localization | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Verify version in csproj | |
| run: | | |
| CSPROJ_VERSION=$(grep -oP '(?<=<Version>)[^<]+' Flowery.NET/Flowery.NET.csproj) | |
| if [ "$CSPROJ_VERSION" != "${{ needs.validate.outputs.version }}" ]; then | |
| echo "::error::Version mismatch! csproj has $CSPROJ_VERSION but you specified ${{ needs.validate.outputs.version }}" | |
| echo "::error::Please update Flowery.NET/Flowery.NET.csproj first!" | |
| exit 1 | |
| fi | |
| echo "✅ Version matches: $CSPROJ_VERSION" | |
| - name: Build library | |
| run: dotnet build Flowery.NET/Flowery.NET.csproj -c Release -o ./build-output | |
| - name: Create NuGet package | |
| run: dotnet pack Flowery.NET/Flowery.NET.csproj -c Release -o ./artifacts | |
| - name: Create library zip | |
| run: | | |
| mkdir -p ./artifacts/lib | |
| cp ./build-output/Flowery.NET.dll ./artifacts/lib/ | |
| cp ./build-output/Flowery.NET.pdb ./artifacts/lib/ | |
| cd ./artifacts/lib | |
| zip ../Flowery.NET-lib.zip * | |
| - name: Upload library artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: library-artifacts | |
| path: | | |
| ./artifacts/*.nupkg | |
| ./artifacts/Flowery.NET-lib.zip | |
| build-gallery: | |
| needs: validate | |
| strategy: | |
| matrix: | |
| include: | |
| - os: windows-latest | |
| rid: win-x64 | |
| artifact_name: Flowery.Gallery-Windows-x64 | |
| - os: ubuntu-latest | |
| rid: linux-x64 | |
| artifact_name: Flowery.Gallery-Linux-x64 | |
| - os: macos-latest | |
| rid: osx-x64 | |
| artifact_name: Flowery.Gallery-macOS-x64 | |
| - os: macos-latest | |
| rid: osx-arm64 | |
| artifact_name: Flowery.Gallery-macOS-arm64 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Publish Gallery App | |
| shell: bash | |
| run: | | |
| dotnet publish Flowery.NET.Gallery/Flowery.NET.Gallery.csproj \ | |
| -c Release \ | |
| -r ${{ matrix.rid }} \ | |
| --self-contained true \ | |
| -p:PublishSingleFile=true \ | |
| -p:IncludeNativeLibrariesForSelfExtract=true \ | |
| -p:EnableCompressionInSingleFile=true \ | |
| -o ./publish | |
| - name: Zip Windows artifact | |
| if: matrix.os == 'windows-latest' | |
| run: Compress-Archive -Path ./publish/* -DestinationPath ./${{ matrix.artifact_name }}.zip | |
| shell: pwsh | |
| - name: Tar Linux/macOS artifact | |
| if: matrix.os != 'windows-latest' | |
| run: | | |
| chmod +x ./publish/Flowery.NET.Gallery | |
| tar -czvf ./${{ matrix.artifact_name }}.tar.gz -C ./publish . | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: | | |
| ./${{ matrix.artifact_name }}.zip | |
| ./${{ matrix.artifact_name }}.tar.gz | |
| if-no-files-found: ignore | |
| publish: | |
| needs: [validate, build-library, build-gallery] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: ./release-artifacts | |
| - name: Collect artifacts | |
| run: | | |
| mkdir -p ./final | |
| find ./release-artifacts -name "*.nupkg" -exec cp {} ./final/ \; | |
| find ./release-artifacts -name "*.zip" -exec cp {} ./final/ \; | |
| find ./release-artifacts -name "*.tar.gz" -exec cp {} ./final/ \; | |
| echo "=== Release artifacts ===" | |
| ls -la ./final/ | |
| - name: Extract changelog | |
| id: changelog | |
| run: | | |
| VERSION="${{ needs.validate.outputs.version }}" | |
| # Extract the section for this version from CHANGELOG.md | |
| # Matches from "## [X.Y.Z]" until the next "## [" or end of file | |
| CHANGELOG=$(awk -v ver="$VERSION" ' | |
| /^## \[/ { | |
| if (found) exit | |
| if ($0 ~ "\\[" ver "\\]") found=1 | |
| } | |
| found && !/^## \[/ { print } | |
| ' CHANGELOG.md) | |
| if [ -z "$CHANGELOG" ]; then | |
| echo "⚠️ No changelog entry found for version $VERSION" | |
| CHANGELOG="No changelog entry for this version." | |
| fi | |
| # Write to file for multiline support | |
| echo "$CHANGELOG" > changelog_section.md | |
| echo "✅ Extracted changelog for v$VERSION" | |
| - name: Publish to NuGet | |
| if: ${{ inputs.skip_nuget != true }} | |
| run: | | |
| dotnet nuget push ./final/*.nupkg \ | |
| -k ${{ secrets.NUGET_API_KEY }} \ | |
| -s https://api.nuget.org/v3/index.json \ | |
| --skip-duplicate | |
| echo "✅ Published to NuGet.org" | |
| - name: Create and push tag | |
| if: ${{ inputs.skip_tag != true }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag ${{ needs.validate.outputs.tag }} | |
| git push origin ${{ needs.validate.outputs.tag }} | |
| echo "✅ Created tag: ${{ needs.validate.outputs.tag }}" | |
| - name: Skip tag creation | |
| if: ${{ inputs.skip_tag == true }} | |
| run: echo "⏭️ Skipping tag creation (tag already exists)" | |
| - name: Build release notes | |
| run: | | |
| cat > release_notes.md << 'HEADER' | |
| ## 📋 What's Changed | |
| HEADER | |
| cat changelog_section.md >> release_notes.md | |
| cat >> release_notes.md << 'FOOTER' | |
| --- | |
| ## 📦 Library | |
| | Asset | Description | | |
| |-------|-------------| | |
| FOOTER | |
| echo "| \`Flowery.NET.${{ needs.validate.outputs.version }}.nupkg\` | NuGet package (also on [nuget.org](https://www.nuget.org/packages/Flowery.NET)) |" >> release_notes.md | |
| echo "| \`Flowery.NET-lib.zip\` | Compiled DLL and PDB |" >> release_notes.md | |
| cat >> release_notes.md << 'GALLERY' | |
| ## 🌸 Gallery Demo App | |
| Self-contained executables - no .NET installation required! | |
| | Platform | Download | | |
| |----------|----------| | |
| | Windows x64 | `Flowery.Gallery-Windows-x64.zip` | | |
| | Linux x64 | `Flowery.Gallery-Linux-x64.tar.gz` | | |
| | macOS Intel | `Flowery.Gallery-macOS-x64.tar.gz` | | |
| | macOS Apple Silicon | `Flowery.Gallery-macOS-arm64.tar.gz` | | |
| GALLERY | |
| echo "=== Release Notes ===" | |
| cat release_notes.md | |
| - name: Load custom release notes | |
| id: release_body | |
| run: | | |
| { | |
| echo 'body<<EOF' | |
| cat release_notes.md | |
| echo '' | |
| echo 'EOF' | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ needs.validate.outputs.tag }} | |
| name: "v${{ needs.validate.outputs.version }}" | |
| files: ./final/* | |
| generate_release_notes: true | |
| body: ${{ steps.release_body.outputs.body }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Summary | |
| run: | | |
| echo "## 🎉 Release Complete!" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version:** ${{ needs.validate.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Tag:** ${{ needs.validate.outputs.tag }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **NuGet:** https://www.nuget.org/packages/Flowery.NET/${{ needs.validate.outputs.version }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Release:** ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ needs.validate.outputs.tag }}" >> $GITHUB_STEP_SUMMARY |