Windows installer: show a finish page after installing (#198) (#257)

The MSI had no UI, so Windows Installer showed only its basic progress box and closed
without saying the install had worked. It now uses WiX's standard WixUI_Minimal dialogs:
a welcome page with the license (MIT or Apache-2.0, built at packaging time from
LICENSE-MIT and LICENSE-APACHE), progress, and a finish page with an optional
"Launch WordCraft" checkbox. The release and Windows ARM64 workflows install the WiX
UI and Util extensions the build now needs. UpgradeCode and component GUIDs are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Valentin KleinandClaude Opus 5.5 authored and GitHub committed 2026-10-10 12:23:03 -04:00
1 parent 075b828c3d
commit 7040751a54
4 files changed
+73 -7

No files matched your search

+6 -1
View File
@@ -150,7 +150,12 @@ jobs:
run: cargo xtask version set "$WORDCRAFT_VERSION"
- name: Install WiX
shell: pwsh
run: dotnet tool install --global wix --version 5.0.2
run: |
$PSNativeCommandUseErrorActionPreference = $true # stop at the first failing command
dotnet tool install --global wix --version 5.0.2
# Installer dialogs and the finish page's "Launch WordCraft" (packaging/windows/wordcraft.wxs).
wix extension add -g WixToolset.UI.wixext/5.0.2
wix extension add -g WixToolset.Util.wixext/5.0.2
- name: Build, sign, package
shell: pwsh
env:
+6 -1
View File
@@ -45,7 +45,12 @@ jobs:
key: windows-arm64-package
- name: Install WiX
shell: pwsh
run: dotnet tool install --global wix --version 5.0.2
run: |
$PSNativeCommandUseErrorActionPreference = $true # stop at the first failing command
dotnet tool install --global wix --version 5.0.2
# Installer dialogs and the finish page's "Launch WordCraft" (packaging/windows/wordcraft.wxs).
wix extension add -g WixToolset.UI.wixext/5.0.2
wix extension add -g WixToolset.Util.wixext/5.0.2
- name: Build and package (unsigned)
shell: pwsh
env:
+36 -1
View File
@@ -12,7 +12,10 @@
warning when no signing secrets are set).
Needs: Rust (MSVC toolchain + the target), the Windows SDK (rc.exe, signtool.exe),
and WiX v5: dotnet tool install --global wix --version 5.0.2
and WiX v5 with its UI and Util extensions (installer dialogs, "Launch WordCraft" on finish):
dotnet tool install --global wix --version 5.0.2
wix extension add -g WixToolset.UI.wixext/5.0.2
wix extension add -g WixToolset.Util.wixext/5.0.2
.EXAMPLE
pwsh packaging/windows/package.ps1 -Arch x64
@@ -91,10 +94,42 @@ Copy-Item (Join-Path $Bin 'wordcraft.exe'), (Join-Path $Bin 'wordcraft-cli.exe')
& (Join-Path $PSScriptRoot 'sign.ps1') (Join-Path $Stage 'wordcraft.exe') (Join-Path $Stage 'wordcraft-cli.exe')
# ---- MSI ---------------------------------------------------------------------------------------
# The installer's welcome page shows the license: build its RTF from LICENSE-MIT and LICENSE-APACHE
# so it never drifts from them. Hard-wrapped lines are rejoined into paragraphs so the text wraps
# to the dialog's width.
function ConvertTo-RtfText([string] $Text) {
$sb = [System.Text.StringBuilder]::new()
foreach ($ch in $Text.ToCharArray()) {
$c = [int] $ch
if ($c -eq 0x5C -or $c -eq 0x7B -or $c -eq 0x7D) { [void] $sb.Append('\').Append($ch) }
elseif ($c -gt 127) { [void] $sb.Append("\u$(if ($c -gt 32767) { $c - 65536 } else { $c })?") }
else { [void] $sb.Append($ch) }
}
return $sb.ToString()
}
$paragraphs = [System.Collections.Generic.List[string]]::new()
$paragraphs.Add('\b WordCraft\b0')
$paragraphs.Add((ConvertTo-RtfText 'WordCraft is dual-licensed under the MIT License or the Apache License, Version 2.0, at your option. Both license texts follow. Bundled fonts and other third-party material keep their own open licenses, listed in NOTICE and ATTRIBUTION.md at https://github.com/storytold/wordcraft.'))
foreach ($file in 'LICENSE-MIT', 'LICENSE-APACHE') {
$title = $true # each file opens with its license's name: show it bold
foreach ($para in ((Get-Content -Raw (Join-Path $Root $file)) -split '\r?\n\s*\r?\n')) {
$joined = (($para -split '\r?\n') | ForEach-Object { $_.Trim() } | Where-Object { $_ }) -join ' '
if (-not $joined) { continue }
$paragraphs.Add($(if ($title) { "\b $(ConvertTo-RtfText $joined)\b0" } else { ConvertTo-RtfText $joined }))
$title = $false
}
}
$LicenseRtf = Join-Path $Stage 'license.rtf'
$rtf = '{\rtf1\ansi\ansicpg1252\deff0{\fonttbl{\f0\fswiss\fcharset0 Tahoma;}}\viewkind4\uc1\pard\sa120\f0\fs16 ' +
($paragraphs -join "\par`r`n") + "\par`r`n}`r`n"
[System.IO.File]::WriteAllText($LicenseRtf, $rtf, [System.Text.Encoding]::ASCII)
$Msi = Join-Path $Dist "wordcraft-$Version-windows-$Arch.msi"
Invoke-Native 'wix build' {
wix build (Join-Path $PSScriptRoot 'wordcraft.wxs') -arch $Arch `
-ext WixToolset.UI.wixext -ext WixToolset.Util.wixext `
-d "Version=$MsiVersion" -d "BinDir=$Stage" -d "IconPath=$(Join-Path $Root 'assets\app-icon\wordcraft.ico')" `
-d "LicenseRtf=$LicenseRtf" `
-o $Msi
}
# wix writes its debug symbols (.wixpdb) next to the MSI; keep them out of the release assets.
+25 -4
View File
@@ -1,17 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
WordCraft MSI (WiX Toolset v5: `dotnet tool install -g wix`). Built by package.ps1:
WordCraft MSI (WiX Toolset v5: `dotnet tool install -g wix`, plus its UI and Util extensions:
`wix extension add -g WixToolset.UI.wixext/5.0.2` and the same for WixToolset.Util.wixext).
Built by package.ps1:
wix build wordcraft.wxs -arch x64 -d Version=1.2.3 -d BinDir=... -d IconPath=... -o out.msi
wix build wordcraft.wxs -arch x64 -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext
-d Version=1.2.3 -d BinDir=... -d IconPath=... -d LicenseRtf=... -o out.msi
Per-machine install into Program Files with a Start Menu shortcut.
Per-machine install into Program Files with a Start Menu shortcut. The installer shows the
standard WiX dialogs: welcome + license, progress, and a finish page.
UpgradeCode and the component GUIDs below belong to WordCraft alone. Never copy them from (or
into) a sibling Craft app: Windows Installer treats every package sharing an UpgradeCode as one
product, so a shared code makes one app's installer refuse to install ("a newer version is
already installed") or silently uninstall the other. packaging-lint.yml checks this.
-->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
<Package
Name="WordCraft"
Manufacturer="Learning Machines LLC"
@@ -33,6 +37,23 @@
<Property Id="ARPURLUPDATEINFO" Value="https://getartcraft.com/apps/wordcraft" />
<Property Id="ARPHELPLINK" Value="https://discord.gg/artcraft" />
<!-- Installer UI (#198): WixUI_Minimal is welcome + license, progress, then a finish page so
people can see the install worked. Its dialogs and bitmaps are WiX's own
(WixToolset.UI.wixext); package.ps1 builds the license RTF from LICENSE-MIT and
LICENSE-APACHE. The finish page offers to launch WordCraft (ticked by default) through
WixShellExec (WixToolset.Util.wixext); silent installs (/qn) never show it. -->
<WixVariable Id="WixUILicenseRtf" Value="$(var.LicenseRtf)" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch WordCraft" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1" />
<Property Id="WixShellExecTarget" Value="[#WordcraftExe]" />
<CustomAction Id="LaunchWordcraft" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" DllEntry="WixShellExec"
Impersonate="yes" Return="ignore" />
<UI>
<ui:WixUI Id="WixUI_Minimal" />
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchWordcraft"
Condition="WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 AND NOT Installed" />
</UI>
<StandardDirectory Id="ProgramFiles6432Folder">
<Directory Id="INSTALLFOLDER" Name="WordCraft" />
</StandardDirectory>