From bf4e8214fcf9a45d3a0b5f687cc2f6d2c0e9cbbd Mon Sep 17 00:00:00 2001 From: Jeremy Date: Tue, 27 Oct 2026 11:46:17 +0000 Subject: [PATCH] fix(installer): correct the wizard and shortcut description in the release docs; harden install-policy checks --- .github/workflows/packaging-lint.yml | 9 +++++++-- docs/releasing.md | 10 +++++----- packaging/windows/check-install-policy.ps1 | 13 ++++++++++--- packaging/windows/test-install-policy.ps1 | 2 +- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.github/workflows/packaging-lint.yml b/.github/workflows/packaging-lint.yml index fd3419d2be..6848088d37 100644 --- a/.github/workflows/packaging-lint.yml +++ b/.github/workflows/packaging-lint.yml @@ -36,6 +36,8 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install the pinned packaging toolchain shell: pwsh run: | @@ -43,8 +45,11 @@ jobs: dotnet tool install --tool-path $tools wix --version 5.0.2 if ($LASTEXITCODE -ne 0) { throw 'WiX installation failed.' } $tools >> $env:GITHUB_PATH - & (Join-Path $tools 'wix.exe') extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2 - if ($LASTEXITCODE -ne 0) { throw 'WiX extension installation failed.' } + $policy = Get-Content -Raw -LiteralPath packaging/windows/install-policy.json | ConvertFrom-Json + foreach ($extension in @($policy.extensions)) { + & (Join-Path $tools 'wix.exe') extension add -g "WixToolset.$extension.wixext/5.0.2" + if ($LASTEXITCODE -ne 0) { throw "WiX extension $extension installation failed." } + } - name: Build and check non-executable installer fixtures shell: pwsh run: ./packaging/windows/test-install-policy.ps1 -OutputDirectory (Join-Path $env:RUNNER_TEMP 'craft-install-policy-tests') diff --git a/docs/releasing.md b/docs/releasing.md index 62fb575f83..89a6837b6d 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -156,9 +156,9 @@ goes in `CARGO_TARGET__RUSTFLAGS`, so host build scripts aren't affected - `photocraft.wxs` (WiX v5) is a dual-scope package (Microsoft "Single Package Authoring"): it installs per-user by default, without a UAC prompt, into `%LOCALAPPDATA%\Programs\ArtCraft\PhotoCraft` (Windows Installer redirects the ProgramFiles folder in a per-user install); the setup - wizard's "Install for all users" box, or `msiexec ... ALLUSERS=1`, installs per-machine into - `Program Files\ArtCraft\PhotoCraft`. It installs a Start Menu shortcut (non-advertised, its - own component keyed on a registry value, as the ArtCraft Launcher's MSI does). PhotoCraft + setup wizard's "Everyone on this computer" option, or `msiexec ... ALLUSERS=1`, installs per-machine into + `Program Files\ArtCraft\PhotoCraft`. It installs an advertised Start Menu shortcut associated with the + `PhotocraftExe` file component. PhotoCraft becomes the default app for `.pcraft` and is listed under "Open with" for PSD/PSB and image files (in a per-user install those registrations and the `InstallDir` record live under HKCU instead of HKLM; the package's registry roots follow the context automatically). It also @@ -168,8 +168,8 @@ goes in `CARGO_TARGET__RUSTFLAGS`, so host build scripts aren't affected The MSI version is the numeric `X.Y.Z`, because MSI has no pre-release field. Same-version upgrades are allowed so that release candidates replace each other. - Double-clicking the MSI opens a setup wizard (WixUI_InstallDir without the licence page): - Welcome, install folder with the all-users box (remembered for upgrades in - `HKLM` or `HKCU\Software\PhotoCraft\InstallDir` according to context), + Welcome, a scope page ("Only for me" or "Everyone on this computer"), the install folder + (remembered for upgrades in `HKLM` or `HKCU\Software\PhotoCraft\InstallDir` according to context), Ready, a progress page, and a Finish page that says where to find the app, with a ticked "Launch PhotoCraft" box. The pages name the full version (`photocraft.en-us.wxl`). `package.ps1` draws the wizard's banner and side bitmaps from the app icon, so no WiX stock art ships. `msiexec /qn` diff --git a/packaging/windows/check-install-policy.ps1 b/packaging/windows/check-install-policy.ps1 index 00b683d708..451295a2e4 100644 --- a/packaging/windows/check-install-policy.ps1 +++ b/packaging/windows/check-install-policy.ps1 @@ -122,9 +122,11 @@ try { Set-SessionProperty $session 'CRAFT_USER_DIR' 'D:\Existing user app' Set-SessionProperty $session 'CRAFT_MACHINE_DIR' 'E:\Existing machine app' $action = if ($scope -eq '1') { 'CraftRestoreMachineFolder' } else { 'CraftRestoreUserFolder' } - if (-not $explicit) { - if ($session.DoAction($action) -ne 1) { throw 'Remembered-folder property action failed.' } - } + # Evaluate the compiled condition, so the test exercises it instead of calling the action directly. + $restore = @(Read-Rows $database ('SELECT `Condition` FROM `InstallExecuteSequence` WHERE `Action` = ''{0}''' -f $action) 1)[0] + $selected = $session.EvaluateCondition($restore[0]) -eq 1 + if ($selected -eq [bool]$explicit) { throw "$action must run only when no folder was supplied (condition: $($restore[0]))." } + if ($selected -and $session.DoAction($action) -ne 1) { throw 'Remembered-folder property action failed.' } $expected = if ($explicit) { $explicit } elseif ($scope -eq '1') { 'E:\Existing machine app' } else { 'D:\Existing user app' } if ((Get-SessionProperty $session 'INSTALLFOLDER') -ne $expected) { throw 'Custom or remembered folder was not preserved.' } } @@ -145,6 +147,11 @@ try { throw 'Silent updates must restore the folder before directory costing.' } } + foreach ($resolver in @('CraftUFromLegacyExe','CraftMFromLegacyExe')) { + if (-not ($execute['AppSearch'] -lt $execute[$resolver] -and $execute[$resolver] -lt $execute['LaunchConditions'])) { + throw 'Both legacy folder resolvers must run after AppSearch and before the launch checks.' + } + } # Switching the first-page choice must reset BOTH properties, including a back-and-forth change. $events = @(Read-Rows $database 'SELECT `Event`, `Argument`, `Condition`, `Ordering` FROM `ControlEvent` WHERE `Dialog_` = ''CraftScopeDlg'' AND `Control_` = ''Next'' ORDER BY `Ordering`' 4) # Model the full-UI upgrade entry with an existing machine-wide copy. Run diff --git a/packaging/windows/test-install-policy.ps1 b/packaging/windows/test-install-policy.ps1 index 21199ca7f4..d455d19d16 100644 --- a/packaging/windows/test-install-policy.ps1 +++ b/packaging/windows/test-install-policy.ps1 @@ -38,7 +38,7 @@ foreach ($arch in $Architectures) { $sources = @($policy.sources | ForEach-Object { Join-Path $PSScriptRoot $_ }) $msi = Join-Path $stage "$($policy.id)-NOT-INSTALLABLE.msi" $extensions = @($policy.extensions | ForEach-Object { '-ext'; "WixToolset.$_.wixext" }) - & wix build @sources -arch $arch -wx @extensions ` + & wix build @sources -arch $arch @extensions ` -culture en-US -d 'Version=0.0.1' -d 'DisplayVersion=0.0.1-metadata-test' ` -d "BinDir=$stage" -d "IconPath=$(Join-Path $root $policy.icon)" ` -d "DocsDir=$(Join-Path $stage 'docs')" -d "ModelsDir=$(Join-Path $stage 'models')" `