added msi to release pipeline

This commit is contained in:
2026-08-13 16:24:59 +05:00
parent f75ab0f077
commit 2c46f41db8
6 changed files with 273 additions and 34 deletions
+48
View File
@@ -0,0 +1,48 @@
<!--
The installer project. It is not part of CursorLang.sln and is not meant to be
built on its own: build-installer.ps1 publishes the application first and then
builds this, passing in the folder it published to.
WiX 5 rather than the current 7: from version 6 onwards the toolset asks every
build to accept the Open Source Maintenance Fee licence, which is a decision for
a person to make and not for a build script. Version 5 carries no such
requirement. Moving up later means changing the version here and adding
-p:AcceptEula=true to the build.
-->
<Project Sdk="WixToolset.Sdk/5.0.2">
<PropertyGroup>
<OutputName>CursorLang</OutputName>
<OutputType>Package</OutputType>
<!--
MSI validation still assumes an installation for the whole machine.
Installing into the user's own profile trips three of its rules: a
component whose key path is a file (ICE38), a folder it wants listed
for removal (ICE64), and a warning that the files will not follow other
users of the machine (ICE91). All three describe exactly what was
intended here, so they are turned off rather than worked around.
-->
<SuppressIces>ICE38;ICE64;ICE91</SuppressIces>
<!--
What the script passes in, gathered into the constants the source file
reads. They are gathered here rather than handed over as one property
from the command line: a semicolon in a property value is where MSBuild
stops reading it, and the separator between constants is a semicolon.
-->
<DefineConstants>
Version=$(CursorLangVersion);
PayloadDir=$(PayloadDir);
IconFile=$(IconFile);
LicenseFile=$(LicenseFile)
</DefineConstants>
</PropertyGroup>
<ItemGroup>
<!-- CloseApplication and the launch at the end of the wizard come from here -->
<PackageReference Include="WixToolset.Util.wixext" Version="5.0.2" />
<PackageReference Include="WixToolset.UI.wixext" Version="5.0.2" />
</ItemGroup>
</Project>
+163
View File
@@ -0,0 +1,163 @@
<#
.SYNOPSIS
Builds the CursorLang installer for handing the application round outside
the Store.
.DESCRIPTION
Nothing has to be installed beyond the .NET SDK: WiX arrives as a NuGet
package, the same way makeappx does for the MSIX build.
Nobody signs the result, so Windows warns about an unknown publisher and the
user has to insist. Short of a certificate from a trusted authority there is
no way round that, and it is the reason this installer is meant for people who
already know where the file came from.
The application is published with its own copy of .NET: Windows carries no
runtime of its own, and a self-contained build is tied to x64.
.PARAMETER Install
Runs the installer once it is built, to see the thing through the way a user
would.
.EXAMPLE
# Build and run it, to see what a user sees
pwsh -File Packaging\build-installer.ps1 -Install
.EXAMPLE
# What a release needs
pwsh -File Packaging\build-installer.ps1 -Version 1.0.1
#>
[CmdletBinding()]
param(
# Three numbers: unlike the Store, an installer keeps no place for a fourth
[string] $Version = '1.0.0',
[switch] $Install,
[string] $OutputPath
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$root = $PSScriptRoot
$repository = Split-Path -Parent $root
$agentProject = Join-Path $repository 'CursorLang.Agent\CursorLang.Agent.csproj'
$settingsProject = Join-Path $repository 'CursorLang.Settings\CursorLang.Settings.csproj'
$installerProject = Join-Path $root 'Installer\CursorLang.wixproj'
$icon = Join-Path $repository 'CursorLang.Core\Resources\CursorLang.ico'
$license = Join-Path $repository 'LICENSE'
if (-not $OutputPath) { $OutputPath = Join-Path $repository 'artifacts' }
$stagingRoot = Join-Path $OutputPath 'installer-staging'
$installersPath = Join-Path $OutputPath 'installers'
if ($Version -notmatch '^\d+\.\d+\.\d+$') {
throw "The version '$Version' does not fit: an installer is versioned by three numbers, for example 1.0.0."
}
if (-not (Test-Path $icon)) {
throw "The icon is missing from '$icon'. Git LFS may have left a pointer in its place: run git lfs pull."
}
function Invoke-Tool {
<#
.SYNOPSIS
Runs a program and fails the build if it returned an error.
.DESCRIPTION
A wrapper of our own is needed because PowerShell does not treat the
failure of an external program as an error and quietly moves on.
#>
param(
[string] $Path,
[string[]] $Arguments
)
& $Path @Arguments
if ($LASTEXITCODE -ne 0) {
throw "'$([System.IO.Path]::GetFileName($Path))' exited with code $LASTEXITCODE."
}
}
function Write-LicenseRtf {
<#
.SYNOPSIS
Turns the plain-text LICENSE into the RTF the wizard needs.
.DESCRIPTION
The licence page of a Windows Installer wizard reads RTF and nothing
else, and keeping a second copy of the licence in the repository would
mean keeping the two in step by hand. The conversion is as plain as it
looks: the text carries no formatting to preserve.
#>
param([string] $Path)
$text = (Get-Content $license -Raw) -replace '\\', '\\\\' -replace '{', '\{' -replace '}', '\}'
$body = ($text -split "`r?`n") -join '\par' + "`r`n"
Set-Content -Path $Path -Value "{\rtf1\ansi\deff0{\fonttbl{\f0 Segoe UI;}}\fs18 $body}" -Encoding ASCII
}
Remove-Item $stagingRoot -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path $installersPath -Force | Out-Null
$licenseRtf = Join-Path $stagingRoot 'License.rtf'
New-Item -ItemType Directory -Path $stagingRoot -Force | Out-Null
Write-LicenseRtf -Path $licenseRtf
Write-Host 'Building...' -ForegroundColor Cyan
$staging = Join-Path $stagingRoot 'x64'
# Both halves go into one folder: each of them looks for the other beside
# itself — the tray menu opens the settings window, and the settings window
# registers the agent for startup
foreach ($half in @($agentProject, $settingsProject)) {
Invoke-Tool -Path 'dotnet' -Arguments @(
'publish', $half,
'--configuration', 'Release',
'--runtime', 'win-x64',
'--self-contained', 'true',
"-p:Version=$Version",
'--output', $staging,
'--nologo'
)
}
# Debug symbols only make the download bigger; they are of no use to anyone
# who installs the application
Get-ChildItem $staging -Recurse -Filter '*.pdb' | Remove-Item -Force
$installer = Join-Path $installersPath "CursorLang-$Version-x64.msi"
Invoke-Tool -Path 'dotnet' -Arguments @(
'build', $installerProject,
'--configuration', 'Release',
# The architecture of the package is the architecture of what goes in it:
# a self-contained build fits nothing else
'-p:InstallerPlatform=x64',
# Handed over one by one rather than as a ready list of constants: a
# semicolon in a property value is where MSBuild stops reading it. The
# project gathers them into DefineConstants itself
"-p:CursorLangVersion=$Version",
"-p:PayloadDir=$staging",
"-p:IconFile=$icon",
"-p:LicenseFile=$licenseRtf",
"-p:OutputPath=$installersPath\",
"-p:OutputName=CursorLang-$Version-x64",
'--nologo'
)
if ($Install) {
Write-Host "Running $([System.IO.Path]::GetFileName($installer))..." -ForegroundColor Cyan
# Started the way a user would, with the wizard showing: the point of -Install
# is to see what the person on the other end sees
Start-Process 'msiexec.exe' -ArgumentList '/i', "`"$installer`"" -Wait
}
Write-Host ''
Write-Host 'Done.' -ForegroundColor Green
Write-Host " $installer"
Write-Host ''
Write-Host ' Nobody signed it, so Windows warns about an unknown publisher.'
+3 -28
View File
@@ -43,9 +43,6 @@ param(
[string] $Publisher = 'CN=Aleksandr Neichev',
[string] $PublisherDisplayName = 'Aleksandr Neichev',
[ValidateSet('x64', 'arm64')]
[string[]] $Architectures = @('x64', 'arm64'),
[switch] $Install,
[string] $OutputPath
@@ -187,7 +184,7 @@ foreach ($installed in @(Get-AppxPackage -Name $IdentityName)) {
Remove-Item $layoutRoot -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path $packagesPath -Force | Out-Null
$built = @()
Write-Host 'Building...' -ForegroundColor Cyan
$layout = Join-Path $layoutRoot 'x64'
@@ -218,30 +215,8 @@ $manifest = (Get-Content $manifestTemplate -Raw).
Set-Content -Path (Join-Path $layout 'AppxManifest.xml') -Value $manifest -Encoding UTF8
$package = Join-Path $packagesPath "CursorLang-$Version-$architecture.msix"
Invoke-Tool -Path $makeappx -Arguments @('pack', '/o', '/d', $layout, '/p', $package)
$built += $package
}
$result = $built[0]
if ($built.Count -gt 1) {
Write-Host 'Bringing the architectures into one package...' -ForegroundColor Cyan
# makeappx bundle takes everything from a folder, so the separate packages
# are gathered into one of their own first — otherwise the results of
# earlier builds would end up in the bundle
$bundleInput = Join-Path $OutputPath 'bundle'
Remove-Item $bundleInput -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path $bundleInput -Force | Out-Null
$built | ForEach-Object { Copy-Item $_ -Destination $bundleInput }
$result = Join-Path $packagesPath "CursorLang-$Version.msixbundle"
Invoke-Tool -Path $makeappx -Arguments @('bundle', '/o', '/d', $bundleInput, '/p', $result, '/bv', $Version)
Remove-Item $bundleInput -Recurse -Force
}
$result = Join-Path $packagesPath "CursorLang-$Version-x64.msix"
Invoke-Tool -Path $makeappx -Arguments @('pack', '/o', '/d', $layout, '/p', $result)
if ($Install) {
Write-Host 'Installing...' -ForegroundColor Cyan