This runbook describes how to prepare and trigger a public Microsoft.UI.Reactor preview release.
Releases are tag-driven, but pushing a tag only starts the pipelines — it does not publish to NuGet.org. The two steps people most often miss (the approval gate and the two-person rule) are called out below.
- Pre-flight — pick the next
v0.1.0-preview.Nand confirm the tag, GitHub release, and the four NuGet packages don't already exist (git tag --list,gh release view, NuGet.org). - Prep PR (bump one property + recompile docs) — bump
<ReactorPublicVersion>in the rootDirectory.Build.propsto the version you're about to tag, runmur docs compile --skip-screenshots --skip-diagrams, and commit the regenerateddocs/guidepages. That single property is the source of truth: the guide's{{reactorVersion}}token and the template'sMicrosoftUIReactorVersionfallback both derive from it, andREADME.mdis version-agnostic (no sweep needed). CI's docs freshness gate fails the PR if you bump the property without recompiling. Land a PR, merge tomain. - Tag
main—git checkout main && git pull, thengit tag -a v<version> -m "Release <version>"andgit push origin v<version>. This starts the GitHubPackageworkflow and the OneBranch official pipeline. - Publish (gated) — the tag push does not publish to NuGet.org. Approve the OneBranch
Production_PublishNuGetstage, then verify the packages appear on NuGet.org. - Two-person rule — the publish approver must be a different person than whoever pushed the tag (a self-approval is rejected by the compliance gate). Line up a second approver before you tag.
- Smoke-test — scaffold from the published template and restore against NuGet.org.
Each step is expanded in the sections below.
Reactor also has an internal nightly channel for signed pre-release packages on
the Azure Artifacts feed used by the OneBranch build. The nightly pipeline is
build/pipelines/OneBranch.Nightly.Reactor.yml; it runs on a daily schedule,
builds main, and publishes versions shaped like
0.2.0-nightly.<yyyyMMdd>.<counter> to the internal feed.
To consume the nightly channel internally, add this feed source to your
nuget.config and float the package version on the nightly label, for example:
https://pkgs.dev.azure.com/github-private/microsoft/_packaging/microsoft-ui-reactor-internal/nuget/v3/index.json
<PackageReference Include="Microsoft.UI.Reactor" Version="0.*-nightly*" />Nightly packages are internal-only and auto-published; they do not go through the public NuGet.org approval gate below.
Reactor versions are tag-driven. A tag named v0.1.0-preview.3 makes MinVer resolve package version 0.1.0-preview.3 for that exact commit. The tag push starts the GitHub packaging workflow and the OneBranch official pipeline; the OneBranch NuGet publish stage is approval-gated.
Prepare release content in a PR before tagging. Do not create the release tag first if docs need to point at the new version; otherwise the release assets are built from a commit whose docs still name the previous version. (The template's framework reference is auto-stamped at pack time, so it no longer needs a pre-tag bump — see Prepare the release PR.)
See the Quick checklist above for the actionable order.
Use SemVer prerelease tags with the v prefix:
$version = "0.1.0-preview.3"
$tag = "v$version"Before starting, check that the tag and package version do not already exist:
git fetch origin --tags
git tag --list $tag
gh release view $tagAlso check NuGet.org for already-published packages:
Microsoft.UI.ReactorMicrosoft.UI.Reactor.AdvancedMicrosoft.UI.Reactor.DevtoolsMicrosoft.UI.Reactor.ProjectTemplates
Start from current main:
git checkout main
git pull origin main
git switch -c release/$versionBump the single source of truth for the public package version — the
<ReactorPublicVersion> property in the root Directory.Build.props — to the version you
are about to tag:
<ReactorPublicVersion>0.1.0-preview.N</ReactorPublicVersion>That one property feeds every version-bearing surface, so there is no rg sweep and no
per-file bump:
- Guide docs —
docs/_pipeline/templates/*.md.dtreference the version through the{{reactorVersion}}token, whichmur docs compilesubstitutes from this property. - Template fallback —
tools/Templates/Microsoft.UI.Reactor.Templates.csprojderives itsMicrosoftUIReactorVersionfallback default from$(ReactorPublicVersion). - README — is deliberately version-agnostic (it names no version and links to NuGet /
Releases), so it needs no edit at all and
mur docs compilenever touches it.
The template's framework reference is also stamped automatically for the published package:
the release workflow's Pack Templates step passes -p:MicrosoftUIReactorVersion=<resolved version> (guarded by TemplateMetadataTests), so the published ProjectTemplates package
always references the framework version shipped in the same run. bootstrap.ps1 runs mur pack-local --framework-version latest, which resolves the newest published package from NuGet
for local scaffolds. The $(ReactorPublicVersion)-derived value is therefore only a fallback
(a bare mur pack-local, or when the latest lookup can't reach NuGet) — but keeping it
current is free now, since you bump the one property anyway.
Two guards keep the bump honest so it can't silently go stale:
- Docs freshness gate (CI
docs-buildjob) fails the PR if<ReactorPublicVersion>changed butdocs/guide/{getting-started,packaging}.mdweren't recompiled. - Tag-vs-property guard (
release.yml, tag pushes only) fails the release if<ReactorPublicVersion>doesn't equal the MinVer-resolved tag version — so the tag, the docs, and the stamped template pack are provably one version.
Then regenerate the guide (authored docs live under docs/_pipeline/templates/; never edit
generated docs/guide/ files directly). Use a full compile for release-prep changes because
some pages (for example getting-started) pull snippets from other topics:
mur docs compile --skip-screenshots --skip-diagramsRun the focused template tests:
dotnet test tests/Reactor.Tests/Reactor.Tests.csproj `
-p:Platform=x64 `
--filter FullyQualifiedName~TemplateMetadataTestsPack the template locally and inspect the generated default. Pass
-p:MicrosoftUIReactorVersion=$version to mirror what the release workflow stamps, so the
generated app references the version being released:
dotnet pack tools/Templates/Microsoft.UI.Reactor.Templates.csproj `
--configuration Release `
-p:Version=0.0.0-local `
-p:MicrosoftUIReactorVersion=$version `
-p:Platform=AnyCPU `
-o local-nupkgsCreate a throwaway app from the packed template and verify its .csproj references the chosen public version. Skip restore before the tag is published because the new package version will not exist on NuGet.org yet:
dotnet new uninstall Microsoft.UI.Reactor.ProjectTemplates
dotnet new install local-nupkgs/Microsoft.UI.Reactor.ProjectTemplates.0.0.0-local.nupkg
$scratch = Join-Path $env:TEMP "reactor-template-smoke"
Remove-Item $scratch -Recurse -Force -ErrorAction SilentlyContinue
dotnet new reactorapp -n ReactorTemplateSmoke -o $scratch --no-restore
Select-String "$scratch\ReactorTemplateSmoke.csproj" -Pattern $versionOpen the PR and wait for CI. Do not tag until the release PR is merged.
After the release PR merges:
git checkout main
git pull origin main
git tag -a v0.1.0-preview.3 -m "Release 0.1.0-preview.3"
git push origin v0.1.0-preview.3Tagging the merge commit ensures the generated packages, template defaults, docs, GitHub Release assets, and OneBranch official packages all correspond to the same source tree.
After pushing the tag:
- Confirm the GitHub
Packageworkflow runs for the tag and creates a GitHub Release. - Confirm the OneBranch official pipeline starts for the tag.
- Approve the
Production_PublishNuGetstage when ready to publish to NuGet.org. - Verify the packages appear on NuGet.org.
- Install the released template package or a locally packed template and create a smoke app that restores against NuGet.org.
Two-person rule (submitter ≠ approver). Publishing passes through two distinct gates, not one: the ADO Environment approval and the OneBranch ApprovalService / ServiceTree compliance check. The OneBranch approver must be a different person than whoever pushed the release tag — a self-approval by the tag pusher will be rejected by the compliance gate (and has bitten past release cycles). Line up a second approver before tagging so the publish is not blocked.
Do not move or rewrite a pushed release tag after release workflows or publishing have started. Instead:
- Fix the issue in a new PR.
- Merge to
main. - Mint the next preview version with a new tag.