Skip to content

build: each package's release notes are its version's changelog section - #77

Open
CaffeinatedCoder wants to merge 1 commit into
release/2.3.0from
chore/release-notes-from-changelog
Open

CaffeinatedCoder wants to merge 1 commit into
release/2.3.0from
chore/release-notes-from-changelog

Conversation

@CaffeinatedCoder

Copy link
Copy Markdown
Owner

Closes #75

Problem

PackageReleaseNotes was a link to CHANGELOG.md at blob/main. nuget.org's Release Notes tab and Visual Studio's Updates tab showed that link instead of the version's notes, and a reader of an older version landed on today's file.

Change

  • ReleaseNotesFromChangelog (src/Directory.Build.targets, before GenerateNuspec) sets PackageReleaseNotes to the section under ## $(Version) in the root CHANGELOG.md, up to the next ##. Previews and Unreleased sections count too, so a CI pack carries the notes of the commit it was built from. One footer line follows: Full changelog: https://github.com/CaffeinatedCoder/CodoMetis.TypeKit/blob/main/CHANGELOG.md. The link in Directory.Build.props is gone.
  • Fails closed, naming CHANGELOG.md, when the section is missing, empty, has a relative link (nuget.org can't resolve one), or makes notes longer than nuget.org's 35,000 characters.
  • CONTRIBUTING.md (Releasing, steps 1 and 4) says so. The empty-section rule has one consequence: the commit that opens the next version (step 4) must write the section's first line, or every pack fails, the conventions fixture's included.
  • A 2.3.0 changelog line.

How nuget.org renders release notes (measured 2026-10-20, from the package pages' HTML)

The notes are plain text. nuget.org HTML-escapes them, turns each line break into <br />, and links bare URLs. It does nothing else:

Package In the nuspec On the page
Bogus 35.6.5 ## v35.6.5, * PR 622: …, `Internet.Avatar()` ## v35.6.5<br />* PR 622: …, backticks shown as they are
Asp.Versioning.Http 10.2.3 [Issue #1221](https://github.com/…/issues/1221) [Issue #1221](<a href="https://…/1221">https://…/1221</a>), brackets and parentheses shown
RazorEngine 3.10.0 thanks ocoanet (https://github.com/…/pull/431) (<a href="…/431">…/431</a>), the closing parenthesis left out of the link
MSTest.TestFramework 4.5.1 …/Changelog.md#4.5.1. the trailing period left out of the link

So the target writes [text](url) as text (url), drops backticks and **, and writes a ### Fixed subheading as Fixed. - list markers stay, since they read fine as plain text. Nuke.Common makes the same choice: its notes are plain • bullets.

Filtering: none, each package carries the whole section

The areas don't map onto the seven packages:

  • Only 2.2.0 writes areas at all (OpenAPI: 18 times, EF Core: 4, Generators: 1). 2.1.0 and earlier lead with bold feature names or nothing.
  • An area names a topic, not a package. OpenAPI is AspNetCore. JSON covers converters in core and generated code in Generators. Analyzer rules ship in Analyzers, but CMTK1xxx come from Generators.
  • One change often spans packages: 2.1.0's Fault<T1, T2> touched core, AspNetCore and Testing.
  • Every satellite depends on CodoMetis.TypeKit, and that package brings the analyzers. A core or analyzer change therefore reaches every satellite's consumer, so filtering by area would hide it from the very readers it affects.

Since every package carries the whole section, no package ever has an empty one, and there is no "no changes for this package" line.

Tests

PackageReleaseNotesTests (conventions, Packs fixture, next to PackageReadmeTests):

  • For every packed nuspec, releaseNotes equals the version's section in plain text plus the footer. The test derives the expected text again, line by line, rather than with the target's regexes.
  • For every packed nuspec, releaseNotes contains no Markdown (`, **, ](, # at the start of a line).
  • The target alone (dotnet msbuild -t:ReleaseNotesFromChangelog -getProperty:PackageReleaseNotes, on a sample changelog given through ReleaseNotesChangelog) writes every shape as plain text: a subheading, code, bold, a link holding code, and text that MSBuild would expand or unescape (50%3B; $(Foo) @(Bar) %(Baz) ' " < & — ü). It is checked with LF and with CRLF, and the section is picked out from between a later and an earlier version.
  • The target refuses each of: no section, an empty section, a relative link, and notes over 35,000 characters. Each refusal names CHANGELOG.md.

The sample tests are there because a seed showed they were needed. Today's 2.3.0 section has no Markdown, so a target that kept backticks passed the nuspec tests.

Seeds (each applied alone, then restored):

Seed Result
Target reads PackageValidationBaselineVersion's section (wrong section) fails: 7 nuspec equality tests
Section reads on to the end of the file fails
Target dropped (Condition="false") fails: 14, "has no <releaseNotes>"
## 2.3.0 renamed (missing section) fails: the pack fails, so the fixture does
2.3.0's line removed (empty section) fails: the pack fails
Over 35,000 characters fails: the pack fails
Backticks kept / bold kept / links kept / ### kept fail: the sample tests
Each refusal's Error disabled (missing, empty, relative link, length) fails: that refusal's test
Control: MSBuild specials and CRLF in the real section passes

Full conventions suite locally (macOS): 1521 passed.

Finding filed: #76. The changelog's own links point at blob/main, so they have the same problem one level down.

🤖 Generated with Claude Code

The ReleaseNotesFromChangelog target writes the section under
'## $(Version)' of CHANGELOG.md into every nuspec, as the plain text
nuget.org shows (measured on package pages: it escapes the notes and
links bare URLs, nothing else), with one line linking the whole
changelog. Every package carries the whole section: the areas name
topics, not packages, and every satellite depends on the base package.
A missing or empty section, a relative link, or notes over 35,000
characters fail the pack, naming the file.

PackageReleaseNotesTests holds the packed nuspecs to the section, and
runs the target on a sample changelog of every Markdown shape and of
MSBuild-special text, in LF and CRLF, and on the four it must refuse.

Closes #75

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CaffeinatedCoder CaffeinatedCoder added this to the 2.3.0 milestone Oct 20, 2026
@CaffeinatedCoder CaffeinatedCoder added chore Build, CI, tooling, packaging, refactoring with no behaviour change package/core Affects CodoMetis.TypeKit package/analyzers Affects CodoMetis.TypeKit.Analyzers package/generators Affects CodoMetis.TypeKit.Generators package/efcore Affects CodoMetis.TypeKit.EntityFrameworkCore package/aspnetcore Affects CodoMetis.TypeKit.AspNetCore package/serilog Affects CodoMetis.TypeKit.Serilog package/testing Affects CodoMetis.TypeKit.Testing area/release Versioning, changelogs, packaging and release wiring labels Oct 20, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/release Versioning, changelogs, packaging and release wiring chore Build, CI, tooling, packaging, refactoring with no behaviour change package/analyzers Affects CodoMetis.TypeKit.Analyzers package/aspnetcore Affects CodoMetis.TypeKit.AspNetCore package/core Affects CodoMetis.TypeKit package/efcore Affects CodoMetis.TypeKit.EntityFrameworkCore package/generators Affects CodoMetis.TypeKit.Generators package/serilog Affects CodoMetis.TypeKit.Serilog package/testing Affects CodoMetis.TypeKit.Testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant