mcpField
D
dotnet-cli-release-pipeline

dotnet-cli-release-pipeline

github.com/wshaddixクラウド・DevOps8670 回インストール35 週間前に更新
リポジトリ

dotnet-cli-release-pipeline について

.NET CLI ツールのメンテナー向けリリースパイプラインスキルで、Git タグを起点にしたビルドと複数形式の配布を説明します。RID ビルドマトリックス、チェックサム、Homebrew、winget、Scoop への更新依頼を扱います。

主な機能

  1. 01Git タグと SemVer のバージョン管理
  2. 02RID 別ビルドマトリックスによる成果物生成
  3. 03ジョブ間の成果物ステージングと集約
  4. 04SHA-256 チェックサム付き GitHub Releases
  5. 05Homebrew、winget、Scoop への更新依頼の自動作成
  6. 06プレリリースの配布制御

活用シーン

  1. 01.NET CLI のバージョンタグ用リリースワークフロー構築
  2. 02複数 RID 向けリリースファイルのビルド
  3. 03リリースアセットのチェックサム生成
  4. 04パッケージマネージャーへの更新依頼の自動送信

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-cli-release-pipeline

インストール後、Claude Code で/dotnet-cli-release-pipelineを入力して読み込まれたか確認します。

詳細

dotnet-cli-release-pipeline

.NET CLI ツール向けの統合リリース CI/CD パイプラインです。単一のバージョンタグをトリガーとしてすべての配布形式を生成する GitHub Actions ワークフロー、Runtime Identifier(RID)ごとのビルドマトリックス、ジョブ間でのアーティファクトのステージング、SHA-256 チェックサム付きの GitHub Releases、自動化された Homebrew formula および winget manifest の PR 作成、git タグを使用した SemVer バージョニング戦略を備えています。

バージョンに関する前提: .NET 8.0 以降を基準とします。GitHub Actions ワークフロー構文 v2 を使用します。パターンはどの CI システムにも適用できますが、例では GitHub Actions を使用しています。

対象範囲: このスキルは、CLI 固有のリリースパイプライン、つまり CLI ツールのアーティファクトをビルド、パッケージ化、リリースするワークフローを対象とします。一般的な CI/CD パターン(ブランチ保護、マトリックステスト戦略、デプロイパイプライン、再利用可能なワークフロー)については、[skill:dotnet-gha-patterns] および [skill:dotnet-ado-patterns] を参照してください。このスキルでは、単一のトリガーから複数のパッケージマネージャー向けに CLI バイナリを配布する際の固有の要件に焦点を当てています。

対象外: 一般的な CI/CD パターン(ブランチ戦略、マトリックステスト、デプロイパイプライン)については、[skill:dotnet-gha-patterns] および [skill:dotnet-ado-patterns] を参照してください。Native AOT コンパイルの構成については、[skill:dotnet-native-aot] を参照してください。配布戦略の決定については、[skill:dotnet-cli-distribution] を参照してください。パッケージ形式の詳細については、[skill:dotnet-cli-packaging] を参照してください。コンテナーイメージの公開については、[skill:dotnet-containers] を参照してください。

相互参照:[skill:dotnet-cli-distribution] では RID マトリックスと発行戦略を、[skill:dotnet-cli-packaging] ではパッケージ形式の作成を、[skill:dotnet-native-aot] では AOT 発行の構成を、[skill:dotnet-containers] ではコンテナーを使用した配布を扱っています。


バージョニング戦略

SemVer + Git タグ

リリースバージョンの唯一の信頼できる情報源として、git タグを使用したセマンティックバージョニング(SemVer)を採用します。

タグ形式: v{major}.{minor}.{patch}(例:v1.2.3)

# Tag a release
git tag -a v1.2.3 -m "Release v1.2.3"
git push origin v1.2.3
バージョンの流れ
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)
タグからのバージョン抽出
- name: Extract version from tag
  id: version
  run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
  # v1.2.3 → 1.2.3
プレリリースバージョン
# Pre-release tag
git tag -a v1.3.0-rc.1 -m "Release candidate 1"

# CI detects pre-release and skips package manager submissions
# but still creates GitHub Release as pre-release

統合 GitHub Actions ワークフロー

完全なワークフロー
name: Release

on:
  push:
    tags:
      - "v[0-9]+.[0-9]+.[0-9]+*"  # v1.2.3, v1.2.3-rc.1

permissions:
  contents: write  # Create GitHub Releases

defaults:
  run:
    shell: bash

