# Automated Release Workflow for AI Studio Proxy API # Creates GitHub releases with source code archives when version tags are pushed, # on pushes to main (nightly builds), or manually triggered via workflow_dispatch. # # Release Types: # - Stable: Push a tag (git tag v0.1.0 && git push origin v0.1.0) # - Nightly: Automatic on every push to main branch (rolling release) # - Manual: Actions -> Release -> Run workflow -> Enter version (e.g., v0.2.0) # The tag will be auto-created on the current HEAD if it doesn't exist. name: Release on: # Trigger on pushes to main branch for nightly builds push: branches: - main tags: - 'v*.*.*' # Matches v1.0.0, v2.1.3, etc. workflow_dispatch: inputs: version: description: 'Version to release (e.g., v0.1.0 or 0.1.0)' required: true type: string # Ensure only one release workflow runs at a time concurrency: group: release-${{ github.ref }} cancel-in-progress: false permissions: contents: write # Required for creating releases jobs: # =========================================================================== # Stable Release Job # =========================================================================== # Runs on tag pushes (v*.*.*) or manual workflow_dispatch # Creates versioned, stable releases release: name: Create Release runs-on: ubuntu-latest # Only run on tag pushes or manual triggers (not on main branch pushes) if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' steps: - name: Validate version format (manual trigger) if: github.event_name == 'workflow_dispatch' run: | VERSION_RAW="${{ github.event.inputs.version }}" if [[ "$VERSION_RAW" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then VERSION="v$VERSION_RAW" else VERSION="$VERSION_RAW" fi if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then echo "::error::Invalid version format '$VERSION_RAW'. Expected format: v1.2.3, 1.2.3, or pre-release variants like v1.2.3-beta.1" exit 1 fi echo "Version format validated: $VERSION" - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 # Full history for changelog generation # For workflow_dispatch, checkout default branch first, then verify/checkout tag # For tag pushes, checkout the tag directly ref: ${{ github.event_name == 'workflow_dispatch' && '' || github.ref }} - name: Determine version id: version run: | if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then VERSION_RAW="${{ github.event.inputs.version }}" if [[ "$VERSION_RAW" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then VERSION="v$VERSION_RAW" else VERSION="$VERSION_RAW" fi else VERSION="${GITHUB_REF#refs/tags/}" fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version_number=${VERSION#v}" >> $GITHUB_OUTPUT echo "Release version: $VERSION" - name: Setup Git for tagging (manual trigger) if: github.event_name == 'workflow_dispatch' run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - name: Create or verify tag (manual trigger) if: github.event_name == 'workflow_dispatch' run: | VERSION="${{ steps.version.outputs.version }}" # Fetch all tags to ensure we have the latest git fetch --tags --force if git rev-parse "refs/tags/$VERSION" >/dev/null 2>&1; then echo "✓ Tag $VERSION already exists" echo "Checking out existing tag..." git checkout "refs/tags/$VERSION" else echo "Tag $VERSION does not exist, creating it on current HEAD..." CURRENT_SHA=$(git rev-parse HEAD) echo "Creating tag $VERSION at commit $CURRENT_SHA" # Create the tag locally git tag -a "$VERSION" -m "Release $VERSION" # Push the tag to remote git push origin "$VERSION" echo "✓ Tag $VERSION created and pushed successfully" fi - name: Generate changelog id: changelog env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | VERSION="${{ steps.version.outputs.version }}" # Find the previous tag for comparison PREVIOUS_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+' | grep -v "^$VERSION$" | head -n 1) if [ -n "$PREVIOUS_TAG" ]; then echo "Generating changelog from $PREVIOUS_TAG to $VERSION" # Generate commit log grouped by type CHANGELOG=$(cat << 'CHANGELOG_EOF' ## What's Changed CHANGELOG_EOF ) TEMP_FILE=$(mktemp) while IFS= read -r commit_sha || [ -n "$commit_sha" ]; do if [ -z "$commit_sha" ]; then continue fi commit_msg=$(git log -1 --pretty=format:"%s" "$commit_sha") short_sha=$(git rev-parse --short "$commit_sha") git_author=$(git log -1 --pretty=format:"%an" "$commit_sha") api_response=$(gh api "repos/${{ github.repository }}/commits/$commit_sha" 2>/dev/null || echo '{}') github_username=$(echo "$api_response" | jq -r '.author.login // empty') if [ -n "$github_username" ]; then echo "- ${commit_msg} by @${github_username} (${short_sha})" >> "$TEMP_FILE" else echo "- ${commit_msg} by ${git_author} (${short_sha})" >> "$TEMP_FILE" fi done < <(git log --pretty=format:"%H" "$PREVIOUS_TAG..$VERSION" 2>/dev/null || true) if [ -s "$TEMP_FILE" ]; then CHANGELOG="$CHANGELOG $(cat "$TEMP_FILE")" else CHANGELOG="$CHANGELOG - Various improvements and updates" fi rm -f "$TEMP_FILE" CHANGELOG="$CHANGELOG **Full Changelog**: https://github.com/${{ github.repository }}/compare/$PREVIOUS_TAG...$VERSION" else echo "No previous tag found, this appears to be the first release" CHANGELOG="## What's Changed This is the first automated release in this repository. See the commit history for details." fi # Write to file for use in release echo "$CHANGELOG" > CHANGELOG.md echo "Changelog generated successfully" - name: Build release body id: release_body run: | VERSION="${{ steps.version.outputs.version }}" VERSION_NUMBER="${{ steps.version.outputs.version_number }}" cat > RELEASE_BODY.md << 'EOF' ${{ steps.version.outputs.version }} brings improvements and updates to the AI Studio Proxy API. EOF # Append the generated changelog cat CHANGELOG.md >> RELEASE_BODY.md cat >> RELEASE_BODY.md << 'EOF' --- ## Installation ### Quick Start ```bash # Clone the repository git clone https://github.com/${{ github.repository }}.git cd AIstudioProxyAPI # Install dependencies with Poetry poetry install # Run the server poetry run python server.py ``` ### Docker ```bash docker-compose -f docker/docker-compose.yml up -d ``` For full installation and configuration details, see the [README](https://github.com/${{ github.repository }}/blob/main/README.md). ## Source Code Source code archives (zip and tar.gz) are automatically attached below. --- **Thank you to all contributors!** EOF echo "Release body generated successfully" - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: name: "AI Studio Proxy API ${{ steps.version.outputs.version }}" tag_name: ${{ steps.version.outputs.version }} body_path: RELEASE_BODY.md draft: false prerelease: ${{ contains(steps.version.outputs.version, '-') }} generate_release_notes: false # We generate our own make_latest: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Release summary run: | VERSION="${{ steps.version.outputs.version }}" echo "## Release Created Successfully!" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Version:** $VERSION" >> $GITHUB_STEP_SUMMARY echo "**Release URL:** https://github.com/${{ github.repository }}/releases/tag/$VERSION" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Included Assets" >> $GITHUB_STEP_SUMMARY echo "- Source code (zip)" >> $GITHUB_STEP_SUMMARY echo "- Source code (tar.gz)" >> $GITHUB_STEP_SUMMARY # =========================================================================== # Nightly Release Job # =========================================================================== # Runs on every push to main branch (not on tags) # Creates/updates a rolling "nightly" release with the latest development code nightly: name: Create Nightly Release runs-on: ubuntu-latest # Only run on pushes to main branch, NOT on tag pushes if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/') steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 # Full history for changelog generation - name: Get build info id: build_info run: | # Get short commit SHA and date for versioning SHORT_SHA=$(git rev-parse --short HEAD) BUILD_DATE=$(date +'%Y-%m-%d') BUILD_TIME=$(date +'%H:%M:%S UTC') COMMIT_MSG=$(git log -1 --pretty=format:"%s") echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT echo "build_date=$BUILD_DATE" >> $GITHUB_OUTPUT echo "build_time=$BUILD_TIME" >> $GITHUB_OUTPUT echo "commit_msg=$COMMIT_MSG" >> $GITHUB_OUTPUT echo "Build info: $SHORT_SHA @ $BUILD_DATE $BUILD_TIME" - name: Generate recent changes id: changes env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Build nightly changelog from latest stable tag to HEAD echo "## What's Changed" > NIGHTLY_CHANGES.md echo "" >> NIGHTLY_CHANGES.md LATEST_STABLE_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) if [ -n "$LATEST_STABLE_TAG" ]; then echo "_Changes since ${LATEST_STABLE_TAG}_" >> NIGHTLY_CHANGES.md echo "" >> NIGHTLY_CHANGES.md echo "Using commit range: ${LATEST_STABLE_TAG}..HEAD" else echo "_No stable release tag found. Showing last 20 commits (first release scenario)._" >> NIGHTLY_CHANGES.md echo "" >> NIGHTLY_CHANGES.md echo "No stable tag found; falling back to last 20 commits" fi TEMP_FILE=$(mktemp) while IFS= read -r commit_sha || [ -n "$commit_sha" ]; do if [ -z "$commit_sha" ]; then continue fi commit_msg=$(git log -1 --pretty=format:"%s" "$commit_sha") short_sha=$(git rev-parse --short "$commit_sha") git_author=$(git log -1 --pretty=format:"%an" "$commit_sha") api_response=$(gh api "repos/${{ github.repository }}/commits/$commit_sha" 2>/dev/null || echo '{}') github_username=$(echo "$api_response" | jq -r '.author.login // empty') if [ -n "$github_username" ]; then echo "- ${commit_msg} by @${github_username} (${short_sha})" >> "$TEMP_FILE" else echo "- ${commit_msg} by ${git_author} (${short_sha})" >> "$TEMP_FILE" fi done < <( if [ -n "$LATEST_STABLE_TAG" ]; then git log --pretty=format:"%H" "${LATEST_STABLE_TAG}..HEAD" 2>/dev/null || true else git log --pretty=format:"%H" -20 2>/dev/null || true fi ) if [ -s "$TEMP_FILE" ]; then cat "$TEMP_FILE" >> NIGHTLY_CHANGES.md else if [ -n "$LATEST_STABLE_TAG" ]; then echo "- No commits found since ${LATEST_STABLE_TAG}" >> NIGHTLY_CHANGES.md else echo "- Various improvements and updates" >> NIGHTLY_CHANGES.md fi fi rm -f "$TEMP_FILE" echo "" >> NIGHTLY_CHANGES.md echo "Recent changes generated" - name: Build nightly release body run: | cat > NIGHTLY_BODY.md << 'EOF' ## ⚠️ Nightly Build (Development Version) **This is an automated nightly build from the `main` branch.** > **Warning**: This release may contain untested features, breaking changes, or bugs. > For stable releases, please use a [versioned release](https://github.com/${{ github.repository }}/releases?q=v&expanded=true). ### Build Information - **Commit**: `${{ steps.build_info.outputs.short_sha }}` - **Date**: ${{ steps.build_info.outputs.build_date }} ${{ steps.build_info.outputs.build_time }} - **Latest Change**: ${{ steps.build_info.outputs.commit_msg }} EOF cat NIGHTLY_CHANGES.md >> NIGHTLY_BODY.md cat >> NIGHTLY_BODY.md << 'EOF' --- ## Quick Start ```bash # Clone the repository git clone https://github.com/${{ github.repository }}.git cd AIstudioProxyAPI # Install dependencies poetry install # Run the server poetry run python server.py ``` --- *This nightly release is automatically updated on every push to the main branch.* **Thank you to all contributors!** EOF echo "Nightly release body generated" - name: Delete existing nightly release run: | # Delete existing nightly release if it exists (to avoid accumulation) echo "Checking for existing nightly release..." if gh release view nightly &>/dev/null; then echo "Deleting existing nightly release..." gh release delete nightly --yes --cleanup-tag echo "Existing nightly release deleted" else echo "No existing nightly release found" fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create nightly release uses: softprops/action-gh-release@v2 with: name: "Nightly Build (Latest)" tag_name: nightly body_path: NIGHTLY_BODY.md draft: false prerelease: true # Mark as prerelease since it's development code generate_release_notes: false make_latest: false # Don't mark nightly as "latest" - reserve that for stable releases target_commitish: ${{ github.sha }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Nightly release summary run: | echo "## Nightly Release Updated!" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Commit:** ${{ steps.build_info.outputs.short_sha }}" >> $GITHUB_STEP_SUMMARY echo "**Build Date:** ${{ steps.build_info.outputs.build_date }}" >> $GITHUB_STEP_SUMMARY echo "**Release URL:** https://github.com/${{ github.repository }}/releases/tag/nightly" >> $GITHUB_STEP_SUMMARY