Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Unified release CI/CD pipeline for .NET CLI tools: GitHub Actions workflow producing all distribution formats from a
single version tag trigger, build matrix per Runtime Identifier (RID), artifact staging between jobs, GitHub Releases
with SHA-256 checksums, automated Homebrew formula and winget manifest PR creation, and SemVer versioning strategy with
git tags.
Version assumptions: .NET 8.0+ baseline. GitHub Actions workflow syntax v2. Patterns apply to any CI system but
examples use GitHub Actions.
Scope
Tag-triggered GitHub Actions release workflow
Build matrix per Runtime Identifier (RID)
Artifact staging between CI jobs
GitHub Releases with SHA-256 checksums
Automated Homebrew formula and winget manifest PR creation
SemVer versioning with git tags
Out of scope
General CI/CD patterns (branch strategies, matrix testing) -- see [skill:dotnet-gha-patterns] and
[skill:dotnet-ado-patterns]
Native AOT compilation configuration -- see [skill:dotnet-native-aot]
Distribution strategy decisions -- see [skill:dotnet-cli-distribution]
Package format details -- see [skill:dotnet-cli-packaging]
Container image publishing -- see [skill:dotnet-containers]
Cross-references: [skill:dotnet-cli-distribution] for RID matrix and publish strategy, [skill:dotnet-cli-packaging] for
package format authoring, [skill:dotnet-native-aot] for AOT publish configuration, [skill:dotnet-containers] for
container-based distribution.
Versioning Strategy
SemVer + Git Tags
Use Semantic Versioning (SemVer) with git tags as the single source of truth for release versions.
Tag format:v{major}.{minor}.{patch} (e.g., v1.2.3)
# Tag a release
git tag -a v1.2.3 -m "Release v1.2.3"
git push origin v1.2.3
```bash
### Version Flow
```text
git tag v1.2.3
│
▼
GitHub Actions trigger (on push tags: v*)
│
▼
Extract version from tag: GITHUB_REF_NAME → v1.2.3 → 1.2.3
│
▼
Pass to dotnet publish /p:Version=1.2.3
│
▼
Embed in binary (--version output)
│
▼
Stamp in package manifests (Homebrew, winget, Scoop, NuGet)
```text
### Extracting Version from Tag
```yaml
- name: Extract version from tag
id: version
run: echo >>
```text
```bash
git tag -a v1.3.0-rc.1 -m
```text
---
```yaml
name: Release
on:
push:
tags:
-
permissions:
contents: write
defaults:
run:
shell: bash
:
PROJECT: src/MyCli/MyCli.csproj
DOTNET_VERSION:
:
build:
strategy:
matrix:
include:
- rid: linux-x64
os: ubuntu-latest
- rid: linux-arm64
os: ubuntu-latest
- rid: osx-arm64
os: macos-latest
- rid: win-x64
os: windows-latest
runs-on: }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: }
- name: Extract version
: version
shell: bash
run: >>
- name: Publish
run: >-
dotnet publish } -c Release -r } -o ./publish /p:Version=}
- name: Package (Unix)
: runner.os !=
run: |
-euo pipefail
publish
tar -czf .
- name: Package (Windows)
: runner.os ==
shell: pwsh
run: |
Compress-Archive -Path `
-DestinationPath
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: release-}
path: |
*.tar.gz
*.zip
release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Extract version
: version
run: >>
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple:
- name: Generate checksums
working-directory: artifacts
run: |
-euo pipefail
shasum -a 256 *.tar.gz *.zip > checksums-sha256.txt
checksums-sha256.txt
- name: Detect pre-release
: prerelease
run: |
-euo pipefail
[[ == *-* ]];
>>
>>
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: v}
prerelease: }
generate_release_notes:
files: |
artifacts/*.tar.gz
artifacts/*.zip
artifacts/checksums-sha256.txt
publish-nuget:
needs: release
: }
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: }
- name: Extract version
: version
run: >>
- name: Pack
run: >-
dotnet pack } -c Release /p:Version=} -o ./nupkgs
- name: Push to NuGet
run: >-
dotnet nuget push ./nupkgs/*.nupkg -- https://api.nuget.org/v3/index.json --api-key }
```json
---
The build matrix produces one artifact per RID. Each RID runs on the appropriate runner OS.
```yaml
strategy:
matrix:
include:
- rid: linux-x64
os: ubuntu-latest
- rid: linux-arm64
os: ubuntu-latest
- rid: osx-arm64
os: macos-latest
- rid: win-x64
os: windows-latest
```text
- **linux-arm64 on ubuntu-latest:** .NET supports cross-compilation managed (non-AOT) builds.
`dotnet publish -r linux-arm64` on an x64 runner produces a valid ARM64 binary without QEMU. For Native AOT,
cross-compiling ARM64 on an x64 runner requires the ARM64 cross-compilation toolchain (`gcc-aarch64-linux-gnu` or
equivalent). See [skill:dotnet-native-aot] cross-compile prerequisites.
- **osx-arm64:** Use `macos-latest` ( provides ARM64 runners) native compilation. Cross-compiling macOS ARM64
from Linux is not supported.
- **win-x64 on windows-latest:** Native compilation on Windows runner.
```yaml
strategy:
matrix:
include:
- rid: linux-x64
os: ubuntu-latest
- rid: linux-arm64
os: ubuntu-latest
- rid: osx-arm64
os: macos-latest
- rid: win-x64
os: windows-latest
- rid: osx-x64
os: macos-13
- rid: linux-musl-x64
os: ubuntu-latest
```text
---
Each matrix job uploads its artifact with a RID-specific name:
```yaml
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: release-}
path: |
*.tar.gz
*.zip
retention-days: 1
```text
The release job downloads all artifacts from the build matrix:
```yaml
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple:
```text
After download, `artifacts/` contains:
```text
artifacts/
mytool-1.2.3-linux-x64.tar.gz
mytool-1.2.3-linux-arm64.tar.gz
mytool-1.2.3-osx-arm64.tar.gz
mytool-1.2.3-win-x64.zip
```text
---
```yaml
- name: Generate checksums
working-directory: artifacts
run: |
-euo pipefail
shasum -a 256 *.tar.gz *.zip > checksums-sha256.txt
checksums-sha256.txt
```text
**Output format (checksums-sha256.txt):**
```text
abc123... mytool-1.2.3-linux-x64.tar.gz
def456... mytool-1.2.3-linux-arm64.tar.gz
ghi789... mytool-1.2.3-osx-arm64.tar.gz
jkl012... mytool-1.2.3-win-x64.zip
```text
```yaml
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: v}
prerelease: }
generate_release_notes:
files: |
artifacts/*.tar.gz
artifacts/*.zip
artifacts/checksums-sha256.txt
```text
`generate_release_notes: ` auto-generates release notes from merged PRs and commit messages since the last tag.
---
After the GitHub Release is published, update the Homebrew tap automatically:
```yaml
update-homebrew:
needs: release
: }
runs-on: ubuntu-latest
steps:
- name: Extract version
: version
run: >>
- uses: actions/checkout@v4
with:
repository: myorg/homebrew-tap
token: }
- name: Download checksums
run: |
-euo pipefail
curl -sL \
-o checksums.txt
- name: Update formula
run: |
-euo pipefail
VERSION=
LINUX_X64_SHA=$(grep checksums.txt | awk )
LINUX_ARM64_SHA=$(grep checksums.txt | awk )
OSX_ARM64_SHA=$(grep checksums.txt | awk )
python3 scripts/update-formula.py \
--version \
--linux-x64-sha \
--linux-arm64-sha \
--osx-arm64-sha
- name: Create PR
uses: peter-evans/create-pull-request@v6
with:
title:
commit-message:
branch:
body: |
Automated update mytool v}
Release: https://github.com/myorg/mytool/releases/tag/v}
```text
```yaml
update-winget:
needs: release
: }
runs-on: windows-latest
steps:
- name: Extract version
: version
shell: bash
run: >>
- name: Submit to winget-pkgs
uses: vedantmgoyal9/winget-releaser@main
with:
identifier: MyOrg.MyTool
version: }
installers-regex:
token: }
```text
```yaml
update-scoop:
needs: release
: }
runs-on: ubuntu-latest
steps:
- name: Extract version
: version
run: >>
- uses: actions/checkout@v4
with:
repository: myorg/scoop-mytool
token: }
- name: Download checksums
run: |
-euo pipefail
curl -sL \
-o checksums.txt
- name: Update manifest
run: |
-euo pipefail
VERSION=
WIN_X64_SHA=$(grep checksums.txt | awk )
jq --arg v --arg h \
\
bucket/mytool.json > tmp.json && tmp.json bucket/mytool.json
- name: Create PR
uses: peter-evans/create-pull-request@v6
with:
title:
commit-message:
branch:
```text
---
| Change Type | Version Bump | Example |
| -------------------------------- | ------------------ | -------------- |
| Breaking CLI flag rename/removal | Major | 1.x.x -> 2.0.0 |
| New or option | Minor | x.1.x -> x.2.0 |
| Bug fix, performance improvement | Patch | x.x.1 -> x.x.2 |
| Release candidate | Pre-release suffix | x.x.x-rc.1 |
The version flows from the git tag through `dotnet publish` into the binary:
```xml
<!-- .csproj -- Version is at publish via /p:Version -->
<PropertyGroup>
<!-- Fallback version development -->
<Version>0.0.0-dev</Version>
</PropertyGroup>
```text
```bash
$ mytool --version
1.2.3
```bash
```bash
git commit -am
git tag -a v1.2.3 -m
git push origin v1.2.3
```text
---
```yaml
```text
```yaml
permissions:
contents: write
```yaml
Use job-level permissions when different need different scopes. Never grant `write-all`.
---
1. **Do not use ` -e` without ` -o pipefail` GitHub Actions bash steps.** Without `pipefail`, a failing
piped to `` or another utility exits 0, masking the failure. Always use ` -euo pipefail`.
2. **Do not hardcode the .NET version the publish path.** Use `dotnet publish -o ./publish` to control the output
directory explicitly. Hardcoding `net8.0` artifact paths breaks when upgrading to .NET 9+.
3. **Do not skip the pre-release detection step.** Package manager submissions (Homebrew, winget, Scoop, Chocolatey,
NuGet) must be gated on stable versions. Publishing a `-rc.1` to winget-pkgs or NuGet as stable causes user
confusion.
4. **Do not use `actions/upload-artifact` v3 with `merge-multiple`.** The `merge-multiple` parameter requires
`actions/download-artifact@v4`. Using v3 silently ignores the flag and creates nested directories.
5. **Do not forget `retention-days: 1` on intermediate build artifacts.** Release artifacts are published to GitHub
Releases (permanent). Workflow artifacts are temporary and should expire quickly to save storage.
6. **Do not create GitHub Releases with `gh release create` a matrix job.** Only the release job (after all builds
complete) should create the release. Matrix upload artifacts; the release job assembles them.
---
**Primary approach:** Use Serena symbol operations efficient code navigation:
1. **Find definitions**: `serena_find_symbol` instead of text search
2. **Understand structure**: `serena_get_symbols_overview` file organization
3. **Track references**: `serena_find_referencing_symbols` impact analysis
4. **Precise edits**: `serena_replace_symbol_body` clean modifications
**When to use Serena vs traditional tools:**
- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations
- ✅ **Fallback**: If Serena unavailable, traditional tools work fine
**Example workflow:**
```text
Read: src/Services/OrderService.cs
Grep:
serena_find_symbol:
serena_get_symbols_overview:
```
- [GitHub Actions workflow syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)
- [softprops/action-gh-release](https://github.com/softprops/action-gh-release)
- [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request)
- [vedantmgoyal9/winget-releaser](https://github.com/vedantmgoyal9/winget-releaser)
- [Semantic Versioning](https://semver.org/)
- [.NET versioning](https://learn.microsoft.com/en-us/dotnet/core/versions/)
- [GitHub Actions artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts)
"version=${GITHUB_REF_NAME#v}"
"$GITHUB_OUTPUT"
# v1.2.3 → 1.2.3
### Pre-release Versions
# Pre-release tag
"Release candidate 1"
# CI detects pre-release and skips package manager submissions