11name : Release
22
3+ # Fires on v*.*.* tags (with prerelease suffixes). Version comes from Directory.Build.props.
4+ # Publishes via NuGet Trusted Publishing (OIDC, no long-lived API key). Setup in docs/RELEASING.md.
35on :
46 push :
57 tags :
6- - ' v*.*.*'
8+ - ' v[0-9]+.[0-9]+.[0-9]+*'
9+ workflow_dispatch :
10+ inputs :
11+ dry_run :
12+ description : " Pack and validate only — skip nuget push"
13+ type : boolean
14+ default : true
15+
16+ concurrency :
17+ group : release-${{ github.ref }}
18+ cancel-in-progress : false
719
820jobs :
9- publish :
21+ release :
1022 runs-on : ubuntu-latest
1123 if : github.event.repository.fork == false
24+ environment : release
25+ permissions :
26+ contents : write # for creating the GitHub Release
27+ id-token : write # required for the Trusted Publishing OIDC exchange
28+ env :
29+ PUBLISH : ${{ github.event_name == 'push' || inputs.dry_run == false }}
1230
1331 steps :
1432 - uses : actions/checkout@v4
@@ -18,46 +36,75 @@ jobs:
1836 with :
1937 dotnet-version : 10.0.x
2038
21- - name : Extract version from tag
39+ - name : Read version
2240 id : version
23- run : echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
41+ run : |
42+ v=$(dotnet msbuild src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj -getProperty:Version)
43+ echo "Directory.Build.props version: $v"
44+ echo "VERSION=$v" >> "$GITHUB_OUTPUT"
45+
46+ - name : Check tag matches version
47+ if : github.event_name == 'push'
48+ run : |
49+ tag="${GITHUB_REF#refs/tags/v}"
50+ if [ "$tag" != "${{ steps.version.outputs.VERSION }}" ]; then
51+ echo "::error::The CLI builds version ${{ steps.version.outputs.VERSION }} but the tag is v$tag. Update Directory.Build.props."
52+ exit 1
53+ fi
2454
25- - name : Restore dependencies
26- run : dotnet restore src/
55+ - name : Extract release notes for this version
56+ run : bash scripts/extract-release-notes.sh "${{ steps.version.outputs.VERSION }}" > "$RUNNER_TEMP/release-body.md"
57+
58+ - name : Restore dependencies (src + tests)
59+ run : |
60+ dotnet restore src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj
61+ dotnet restore tests/ShellUI.Native.Tests/ShellUI.Native.Tests.csproj
2762
2863 - name : Build
29- run : dotnet build src/ --no-restore --configuration Release
64+ run : |
65+ dotnet build src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj --no-restore --configuration Release
66+ dotnet build tests/ShellUI.Native.Tests/ShellUI.Native.Tests.csproj --no-restore --configuration Release
67+
68+ - name : Run tests
69+ run : dotnet test tests/ShellUI.Native.Tests/ShellUI.Native.Tests.csproj --no-restore --no-build --configuration Release
3070
71+ # Core and Templates ship inside the CLI tool package; only the CLI is published.
3172 - name : Pack CLI tool
32- run : dotnet pack src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj --no-restore --configuration Release -o ./nupkg
73+ run : dotnet pack src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj --no-build --configuration Release -p:ContinuousIntegrationBuild=true -o ./nupkg
3374
34- - name : Publish CLI to NuGet
75+ # nuget.org locks a version forever after first publish, and --skip-duplicate would skip it silently.
76+ - name : Verify version is not already published
77+ if : env.PUBLISH == 'true'
3578 run : |
36- dotnet nuget push ./nupkg/ShellUI.Native.CLI.*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
79+ v="${{ steps.version.outputs.VERSION }}"
80+ existing=$(curl -sf "https://api.nuget.org/v3-flatcontainer/shellui.native.cli/index.json" | grep -oE '"[^"]*"' | tr -d '"' || true)
81+ if echo "$existing" | grep -Fxiq "$v"; then
82+ echo "::error::ShellUI.Native.CLI $v is already on nuget.org. Bump Directory.Build.props and re-tag."
83+ exit 1
84+ fi
85+
86+ # Right before the push: the temporary API key is valid for one hour.
87+ # `user` is the nuget.org profile name (not the email, not the GitHub org), kept as a secret.
88+ - name : Login to NuGet via Trusted Publishing
89+ if : env.PUBLISH == 'true'
90+ id : nuget-login
91+ uses : NuGet/login@v1
92+ with :
93+ user : ${{ secrets.NUGET_USER }}
94+
95+ - name : Publish CLI to NuGet
96+ if : env.PUBLISH == 'true'
97+ env :
98+ NUGET_API_KEY : ${{ steps.nuget-login.outputs.NUGET_API_KEY }}
99+ run : dotnet nuget push ./nupkg/ShellUI.Native.CLI.*.nupkg --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate
37100
38101 - name : Create GitHub Release
39- uses : softprops/action-gh-release@v1
102+ if : github.event_name == 'push'
103+ uses : softprops/action-gh-release@v2
40104 with :
41105 name : ShellUI Native v${{ steps.version.outputs.VERSION }}
42- body : |
43- ## ShellUI Native v${{ steps.version.outputs.VERSION }}
44-
45- ### Installation
46- ```bash
47- dotnet tool install -g ShellUI.Native.CLI
48- ```
49-
50- ### Quick Start
51- ```bash
52- cd YourMAUIProject
53- shellui-native init --yes
54- shellui-native add button input card
55- ```
56-
57- See the [documentation](https://github.com/shellui-dev/shellui-native) for usage.
106+ body_path : ${{ runner.temp }}/release-body.md
58107 draft : false
59- prerelease : ${{ contains(github.ref , '-') }}
108+ prerelease : ${{ contains(steps.version.outputs.VERSION , '-') }}
60109 files : |
61110 ./nupkg/*.nupkg
62- env :
63- GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
0 commit comments