|
| 1 | +name: Release CLI |
| 2 | + |
| 3 | +# Builds, signs, and (optionally) deploys the Stride CLI dotnet tool (Stride.Cli) to NuGet.org. |
| 4 | +# |
| 5 | +# Versioned independently of the engine: the SemVer in sources/launcher/Stride.Cli/Stride.Cli.csproj |
| 6 | +# is the source of truth - bump <Version> by hand before a stable release. A deploy pushes to |
| 7 | +# NuGet.org, tags cli/<version> at the built commit, and creates a GitHub Release. |
| 8 | + |
| 9 | +on: |
| 10 | + workflow_dispatch: |
| 11 | + inputs: |
| 12 | + sign: |
| 13 | + description: Code sign the package |
| 14 | + default: true |
| 15 | + type: boolean |
| 16 | + deploy: |
| 17 | + description: Deploy to NuGet.org and create a GitHub Release |
| 18 | + default: false |
| 19 | + type: boolean |
| 20 | + version-suffix: |
| 21 | + description: Prerelease suffix, no leading dash (e.g. "beta1" -> 1.0.0-beta1). Empty = stable. |
| 22 | + default: '' |
| 23 | + type: string |
| 24 | + |
| 25 | +concurrency: |
| 26 | + group: release-cli-${{ github.ref }} |
| 27 | + cancel-in-progress: false |
| 28 | + |
| 29 | +jobs: |
| 30 | + Validate: |
| 31 | + runs-on: ubuntu-latest |
| 32 | + steps: |
| 33 | + - name: Check deploy requires signing |
| 34 | + if: ${{ inputs.deploy && !inputs.sign }} |
| 35 | + run: | |
| 36 | + echo "::error::Cannot deploy unsigned packages. Enable 'sign' to deploy." |
| 37 | + exit 1 |
| 38 | +
|
| 39 | + - name: Check release permissions |
| 40 | + if: ${{ inputs.sign || inputs.deploy }} |
| 41 | + env: |
| 42 | + GH_TOKEN: ${{ secrets.GH_PAT }} |
| 43 | + run: | |
| 44 | + ORG=$(echo "${{ github.repository }}" | cut -d/ -f1) |
| 45 | + TEAM="stride-release-managers" |
| 46 | + USER="${{ github.actor }}" |
| 47 | + if ! gh api "orgs/$ORG/teams/$TEAM/memberships/$USER" --silent 2>/dev/null; then |
| 48 | + echo "::error::User $USER is not a member of $ORG/$TEAM. Sign/deploy requires stride-release-managers team membership." |
| 49 | + exit 1 |
| 50 | + fi |
| 51 | +
|
| 52 | + Package: |
| 53 | + name: Package |
| 54 | + needs: Validate |
| 55 | + runs-on: windows-2025-vs2026 |
| 56 | + environment: production # signing secrets live here |
| 57 | + outputs: |
| 58 | + version: ${{ steps.version.outputs.version }} |
| 59 | + steps: |
| 60 | + - uses: actions/checkout@v4 |
| 61 | + with: |
| 62 | + lfs: true |
| 63 | + fetch-depth: 0 # full history + tags for the re-publish guard |
| 64 | + fetch-tags: true |
| 65 | + |
| 66 | + - uses: actions/setup-dotnet@v4 |
| 67 | + with: |
| 68 | + dotnet-version: 10.0.x |
| 69 | + |
| 70 | + - name: Build and sign package |
| 71 | + run: | |
| 72 | + dotnet build build\Stride.build ` |
| 73 | + -t:PackageCli ` |
| 74 | + -p:StrideSign=${{ inputs.sign }} ` |
| 75 | + -p:VersionSuffix=${{ inputs.version-suffix }} |
| 76 | + env: |
| 77 | + StrideSignTenantId: ${{ inputs.sign && secrets.STRIDE_SIGN_TENANT_ID || '' }} |
| 78 | + StrideSignClientId: ${{ inputs.sign && secrets.STRIDE_SIGN_CLIENT_ID || '' }} |
| 79 | + StrideSignClientSecret: ${{ inputs.sign && secrets.STRIDE_SIGN_CLIENT_SECRET || '' }} |
| 80 | + StrideSignKeyVaultCertificate: ${{ inputs.sign && secrets.STRIDE_SIGN_KEYVAULT_CERTIFICATE || '' }} |
| 81 | + StrideSignKeyVaultName: ${{ inputs.sign && secrets.STRIDE_SIGN_KEYVAULT_NAME || '' }} |
| 82 | + |
| 83 | + - name: Detect version |
| 84 | + id: version |
| 85 | + shell: pwsh |
| 86 | + run: | |
| 87 | + $pkg = Get-ChildItem -Path bin/cli -Filter "Stride.Cli.*.nupkg" | Select-Object -First 1 |
| 88 | + if (-not $pkg) { |
| 89 | + echo "::error::No Stride.Cli package found" |
| 90 | + exit 1 |
| 91 | + } |
| 92 | + $version = $pkg.Name -replace 'Stride\.Cli\.(.*?)\.nupkg','$1' |
| 93 | + if ($version -notmatch '^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$') { |
| 94 | + echo "::error::Detected package version '$version' is not a valid version - check Stride.Cli.csproj" |
| 95 | + exit 1 |
| 96 | + } |
| 97 | + echo "version=$version" >> $env:GITHUB_OUTPUT |
| 98 | + echo "::notice::CLI package version: $version" |
| 99 | +
|
| 100 | + # Forget-to-bump guard: <Version> is committed and bumped per release. If it wasn't bumped, its |
| 101 | + # cli/<version> tag already exists on a previously-released commit, so fail rather than re-publish. |
| 102 | + - name: Guard against re-publishing an existing version |
| 103 | + if: ${{ inputs.deploy }} |
| 104 | + shell: bash |
| 105 | + run: | |
| 106 | + TAG="cli/${{ steps.version.outputs.version }}" |
| 107 | + if git rev-parse "$TAG" >/dev/null 2>&1 && [ -z "$(git tag --points-at HEAD --list "$TAG")" ]; then |
| 108 | + echo "::error::CLI version ${{ steps.version.outputs.version }} is already released (tag $TAG on another commit). Bump <Version> in Stride.Cli.csproj." |
| 109 | + exit 1 |
| 110 | + fi |
| 111 | +
|
| 112 | + - name: Upload package |
| 113 | + uses: actions/upload-artifact@v4 |
| 114 | + with: |
| 115 | + name: cli-package |
| 116 | + path: bin/cli/*.nupkg |
| 117 | + if-no-files-found: error |
| 118 | + |
| 119 | + # Surface the version in the run UI even when Deploy is skipped. |
| 120 | + Version: |
| 121 | + name: Packaged CLI ${{ needs.Package.outputs.version }} |
| 122 | + needs: Package |
| 123 | + if: ${{ always() && needs.Package.result == 'success' }} |
| 124 | + runs-on: ubuntu-latest |
| 125 | + steps: |
| 126 | + - run: echo "::notice title=Packaged CLI::${{ needs.Package.outputs.version }}" |
| 127 | + |
| 128 | + Deploy: |
| 129 | + name: Deploy ${{ needs.Package.outputs.version }} |
| 130 | + if: ${{ inputs.deploy && inputs.sign }} |
| 131 | + needs: Package |
| 132 | + runs-on: ubuntu-latest # NuGet push doesn't need Windows |
| 133 | + environment: production # requires manual approval in GitHub settings |
| 134 | + permissions: |
| 135 | + contents: write |
| 136 | + steps: |
| 137 | + - name: Check release permissions |
| 138 | + env: |
| 139 | + GH_TOKEN: ${{ secrets.GH_PAT }} |
| 140 | + run: | |
| 141 | + ORG=$(echo "${{ github.repository }}" | cut -d/ -f1) |
| 142 | + TEAM="stride-release-managers" |
| 143 | + USER="${{ github.actor }}" |
| 144 | + if ! gh api "orgs/$ORG/teams/$TEAM/memberships/$USER" --silent 2>/dev/null; then |
| 145 | + echo "::error::User $USER is not a member of $ORG/$TEAM. Deploy requires stride-release-managers team membership." |
| 146 | + exit 1 |
| 147 | + fi |
| 148 | +
|
| 149 | + - uses: actions/checkout@v4 |
| 150 | + with: |
| 151 | + fetch-depth: 0 |
| 152 | + fetch-tags: true |
| 153 | + token: ${{ secrets.GH_PAT }} # push the cli/<version> tag |
| 154 | + |
| 155 | + - uses: actions/setup-dotnet@v4 |
| 156 | + with: |
| 157 | + dotnet-version: '10.0.x' |
| 158 | + |
| 159 | + - name: Download package |
| 160 | + uses: actions/download-artifact@v4 |
| 161 | + with: |
| 162 | + name: cli-package |
| 163 | + path: bin/cli |
| 164 | + |
| 165 | + - name: Push to NuGet.org |
| 166 | + shell: bash |
| 167 | + run: | |
| 168 | + for pkg in bin/cli/*.nupkg; do |
| 169 | + echo "Pushing $(basename "$pkg")..." |
| 170 | + dotnet nuget push "$pkg" --api-key "$STRIDE_NUGET_API_KEY" --source "https://api.nuget.org/v3/index.json" --timeout 1800 --skip-duplicate |
| 171 | + done |
| 172 | + env: |
| 173 | + STRIDE_NUGET_API_KEY: ${{ secrets.STRIDE_NUGET_API_KEY }} |
| 174 | + |
| 175 | + - name: Tag release |
| 176 | + run: | |
| 177 | + TAG="cli/${{ needs.Package.outputs.version }}" |
| 178 | + if git rev-parse "$TAG" >/dev/null 2>&1; then |
| 179 | + echo "Tag $TAG already exists, skipping" |
| 180 | + else |
| 181 | + git tag "$TAG" |
| 182 | + git push origin "$TAG" |
| 183 | + fi |
| 184 | +
|
| 185 | + - name: Create GitHub Release |
| 186 | + env: |
| 187 | + GH_TOKEN: ${{ secrets.GH_PAT }} |
| 188 | + run: | |
| 189 | + TAG="cli/${{ needs.Package.outputs.version }}" |
| 190 | + if gh release view "$TAG" >/dev/null 2>&1; then |
| 191 | + echo "Release $TAG already exists, skipping" |
| 192 | + else |
| 193 | + gh release create "$TAG" \ |
| 194 | + --title "Stride CLI ${{ needs.Package.outputs.version }}" \ |
| 195 | + --generate-notes \ |
| 196 | + bin/cli/*.nupkg |
| 197 | + fi |
| 198 | +
|
| 199 | + - name: Publish summary |
| 200 | + if: success() |
| 201 | + shell: bash |
| 202 | + run: | |
| 203 | + V="${{ needs.Package.outputs.version }}" |
| 204 | + { |
| 205 | + echo "## Published Stride.Cli $V" |
| 206 | + echo "" |
| 207 | + echo "- [nuget.org](https://www.nuget.org/packages/Stride.Cli/$V)" |
| 208 | + echo "- [GitHub Release](${{ github.server_url }}/${{ github.repository }}/releases/tag/cli/$V)" |
| 209 | + echo "" |
| 210 | + echo "Install: \`dotnet tool install -g Stride.Cli --version $V\`" |
| 211 | + } >> "$GITHUB_STEP_SUMMARY" |
0 commit comments