Automated GitHub Release creation on NuGet publish#45
Automated GitHub Release creation on NuGet publish#45jfversluis wants to merge 3 commits intomainfrom
Conversation
When publishDevFlowNuget or publishCliNuget is set in the AzDO pipeline, a new create_git_tag stage runs after successful publish. It reads eng/Versions.props to compute the version tag (e.g. v0.1.0-preview.3) and pushes it to GitHub. A new GitHub Actions workflow (create-release.yml) triggers on v* tag pushes and auto-creates a GitHub Release with: - NuGet package table with badges - Auto-generated changelog from PRs since the previous tag - Prerelease flag for preview/rc versions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Azure Pipelines GitHub App only provides read access to code. Push the tag using a GitHubPushToken secret variable with repo scope. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR automates GitHub Release creation as an output of the existing AzDO NuGet publish pipeline by pushing a v* tag after a successful publish and letting a GitHub Actions workflow generate the Release.
Changes:
- Add an AzDO stage that computes the repo version from
eng/Versions.propsand pushes an annotatedv<version>Git tag after successful NuGet publishing. - Add a GitHub Actions workflow that triggers on
v*tag pushes and creates a GitHub Release with generated notes and a NuGet package table.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| eng/pipelines/devflow-official.yml | Adds a conditional create_git_tag stage to compute/push a version tag after NuGet publish. |
| .github/workflows/create-release.yml | New workflow to create a GitHub Release on v* tags and populate release notes/package badges. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for pkg in Agent Agent.Core Agent.Gtk Blazor Blazor.Gtk Driver Logging; do | ||
| echo "| Microsoft.Maui.DevFlow.${pkg} | [](https://www.nuget.org/packages/Microsoft.Maui.DevFlow.${pkg}) |" | ||
| done | ||
|
|
There was a problem hiding this comment.
The release notes package table is missing Microsoft.Maui.DevFlow.CLI (the DevFlow global tool package), even though it’s part of the DevFlow product and is likely published alongside the other DevFlow packages. Add it to the table (or generate the list from a source-of-truth) so the GitHub Release accurately reflects the shipped artifacts for the tag.
| echo "| Microsoft.Maui.DevFlow.CLI | [](https://www.nuget.org/packages/Microsoft.Maui.DevFlow.CLI) |" |
| # Build package table | ||
| { | ||
| echo "### NuGet Packages" | ||
| echo "" | ||
| echo "| Package | NuGet |" | ||
| echo "|---------|-------|" | ||
|
|
||
| for pkg in Agent Agent.Core Agent.Gtk Blazor Blazor.Gtk Driver Logging; do | ||
| echo "| Microsoft.Maui.DevFlow.${pkg} | [](https://www.nuget.org/packages/Microsoft.Maui.DevFlow.${pkg}) |" | ||
| done | ||
|
|
||
| echo "| Microsoft.Maui.Cli | [](https://www.nuget.org/packages/Microsoft.Maui.Cli) |" | ||
| echo "" | ||
| echo "Install with:" | ||
| echo '```' | ||
| echo "dotnet add package Microsoft.Maui.DevFlow.Agent --version \"${VERSION}.*\"" | ||
| echo '```' | ||
| echo "" |
There was a problem hiding this comment.
This workflow always lists both DevFlow and Microsoft.Maui.Cli packages, but the AzDO pipeline can push a tag when either publish parameter is true. That means the GitHub Release could advertise packages that were not actually published for that run/version. Consider either (a) only creating the tag when both products were published, or (b) making the workflow build the package table dynamically (e.g., by querying NuGet for which packages exist at VERSION).
| # Build package table | |
| { | |
| echo "### NuGet Packages" | |
| echo "" | |
| echo "| Package | NuGet |" | |
| echo "|---------|-------|" | |
| for pkg in Agent Agent.Core Agent.Gtk Blazor Blazor.Gtk Driver Logging; do | |
| echo "| Microsoft.Maui.DevFlow.${pkg} | [](https://www.nuget.org/packages/Microsoft.Maui.DevFlow.${pkg}) |" | |
| done | |
| echo "| Microsoft.Maui.Cli | [](https://www.nuget.org/packages/Microsoft.Maui.Cli) |" | |
| echo "" | |
| echo "Install with:" | |
| echo '```' | |
| echo "dotnet add package Microsoft.Maui.DevFlow.Agent --version \"${VERSION}.*\"" | |
| echo '```' | |
| echo "" | |
| # Helper to check if a NuGet package exists at a given version | |
| package_exists() { | |
| local id="$1" | |
| local ver="$2" | |
| local lower | |
| lower=$(echo "$id" | tr '[:upper:]' '[:lower:]') | |
| # NuGet v3 flat container: https://api.nuget.org/v3-flatcontainer/{id-lower}/{version}/{id-lower}.nuspec | |
| if curl -sSfI "https://api.nuget.org/v3-flatcontainer/${lower}/${ver}/${lower}.nuspec" >/dev/null 2>&1; then | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| # Determine which packages actually exist for this VERSION | |
| DEVFLOW_PACKAGES=() | |
| DEVFLOW_AGENT_PUBLISHED=false | |
| for pkg in Agent Agent.Core Agent.Gtk Blazor Blazor.Gtk Driver Logging; do | |
| pkg_id="Microsoft.Maui.DevFlow.${pkg}" | |
| if package_exists "${pkg_id}" "${VERSION}"; then | |
| DEVFLOW_PACKAGES+=("${pkg_id}") | |
| if [ "${pkg_id}" = "Microsoft.Maui.DevFlow.Agent" ]; then | |
| DEVFLOW_AGENT_PUBLISHED=true | |
| fi | |
| fi | |
| done | |
| CLI_ID="Microsoft.Maui.Cli" | |
| if package_exists "${CLI_ID}" "${VERSION}"; then | |
| CLI_PUBLISHED=true | |
| else | |
| CLI_PUBLISHED=false | |
| fi | |
| # Build package table | |
| { | |
| echo "### NuGet Packages" | |
| echo "" | |
| if [ "${#DEVFLOW_PACKAGES[@]}" -eq 0 ] && [ "${CLI_PUBLISHED}" != "true" ]; then | |
| echo "_No NuGet packages were found for version ${VERSION}._" | |
| else | |
| echo "| Package | NuGet |" | |
| echo "|---------|-------|" | |
| for pkg_id in "${DEVFLOW_PACKAGES[@]}"; do | |
| echo "| ${pkg_id} | [](https://www.nuget.org/packages/${pkg_id}) |" | |
| done | |
| if [ "${CLI_PUBLISHED}" = "true" ]; then | |
| echo "| ${CLI_ID} | [](https://www.nuget.org/packages/${CLI_ID}) |" | |
| fi | |
| echo "" | |
| # Only show install snippet if the DevFlow Agent package exists at this version | |
| if [ "${DEVFLOW_AGENT_PUBLISHED}" = "true" ]; then | |
| echo "Install with:" | |
| echo '```' | |
| echo "dotnet add package Microsoft.Maui.DevFlow.Agent --version \"${VERSION}.*\"" | |
| echo '```' | |
| echo "" | |
| fi | |
| fi |
.github/workflows/create-release.yml
Outdated
| echo "" | ||
| echo "Install with:" | ||
| echo '```' | ||
| echo "dotnet add package Microsoft.Maui.DevFlow.Agent --version \"${VERSION}.*\"" |
There was a problem hiding this comment.
The install snippet uses --version "${VERSION}.*", which will expand the tag version into something like 0.1.0-preview.3.*. That string is not the actual package version and may not be accepted by NuGet tooling as a valid floating version. Prefer emitting the exact ${VERSION} (or a known-valid floating pattern) to avoid publishing release notes with a non-working install command.
| echo "dotnet add package Microsoft.Maui.DevFlow.Agent --version \"${VERSION}.*\"" | |
| echo "dotnet add package Microsoft.Maui.DevFlow.Agent --version \"${VERSION}\"" |
| ARGS=( | ||
| "$TAG" | ||
| --title "$TAG" | ||
| --notes-file "$RUNNER_TEMP/release-notes.md" | ||
| --generate-notes | ||
| ) |
There was a problem hiding this comment.
gh release create is invoked with both --notes-file (custom NuGet table) and --generate-notes (auto-generated changelog). To ensure the final release body reliably contains both the package table and the generated notes, consider explicitly generating notes (e.g., via the releases/generate-notes API) and concatenating with the table, or generating notes first and then updating the release body. As written, it’s unclear which input will take precedence across gh versions.
eng/pipelines/devflow-official.yml
Outdated
| # Create a Git tag after successful NuGet publish. | ||
| # This triggers the create-release.yml GitHub Actions workflow | ||
| # which auto-creates a GitHub Release with generated notes. | ||
| - ${{ if or(eq(parameters.publishDevFlowNuget, true), eq(parameters.publishCliNuget, true)) }}: | ||
| - stage: create_git_tag | ||
| displayName: 'Create Git Tag' | ||
| dependsOn: | ||
| - ${{ if eq(parameters.publishDevFlowNuget, true) }}: | ||
| - publish_devflow_nuget | ||
| - ${{ if eq(parameters.publishCliNuget, true) }}: | ||
| - publish_cli_nuget |
There was a problem hiding this comment.
create_git_tag runs when either publishDevFlowNuget or publishCliNuget is true. Since the downstream GitHub Release workflow currently advertises a fixed set of packages, this can produce a release that implies both product sets were published even when only one publish stage ran. If partial publishes are expected, consider gating tag creation on both publishes (or passing along which product(s) were published so the release notes can be accurate).
…ally - Add standalone createGitHubRelease parameter instead of checking each publish param (no changes needed when products are added) - Tag stage depends on Validate + publish_using_darc (always exist) - GitHub Action discovers packages from <PackageId> in csproj files instead of a hardcoded list - gh release create --generate-notes already links PRs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Automates GitHub Release creation so it's a zero-effort byproduct of the existing AzDO publish flow.
How it works
AzDO New
create_git_tagstage runs afterpublish_devflow_nugetand/orpublish_cli_nugetsucceedpipelineeng/Versions.propsto compute the tag (e.g.v0.1.0-preview.4)trueGitHub New
create-release.ymltriggers onv*tag pushActionsSingle trigger point
GitHub Release is created. No manual steps.
Replaces
The manual process of tagging commits and writing release notes (as we just did for preview.3).1