name: Publish (NuGet + GitHub Release) # 触发:推送 tag 时触发(例如 v1.0.0 或 1.0.0) on: push: tags: - '*' # 顶级权限:允许创建 release、写 packages,并允许 id-token(OIDC) permissions: contents: write packages: write id-token: write jobs: build-and-publish: runs-on: ubuntu-latest permissions: id-token: write contents: write packages: write steps: - name: Checkout repository (at tag) uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: true - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 9.0.x - name: Install unzip (for reading .nuspec from .nupkg) run: sudo apt-get update && sudo apt-get install -y unzip - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build -c Release --no-restore -p:DebugType=portable - name: Test run: dotnet test --no-build -c Release --verbosity normal - name: Determine tag version id: tag_version run: | set -e echo "GITHUB_REF = ${GITHUB_REF}" TAG=${GITHUB_REF#refs/tags/} VERSION=${TAG#v} VERSION=${VERSION#V} echo "tag='$TAG' -> version='$VERSION'" echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Pack (use tag version) run: | set -e echo "Packing with version=${{ steps.tag_version.outputs.version }}" dotnet pack -c Release -o ./packages -p:PackageVersion=${{ steps.tag_version.outputs.version }} -p:IncludeSymbols=false - name: Show packages run: ls -la ./packages || true - name: NuGet login (OIDC → temporary API key) id: nuget_login uses: NuGet/login@v1 with: user: ${{ secrets.NUGET_USER }} # 推荐将用户名放 secrets - name: Push all packages to nuget.org env: NUGET_API_KEY: ${{ steps.nuget_login.outputs.NUGET_API_KEY }} run: | set -e echo "Found API key: ${NUGET_API_KEY:+*** present ***}" pushed_any=false for PKG in ./packages/*.nupkg; do [ -f "$PKG" ] || continue pushed_any=true echo "Pushing $PKG to nuget.org..." dotnet nuget push "$PKG" \ --api-key "${NUGET_API_KEY}" \ --source https://api.nuget.org/v3/index.json \ --skip-duplicate done if [ "$pushed_any" = false ]; then echo "No packages found to push." fi - name: Get Version and First Package Path id: get_version run: | set -e PACKAGE_FILE=$(find ./packages -name "*.nupkg" | head -n 1 || true) if [ -z "$PACKAGE_FILE" ]; then echo "No .nupkg file found in ./packages" exit 1 fi # 从 .nupkg(zip)里读取 .nuspec 并提取 VERSION=$(unzip -p "$PACKAGE_FILE" '*.nuspec' 2>/dev/null | sed -n 's:.*\(.*\).*:\1:p' | head -n1) if [ -z "$VERSION" ]; then echo "Failed to parse version from $PACKAGE_FILE" exit 1 fi BASENAME=$(basename "$PACKAGE_FILE") echo "package_file=$PACKAGE_FILE" >> $GITHUB_OUTPUT echo "package_basename=$BASENAME" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Create GitHub Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref_name }} release_name: "Release ${{ github.ref_name }}" body: "Release created by CI for tag ${{ github.ref_name }} (package version ${{ steps.get_version.outputs.version }})" draft: false prerelease: false - name: Upload all .nupkg to Release (curl) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} UPLOAD_URL_TEMPLATE: ${{ steps.create_release.outputs.upload_url }} run: | set -e # upload_url from create-release is like: https://uploads.github.com/repos/OWNER/REPO/releases/ID/assets{?name,label} # strip template part "{?name,label}" UPLOAD_URL="${UPLOAD_URL_TEMPLATE%\{*}" echo "Upload base URL: $UPLOAD_URL" for package_file in ./packages/*.nupkg; do if [ -f "$package_file" ]; then basename=$(basename "$package_file") echo "Uploading $basename to release..." curl --fail -sS -X POST \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Content-Type: application/octet-stream" \ --data-binary @"$package_file" \ "$UPLOAD_URL?name=$basename" echo "Uploaded $basename" fi done