Pull request / build (pull_request) Successful in 37s
Upstream v4 refuses to run anywhere but github.com: it reads GITHUB_SERVER_URL, calls everything else a GitHub Enterprise Server and throws before making a request. A probe run settled the rest — the name cannot be overridden from a step, because the runner sets the GITHUB_ ones last. So the action now comes from this Gitea, forked with that one check removed, on a fixed tag rather than a floating one.
208 lines
9.4 KiB
YAML
208 lines
9.4 KiB
YAML
# The release: a tag of the form v1.2.3 builds the solution, runs the tests and
|
|
# packs the MSIX with the version taken from the tag — three numbers of the tag
|
|
# and a zero the Store keeps for itself.
|
|
#
|
|
# The package goes to the Store and nowhere else, so it leaves the run as an
|
|
# artifact: someone picks it up and uploads it to Partner Center, which puts its
|
|
# own signature on it. Nothing is signed here and nothing is attached to the
|
|
# release — a publicly trusted code signing certificate is not to be had, and an
|
|
# unsigned package would look like something to install and install nowhere.
|
|
# Gitea makes the release for the tag itself, and it carries the tag alone.
|
|
#
|
|
# 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:
|
|
- name: Check out the sources
|
|
uses: actions/checkout@v4
|
|
|
|
# The exe icon and the MSIX logos live in Git LFS, and without them the
|
|
# checkout leaves text pointers in their place — the build fails on the
|
|
# icon and the package would carry broken logos.
|
|
#
|
|
# They are fetched here rather than by `lfs: true` on the checkout: that
|
|
# way the objects arrive over a request the LFS endpoint accepts. See the
|
|
# comment on the header below
|
|
- name: Fetch the LFS objects
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
# actions/checkout leaves its own token in the config as an
|
|
# http.<server>/.extraheader, and git-lfs sends that header on to the
|
|
# LFS endpoint, which turns down the token of a workflow: every object
|
|
# comes back 401 and the fetch gives up. The repository is public and
|
|
# its LFS objects are readable without a token at all, so the header
|
|
# simply goes. A private repository would need credentials of its own
|
|
# in lfs.url instead
|
|
$keys = git config --local --list --name-only | Where-Object { $_ -like '*.extraheader' }
|
|
foreach ($key in $keys) { git config --local --unset-all $key }
|
|
|
|
git lfs pull
|
|
if ($LASTEXITCODE -ne 0) { throw "git lfs pull ended with exit code $LASTEXITCODE." }
|
|
|
|
# A pointer left in place of a file shows itself much later and in a
|
|
# way that is hard to read back: the icon breaks the build, and a logo
|
|
# quietly ends up broken inside the package
|
|
$pointers = git lfs ls-files --name-only |
|
|
Where-Object { (Get-Content $_ -TotalCount 1) -like 'version https://git-lfs*' }
|
|
|
|
if ($pointers) {
|
|
throw "Git LFS left pointers instead of files: $($pointers -join ', ')."
|
|
}
|
|
|
|
# The tag is the only place the version comes from, and it is a plain
|
|
# version of three numbers — the same shape the application itself looks
|
|
# for in the releases when it checks for an update. A tag of any other
|
|
# shape 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+$') {
|
|
throw "The tag '$tag' does not fit: a release is tagged as v1.2.3 — three numbers. A fourth one does not belong in the tag: the Store keeps the revision for itself, and the package always gets a zero there."
|
|
}
|
|
|
|
# The package takes four numbers with a zero at the end: the Store
|
|
# reserves the last one, so it carries nothing the tag could tell
|
|
"version=$($tag.Substring(1)).0" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8
|
|
|
|
# The installer answers to nobody about a fourth number and takes the
|
|
# tag as it is
|
|
"plain=$($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 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
|
|
|
|
# The other half of the release: the same application as an ordinary
|
|
# installer, for handing round outside the Store. Nobody signs it, so
|
|
# SmartScreen warns about it — see Packaging\installer.iss
|
|
- name: Build the installer
|
|
run: ./Packaging/build-installer.ps1 -Version ${{ steps.version.outputs.plain }}
|
|
|
|
# The artifact is where the package waits to be uploaded to Partner Center.
|
|
#
|
|
# The action comes from this Gitea rather than from github.com, and it is
|
|
# a fork of actions/upload-artifact with one line changed. Upstream reads
|
|
# GITHUB_SERVER_URL, takes every host but github.com and its enterprise
|
|
# ones for a GitHub Enterprise Server — where the v4 artifact backend does
|
|
# not exist — and throws before it makes a request. Here that backend does
|
|
# exist and answers, but the check runs on the runner ahead of the network
|
|
# and never lets it prove itself; nor can the name be corrected from this
|
|
# file, as the runner sets the GITHUB_ ones over the env of a step. The
|
|
# fork carries the reasoning in full in its GITEA-PATCH.md.
|
|
#
|
|
# The tag is a fixed one. A floating tag is how this broke before: the
|
|
# action is fetched at run time, and what arrives can change on its own
|
|
- name: Keep the package
|
|
uses: https://git.alrakis.kz/actions/upload-artifact@v4-gitea
|
|
with:
|
|
name: msix-${{ steps.version.outputs.version }}
|
|
path: artifacts/packages/
|
|
if-no-files-found: error
|
|
|
|
# The .wixpdb next to each installer is left out on purpose: it is of use
|
|
# only when something has to be traced back to the WiX source
|
|
- name: Keep the installer
|
|
uses: https://git.alrakis.kz/actions/upload-artifact@v4-gitea
|
|
with:
|
|
name: installer-${{ steps.version.outputs.plain }}
|
|
path: artifacts/installers/*.msi
|
|
if-no-files-found: error
|
|
|
|
# Only the installers go into the release. The MSIX stays in the artifacts
|
|
# of the run: unsigned, it installs nowhere, and its one destination is
|
|
# Partner Center
|
|
- 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
|
|
$api = "$env:GITHUB_API_URL/repos/$env:GITHUB_REPOSITORY/releases"
|
|
$headers = @{ Authorization = "token $env:GITEA_TOKEN" }
|
|
|
|
# Gitea makes a release of its own for a pushed tag, so the release is
|
|
# looked up first and only made when it is not there
|
|
$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/installers -File -Filter *.msi) {
|
|
# 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
|
|
}
|