# The release: a tag of the form v1.2.3.0 builds the solution, runs the tests # and packs the MSIX with the version taken from the tag. # # The same requirements to the runner as in pull-request.yml apply: Windows, the # .NET 10 SDK and an interactive desktop session for the tests. makeappx comes # with a NuGet package (Packaging\Tools\SdkTools.csproj), so the Windows SDK does # not have to be installed. name: Release on: push: tags: - 'v*' defaults: run: shell: pwsh jobs: release: runs-on: windows-x64 steps: # lfs: true is not a nicety: the exe icon and the MSIX logos live in Git # LFS, and without it the checkout leaves text pointers in their place — # the build fails on the icon and the package would carry broken logos - name: Check out the sources uses: actions/checkout@v4 with: lfs: true # The tag is the only place the version comes from: the Store takes four # numbers ending in zero, so anything else is stopped here rather than # halfway through the packaging - name: Read the version from the tag id: version run: | $ErrorActionPreference = 'Stop' $tag = '${{ github.ref_name }}' if ($tag -notmatch '^v\d+\.\d+\.\d+\.0$') { throw "The tag '$tag' does not fit: a release is tagged as v1.2.3.0 — four numbers ending in zero." } "version=$($tag.Substring(1))" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 - name: Show the toolchain run: dotnet --info - name: Restore run: dotnet restore CursorLang.sln --nologo - name: Build run: dotnet build CursorLang.sln --configuration Release --no-restore --nologo - name: Test run: > dotnet test CursorLang.sln --configuration Release --no-build --nologo --settings CursorLang.Tests/coverage.runsettings # The package comes out as Partner Center wants it — the Store puts its own # signature on it. The identity comes from repository variables and falls # back to the defaults of the script when a variable is not set. - name: Pack the MSIX env: IDENTITY_NAME: ${{ vars.MSIX_IDENTITY_NAME }} PUBLISHER: ${{ vars.MSIX_PUBLISHER }} PUBLISHER_DISPLAY_NAME: ${{ vars.MSIX_PUBLISHER_DISPLAY_NAME }} run: | $ErrorActionPreference = 'Stop' $arguments = @{ Version = '${{ steps.version.outputs.version }}' } # An empty variable is left out rather than passed on: the script has # defaults of its own, and an empty string would wipe them $variables = @{ IdentityName = $env:IDENTITY_NAME Publisher = $env:PUBLISHER PublisherDisplayName = $env:PUBLISHER_DISPLAY_NAME } foreach ($name in $variables.Keys) { if ($variables[$name]) { $arguments[$name] = $variables[$name] } } ./Packaging/build-msix.ps1 @arguments - name: Keep the packages uses: actions/upload-artifact@v4 with: name: msix-${{ steps.version.outputs.version }} path: artifacts/packages/ if-no-files-found: error # Gitea creates a release of its own for a pushed tag, so the release is # looked up first and only made when it is not there - name: Publish the release env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} TAG: ${{ github.ref_name }} run: | $ErrorActionPreference = 'Stop' # The GITHUB_ names are what Gitea itself hands to the workflow — its # actions repeat those of GitHub, and the addresses in them point at # this Gitea instance. GITHUB_API_URL used not to reach the steps at # all, so the address is put together from the server one when empty $root = if ($env:GITHUB_API_URL) { $env:GITHUB_API_URL } else { "$env:GITHUB_SERVER_URL/api/v1" } $api = "$root/repos/$env:GITHUB_REPOSITORY/releases" $headers = @{ Authorization = "token $env:GITEA_TOKEN" } $release = $null try { $release = Invoke-RestMethod "$api/tags/$env:TAG" -Headers $headers } catch { } if (-not $release) { $body = @{ tag_name = $env:TAG; name = $env:TAG; draft = $false; prerelease = $false } | ConvertTo-Json $release = Invoke-RestMethod $api -Method Post -Headers $headers -ContentType 'application/json' -Body $body } foreach ($file in Get-ChildItem artifacts/packages -File) { # A tag can be pushed again after it was deleted; the old file of # the same name is dropped, otherwise the upload is refused $existing = $release.assets | Where-Object { $_.name -eq $file.Name } foreach ($asset in $existing) { Invoke-RestMethod "$api/$($release.id)/assets/$($asset.id)" -Method Delete -Headers $headers | Out-Null } Write-Host "Uploading $($file.Name)" Invoke-RestMethod "$api/$($release.id)/assets?name=$($file.Name)" -Method Post -Headers $headers -Form @{ attachment = $file } | Out-Null }