Skip to content

Commit febaaba

Browse files
Add NuGet CI/CD workflows with MinVer versioning (#7)
- ci.yml: build + test (net8.0/net10.0) on PRs to master - release.yml: auto-publish alpha prereleases on every merge to master; workflow_dispatch to cut beta/rc/stable — creates the git tag, publishes to nuget.org, opens a GitHub Release (softprops/action-gh-release@v3); stable is gated behind a nuget.org environment - Publish via NuGet trusted publishing (OIDC): NuGet/login@v1 exchanges a GitHub OIDC token for a short-lived key — no long-lived NUGET_API_KEY secret - Adopt MinVer (git-tag driven, prefix "v"); drop the hardcoded <Version>. First modern release is 2.0.0 (breaking: dropped netstandard/Framework, removed AddAppSettings). The legacy version-* tags belong to the old ExistAll.SimpleConfig package and are deliberately ignored. - Package quality: symbol packages (snupkg), SourceLink, deterministic builds, embedded README + icon; fix RepositoryUrl -> existall/SimpleSettings - Remove the non-functional dotnet.yml stub Co-authored-by: Guy Ludvig <guy@frontegg.com>
1 parent bbbe004 commit febaaba

6 files changed

Lines changed: 350 additions & 27 deletions

File tree

.github/workflows/ci.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches: [ master ]
6+
7+
# PR validation only reads the repo.
8+
permissions:
9+
contents: read
10+
11+
# Cancel superseded runs for the same PR.
12+
concurrency:
13+
group: ci-${{ github.ref }}
14+
cancel-in-progress: true
15+
16+
env:
17+
DOTNET_NOLOGO: true
18+
DOTNET_CLI_TELEMETRY_OPTOUT: true
19+
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
20+
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
21+
22+
jobs:
23+
build-test:
24+
runs-on: ubuntu-latest
25+
defaults:
26+
run:
27+
working-directory: src # global.json (Microsoft.Testing.Platform opt-in) lives here
28+
steps:
29+
- name: Checkout
30+
uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 0 # MinVer needs full history + tags to compute the version
33+
34+
- name: Setup .NET (8 + 10)
35+
uses: actions/setup-dotnet@v4
36+
with:
37+
dotnet-version: |
38+
8.0.x
39+
10.0.x
40+
# Build uses the SDK pinned by src/global.json (10.0.100); the 8.0 runtime
41+
# installed here is what actually runs the net8.0 test target.
42+
43+
- name: Cache NuGet packages
44+
uses: actions/cache@v4
45+
with:
46+
path: ${{ github.workspace }}/.nuget/packages
47+
key: nuget-${{ runner.os }}-${{ hashFiles('src/Directory.Packages.props') }}
48+
restore-keys: nuget-${{ runner.os }}-
49+
50+
- name: Restore
51+
run: dotnet restore ExistAll.SimpleConfig.slnx
52+
53+
- name: Build (Release, deterministic)
54+
run: dotnet build ExistAll.SimpleConfig.slnx -c Release --no-restore -p:ContinuousIntegrationBuild=true
55+
56+
- name: Test (net8.0 + net10.0)
57+
run: dotnet test ExistAll.SimpleConfig.slnx -c Release --no-build

.github/workflows/dotnet.yml

Lines changed: 0 additions & 21 deletions
This file was deleted.

.github/workflows/release.yml

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches: [ master ] # every merge -> fresh alpha prerelease
6+
workflow_dispatch:
7+
inputs:
8+
channel:
9+
description: Release channel
10+
type: choice
11+
options: [ beta, rc, stable ]
12+
default: beta
13+
bump:
14+
description: SemVer part to bump (relative to the latest stable tag; ignored on the first release)
15+
type: choice
16+
options: [ patch, minor, major ]
17+
default: minor
18+
dry_run:
19+
description: Build/pack/upload only - do NOT tag, push to NuGet, or create a Release
20+
type: boolean
21+
default: false
22+
23+
permissions:
24+
contents: read
25+
26+
# Serialize releases; never cancel an in-flight publish.
27+
concurrency:
28+
group: release
29+
cancel-in-progress: false
30+
31+
env:
32+
DOTNET_NOLOGO: true
33+
DOTNET_CLI_TELEMETRY_OPTOUT: true
34+
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
35+
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
36+
ARTIFACTS: ${{ github.workspace }}/artifacts
37+
NUGET_SOURCE: https://api.nuget.org/v3/index.json
38+
TAG_PREFIX: v # deliberately NOT "version-": those tags are the dead ExistAll.SimpleConfig line
39+
BASELINE_VERSION: 2.0.0 # first release on the v* line (modernized, breaking vs published 1.0.0)
40+
41+
jobs:
42+
# ---------------------------------------------------------------------------
43+
# A) Automatic ALPHA prerelease on every push to master.
44+
# MinVer stamps a height-based prerelease, e.g. 2.0.0-alpha.0.7.
45+
# ---------------------------------------------------------------------------
46+
prerelease:
47+
if: github.event_name == 'push'
48+
runs-on: ubuntu-latest
49+
permissions:
50+
contents: read # no tag/release here
51+
id-token: write # OIDC for NuGet trusted publishing
52+
defaults:
53+
run:
54+
working-directory: src
55+
steps:
56+
- uses: actions/checkout@v4
57+
with:
58+
fetch-depth: 0
59+
60+
- uses: actions/setup-dotnet@v4
61+
with:
62+
dotnet-version: |
63+
8.0.x
64+
10.0.x
65+
66+
- uses: actions/cache@v4
67+
with:
68+
path: ${{ github.workspace }}/.nuget/packages
69+
key: nuget-${{ runner.os }}-${{ hashFiles('src/Directory.Packages.props') }}
70+
restore-keys: nuget-${{ runner.os }}-
71+
72+
- name: Restore
73+
run: dotnet restore ExistAll.SimpleConfig.slnx
74+
75+
- name: Build
76+
run: dotnet build ExistAll.SimpleConfig.slnx -c Release --no-restore -p:ContinuousIntegrationBuild=true
77+
78+
- name: Test
79+
run: dotnet test ExistAll.SimpleConfig.slnx -c Release --no-build
80+
81+
- name: Pack (MinVer -> height-based alpha)
82+
run: dotnet pack ExistAll.SimpleConfig.slnx -c Release --no-build -p:ContinuousIntegrationBuild=true -o "$ARTIFACTS"
83+
84+
- name: List packages
85+
run: ls -1 "$ARTIFACTS"
86+
87+
- name: Upload packages
88+
uses: actions/upload-artifact@v4
89+
with:
90+
name: nuget-prerelease
91+
path: |
92+
${{ env.ARTIFACTS }}/*.nupkg
93+
${{ env.ARTIFACTS }}/*.snupkg
94+
95+
- name: NuGet login (OIDC -> short-lived API key)
96+
uses: NuGet/login@v1
97+
id: login
98+
with:
99+
user: ${{ secrets.NUGET_USER }}
100+
101+
# dotnet nuget push globs natively; each .nupkg also pushes its adjacent .snupkg.
102+
- name: Push to NuGet.org
103+
run: dotnet nuget push "$ARTIFACTS/*.nupkg" --api-key "${{ steps.login.outputs.NUGET_API_KEY }}" --source "$NUGET_SOURCE" --skip-duplicate
104+
105+
# ---------------------------------------------------------------------------
106+
# B) Manual, curated RELEASE (beta.N / rc.N / stable) via workflow_dispatch.
107+
# Tag is created LOCALLY (MinVer reads it), pushed only AFTER a successful
108+
# NuGet push, then a GitHub Release is created.
109+
# ---------------------------------------------------------------------------
110+
release:
111+
if: github.event_name == 'workflow_dispatch'
112+
runs-on: ubuntu-latest
113+
permissions:
114+
contents: write # push tag + create GitHub Release
115+
id-token: write # OIDC for NuGet trusted publishing
116+
environment:
117+
name: nuget.org # add a Required reviewer here to gate stable publishes
118+
defaults:
119+
run:
120+
working-directory: src
121+
steps:
122+
- uses: actions/checkout@v4
123+
with:
124+
fetch-depth: 0 # all tags -> correct next-version computation
125+
126+
- uses: actions/setup-dotnet@v4
127+
with:
128+
dotnet-version: |
129+
8.0.x
130+
10.0.x
131+
132+
- uses: actions/cache@v4
133+
with:
134+
path: ${{ github.workspace }}/.nuget/packages
135+
key: nuget-${{ runner.os }}-${{ hashFiles('src/Directory.Packages.props') }}
136+
restore-keys: nuget-${{ runner.os }}-
137+
138+
- name: Compute next version tag
139+
id: ver
140+
env:
141+
BUMP: ${{ inputs.bump }}
142+
CHANNEL: ${{ inputs.channel }}
143+
run: |
144+
set -euo pipefail
145+
PREFIX="$TAG_PREFIX"
146+
git fetch --tags --force --quiet
147+
148+
# Highest STABLE tag under the "v" prefix: vMAJOR.MINOR.PATCH (no pre-release).
149+
# The "v[0-9]" glob excludes the legacy "version-*" tags by construction.
150+
latest_stable="$(
151+
git tag --list "${PREFIX}[0-9]*" \
152+
| grep -E "^${PREFIX}[0-9]+\.[0-9]+\.[0-9]+$" \
153+
| sed "s/^${PREFIX}//" \
154+
| sort -V | tail -n1 || true
155+
)"
156+
157+
if [ -z "$latest_stable" ]; then
158+
# First release on the v* line -> the baseline itself (bump ignored).
159+
TARGET="$BASELINE_VERSION"
160+
echo "No v* stable tag yet -> first release targets baseline $TARGET (bump '$BUMP' ignored)."
161+
else
162+
IFS='.' read -r MA MI PA <<< "$latest_stable"
163+
case "$BUMP" in
164+
major) MA=$((MA+1)); MI=0; PA=0 ;;
165+
minor) MI=$((MI+1)); PA=0 ;;
166+
patch) PA=$((PA+1)) ;;
167+
*) echo "::error::invalid bump '$BUMP'"; exit 1 ;;
168+
esac
169+
TARGET="${MA}.${MI}.${PA}"
170+
echo "Latest stable $latest_stable + $BUMP -> $TARGET"
171+
fi
172+
173+
if [ "$CHANNEL" = "stable" ]; then
174+
NEW_TAG="${PREFIX}${TARGET}"
175+
if git rev-parse -q --verify "refs/tags/${NEW_TAG}" >/dev/null; then
176+
echo "::error::tag ${NEW_TAG} already exists"; exit 1
177+
fi
178+
PRERELEASE=false
179+
else
180+
# Continue the -beta.N / -rc.N series for this target base.
181+
last_n="$(
182+
git tag --list "${PREFIX}${TARGET}-${CHANNEL}.*" \
183+
| sed -E "s/^${PREFIX}${TARGET}-${CHANNEL}\.([0-9]+)$/\1/" \
184+
| grep -E '^[0-9]+$' | sort -n | tail -n1 || true
185+
)"
186+
next_n=$(( ${last_n:-0} + 1 ))
187+
NEW_TAG="${PREFIX}${TARGET}-${CHANNEL}.${next_n}"
188+
PRERELEASE=true
189+
fi
190+
191+
echo "Computed tag: $NEW_TAG (prerelease=$PRERELEASE)"
192+
{
193+
echo "tag=$NEW_TAG"
194+
echo "prerelease=$PRERELEASE"
195+
} >> "$GITHUB_OUTPUT"
196+
197+
- name: Create local tag (MinVer reads it at pack time)
198+
run: |
199+
set -euo pipefail
200+
git config user.name "github-actions[bot]"
201+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
202+
git tag -a "${{ steps.ver.outputs.tag }}" -m "Release ${{ steps.ver.outputs.tag }}"
203+
204+
- name: Restore
205+
run: dotnet restore ExistAll.SimpleConfig.slnx
206+
207+
- name: Build
208+
run: dotnet build ExistAll.SimpleConfig.slnx -c Release --no-restore -p:ContinuousIntegrationBuild=true
209+
210+
- name: Test
211+
run: dotnet test ExistAll.SimpleConfig.slnx -c Release --no-build
212+
213+
- name: Pack (MinVer -> exact tagged version)
214+
run: dotnet pack ExistAll.SimpleConfig.slnx -c Release --no-build -p:ContinuousIntegrationBuild=true -o "$ARTIFACTS"
215+
216+
- name: Upload packages
217+
uses: actions/upload-artifact@v4
218+
with:
219+
name: nuget-${{ steps.ver.outputs.tag }}
220+
path: |
221+
${{ env.ARTIFACTS }}/*.nupkg
222+
${{ env.ARTIFACTS }}/*.snupkg
223+
224+
- name: NuGet login (OIDC -> short-lived API key)
225+
if: ${{ !inputs.dry_run }}
226+
uses: NuGet/login@v1
227+
id: login
228+
with:
229+
user: ${{ secrets.NUGET_USER }}
230+
231+
# dotnet nuget push globs natively; each .nupkg also pushes its adjacent .snupkg.
232+
- name: Push to NuGet.org
233+
if: ${{ !inputs.dry_run }}
234+
run: dotnet nuget push "$ARTIFACTS/*.nupkg" --api-key "${{ steps.login.outputs.NUGET_API_KEY }}" --source "$NUGET_SOURCE" --skip-duplicate
235+
236+
- name: Push git tag (only after a successful publish)
237+
if: ${{ !inputs.dry_run }}
238+
run: git push origin "${{ steps.ver.outputs.tag }}"
239+
240+
- name: Create GitHub Release
241+
if: ${{ !inputs.dry_run }}
242+
uses: softprops/action-gh-release@v3
243+
with:
244+
tag_name: ${{ steps.ver.outputs.tag }}
245+
name: ${{ steps.ver.outputs.tag }}
246+
prerelease: ${{ steps.ver.outputs.prerelease }}
247+
generate_release_notes: true
248+
files: |
249+
${{ env.ARTIFACTS }}/*.nupkg
250+
${{ env.ARTIFACTS }}/*.snupkg

icon.png

8.13 KB
Loading

src/Directory.Build.props

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,52 @@
44
<PropertyGroup>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
7-
<LangVersion>latest</LangVersion>
7+
<!-- LangVersion intentionally unset: each target framework uses its default
8+
(net8.0 -> C# 12, net10.0 -> C# 14), which is reproducible and idiomatic. -->
89
</PropertyGroup>
910

10-
<!-- Shared NuGet package metadata (formerly version.props) -->
11+
<!-- Shared NuGet package metadata (formerly version.props).
12+
Version is intentionally omitted: MinVer derives it from git tags (see below). -->
1113
<PropertyGroup>
12-
<Version>1.0.0</Version>
1314
<Authors>Guy Ludvig</Authors>
1415
<Company>ExistsForAll</Company>
1516
<Description>SimpleSettings allow applications to use configurations values from any data store in the type of interfaces, thus decouples the frameworks from your appliaction and allowing application to inject interfaces and nothing else. This framework should be used instead of .Net Core IOptions&lt;T&gt;.</Description>
16-
<PackageProjectUrl>https://github.com/existall/SimpleConfig</PackageProjectUrl>
17-
<RepositoryUrl>https://github.com/existall/SimpleConfig</RepositoryUrl>
18-
<RepositoryType>GIT</RepositoryType>
17+
<PackageProjectUrl>https://github.com/existall/SimpleSettings</PackageProjectUrl>
18+
<RepositoryUrl>https://github.com/existall/SimpleSettings</RepositoryUrl>
19+
<RepositoryType>git</RepositoryType>
1920
<PackageLicenseExpression>MIT</PackageLicenseExpression>
2021
<PackageTags>Configuration, Settings, Ioc, DI, ExistsForAll, Dependency injection, Options, SimpleConfig</PackageTags>
22+
<PackageIcon>icon.png</PackageIcon>
23+
<PackageReadmeFile>README.md</PackageReadmeFile>
2124
</PropertyGroup>
2225

26+
<!-- Pack the shared README + icon into every package.
27+
Paths are relative to this file (src/), so ../ resolves to the repo root. -->
28+
<ItemGroup>
29+
<None Include="$(MSBuildThisFileDirectory)..\README.md" Pack="true" PackagePath="\" Visible="false" />
30+
<None Include="$(MSBuildThisFileDirectory)..\icon.png" Pack="true" PackagePath="\" Visible="false" />
31+
</ItemGroup>
32+
33+
<!-- Versioning: MinVer, git-tag driven, tag prefix "v".
34+
MinVerMinimumMajorMinor bridges to the modernized 2.x line: until the first v* tag exists,
35+
master builds are stamped 2.0.0-alpha.0.<height> (above the published 1.0.0, below 2.0.0). -->
36+
<PropertyGroup>
37+
<MinVerTagPrefix>v</MinVerTagPrefix>
38+
<MinVerMinimumMajorMinor>2.0</MinVerMinimumMajorMinor>
39+
</PropertyGroup>
40+
41+
<!-- Reproducible, debuggable packages. SourceLink is built into the SDK (>= .NET 8).
42+
ContinuousIntegrationBuild is passed on the CLI in CI (-p:ContinuousIntegrationBuild=true). -->
43+
<PropertyGroup>
44+
<IncludeSymbols>true</IncludeSymbols>
45+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
46+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
47+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
48+
<Deterministic>true</Deterministic>
49+
</PropertyGroup>
50+
51+
<ItemGroup>
52+
<PackageReference Include="MinVer" PrivateAssets="all" />
53+
</ItemGroup>
54+
2355
</Project>

src/Directory.Packages.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,9 @@
2121
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
2222
</ItemGroup>
2323

24+
<ItemGroup>
25+
<!-- Build / versioning -->
26+
<PackageVersion Include="MinVer" Version="7.0.0" />
27+
</ItemGroup>
28+
2429
</Project>

0 commit comments

Comments
 (0)