Files
cursor-lang/Packaging/build-msix.ps1
T
alex 42974ecc8f
Release / release (push) Canceled after 47s
modified release pipeline (#4)
await certificate from ssl.com

Reviewed-on: #4
Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
2026-08-13 11:17:22 +00:00

243 lines
9.0 KiB
PowerShell

<#
.SYNOPSIS
Builds the CursorLang MSIX package.
.DESCRIPTION
Neither Visual Studio nor the Windows SDK is needed: makeappx arrives as a
NuGet package (Tools\SdkTools.csproj) and the application is built by the
plain .NET SDK.
The package is handed to Partner Center as it comes out of here — the Store
puts its own signature on it and does the rest. Nothing is signed here: a
publicly trusted code signing certificate is not to be had, and the Store
asks for none.
The application is published with its own copy of .NET: Windows does not
carry one, and MSIX cannot install the runtime as a package dependency.
.PARAMETER IdentityName
The identity of the package — from Partner Center, the "Product identity"
section. It is handed out there together with the reserved application name;
the default is only good enough for a check on your own machine.
.PARAMETER Install
Puts the package onto this machine to see it working. Developer mode has to
be on; a signature is not needed, because what gets registered is the layout
the package is made of rather than the package file.
.EXAMPLE
# Build and install in one go, to click through the application
pwsh -File Packaging\build-msix.ps1 -Install
.EXAMPLE
# A build for the Store — the identity comes from Partner Center
pwsh -File Packaging\build-msix.ps1 -Version 1.0.1.0 `
-IdentityName 12345AleksandrNeichev.CursorLang -Publisher "CN=ABCD1234-..."
#>
[CmdletBinding()]
param(
# Four numbers, the last one has to be 0: that is what the Store requires
[string] $Version = '1.0.0.0',
[string] $IdentityName = 'CursorLang',
[string] $Publisher = 'CN=Aleksandr Neichev',
[string] $PublisherDisplayName = 'Aleksandr Neichev',
[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'
$assets = Join-Path $root 'Assets'
$manifestTemplate = Join-Path $root 'AppxManifest.xml'
$toolsProject = Join-Path $root 'Tools\SdkTools.csproj'
if (-not $OutputPath) {
$OutputPath = Join-Path $repository 'artifacts'
} elseif (-not [System.IO.Path]::IsPathRooted($OutputPath)) {
# Windows names the folder a package was registered from in full, and the
# layout is looked for by that name below. A relative path would never be
# found there, and the registration of a build gone by would be left behind
$OutputPath = Join-Path (Get-Location).Path $OutputPath
}
$layoutRoot = Join-Path $OutputPath 'layout'
$packagesPath = Join-Path $OutputPath 'packages'
if ($Version -notmatch '^\d+\.\d+\.\d+\.0$') {
throw "The version '$Version' does not fit: the Store takes four numbers ending in zero, for example 1.0.0.0."
}
if (-not (Test-Path $assets)) {
throw "No logos found in '$assets'. Run Packaging\New-Assets.ps1 first."
}
if ($Install) {
# Checked before the build rather than after it: the build takes minutes, and
# this does not get any truer while it runs
$developerMode = Get-ItemPropertyValue `
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock' `
-Name 'AllowDevelopmentWithoutDevLicense' -ErrorAction SilentlyContinue
if ($developerMode -ne 1) {
throw 'Installing needs developer mode: Settings - System - For developers - Developer mode. Without a signature Windows registers a package no other way.'
}
}
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 Get-SdkToolsPath {
<#
.SYNOPSIS
Returns the folder of the restored Windows SDK Build Tools package.
.DESCRIPTION
The path is asked of MSBuild rather than searched for in the package
cache: the project pins one version of the package, and MSBuild
names exactly it. A search would also find the versions left over
from other builds.
#>
$path = (& dotnet msbuild $toolsProject -getProperty:PkgMicrosoft_Windows_SDK_BuildTools -nologo) |
Where-Object { $_ -and $_.Trim() } |
Select-Object -Last 1
if ($LASTEXITCODE -ne 0 -or -not $path -or -not (Test-Path $path.Trim())) {
throw 'The Windows SDK Build Tools package folder could not be found. Did the restore go through?'
}
return $path.Trim()
}
function Get-SdkTool {
<#
.SYNOPSIS
Returns the path of a program from the Windows SDK Build Tools package.
#>
param(
[string] $Name,
[string] $PackagePath
)
# The bitness of the program has nothing to do with the bitness of the
# package being built: we take the one that runs on this machine
$host64 = if ([Environment]::Is64BitOperatingSystem) { 'x64' } else { 'x86' }
$tool = Get-ChildItem (Join-Path $PackagePath 'bin') -Recurse -Filter $Name -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "\\$host64\\" } |
Select-Object -First 1
if (-not $tool) {
throw "'$Name' was not found in the Windows SDK Build Tools package."
}
return $tool.FullName
}
Write-Host 'Fetching the Windows SDK programs...' -ForegroundColor Cyan
Invoke-Tool -Path 'dotnet' -Arguments @('restore', $toolsProject, '--nologo')
$sdkTools = Get-SdkToolsPath
$makeappx = Get-SdkTool -Name 'makeappx.exe' -PackagePath $sdkTools
# A package registered out of the layout runs straight from that folder, and
# the folder is about to be wiped. Left in place, the registration would point
# at files that are gone — an application that neither starts nor uninstalls
# the usual way. This happens whatever the build was asked for, so -Install has
# no say in it.
#
# Only a registration made from this very folder is removed. A copy installed
# from the Store answers to the same name and is none of our business
foreach ($installed in @(Get-AppxPackage -Name $IdentityName)) {
$location = $installed.InstallLocation
if (-not $location) { continue }
if (-not $location.TrimEnd('\').StartsWith($layoutRoot.TrimEnd('\'), [StringComparison]::OrdinalIgnoreCase)) {
continue
}
Write-Host "Removing the registered $($installed.PackageFullName)..." -ForegroundColor Cyan
Remove-AppxPackage -Package $installed.PackageFullName
}
Remove-Item $layoutRoot -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path $packagesPath -Force | Out-Null
Write-Host 'Building...' -ForegroundColor Cyan
$layout = Join-Path $layoutRoot 'x64'
foreach ($half in @($agentProject, $settingsProject)) {
Invoke-Tool -Path 'dotnet' -Arguments @(
'publish', $half,
'--configuration', 'Release',
'--runtime', 'win-x64',
'--self-contained', 'true',
"-p:Version=$($Version.Substring(0, $Version.LastIndexOf('.')))",
'--output', $layout,
'--nologo'
)
}
# Debug symbols have no place in the package: they take up room, the user
# has no use for them, and for crash reports the Store takes them separately
Get-ChildItem $layout -Recurse -Filter '*.pdb' | Remove-Item -Force
Copy-Item $assets -Destination (Join-Path $layout 'Assets') -Recurse -Force
$manifest = (Get-Content $manifestTemplate -Raw).
Replace('{IdentityName}', $IdentityName).
Replace('{Publisher}', $Publisher).
Replace('{PublisherDisplayName}', $PublisherDisplayName).
Replace('{Version}', $Version).
Replace('{Architecture}', 'x64')
Set-Content -Path (Join-Path $layout 'AppxManifest.xml') -Value $manifest -Encoding UTF8
$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
# The layout is registered rather than the package file: the two hold the
# same thing, but a package file Windows only installs when it is signed
Add-AppxPackage -Register (Join-Path $layout 'AppxManifest.xml')
}
Write-Host ''
Write-Host 'Done.' -ForegroundColor Green
Write-Host " $result"
Write-Host ''
Write-Host ' This file is uploaded to Partner Center as it is.'
if ($Install) {
Write-Host ''
Write-Host ' The application is installed and runs out of the layout folder, so'
Write-Host ' building again would pull the files out from under it. This script'
Write-Host ' takes care of that itself; to remove the application by hand:'
Write-Host " Remove-AppxPackage (Get-AppxPackage -Name $IdentityName).PackageFullName"
}