Skip to content

Commit 22263e4

Browse files
committed
Add CI and NuGet publishing workflows with GitVersion integration and update project status
1 parent 200326e commit 22263e4

6 files changed

Lines changed: 484 additions & 14 deletions

File tree

.github/workflows/ci.yml

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- develop
8+
- feature/**
9+
- release/**
10+
- hotfix/**
11+
tags:
12+
- '*'
13+
pull_request:
14+
branches:
15+
- main
16+
- develop
17+
workflow_dispatch:
18+
inputs:
19+
publish:
20+
description: Pack and publish when the ref is main, release/*, or hotfix/*.
21+
required: true
22+
type: boolean
23+
default: false
24+
dry_run:
25+
description: Pack and verify, but do not push to NuGet.
26+
required: true
27+
type: boolean
28+
default: true
29+
channel:
30+
description: Expected channel resolved by GitVersion.
31+
required: false
32+
type: choice
33+
options:
34+
- stable
35+
- beta
36+
37+
permissions:
38+
contents: read
39+
40+
concurrency:
41+
group: ${{ github.workflow }}-${{ github.ref }}
42+
cancel-in-progress: true
43+
44+
env:
45+
DOTNET_VERSION: 10.0.x
46+
SOLUTION: Mammoth.LiteMapper.sln
47+
PACKAGE_OUTPUT: artifacts/packages
48+
49+
jobs:
50+
build-and-test:
51+
name: Build & Test (${{ matrix.os }})
52+
runs-on: ${{ matrix.os }}
53+
strategy:
54+
fail-fast: false
55+
matrix:
56+
os: [ubuntu-latest, windows-latest]
57+
58+
steps:
59+
- name: Checkout
60+
uses: actions/checkout@v6
61+
with:
62+
fetch-depth: 0
63+
64+
- name: Setup .NET
65+
uses: actions/setup-dotnet@v5
66+
with:
67+
dotnet-version: ${{ env.DOTNET_VERSION }}
68+
69+
- name: Cache NuGet packages
70+
uses: actions/cache@v5
71+
with:
72+
path: ~/.nuget/packages
73+
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Build.props', '**/Directory.Packages.props', 'dotnet-tools.json') }}
74+
restore-keys: |
75+
${{ runner.os }}-nuget-
76+
77+
- name: Install Native AOT prerequisites
78+
if: runner.os == 'Linux'
79+
run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev
80+
81+
- name: Restore dotnet tools
82+
run: dotnet tool restore
83+
84+
- name: Determine version
85+
id: gitversion
86+
shell: pwsh
87+
run: |
88+
$version = dotnet gitversion /output json | ConvertFrom-Json
89+
"semver=$($version.SemVer)" >> $env:GITHUB_OUTPUT
90+
"GitVersion SemVer: $($version.SemVer)"
91+
92+
- name: Restore
93+
run: dotnet restore ${{ env.SOLUTION }} --verbosity minimal
94+
95+
- name: Build
96+
run: dotnet build ${{ env.SOLUTION }} -c Release --no-restore -p:ContinuousIntegrationBuild=True
97+
98+
- name: Test
99+
if: runner.os == 'Linux'
100+
run: dotnet test ${{ env.SOLUTION }} -c Release --no-build
101+
env:
102+
LITEMAPPER_REQUIRE_NATIVE_AOT: '1'
103+
104+
- name: Test
105+
if: runner.os == 'Windows'
106+
shell: pwsh
107+
run: |
108+
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
109+
$installPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
110+
if (-not $installPath) {
111+
throw "Visual Studio C++ toolchain was not found."
112+
}
113+
114+
$vsDevCmd = Join-Path $installPath "Common7\Tools\VsDevCmd.bat"
115+
cmd /s /c "call `"$vsDevCmd`" -arch=x64 -host_arch=x64 && dotnet test ${{ env.SOLUTION }} -c Release --no-build"
116+
env:
117+
LITEMAPPER_REQUIRE_NATIVE_AOT: '1'
118+
119+
- name: Validate solution paths
120+
shell: pwsh
121+
run: |
122+
dotnet sln Mammoth.LiteMapper.sln list
123+
dotnet sln Mammoth.LiteMapper.slnx list
124+
125+
publish:
126+
name: Pack & Publish
127+
needs: build-and-test
128+
runs-on: windows-latest
129+
if: >
130+
github.event_name != 'pull_request' &&
131+
(
132+
github.ref == 'refs/heads/main' ||
133+
startsWith(github.ref, 'refs/heads/release/') ||
134+
startsWith(github.ref, 'refs/heads/hotfix/')
135+
) &&
136+
(
137+
github.event_name != 'workflow_dispatch' ||
138+
inputs.publish == true
139+
)
140+
141+
steps:
142+
- name: Checkout
143+
uses: actions/checkout@v6
144+
with:
145+
fetch-depth: 0
146+
147+
- name: Setup .NET
148+
uses: actions/setup-dotnet@v5
149+
with:
150+
dotnet-version: ${{ env.DOTNET_VERSION }}
151+
152+
- name: Restore dotnet tools
153+
run: dotnet tool restore
154+
155+
- name: Determine version
156+
id: gitversion
157+
shell: pwsh
158+
run: |
159+
$version = dotnet gitversion /output json | ConvertFrom-Json
160+
$semver = $version.SemVer
161+
if ([string]::IsNullOrWhiteSpace($semver)) {
162+
throw 'GitVersion did not return a SemVer value.'
163+
}
164+
165+
$channel = if ($semver -match '-') { 'beta' } else { 'stable' }
166+
if ('${{ inputs.channel }}' -and '${{ inputs.channel }}' -ne $channel) {
167+
throw "GitVersion resolved $semver as $channel, but channel '${{ inputs.channel }}' was requested."
168+
}
169+
170+
if ($channel -eq 'beta' -and ($semver -notmatch '-beta([.-].*)?$')) {
171+
throw "GitVersion resolved prerelease version '$semver', but beta publishing requires a '-beta' prerelease label."
172+
}
173+
174+
"semver=$semver" >> $env:GITHUB_OUTPUT
175+
"channel=$channel" >> $env:GITHUB_OUTPUT
176+
"GitVersion SemVer: $semver"
177+
"Publish channel: $channel"
178+
179+
- name: Restore
180+
run: dotnet restore ${{ env.SOLUTION }} --verbosity minimal
181+
182+
- name: Pack
183+
shell: pwsh
184+
run: |
185+
$projects = @(
186+
'src/Mammoth.LiteMapper.Abstractions/Mammoth.LiteMapper.Abstractions.csproj',
187+
'src/Mammoth.LiteMapper.Generator/Mammoth.LiteMapper.Generator.csproj',
188+
'src/Mammoth.LiteMapper/Mammoth.LiteMapper.csproj'
189+
)
190+
191+
New-Item -ItemType Directory -Force -Path '${{ env.PACKAGE_OUTPUT }}' | Out-Null
192+
foreach ($project in $projects) {
193+
dotnet pack $project -c Release --no-restore -o '${{ env.PACKAGE_OUTPUT }}' /p:PackageVersion=${{ steps.gitversion.outputs.semver }}
194+
}
195+
196+
$packages = Get-ChildItem -Path '${{ env.PACKAGE_OUTPUT }}' -Filter "Mammoth.LiteMapper*.${{ steps.gitversion.outputs.semver }}.nupkg" |
197+
Where-Object { $_.Name -notlike '*.symbols.nupkg' }
198+
if ($packages.Count -ne 3) {
199+
throw "Expected 3 packages, found $($packages.Count)."
200+
}
201+
202+
- name: Publish
203+
if: github.event_name != 'workflow_dispatch' || inputs.dry_run == false
204+
shell: pwsh
205+
env:
206+
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
207+
run: |
208+
if ([string]::IsNullOrWhiteSpace($env:NUGET_API_KEY)) {
209+
throw 'NUGET_API_KEY secret is required to publish.'
210+
}
211+
212+
Get-ChildItem -Path '${{ env.PACKAGE_OUTPUT }}' -Filter '*.nupkg' |
213+
Where-Object { $_.Name -notlike '*.symbols.nupkg' } |
214+
ForEach-Object {
215+
dotnet nuget push $_.FullName --source https://api.nuget.org/v3/index.json --api-key $env:NUGET_API_KEY --skip-duplicate
216+
}
217+
218+
- name: Dry run summary
219+
if: github.event_name == 'workflow_dispatch' && inputs.dry_run == true
220+
shell: pwsh
221+
run: |
222+
Get-ChildItem -Path '${{ env.PACKAGE_OUTPUT }}' -Filter '*.nupkg' |
223+
Where-Object { $_.Name -notlike '*.symbols.nupkg' } |
224+
ForEach-Object { "Dry run: would publish $($_.FullName)" }
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
name: publish-nuget
2+
3+
on:
4+
workflow_run:
5+
workflows:
6+
- ci-windows
7+
types:
8+
- completed
9+
branches:
10+
- main
11+
- release/**
12+
- hotfix/**
13+
workflow_dispatch:
14+
inputs:
15+
channel:
16+
description: Expected publishing channel resolved by GitVersion.
17+
required: true
18+
type: choice
19+
options:
20+
- stable
21+
- beta
22+
dry_run:
23+
description: Pack and verify, but do not push to NuGet.
24+
required: true
25+
type: boolean
26+
default: true
27+
28+
permissions:
29+
actions: read
30+
contents: read
31+
32+
jobs:
33+
publish:
34+
runs-on: windows-latest
35+
if: >
36+
github.event_name == 'workflow_dispatch' ||
37+
github.event.workflow_run.conclusion == 'success'
38+
steps:
39+
- name: Resolve publish inputs
40+
id: inputs
41+
shell: pwsh
42+
run: |
43+
if ('${{ github.event_name }}' -eq 'workflow_dispatch') {
44+
$branch = '${{ github.ref_name }}'
45+
$sha = '${{ github.sha }}'
46+
$channel = '${{ inputs.channel }}'
47+
$dryRun = '${{ inputs.dry_run }}'
48+
}
49+
else {
50+
$branch = '${{ github.event.workflow_run.head_branch }}'
51+
$sha = '${{ github.event.workflow_run.head_sha }}'
52+
$channel = ''
53+
$dryRun = 'false'
54+
}
55+
56+
if ($branch -notmatch '^(main|release/.+|hotfix/.+)$') {
57+
throw "NuGet publishing is allowed only from main, release/*, or hotfix/* branches. Current branch: $branch"
58+
}
59+
60+
"branch=$branch" >> $env:GITHUB_OUTPUT
61+
"sha=$sha" >> $env:GITHUB_OUTPUT
62+
"channel=$channel" >> $env:GITHUB_OUTPUT
63+
"dry_run=$dryRun" >> $env:GITHUB_OUTPUT
64+
65+
- uses: actions/checkout@v6
66+
with:
67+
ref: ${{ steps.inputs.outputs.sha }}
68+
fetch-depth: 0
69+
70+
- uses: actions/setup-dotnet@v5
71+
with:
72+
dotnet-version: '10.0.x'
73+
74+
- name: Verify compile and test workflows passed
75+
shell: pwsh
76+
env:
77+
GH_TOKEN: ${{ github.token }}
78+
PUBLISH_SHA: ${{ steps.inputs.outputs.sha }}
79+
run: |
80+
$required = @('ci-linux', 'ci-windows')
81+
$deadline = (Get-Date).AddMinutes(20)
82+
83+
do {
84+
$missing = @()
85+
foreach ($workflow in $required) {
86+
$run = gh run list --workflow "$workflow" --commit "$env:PUBLISH_SHA" --json conclusion,databaseId,headSha,status --limit 10 |
87+
ConvertFrom-Json |
88+
Where-Object { $_.headSha -eq $env:PUBLISH_SHA -and $_.status -eq 'completed' } |
89+
Select-Object -First 1
90+
91+
if ($null -eq $run) {
92+
$missing += $workflow
93+
continue
94+
}
95+
96+
if ($run.conclusion -ne 'success') {
97+
throw "Required workflow '$workflow' completed with conclusion '$($run.conclusion)' for commit $env:PUBLISH_SHA."
98+
}
99+
}
100+
101+
if ($missing.Count -eq 0) {
102+
exit 0
103+
}
104+
105+
Start-Sleep -Seconds 30
106+
} while ((Get-Date) -lt $deadline)
107+
108+
throw "Required workflows did not complete for commit ${env:PUBLISH_SHA}: $($missing -join ', ')"
109+
110+
- name: Publish NuGet packages
111+
shell: pwsh
112+
env:
113+
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
114+
run: |
115+
$arguments = @('-SkipValidation')
116+
117+
if ('${{ steps.inputs.outputs.channel }}') {
118+
$arguments += @('-Channel', '${{ steps.inputs.outputs.channel }}')
119+
}
120+
121+
if ('${{ steps.inputs.outputs.dry_run }}' -eq 'true') {
122+
$arguments += '-DryRun'
123+
}
124+
125+
./scripts/publish-nuget.ps1 @arguments

GitVersion.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
workflow: GitFlow/v1
2+
mode: ContinuousDeployment
3+
branches:
4+
release:
5+
regex: ^release[/-]
6+
increment: None # Do not bump the version automatically, previus value: Patch
7+
hotfix:
8+
regex: ^hotfix[/-]
9+
increment: None # previous value: Patch
10+
unknown:
11+
increment: Patch
12+
ignore:
13+
sha: []
14+
merge-message-formats: {}

STATUS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
- Current milestone: Milestone 16, usage documentation assembled from compiling samples.
44
- Current state: Complete. GitHub Actions verified CI Native AOT validation, so Mammoth.LiteMapper 1.0 is done against the specification definition of done.
55
- Completed milestones: Milestone 1; Milestone 2; Milestone 3; Milestone 4; Milestone 5; Milestone 6; Milestone 7; Milestone 8; Milestone 9; Milestone 10; Milestone 11; Milestone 12; Milestone 13; Milestone 14; Milestone 15; Milestone 16.
6-
- Current work: None.
7-
- Work completed: Added `docs/USAGE.md` assembled from the compiling Basic, Collections, and ASP.NET Core samples; documented static, instance, cycle, collection, ASP.NET Core, installation, declaration, nested and inherited mapper behavior, public API surface, configuration precedence/defaults, method-level and assembly-level options, member matching, source/target path rules, mapping signatures, construction, explicit configuration, converter, default mapping, external mapper registration, extension/post-processing, built-in and numeric conversion, nullability, nested object, existing-target, enum, recursive exception details, unsupported/special types, generated-code, package/platform, Native AOT, analyzer-option, diagnostic severity/location, and deferred-feature usage without adding new semantics or runtime API claims.
6+
- Current work: NuGet publishing workflow and manual publishing script added.
7+
- Work completed: Added `docs/USAGE.md` assembled from the compiling Basic, Collections, and ASP.NET Core samples; documented static, instance, cycle, collection, ASP.NET Core, installation, declaration, nested and inherited mapper behavior, public API surface, configuration precedence/defaults, method-level and assembly-level options, member matching, source/target path rules, mapping signatures, construction, explicit configuration, converter, default mapping, external mapper registration, extension/post-processing, built-in and numeric conversion, nullability, nested object, existing-target, enum, recursive exception details, unsupported/special types, generated-code, package/platform, Native AOT, analyzer-option, diagnostic severity/location, and deferred-feature usage without adding new semantics or runtime API claims. Added a NuGet publishing GitHub Actions workflow gated to `main`, `release/*`, and `hotfix/*`, requiring successful `ci-linux` and `ci-windows` runs for the target commit, and added `scripts/publish-nuget.ps1` for manual stable/beta package publishing with SemVer resolved by GitVersion.
88
- Tests added or updated: Added and expanded `Milestone16UsageDocumentationTests` with sample compile/run validation, source-backed documentation checks, full capability-section checks, public configuration/API coverage checks, diagnostic/deferred-feature checks, and negative checks for runtime mapper API documentation.
99
- Diagnostics added: None.
1010
- Generated-source review: Milestone 16 did not add generated-source snapshots or generator behavior. Sample compile/run validation exercised generated implementations from compiling sample source. Documentation and sample text were checked for absence of `System.Reflection`, `dynamic`, runtime mapper registration, and runtime object-dispatch API claims except in deferred-feature wording.

0 commit comments

Comments
 (0)