env:
  PROJECT: src/MyCli/MyCli.csproj
  DOTNET_VERSION: "8.0.x"

jobs:
  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: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ env.DOTNET_VERSION }}

      - name: Extract version
        id: version
        shell: bash
        run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

      - name: Publish
        run: >-
          dotnet publish ${{ env.PROJECT }}
          -c Release
          -r ${{ matrix.rid }}
          -o ./publish
          /p:Version=${{ steps.version.outputs.version }}

      - name: Package (Unix)
        if: runner.os != 'Windows'
        run: |
          set -euo pipefail
          cd publish
          tar -czf "$GITHUB_WORKSPACE/mytool-${{ steps.version.outputs.version }}-${{ matrix.rid }}.tar.gz" .

      - name: Package (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          Compress-Archive -Path "publish/*" `
            -DestinationPath "mytool-${{ steps.version.outputs.version }}-${{ matrix.rid }}.zip"

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: release-${{ matrix.rid }}
          path: |
            *.tar.gz
            *.zip

  release:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Extract version
        id: version
        run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts
          merge-multiple: true

      - name: Generate checksums
        working-directory: artifacts
        run: |
          set -euo pipefail
          shasum -a 256 *.tar.gz *.zip > checksums-sha256.txt
          cat checksums-sha256.txt

      - name: Detect pre-release
        id: prerelease
        run: |
          set -euo pipefail
          if [[ "${{ steps.version.outputs.version }}" == *-* ]]; then
            echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
          else
            echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
          fi

      # Pin third-party actions to a commit SHA in production for supply-chain security
      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          name: v${{ steps.version.outputs.version }}
          prerelease: ${{ steps.prerelease.outputs.is_prerelease }}
          generate_release_notes: true
          files: |
            artifacts/*.tar.gz
            artifacts/*.zip
            artifacts/checksums-sha256.txt

  publish-nuget:
    needs: release
    if: ${{ !contains(github.ref_name, '-') }}  # Skip pre-releases
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ env.DOTNET_VERSION }}

      - name: Extract version
        id: version
        run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

      - name: Pack
        run: >-
          dotnet pack ${{ env.PROJECT }}
          -c Release
          /p:Version=${{ steps.version.outputs.version }}
          -o ./nupkgs

      - name: Push to NuGet
        run: >-
          dotnet nuget push ./nupkgs/*.nupkg
          --source https://api.nuget.org/v3/index.json
          --api-key ${{ secrets.NUGET_API_KEY }}

RID ごとのビルドマトリックス

マトリックス戦略

ビルドマトリックスは、RID ごとに 1 つのアーティファクトを生成します。各 RID は、適切なランナー OS 上で実行されます。

strategy:
  matrix:
    include:
      - rid: linux-x64
        os: ubuntu-latest
      - rid: linux-arm64
        os: ubuntu-latest        # Cross-compile ARM64 on x64 runner
      - rid: osx-arm64
        os: macos-latest         # Native ARM64 runner
      - rid: win-x64
        os: windows-latest
クロスコンパイルに関する注意事項
  • ubuntu-latest 上の linux-arm64: .NET はマネージドビルド(AOT なし)のクロスコンパイルをサポートしています。x64 ランナーで dotnet publish -r linux-arm64 を実行すると、QEMU を使用せずに有効な ARM64 バイナリが生成されます。Native AOT の場合、x64 ランナーで ARM64 をクロスコンパイルするには、ARM64 クロスコンパイルツールチェーン(gcc-aarch64-linux-gnu など)が必要です。クロスコンパイルの前提条件については、[skill:dotnet-native-aot] を参照してください。
  • osx-arm64: ネイティブコンパイルには ARM64 ランナーを提供する macos-latest を使用します。Linux から macOS ARM64 へのクロスコンパイルはサポートされていません。
  • windows-latest 上の win-x64: Windows ランナー上でネイティブコンパイルを行います。
拡張マトリックス(任意)
strategy:
  matrix:
    include:
      # Primary targets
      - rid: linux-x64
        os: ubuntu-latest
      - rid: linux-arm64
        os: ubuntu-latest
      - rid: osx-arm64
        os: macos-latest
      - rid: win-x64
        os: windows-latest
      # Extended targets
      - rid: osx-x64
        os: macos-13           # Intel macOS runner
      - rid: linux-musl-x64
        os: ubuntu-latest      # Alpine musl cross-compile

アーティファクトのステージング

RID ごとのアーティファクトのアップロード

各マトリックスジョブは、RID 固有の名前でアーティファクトをアップロードします。

- name: Upload artifact
  uses: actions/upload-artifact@v4
  with:
    name: release-${{ matrix.rid }}
    path: |
      *.tar.gz
      *.zip
    retention-days: 1  # Short retention -- artifacts are published to GitHub Releases
リリースジョブでのダウンロード

リリースジョブは、ビルドマトリックスのすべてのアーティファクトをダウンロードします。

- name: Download all artifacts
  uses: actions/download-artifact@v4
  with:
    path: artifacts
    merge-multiple: true  # Merge all release-* artifacts into one directory

ダウンロード後、artifacts/ には次のものが含まれます。

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

チェックサム付き GitHub Releases

チェックサムの生成
- name: Generate checksums
  working-directory: artifacts
  run: |
    set -euo pipefail
    shasum -a 256 *.tar.gz *.zip > checksums-sha256.txt
    cat checksums-sha256.txt

出力形式(checksums-sha256.txt):

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
リリースの作成
- name: Create GitHub Release
  uses: softprops/action-gh-release@v2
  with:
    name: v${{ steps.version.outputs.version }}
    prerelease: ${{ steps.prerelease.outputs.is_prerelease }}
    generate_release_notes: true
    files: |
      artifacts/*.tar.gz
      artifacts/*.zip
      artifacts/checksums-sha256.txt

generate_release_notes: true を指定すると、最後のタグ以降にマージされた PR とコミットメッセージからリリースノートが自動生成されます。


Formula/Manifest PR の自動作成

Homebrew Formula の更新

GitHub Release の公開後、Homebrew tap を自動的に更新します。

  update-homebrew:
    needs: release
    if: ${{ !contains(github.ref_name, '-') }}
    runs-on: ubuntu-latest
    steps:
      - name: Extract version
        id: version
        run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

      - uses: actions/checkout@v4
        with:
          repository: myorg/homebrew-tap
          token: ${{ secrets.TAP_GITHUB_TOKEN }}

      - name: Download checksums
        run: |
          set -euo pipefail
          curl -sL "https://github.com/myorg/mytool/releases/download/v${{ steps.version.outputs.version }}/checksums-sha256.txt" \
            -o checksums.txt

      - name: Update formula
        run: |
          set -euo pipefail
          VERSION="${{ steps.version.outputs.version }}"
          LINUX_X64_SHA=$(grep "linux-x64" checksums.txt | awk '{print $1}')
          LINUX_ARM64_SHA=$(grep "linux-arm64" checksums.txt | awk '{print $1}')
          OSX_ARM64_SHA=$(grep "osx-arm64" checksums.txt | awk '{print $1}')

          # Use sed or a templating script to update Formula/mytool.rb
          # with new version and SHA-256 values
          python3 scripts/update-formula.py \
            --version "$VERSION" \
            --linux-x64-sha "$LINUX_X64_SHA" \
            --linux-arm64-sha "$LINUX_ARM64_SHA" \
            --osx-arm64-sha "$OSX_ARM64_SHA"

      - name: Create PR
        uses: peter-evans/create-pull-request@v6
        with:
          title: "mytool ${{ steps.version.outputs.version }}"
          commit-message: "Update mytool to ${{ steps.version.outputs.version }}"
          branch: "update-mytool-${{ steps.version.outputs.version }}"
          body: |
            Automated update for mytool v${{ steps.version.outputs.version }}
            Release: https://github.com/myorg/mytool/releases/tag/v${{ steps.version.outputs.version }}
winget Manifest の更新
  update-winget:
    needs: release
    if: ${{ !contains(github.ref_name, '-') }}
    runs-on: windows-latest
    steps:
      - name: Extract version
        id: version
        shell: bash
        run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

      - name: Submit to winget-pkgs
        uses: vedantmgoyal9/winget-releaser@main
        with:
          identifier: MyOrg.MyTool
          version: ${{ steps.version.outputs.version }}
          installers-regex: '\.zip$'
          token: ${{ secrets.WINGET_GITHUB_TOKEN }}
Scoop Manifest の更新
  update-scoop:
    needs: release
    if: ${{ !contains(github.ref_name, '-') }}
    runs-on: ubuntu-latest
    steps:
      - name: Extract version
        id: version
        run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

      - uses: actions/checkout@v4
        with:
          repository: myorg/scoop-mytool
          token: ${{ secrets.SCOOP_GITHUB_TOKEN }}

      - name: Download checksums
        run: |
          set -euo pipefail
          curl -sL "https://github.com/myorg/mytool/releases/download/v${{ steps.version.outputs.version }}/checksums-sha256.txt" \
            -o checksums.txt

      - name: Update manifest
        run: |
          set -euo pipefail
          VERSION="${{ steps.version.outputs.version }}"
          WIN_X64_SHA=$(grep "win-x64" checksums.txt | awk '{print $1}')

          # Update bucket/mytool.json with new version and hash
          jq --arg v "$VERSION" --arg h "$WIN_X64_SHA" \
            '.version = $v | .architecture."64bit".hash = $h |
             .architecture."64bit".url = "https://github.com/myorg/mytool/releases/download/v\($v)/mytool-\($v)-win-x64.zip"' \
            bucket/mytool.json > tmp.json && mv tmp.json bucket/mytool.json

      - name: Create PR
        uses: peter-evans/create-pull-request@v6
        with:
          title: "mytool ${{ steps.version.outputs.version }}"
          commit-message: "Update mytool to ${{ steps.version.outputs.version }}"
          branch: "update-mytool-${{ steps.version.outputs.version }}"

バージョニング戦略の詳細

CLI ツールの SemVer
変更の種類バージョンの更新例
CLI フラグの名前変更/削除による互換性のない変更メジャー1.x.x -> 2.0.0
新しいコマンドまたはオプションマイナーx.1.x -> x.2.0
バグ修正、パフォーマンス改善パッチx.x.1 -> x.x.2
リリース候補プレリリースサフィックスx.x.x-rc.1
バージョンの埋め込み

バージョンは git タグから dotnet publish を経由してバイナリに反映されます。


<PropertyGroup>
  
  <Version>0.0.0-dev</Version>
</PropertyGroup>
# --version output matches the git tag
$ mytool --version
1.2.3
タグ付けのワークフロー
# 1. Update CHANGELOG.md (if applicable)
# 2. Commit the changelog
git commit -am "docs: update changelog for v1.2.3"

# 3. Tag the release
git tag -a v1.2.3 -m "Release v1.2.3"

# 4. Push tag -- triggers the release workflow
git push origin v1.2.3

ワークフローのセキュリティ

シークレットの管理
# Required repository secrets:
# NUGET_API_KEY         - NuGet.org API key for package publishing
# TAP_GITHUB_TOKEN      - PAT with repo scope for homebrew-tap
# WINGET_GITHUB_TOKEN   - PAT with public_repo scope for winget-pkgs PRs
# SCOOP_GITHUB_TOKEN    - PAT with repo scope for scoop bucket
# CHOCO_API_KEY         - Chocolatey API key for package push
権限
permissions:
  contents: write  # Minimum: create GitHub Releases and upload assets

ジョブごとに異なるスコープが必要な場合は、ジョブレベルの権限を使用してください。write-all は決して付与しないでください。


エージェントが注意すべき点

  1. GitHub Actions の bash ステップでは、set -o pipefail を指定せずに set -e を使用しないでください。 pipefail がないと、tee やその他のユーティリティにパイプされたコマンドが失敗しても終了コードが 0 になり、失敗が見逃されます。必ず set -euo pipefail を使用してください。
  2. 公開パスに .NET のバージョンをハードコードしないでください。 出力ディレクトリを明示的に指定するには、dotnet publish -o ./publish を使用してください。アーティファクトのパスに net8.0 をハードコードすると、.NET 9 以降へのアップグレード時に問題が発生します。
  3. プレリリース検出のステップを省略しないでください。 パッケージマネージャーへの登録(Homebrew、winget、Scoop、Chocolatey、NuGet)は、安定版のみに限定する必要があります。-rc.1 を winget-pkgs や NuGet に安定版として公開すると、ユーザーの混乱を招きます。
  4. actions/upload-artifact v3 と merge-multiple を併用しないでください。 merge-multiple パラメーターには actions/download-artifact@v4 が必要です。v3 を使用すると、このフラグは無視され、入れ子のディレクトリが作成されます。
  5. 中間ビルドアーティファクトに retention-days: 1 を設定し忘れないでください。 リリースアーティファクトは GitHub Releases に公開され、永続的に保存されます。ワークフローアーティファクトは一時的なものなので、ストレージを節約するため、早めに期限切れになるようにしてください。
  6. マトリックスジョブで gh release create を使って GitHub Releases を作成しないでください。 リリースを作成するのは、すべてのビルドが完了した後のリリースジョブだけにしてください。マトリックスジョブではアーティファクトをアップロードし、リリースジョブでそれらをまとめます。

参考資料

コメント