modified release pipeline (#4)
Release / release (push) Canceled after 47s

await certificate from ssl.com

Reviewed-on: #4
Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
This commit was merged in pull request #4.
This commit is contained in:
2026-08-13 11:17:22 +00:00
committed by alex
parent 5e11b16758
commit 42974ecc8f
34 changed files with 495 additions and 2091 deletions
+152
View File
@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
The installer for handing the application round outside the Store.
The Store gets an MSIX and signs it itself; this one nobody signs, so Windows
warns about an unknown publisher and the user has to insist. That is the price
of an unsigned build, and short of a certificate from a trusted authority there
is no way round it.
Built by build-installer.ps1 — it publishes both halves of the application and
passes the version, the architecture and the folders in as preprocessor
variables.
-->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui"
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
<Package
Name="CursorLang"
Manufacturer="Aleksandr Neichev"
Version="$(var.Version)"
UpgradeCode="B7E4C9A2-5D31-4F86-9E27-3A1C8B0D6F45"
Compressed="yes"
Scope="perUser">
<!--
Installing for the current user alone asks for no administrator rights,
and so for no consent dialog either. The application needs nothing
beyond the user's own account, and one awkward question the less is
worth more here than a copy shared by everyone who signs in.
MSI was designed around installing for the whole machine, and its
validation rules still say so: the ICE checks this trips over are
turned off in the project file, not answered.
-->
<!--
Everything goes inside the .msi. Left to itself WiX puts the cabinet
beside the file, and an installer that has to travel with a second
file next to it is no use to anyone it is sent to
-->
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
<MajorUpgrade DowngradeErrorMessage="A newer version of CursorLang is already installed." />
<!--
Both halves may be running, and both hold on to files in the install
folder. Named here, they are asked to close instead of the install
failing halfway through on a file it cannot replace
-->
<util:CloseApplication Id="CloseAgent" Target="CursorLang.exe"
CloseMessage="yes" RebootPrompt="no" />
<util:CloseApplication Id="CloseSettings" Target="CursorLang.Settings.exe"
CloseMessage="yes" RebootPrompt="no" />
<Icon Id="CursorLangIcon" SourceFile="$(var.IconFile)" />
<Property Id="ARPPRODUCTICON" Value="CursorLangIcon" />
<Property Id="ARPNOREPAIR" Value="1" />
<!-- Only the folder is asked about: everything here is installed, always -->
<ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLFOLDER" />
<WixVariable Id="WixUILicenseRtf" Value="$(var.LicenseFile)" />
<StandardDirectory Id="LocalAppDataFolder">
<Directory Id="ProgramsFolder" Name="Programs">
<Directory Id="INSTALLFOLDER" Name="CursorLang" />
</Directory>
</StandardDirectory>
<StandardDirectory Id="ProgramMenuFolder">
<Directory Id="ShortcutFolder" Name="CursorLang" />
</StandardDirectory>
<Feature Id="Main" Title="CursorLang" Level="1">
<ComponentGroupRef Id="Payload" />
<ComponentRef Id="StartMenuShortcut" />
</Feature>
<!--
The whole published folder: two executables, the libraries and the
runtime. Listing them one by one would mean rewriting this file every
time the runtime changes shape
-->
<ComponentGroup Id="Payload" Directory="INSTALLFOLDER">
<Files Include="$(var.PayloadDir)\**" />
</ComponentGroup>
<!--
The shortcut starts the agent — the half that lives in the tray and
shows the layout. The settings window opens from its menu, so it needs
no shortcut of its own.
The key path is a registry value rather than the shortcut file: for
anything installed into the user's profile Windows Installer wants it
that way, and a shortcut cannot serve as one
-->
<Component Id="StartMenuShortcut" Directory="ShortcutFolder" Guid="D4A81C36-7E52-4B19-A0F3-6C2E9D5B8471">
<Shortcut Id="CursorLangShortcut"
Name="CursorLang"
Description="Shows the keyboard layout at the cursor"
Target="[INSTALLFOLDER]CursorLang.exe"
WorkingDirectory="INSTALLFOLDER" />
<RemoveFolder Id="RemoveShortcutFolder" Directory="ShortcutFolder" On="uninstall" />
<RegistryValue Root="HKCU" Key="Software\CursorLang" Name="Shortcut"
Type="integer" Value="1" KeyPath="yes" />
</Component>
<!--
The startup entry is written by the application itself, when startup is
switched on in its settings, and has to go when the application does:
left behind, it would point at an executable that is gone and Windows
would go on listing it in the startup list for good.
RemoveRegistryValue would have been the obvious way to say that, but it
means the opposite of what it sounds like — it wipes the value as the
product is installed, which would quietly switch startup off for anyone
who had turned it on and then upgraded. Hence reg.exe on the way out.
UPGRADINGPRODUCTCODE tells an uninstall apart from the removal of the
old version during an upgrade: the second one has to leave the entry
alone, or upgrading would cost the user their startup setting anyway.
-->
<CustomAction Id="ForgetStartup" Directory="INSTALLFOLDER" Execute="deferred"
Impersonate="yes" Return="ignore"
ExeCommand="&quot;[SystemFolder]reg.exe&quot; delete &quot;HKCU\Software\Microsoft\Windows\CurrentVersion\Run&quot; /v CursorLang /f" />
<CustomAction Id="ForgetStartupApproval" Directory="INSTALLFOLDER" Execute="deferred"
Impersonate="yes" Return="ignore"
ExeCommand="&quot;[SystemFolder]reg.exe&quot; delete &quot;HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run&quot; /v CursorLang /f" />
<InstallExecuteSequence>
<Custom Action="ForgetStartup" Before="RemoveFiles"
Condition="REMOVE=&quot;ALL&quot; AND NOT UPGRADINGPRODUCTCODE" />
<Custom Action="ForgetStartupApproval" After="ForgetStartup"
Condition="REMOVE=&quot;ALL&quot; AND NOT UPGRADINGPRODUCTCODE" />
</InstallExecuteSequence>
<!-- Offered at the end of the wizard, the way an installer usually does -->
<Property Id="WixShellExecTarget" Value="[#CursorLang.exe]" />
<CustomAction Id="LaunchApplication" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
DllEntry="WixShellExec" Impersonate="yes" Return="ignore" />
<UI>
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication"
Condition="WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed" />
</UI>
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Start CursorLang" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1" />
</Package>
</Wix>
+3
View File
@@ -5,6 +5,9 @@
meant to be built — build-msix.ps1 restores it and takes the program from the
packages folder. It is kept out of the build in CursorLang.sln for the same
reason.
The installer needs nothing from here: WiX arrives with its own project, at
Installer\CursorLang.wixproj.
-->
<Project Sdk="Microsoft.NET.Sdk">
+45 -75
View File
@@ -1,6 +1,6 @@
<#
.SYNOPSIS
Builds the CursorLang MSIX package for the Microsoft Store.
Builds the CursorLang MSIX package.
.DESCRIPTION
Neither Visual Studio nor the Windows SDK is needed: makeappx arrives as a
@@ -8,7 +8,9 @@
plain .NET SDK.
The package is handed to Partner Center as it comes out of here — the Store
is where it gets everything else done to it.
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.
@@ -23,18 +25,14 @@
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
# A check on your own machine: your architecture alone
pwsh -File Packaging\build-msix.ps1 -Architectures x64
.EXAMPLE
# Build and install in one go, to click through the application
pwsh -File Packaging\build-msix.ps1 -Architectures x64 -Install
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 12345AleksandrNeychev.CursorLang -Publisher "CN=ABCD1234-..."
-IdentityName 12345AleksandrNeichev.CursorLang -Publisher "CN=ABCD1234-..."
#>
[CmdletBinding()]
param(
@@ -42,11 +40,8 @@ param(
[string] $Version = '1.0.0.0',
[string] $IdentityName = 'CursorLang',
[string] $Publisher = 'CN=Aleksandr Neychev',
[string] $PublisherDisplayName = 'Aleksandr Neychev',
[ValidateSet('x64', 'arm64')]
[string[]] $Architectures = @('x64', 'arm64'),
[string] $Publisher = 'CN=Aleksandr Neichev',
[string] $PublisherDisplayName = 'Aleksandr Neichev',
[switch] $Install,
@@ -64,7 +59,14 @@ $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' }
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'
@@ -77,9 +79,8 @@ if (-not (Test-Path $assets)) {
}
if ($Install) {
# Both things below are checked before the build rather than after it: the
# build takes minutes, and neither of them gets any truer while it runs
# 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
@@ -87,12 +88,6 @@ if ($Install) {
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.'
}
$machineArchitecture = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'arm64' } else { 'x64' }
if ($Architectures -notcontains $machineArchitecture) {
throw "This machine is $machineArchitecture, and that architecture is not being built. Add it to -Architectures, or drop -Install."
}
}
function Invoke-Tool {
@@ -189,71 +184,46 @@ 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
foreach ($architecture in $Architectures) {
Write-Host "Building $architecture..." -ForegroundColor Cyan
$layout = Join-Path $layoutRoot 'x64'
$layout = Join-Path $layoutRoot $architecture
foreach ($half in @($agentProject, $settingsProject)) {
Invoke-Tool -Path 'dotnet' -Arguments @(
'publish', $half,
'--configuration', 'Release',
'--runtime', "win-$architecture",
'--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}', $architecture)
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
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'
)
}
$result = $built[0]
# 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
if ($built.Count -gt 1) {
Write-Host 'Bringing the architectures into one package...' -ForegroundColor Cyan
Copy-Item $assets -Destination (Join-Path $layout 'Assets') -Recurse -Force
# 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 }
$manifest = (Get-Content $manifestTemplate -Raw).
Replace('{IdentityName}', $IdentityName).
Replace('{Publisher}', $Publisher).
Replace('{PublisherDisplayName}', $PublisherDisplayName).
Replace('{Version}', $Version).
Replace('{Architecture}', 'x64')
$result = Join-Path $packagesPath "CursorLang-$Version.msixbundle"
Invoke-Tool -Path $makeappx -Arguments @('bundle', '/o', '/d', $bundleInput, '/p', $result, '/bv', $Version)
Set-Content -Path (Join-Path $layout 'AppxManifest.xml') -Value $manifest -Encoding UTF8
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 the $machineArchitecture build..." -ForegroundColor Cyan
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 $layoutRoot "$machineArchitecture\AppxManifest.xml")
Add-AppxPackage -Register (Join-Path $layout 'AppxManifest.xml')
}
Write-Host ''