-
Notifications
You must be signed in to change notification settings - Fork 55
Add Prepare Release GitHub Action for automated release kickoff #686
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| # Prepare Release Workflow | ||
| # This workflow automates the release preparation process: | ||
| # 1. Updates the version in eng/targets/Release.props | ||
| # 2. Generates changelog from git diff between main and last release tag | ||
| # 3. Updates CHANGELOG.md with the new version section | ||
| # 4. Creates a release branch and tag | ||
| # After the workflow completes, manually create a PR from the release branch to main. | ||
|
|
||
| name: Prepare Release | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: 'Release version (e.g., 1.24.0 or 1.24.0-preview.1). Leave empty to auto-increment (patch for stable, pre-release number for pre-release).' | ||
| required: false | ||
| type: string | ||
|
|
||
| permissions: | ||
| contents: write | ||
|
|
||
| jobs: | ||
| prepare-release: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 # Full history needed for git diff and tags | ||
|
|
||
| - name: Get current version | ||
| id: get-current-version | ||
| run: | | ||
| # Extract VersionPrefix and VersionSuffix from Release.props | ||
| VERSION_PREFIX=$(grep -oP '(?<=<VersionPrefix>)[^<]+' eng/targets/Release.props) | ||
| VERSION_SUFFIX=$(grep -oP '(?<=<VersionSuffix>)[^<]*' eng/targets/Release.props) | ||
|
|
||
| if [ -n "$VERSION_SUFFIX" ]; then | ||
| CURRENT_VERSION="${VERSION_PREFIX}-${VERSION_SUFFIX}" | ||
| else | ||
| CURRENT_VERSION="$VERSION_PREFIX" | ||
| fi | ||
|
|
||
| echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT | ||
| echo "Current version: $CURRENT_VERSION" | ||
|
|
||
| - name: Calculate next version | ||
| id: calc-version | ||
| run: | | ||
| INPUT_VERSION="${{ github.event.inputs.version }}" | ||
| CURRENT_VERSION="${{ steps.get-current-version.outputs.current_version }}" | ||
|
|
||
| if [ -n "$INPUT_VERSION" ]; then | ||
| NEW_VERSION="$INPUT_VERSION" | ||
| else | ||
| # Auto-increment: parse current version and bump appropriately | ||
| # Handle pre-release versions like 1.23.0-preview.1 -> 1.23.0-preview.2 | ||
| # Handle stable versions like 1.23.0 -> 1.23.1 | ||
| NEW_VERSION=$(python3 -c " | ||
| import re, sys | ||
| v = '$CURRENT_VERSION' | ||
| m = re.match(r'^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z]+)\.(\d+))?$', v) | ||
| if not m: | ||
| print(v) | ||
| sys.exit(0) | ||
| major, minor, patch, pre_type, pre_num = m.groups() | ||
| if pre_type and pre_num: | ||
| print(f'{major}.{minor}.{patch}-{pre_type}.{int(pre_num) + 1}') | ||
| else: | ||
| print(f'{major}.{minor}.{int(patch) + 1}') | ||
| ") | ||
| fi | ||
|
|
||
| echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT | ||
| echo "New version: $NEW_VERSION" | ||
|
|
||
| - name: Get latest release tag | ||
| id: get-latest-tag | ||
| run: | | ||
| NEW_VERSION="${{ steps.calc-version.outputs.new_version }}" | ||
| # Get the latest tag that looks like a version (v*), excluding the | ||
| # tag being created so that re-runs don't use it as the baseline. | ||
| LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | \ | ||
| grep -v "^v${NEW_VERSION}$" | head -n 1) | ||
| if [ -z "$LATEST_TAG" ]; then | ||
| echo "No previous release tag found, using initial commit" | ||
| LATEST_TAG=$(git rev-list --max-parents=0 HEAD) | ||
| fi | ||
| echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT | ||
| echo "Latest tag: $LATEST_TAG" | ||
|
|
||
| - name: Generate changelog diff | ||
| id: changelog-diff | ||
| run: | | ||
| LATEST_TAG="${{ steps.get-latest-tag.outputs.latest_tag }}" | ||
| NEW_VERSION="${{ steps.calc-version.outputs.new_version }}" | ||
|
|
||
| echo "Generating changelog for changes between $LATEST_TAG and HEAD..." | ||
|
|
||
| # Get commits between last tag and HEAD. | ||
| # GitHub squash-merges put the PR number in parentheses, e.g. "Some change (#123)". | ||
| # We convert "(#N)" to a markdown link. | ||
| CHANGELOG_CONTENT=$(git log "$LATEST_TAG"..HEAD --pretty=format:"%s" --no-merges | \ | ||
| sed 's/(#\([0-9]*\))/([#\1](https:\/\/github.com\/microsoft\/durabletask-dotnet\/pull\/\1))/' | \ | ||
| sed 's/^/- /' | \ | ||
| grep -v '^- $' || echo "") | ||
|
|
||
| # Save to file for multi-line output | ||
| echo "$CHANGELOG_CONTENT" > /tmp/changelog_content.txt | ||
| echo "changelog_file=/tmp/changelog_content.txt" >> $GITHUB_OUTPUT | ||
|
|
||
| echo "Generated changelog:" | ||
| cat /tmp/changelog_content.txt | ||
|
|
||
| - name: Update Release.props version | ||
| run: | | ||
| NEW_VERSION="${{ steps.calc-version.outputs.new_version }}" | ||
|
|
||
| # Parse version into prefix and suffix | ||
| if [[ "$NEW_VERSION" == *-* ]]; then | ||
| VERSION_PREFIX="${NEW_VERSION%%-*}" | ||
| VERSION_SUFFIX="${NEW_VERSION#*-}" | ||
| else | ||
| VERSION_PREFIX="$NEW_VERSION" | ||
| VERSION_SUFFIX="" | ||
| fi | ||
|
|
||
| echo "Updating Release.props: VersionPrefix=$VERSION_PREFIX, VersionSuffix=$VERSION_SUFFIX" | ||
|
|
||
| # Update VersionPrefix | ||
| sed -i "s|<VersionPrefix>[^<]*</VersionPrefix>|<VersionPrefix>$VERSION_PREFIX</VersionPrefix>|" eng/targets/Release.props | ||
|
|
||
| # Update VersionSuffix | ||
| sed -i "s|<VersionSuffix>[^<]*</VersionSuffix>|<VersionSuffix>$VERSION_SUFFIX</VersionSuffix>|" eng/targets/Release.props | ||
|
|
||
| echo "Updated eng/targets/Release.props:" | ||
| cat eng/targets/Release.props | ||
|
|
||
| - name: Update CHANGELOG.md | ||
| run: | | ||
| NEW_VERSION="${{ steps.calc-version.outputs.new_version }}" | ||
| CHANGELOG_FILE="/tmp/changelog_content.txt" | ||
|
|
||
| python3 -c " | ||
| import sys | ||
|
|
||
| version = '${NEW_VERSION}' | ||
| changelog_file = '${CHANGELOG_FILE}' | ||
|
|
||
| with open(changelog_file, 'r') as f: | ||
| changes = f.read().strip() | ||
|
|
||
| with open('CHANGELOG.md', 'r') as f: | ||
| content = f.read() | ||
|
|
||
| new_section = f'\n## v{version}\n{changes}\n' | ||
|
|
||
| # Insert the new version section after '## Unreleased' | ||
| marker = '## Unreleased' | ||
| idx = content.find(marker) | ||
| if idx != -1: | ||
| insert_pos = idx + len(marker) | ||
| # Skip any whitespace/newlines after the marker | ||
| while insert_pos < len(content) and content[insert_pos] in (' ', '\t', '\n', '\r'): | ||
| # Stop if we hit another section header | ||
| if content[insert_pos] == '\n' and insert_pos + 1 < len(content) and content[insert_pos + 1] == '#': | ||
| break | ||
| insert_pos += 1 | ||
| content = content[:insert_pos] + '\n' + new_section + content[insert_pos:] | ||
| else: | ||
| # No Unreleased section found, insert at top after title | ||
| title_end = content.find('\n', content.find('# Changelog')) | ||
| if title_end != -1: | ||
| content = content[:title_end + 1] + '\n## Unreleased\n' + new_section + content[title_end + 1:] | ||
|
|
||
| with open('CHANGELOG.md', 'w') as f: | ||
| f.write(content) | ||
|
|
||
| print(f'Updated CHANGELOG.md with v{version}') | ||
| " | ||
|
|
||
| - name: Create release branch and commit | ||
| id: create-branch | ||
| run: | | ||
| NEW_VERSION="${{ steps.calc-version.outputs.new_version }}" | ||
| BRANCH_NAME="release/v${NEW_VERSION}" | ||
|
|
||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
|
|
||
| # Create and checkout release branch (idempotent) | ||
| git checkout -B "$BRANCH_NAME" | ||
|
|
||
| # Stage and commit changes | ||
| git add eng/targets/Release.props | ||
| git add CHANGELOG.md | ||
| if ! git diff --cached --quiet; then | ||
| git commit -m "Release v${NEW_VERSION}" | ||
| else | ||
| echo "No changes to commit (re-run with same version)" | ||
| fi | ||
|
|
||
YunchuWang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # Create release tag (idempotent) | ||
| git tag -f "v${NEW_VERSION}" | ||
|
|
||
| # Push branch and tag (force to handle re-runs) | ||
| git push -f origin "$BRANCH_NAME" | ||
| git push -f origin "v${NEW_VERSION}" | ||
YunchuWang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | ||
| echo "Created branch $BRANCH_NAME and tag v${NEW_VERSION}" | ||
|
|
||
| - name: Summary | ||
| run: | | ||
| NEW_VERSION="${{ steps.calc-version.outputs.new_version }}" | ||
| BRANCH_NAME="${{ steps.create-branch.outputs.branch_name }}" | ||
|
|
||
| echo "## Release Preparation Complete! :rocket:" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "- **Version**: v${NEW_VERSION}" >> $GITHUB_STEP_SUMMARY | ||
| echo "- **Branch**: ${BRANCH_NAME}" >> $GITHUB_STEP_SUMMARY | ||
| echo "- **Tag**: v${NEW_VERSION}" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "### Next Steps" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "1. **Create a Pull Request** from **${BRANCH_NAME}** → **main** with title: **Release v${NEW_VERSION}**" >> $GITHUB_STEP_SUMMARY | ||
| echo "2. Review the version bump in \`eng/targets/Release.props\` and changelog updates" >> $GITHUB_STEP_SUMMARY | ||
| echo "3. Update per-package \`RELEASENOTES.md\` files if needed" >> $GITHUB_STEP_SUMMARY | ||
| echo "4. Merge the PR after CI passes" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "[Create PR](https://github.com/microsoft/durabletask-dotnet/compare/main...${BRANCH_NAME}?expand=1&title=Release+v${NEW_VERSION})" >> $GITHUB_STEP_SUMMARY | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,79 @@ | ||
| # Release Process | ||
|
|
||
| ## Overview | ||
|
|
||
| | Package prefix | Registry | | ||
| |---|---| | ||
| | `Microsoft.DurableTask.*` | [NuGet](https://www.nuget.org/profiles/durabletask) | | ||
|
|
||
| This repo publishes multiple NuGet packages. Most share a single version defined in `eng/targets/Release.props`. Individual packages can version independently by adding `<VersionPrefix>` and `<VersionSuffix>` properties directly in their `.csproj`. | ||
|
|
||
| We follow an approach of releasing everything together, even if a package has no changes — unless we intentionally hold a package back. | ||
|
|
||
| ### Versioning Scheme | ||
|
|
||
| We follow [semver](https://semver.org/) with optional pre-release tags: | ||
|
|
||
| ``` | ||
| X.Y.Z-preview.N → X.Y.Z-rc.N → X.Y.Z (stable) | ||
| ``` | ||
|
|
||
| ## Automated Release Preparation (Recommended) | ||
|
|
||
| Use the **Prepare Release** GitHub Action to automate the release preparation process. | ||
|
|
||
| ### Running the Workflow | ||
|
|
||
| 1. Go to **Actions** → **Prepare Release** in GitHub | ||
| 2. Click **Run workflow** | ||
| 3. Optionally specify a version (e.g., `1.24.0` or `1.24.0-preview.1`). Leave empty to auto-increment (patch for stable, pre-release number for pre-release). | ||
| 4. Click **Run workflow** | ||
|
|
||
| ### What the Workflow Does | ||
|
|
||
| 1. **Determines the next version**: If not specified, auto-increments the current version from `Release.props` | ||
| 2. **Generates changelog**: Uses `git log` to find changes between `main` and the last release tag | ||
| 3. **Updates `Release.props`**: Bumps `VersionPrefix` and `VersionSuffix` to the new version | ||
| 4. **Updates `CHANGELOG.md`**: Adds a new version section with the discovered changes | ||
| 5. **Creates a release branch**: `release/vX.Y.Z` | ||
| 6. **Creates a release tag**: `vX.Y.Z` | ||
|
|
||
| ### After the Workflow Completes | ||
|
|
||
| The workflow summary will include a link to create a PR. You must **manually create a pull request** from the release branch (`release/vX.Y.Z`) to `main`: | ||
|
|
||
| 1. Go to the workflow run summary and click the **Create PR** link, or navigate to: `https://github.com/microsoft/durabletask-dotnet/compare/main...release/vX.Y.Z` | ||
| 2. Set the PR title to `Release vX.Y.Z` | ||
| 3. Review the version bump in `eng/targets/Release.props` and changelog updates | ||
| 4. Update per-package `RELEASENOTES.md` files if needed | ||
| 5. Merge the PR after CI passes | ||
|
|
||
| After the PR is merged, follow the **Publishing** steps below. | ||
|
|
||
| ## Publishing (After Release PR is Merged) | ||
|
|
||
| 1. Kick off [ADO release build](https://dev.azure.com/durabletaskframework/Durable%20Task%20Framework%20CI/_build?definitionId=29) (use the tag as the build target, enter `refs/tags/<tag>`) | ||
| 2. Validate signing, package contents (if build changes were made.) | ||
| 3. Create ADO release. | ||
| - From successful ADO release build, click 3 dots in top right → 'Release'. | ||
| 4. Release to ADO feed. | ||
| 5. Release to NuGet once validated (and dependencies are also released to NuGet.) | ||
| 6. Publish GitHub draft release. | ||
| 7. Delete contents of all `RELEASENOTES.md` files. | ||
|
|
||
| ## Manual Release Process (Alternative) | ||
|
|
||
| If you prefer to prepare the release manually, follow these steps: | ||
|
|
||
| 1. Rev versions as appropriate following semver. | ||
| - Repo-wide versions can be found in `eng/targets/Release.props` | ||
| - Individual packages can version independently by adding the `<VersionPrefix>` and `<VersionSuffix>` properties directly in their `.csproj`. | ||
| - We follow an approach of just releasing everything, even if the package has no changes. _Unless_ we intentionally do not want to release a package. | ||
| 2. Ensure appropriate `RELEASENOTES.md` are updated in the repository. | ||
| - These are per-package and contain only that packages changes. | ||
| - The contents will be included in the `.nupkg` and show up in nuget.org's release notes tab. | ||
| - Clear these files after a release. | ||
| 3. Ensure `CHANGELOG.md` in repo root is updated. | ||
| 4. Tag the release. | ||
| - `git tag v<version>`, `git push <remote> -u <tag>` | ||
| 5. Draft github release for new tag. | ||
| 6. Kick off [ADO release build](https://dev.azure.com/durabletaskframework/Durable%20Task%20Framework%20CI/_build?definitionId=29) (use the tag as the build target, enter `refs/tags/<tag>`) | ||
| 7. Validate signing, package contents (if build changes were made.) | ||
| 8. Create ADO release. | ||
| - From successful ADO release build, click 3 dots in top right -> 'Release'. | ||
| 9. Release to ADO feed. | ||
| 10. Release to nuget once validated (and dependencies are also released to nuget.) | ||
| 11. Publish github draft release. | ||
| 12. Delete contents of all `RELEASENOTES.md` files. | ||
| 5. Draft GitHub release for new tag. | ||
| 6. Follow the **Publishing** steps above. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.