Docs/Architecture/ReleaseStrategy.md
Goal: Replace the current manual publish.ps1 flow with an automated GitHub Actions pipeline that publishes both regular pre-release builds (cheap, frequent, on demand) and final releases (tag-driven, with GitHub Releases attached). Rebrand naudio3dev as the default branch, retire NAudio 2 to a maintenance branch, and put a lightweight discipline around release notes that keeps the maintainer in control without blocking PRs.
Branch: work continues on main (renamed from naudio3dev in step 8). NAudio 2 maintenance lives on release/2.x (renamed from master). Both are protected; all changes via PR.
| Phase | State | Notes |
|---|---|---|
| 0. Validate GitHub Actions | ✅ done | Green build on windows-latest. 2028 passing / 0 failed / 7 skipped, 1m49s (Azure was 2m51s). Draft PR closed unmerged; throwaway branch ci/validate-github-actions retained for reference. |
1. Merge origin/master into naudio3dev | ✅ done | Auto-merged via ORT strategy with no conflicts. The expected Mp3FileReader clash didn't materialise — master's MP3 sample-rate fix touched different lines from the lazy-TOC work. Local tests post-merge: 2037/0/6. Pushed. |
2. Land build workflow on naudio3dev | ✅ done | Workflow merged. Azure Pipelines kept running in parallel as a safety net per step 12. |
3. Centralize version in Directory.Build.props | ✅ done | <VersionPrefix>3.0.0</VersionPrefix> set at root, <Version>2.3.0</Version> removed from all 8 NAudio package csprojs. All NAudio packages now build as *.3.0.0.nupkg. Tool/sample apps keep their own explicit <Version>. |
4. Release-notes plumbing + labels + CLAUDE.md | ✅ done | PR #1269 merged. .github/release.yml, .github/PULL_REQUEST_TEMPLATE.md, CLAUDE.md, and ### Unreleased placeholder all live. breaking and release-notes-skip labels added in the UI. |
5. Backfill RELEASE_NOTES.md | ✅ done | PR #1270 merged. ~60 categorised bullets across six sub-sections under ### Unreleased, ready for the maintainer to curate further as PRs land. |
| 6. Release workflow | ✅ done | PR #1271 merged. Two triggers (workflow_dispatch for previews, push tags v* for finals), version resolution from <VersionPrefix>, validation guards, pack of all 8 NAudio packages, NuGet trusted-publishing push, and a final-only GitHub Release with body extracted from RELEASE_NOTES.md. |
| 7. NuGet trusted publishing + smoke test | ✅ done | Trusted publisher configured on NuGet.org; vars.NUGET_USER set in repo. First dispatch (preview.1) burned at the push step on a PowerShell glob-expansion bug, fixed in PR #1273. Re-dispatch shipped all 8 packages as 3.0.0-preview.2. |
| 8. Branch flip + protection | ✅ done | master → release/2.x and naudio3dev → main. main is default. Ruleset "Protected branches" on default + release/* (require PR, require build status check, require linear history, block force-push, block deletion). Cleanup PR #1272 followed up workflow files and doc/README links. |
| 9. First public preview + retire Azure Pipelines | ✅ done | 3.0.0-preview.2 shipped to NuGet via the new flow. PR #1274 deleted azure-pipelines.yml and publish.ps1, dropped <GeneratePackageOnBuild> from all 8 NAudio packages, refreshed badges and READMEs, and consolidated duplicated csproj boilerplate into Directory.Build.props and Directory.Build.targets. Contributor announcement and stale-PR triage remain as non-blocking manual steps (see §"Communication"). Azure DevOps cleanup is in §"Retiring Azure DevOps". |
| 10. SourceLink + symbol packages + deterministic CI | ✅ done | PR #1275 merged. .NET 8+ SDK SourceLink activated via <PublishRepositoryUrl> (no PackageReference needed). .snupkg symbol packages produced via <IncludeSymbols> + <SymbolPackageFormat>snupkg; pushed alongside the .nupkg by dotnet nuget push. <ContinuousIntegrationBuild> gated on GITHUB_ACTIONS=true for reproducible CI binaries. 3.0.0-preview.3 shipped with all three active. |
11. Embed PackageReleaseNotes in the nupkg | ✅ done | The release workflow extracts the appropriate RELEASE_NOTES.md section (### Unreleased for previews, ### <version> for finals) into a file before pack runs, then passes the file path via -p:PackageReleaseNotesFile=.... A small MSBuild target in Directory.Build.targets reads the file and sets <PackageReleaseNotes> so NuGet displays the content on the package page. The same file is reused for the GitHub Release body on finals. The extract step also enforces NuGet's 35,000-character limit, so an oversized section fails the run before pack rather than at push. |
| 12. First final 3.0.0 release | ⏳ in progress | Pre-flight underway: docfx.json extended to cover NAudio.Sampler and the NAudio meta-package (AudioFileReader / Mp3FileReader were missing from the API reference), README.md and the meta-package NuGet README rewritten for the NAudio 3 shape, Docs/MigratingFromNAudio2.md completed against the breaking-change tables in MODERNIZATION.md, and RELEASE_NOTES.md trimmed to headline features + breaking changes. AudioMediaSubtypes moved out of the NAudio.Dmo namespace into NAudio.Wave, and seven members that had been [Obsolete] since NAudio 2 were removed (AsioOut.Volume kept — it is an IWavePlayer interface member). EffectsDesign.md's licence policy was amended to settle on in-file attribution as the sole mechanism, dropping its reference to a central THIRD-PARTY-NOTICES.txt that was never added. Pre-flight is complete; next step is ReleaseInstructions.md §3 — bump <VersionPrefix>, rename ### Unreleased, tag. |
actions/setup-dotnet is not required on windows-latest — .NET 10 SDK (currently 10.0.203) and runtime are pre-installed. Removing the explicit setup step shaved ~10s and removed dead config. Trade-off: when the runner image eventually rotates and drops .NET 10, the build will silently break. If determinism becomes important before then, add setup-dotnet back with a pinned version. Deferred for now.dotnet test in .NET 10 requires --project when invoking with a project path. The Azure DotNetCoreCLI@2 task papers over this; raw dotnet test path/to.csproj now errors with "Specifying a project for 'dotnet test' should be via '--project'." The validated workflow uses --project NAudioTests/NAudioTests.csproj. Same applies to any future test invocations.The current process — bump <Version> in eight .csproj files, build locally, run publish.ps1 against a hand-set $apiKey — works but does not scale to the NAudio 3 cadence:
NAudio.MediaFoundation, NAudio.Dmo, NAudio.DirectSound), so the per-csproj <Version> duplication grows from 8 to 11.Azure Pipelines builds and tests reliably today, but every reason to keep it is operational inertia. GitHub Actions wins on three points specific to this work:
gh release create --generate-notes) directly from the same workflow that publishes packages.GitHub-hosted windows-latest is the same Windows Server 2022 image family Azure Pipelines uses for windows-latest, with the same .NET SDKs and the same lack of real audio hardware (which is fine — the existing pipeline already filters TestCategory!=IntegrationTest). No regression risk on the build/test side.
All packages share one version, declared once in Directory.Build.props as <VersionPrefix>. The per-csproj <Version> lines are deleted. Rationale:
NAudio meta-package transitively pins specific versions; partial updates produce confusing combinations.CI sets <VersionSuffix> for pre-release builds via dotnet pack -p:VersionSuffix=preview.NN; final-release builds set neither suffix.
Pre-releases are triggered manually via workflow_dispatch. The workflow uses github.run_number as the auto-incrementing preview counter, producing versions like 3.0.0-preview.142. A milestone input on the dispatch lets the maintainer override the suffix when cutting a named milestone (alpha.1, beta.2, rc.1).
NuGet's SemVer 2 ordering means the resulting versions sort as expected:
3.0.0-alpha.1 < 3.0.0-beta.1 < 3.0.0-preview.142 < 3.0.0-rc.1 < 3.0.0
(preview is between beta and rc alphabetically; numeric identifiers like 142 compare numerically.)
We considered "publish on every push" and rejected it: NuGet version sprawl, reviewer confusion about which preview to test, and pressure to treat every commit as releasable. Manual is the right starting point and can be upgraded later if needed.
Pushing a tag of the form v3.0.0 triggers the release workflow, which:
<VersionPrefix> in Directory.Build.props.RELEASE_NOTES.md has a matching ### 3.0.0 (date) section — fails the build if missing.RELEASE_NOTES.md section and the auto-generated PR list appended.Tags are explicit, auditable, and don't fire on every typo-fix to Directory.Build.props.
naudio3dev becomes main; current master becomes release/2.x. Both renames are done via the GitHub UI's "Rename branch" feature, which preserves history, redirects URLs, and re-targets open PRs without any git push --force.
NAudio 2 stable consumers are unaffected by main representing NAudio 3, because Install-Package NAudio requires -IncludePrerelease to pick up 3.0.0-preview.*. New PRs default to main (NAudio 3), which matches where contributor effort should be aimed.
Both main and release/2.x are protected:
build workflow must pass.git log main readable.This is what gets us reliable release notes coverage: every change goes through a PR, every PR can be labelled, every label feeds the auto-generated changelog.
Four complementary layers, none of them blocking:
.github/release.yml. gh release create --generate-notes produces a categorized PR list (Breaking, Features, Bug fixes, Docs, Other) — a safety net that catches anything missed elsewhere.RELEASE_NOTES.md is hand-curated with an ### Unreleased section at the top. Contributors and agents add bullets as PRs land; the maintainer edits prose and renames Unreleased → 3.0.0 (date) at release time. This is what NuGet displays via <PackageReleaseNotes>.CLAUDE.md instruction tells AI agents to add an Unreleased bullet for any user-visible change. This automates most of the load.Why no CI block on release notes? Because the maintainer often wants to write the entry anyway, so a blocking check produces friction without value. Layer 1 (auto-generated PR list) means nothing is ever lost; layer 2 gives the maintainer a draft to curate.
GitHub provides bug, enhancement, documentation by default. We add two new labels:
| Label | Purpose | Suggested colour |
|---|---|---|
breaking | Breaking change — top of the changelog | red |
release-notes-skip | Internal/refactor PR — exclude from changelog | grey |
Labels are applied by the maintainer (or any collaborator) at PR review/merge time. Untagged PRs fall into the Other catch-all section, which is acceptable.
The plan is sequenced so that the highest-uncertainty item (CI migration) is validated first, on a throwaway PR, before any irreversible changes (branch renames, label changes, doc edits in main). Each phase is independently committable and reversible.
Goal: prove that GitHub Actions can build and test NAudio with the same coverage as Azure Pipelines, before committing to anything else. This phase produces no merged change.
naudio3dev, add .github/workflows/build.yml that mirrors the current Azure pipeline: dotnet restore, dotnet build, dotnet test --filter "TestCategory!=IntegrationTest".windows-latest.TestCategory exclusion. Stop here and resolve before proceeding.Outcome: green build on windows-latest. 2028 passing / 0 failed / 7 skipped, 1m49s (Azure baseline was 2m51s). Two YAML adjustments made during validation: dropped the now-redundant setup-dotnet step (.NET 10 is preinstalled), and switched to dotnet test --project ... to match the .NET 10 CLI's stricter argument parsing — see "Findings carried forward from Phase 0" above. Draft PR closed unmerged.
naudio3dev up to date with master ✅git fetch origin.git checkout naudio3dev, git merge origin/master. Resolve conflicts. Likely affected files: Mp3FileReader.cs (overlaps with the lazy-TOC work), possibly WaveStream.cs (block-alignment fix), and MIDI files (running-status fix). Conflicts in RELEASE_NOTES.md are unlikely because master hasn't touched it since 2.3.0 shipped.naudio3dev.Outcome: clean ORT-strategy merge with zero conflicts. Auto-merged files: BiQuadFilter.cs, WaveStream.cs, MidiEvent.cs, MidiFile.cs. New files: WaveStreamTests.cs, additions to MidiFileTests.cs. Local test run post-merge: 2037 passing / 0 failed / 6 skipped — the +9 vs Phase 0 corresponds to the new regression tests from master.
naudio3dev ✅.github/workflows/build.yml (the validated version from phase 0, with --project arg fix and no setup-dotnet). Add push: branches: [naudio3dev] to the existing pull_request and workflow_dispatch triggers so post-merge pushes also run CI.Outcome: PR opened, CI green on the PR's own diff, merged. Azure Pipelines kept running in parallel as the safety net per step 12; will be retired in phase 9.
<VersionPrefix>3.0.0</VersionPrefix> to Directory.Build.props.<Version>2.3.0</Version> from all 8 .csproj files (and the 3 NAudio 3 packages once they exist).3.0.0.nupkg filenames.Outcome: PR #1268 merged. All 8 NAudio packages now produce *.3.0.0.nupkg from a single <VersionPrefix> declaration. Tool/sample apps left untouched and continue to override with their own explicit <Version>.
.github/release.yml configuring the auto-changelog categories..github/PULL_REQUEST_TEMPLATE.md with the release-notes checkbox.CLAUDE.md (or extend the existing one if introduced elsewhere) instructing agents to maintain ### Unreleased bullets.breaking and release-notes-skip labels.Outcome: PR #1269 merged. Plumbing in place; new PRs render the template, the auto-changelog config will categorise correctly when invoked, and CLAUDE.md documents the release-notes process for AI agents.
RELEASE_NOTES.md ✅git log v2.3.0..naudio3dev (174 commits).### Unreleased section at the top of RELEASE_NOTES.md with one bullet per user-visible change.Outcome: PR #1270 merged. ~60 bullets under ### Unreleased organised into six sub-sections (Breaking changes, New features, Performance, Reliability and bug fixes, Modernisation, Packaging). Bullets carry no PR numbers — those can be added selectively before final release.
.github/workflows/release.yml. Two triggers:
workflow_dispatch with optional milestone input (default empty → uses preview.${{ github.run_number }}).push of tags matching v*.${{ github.ref_name }}; dispatch-driven uses <VersionPrefix> + suffix.<VersionPrefix> (final releases only).RELEASE_NOTES.md has a matching section (final releases only — pre-releases inherit Unreleased).dotnet pack all packages with the resolved version.RELEASE_NOTES.md plus auto-generated PR list.Exit criterion: workflow exists, no run yet.
permissions: id-token: write block.3.0.0-preview.0 from the workflow_dispatch UI as an end-to-end smoke test.preview.1.Exit criterion: a valid pre-release exists on NuGet.org and installs cleanly into a test project.
Note on order: in practice this phase ran before Phase 7. The Phase 7 smoke test requires
workflow_dispatchto be visible in the GitHub UI, which only happens for workflows on the default branch. The rename tomainwas therefore prerequisite, not subsequent.
master → release/2.x. Confirm any open PRs are re-targeted (open PRs against master follow automatically).naudio3dev → main. Confirm open PRs are re-targeted.main as the default branch.release/*:
build.build.yml push trigger to [main], drop naudio3dev from release.yml branch allowlist, refresh CLAUDE.md and README.md links, update ReleaseStrategy.md status.git fetch origin --prune, git branch -m naudio3dev main, git branch -u origin/main main, git remote set-head origin -a).Exit criterion: main is the default; both protected branches block direct pushes; CI is required on every PR; the cleanup PR has merged so workflow files reflect the new branch names.
3.0.0-preview.1 via the release workflow. This is the first preview that ships from main. (Done as a side-effect of Phase 7's smoke test — actually shipped as preview.2 because preview.1 was burned on the glob-expansion bug.)azure-pipelines.yml.publish.ps1.<GeneratePackageOnBuild>true</GeneratePackageOnBuild> from all 8 NAudio package csprojs — the release workflow now packs explicitly, and leaving it on accumulates stale .nupkg files in bin/Release/.README.md for a GitHub Actions badge.net8.0 (NAudio.Core, Asio, Wasapi, WinMM, WinForms, Midi, Extras) to net9.0.Exit criterion: the old flow no longer exists in the repo; the new flow has produced a publicly available pre-release.
Branch-rename auto-banner only mentions the naudio3dev → main step, which most upstream contributors never saw. The real story for them is master → release/2.x plus the appearance of main. Notify in three places:
master; case B: local fork no PR; case C: brand new clone).README.md linking to the discussion.release/2.x clarifying whether the PR should stay on release/2.x or rebase against main.This is a manual step that does not block Phase 9's cleanup PR.
When the time comes for 3.0.0 proper:
RELEASE_NOTES.md has a curated ### 3.0.0 (date) section that fits NuGet's 35,000-character limit.<VersionPrefix> to 3.0.0 (no suffix needed — handled by the workflow).v3.0.0. The release workflow does the rest.The operational checklist for this lives in ReleaseInstructions.md §3; the numbered steps here exist only to keep the phase sequence complete.
The azure-pipelines.yml and publish.ps1 files are gone in Phase 9, but the Azure DevOps project at dev.azure.com/naudio and its associated GitHub access still exist. Recommended order:
dev.azure.com.... menu → Disable (preserves history) or Delete.naudio name in the ADO org.The pragmatic path: disable the pipeline first, wait a couple of weeks, then delete the project.
.github/release.yml). If we outgrow it, swap in something heavier later.gh release create makes --notes-file and --generate-notes mutually exclusive; adding both would require fetching auto-notes via gh api and concatenating. Possible follow-up if the curated notes alone prove insufficient.