Release pipeline via GitHub Releases + StrongTypes.FsCheck package (#34, #40)#48
Merged
Conversation
- Workflow: build/test on main push + PR; publish only on `release: published` - Version and release notes come from the Release: `-p:Version` from the tag name (stripped of leading `v`), `-p:PackageReleaseNotes` from the body - csproj: drop hardcoded `<Version>`, `<AssemblyVersion>`, `<FileVersion>`, and `<PackageReleaseNotes>`; keep a `0.0.0-dev` local-build placeholder Closes #34 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop --skip-duplicate so re-publishing an already-shipped version surfaces as a workflow failure instead of silently no-opping. A Release whose tag collides with a prior one should never look green in Actions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tag `core-vX.Y.Z` publishes Kalicz.StrongTypes at X.Y.Z; tag `efcore-vX.Y.Z` publishes Kalicz.StrongTypes.EfCore at X.Y.Z. Unknown prefixes fail the publish step with a clear message. Build and test still run on every push/PR and on the release event (via `needs: build`), so no release ships without a green test run. Because EfCore's ProjectReference to core picks up the cascading `-p:Version` MSBuild property, the EfCore nuspec pins its core dependency to the same version as the tag. This enforces the "dependent packages ship at the same version as the core they need" rule without extra workflow logic — you just have to publish core first when bumping the shared version. Switched from `dotnet pack` to `dotnet build -p:PackageOutputPath` because GeneratePackageOnBuild=true in the csproj files makes `dotnet build` produce the nupkg natively. The push step pins to ./out/$PACKAGE.$VERSION.nupkg so building a dependent doesn't accidentally republish its transitively-built core nupkg. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-package tags added meaningful ceremony (three tag prefixes, remembering to release core first, three Release drafts) for a benefit — independent release notes per package — that doesn't justify the cost for a tightly coupled package family that will almost always ship together. Tag `vX.Y.Z` now publishes all packages at X.Y.Z with a single shared release body. Version matching between dependents and core is still enforced by MSBuild's `-p:Version` cascading through ProjectReferences. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Publish job now iterates over all three publishable csproj files, skipping any that don't exist yet. When StrongTypes.FsCheck lands (#40), the next release automatically picks it up with no workflow change. Added .github/RELEASE_TEMPLATE.md as a copy-paste structure for the Release body — GitHub has no native release body template feature, so this is a manual template contributors paste into the Release form. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… convention Replace the hardcoded list of csproj paths with a single `dotnet build StrongTypes.slnx`. Non-publishable projects (tests, analyzers, source generators) already set IsPackable=false, so they produce no nupkg; only projects with GeneratePackageOnBuild=true emit packages. New publishable packages are picked up automatically by setting those two properties. Verified locally: solution build at version X emits exactly Kalicz.StrongTypes.X.nupkg and Kalicz.StrongTypes.EfCore.X.nupkg, with the latter pinning its core dependency to version X. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moves the shared FsCheck arbitraries (NonEmptyString, numeric wrappers,
Maybe<T>, NonEmptyEnumerable) from src/StrongTypes.Tests/Generators.cs
into a new publishable package, so downstream users can property-test
against StrongTypes types via:
[Properties(Arbitrary = new[] { typeof(Generators) })]
Namespace: StrongTypes.FsCheck (sibling to StrongTypes.EfCore).
Runtime dep: FsCheck 3.3.2 (not the xunit-specific bits).
Test project picks up the type via a new GlobalUsings.cs so the existing
11 test files keep referencing Generators unqualified.
The publish pipeline added earlier in this PR auto-includes FsCheck the
moment the csproj lands in the solution — no workflow change needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add `github.event.release.target_commitish == 'main'` to the publish job's if-condition. A Release drafted via the UI captures its target branch in this field; this gate blocks any Release that was drafted against a feature branch or pre-existing tag from shipping to NuGet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ci.yml runs on push/PR — restore, build, test. Produces the `build` status check the tag ruleset requires. release.yml runs on release:published — re-runs the full test suite on the release commit, then packs and pushes. Publish stays gated on target_commitish == 'main', still uses the Nuget.org environment, and picks up all publishable packages via `dotnet build StrongTypes.slnx`. Each workflow has one clear job of work; a reader can understand CI without reading CD and vice versa. Mild duplication of the restore/build/ test block — small price for the separation of concerns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This reverts commit 7207f01.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
.github/workflows/build.ymlCloses #34. Closes #40.
How a release works
v0.4.0, targetmain.github/RELEASE_TEMPLATE.mdif helpful)build.ymlruns:buildjob verifies the commit (tests included), thenpublishjob packs and pushes every publishable nupkg to nuget.org0.4.0git push origin v0.4.0alone does nothing. Only publishing a GitHub Release triggerspublish.Workflow structure
Single
build.ymlwith two jobs:build— runs on push to main, PR to main, and release:published. Restore, build, test, upload test logs.publish— runs on release:published only,needs: build. Parses the tag, passes version + release notes todotnet build, pushes the resulting nupkgs.needs: buildguarantees tests pass on the release commit before any package leaves the box.Safety layers stacking
v*tags; tag creation requires a greenbuildcheck frombuild.ymlon the tagged commit.Nuget.orgenvironment): allowed refs restricted tov*tags.publishjob conditional ongithub.event.release.target_commitish == 'main'— rejects Releases drafted against any branch other than main.buildjob runs on the release event itself beforepublishstarts.dotnet nuget push(no--skip-duplicate) fails loudly if a version was already shipped.Publish is convention-based, not hardcoded
The pack step is a single
dotnet build StrongTypes.slnxwith-p:Version,-p:PackageReleaseNotes, and-p:PackageOutputPath. Publishable projects have<PackageId>+<GeneratePackageOnBuild>true</GeneratePackageOnBuild>; non-publishable ones set<IsPackable>false</IsPackable>. Future publishable packages join the release stream automatically just by following the convention.Today that covers three packages:
Kalicz.StrongTypes,Kalicz.StrongTypes.EfCore,Kalicz.StrongTypes.FsCheck.Version-matching across packages
EfCore's and FsCheck's
<ProjectReference>to core picks up the cascading-p:VersionMSBuild property, so when CI builds them at0.4.0, the generated nuspec writes<dependency id="Kalicz.StrongTypes" version="0.4.0" />automatically. Verified locally.New FsCheck package
Moves the shared arbitraries from
src/StrongTypes.Tests/Generators.csintosrc/StrongTypes.FsCheck/withnamespace StrongTypes.FsCheck. Consumers register everything with a single attribute:Runtime dep is
FsCheck 3.3.2(no xunit-specific bits). Internal test project picks up the type via a newGlobalUsings.csso the existing 11 test files keep referencingGeneratorsunqualified.csproj changes (Version/Notes)
Removed from the publishable csprojs:
<Version>,<AssemblyVersion>,<FileVersion>(now-p:Versionfrom the tag)<PackageReleaseNotes>(now-p:PackageReleaseNotesfrom the Release body)Each publishable csproj keeps
<Version>0.0.0-dev</Version>as a local-build placeholder.Test plan
v*, restrict creations/updates/deletions (admin bypass), require status checkbuildv*v0.3.1-test.1) to validate end-to-end — all three packages should land on nuget.org at that versionmaindoes not triggerpublishv0.4.0🤖 Generated with Claude Code