mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
* #305: harden Windows MSI deployment and progress UI Co-authored-by: Codex <[email protected]> * Keep the MSI manufacturer as Learning Machines LLC The company name matches the Linux packages; changing it is the owner's call. Co-Authored-By: Claude Opus 5.5 <[email protected]> * #305: keep compiled-MSI checks compatible with PowerShell COM Validated the compiled x64 MSI and negative scope, publisher and progress mutations. Co-authored-by: Codex <[email protected]> * ROADMAP: the publisher name is unchanged Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Codex <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
6 files changed
+75
-6
No files matched your search
@@ -73,6 +73,13 @@ jobs:
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$msi = (Get-ChildItem dist/*.msi | Select-Object -First 1).FullName
|
||||
# A command-line override must not put files in a user profile while registering
|
||||
# their handlers machine-wide (#305). This runs on a disposable CI runner.
|
||||
$p = Start-Process msiexec.exe -WindowStyle Hidden -ArgumentList '/i', "`"$msi`"", '/qn', 'ALLUSERS=2', 'MSIINSTALLPERUSER=1', '/l*v', 'per-user.log' -Wait -PassThru
|
||||
if ($p.ExitCode -ne 1603) { Get-Content per-user.log -Tail 60; throw "per-user install should fail with 1603, got $($p.ExitCode)" }
|
||||
if (-not (Select-String -Path per-user.log -SimpleMatch 'PdfCraft must be installed for all users.')) {
|
||||
Get-Content per-user.log -Tail 60; throw 'per-user install did not fail on the scope guard'
|
||||
}
|
||||
$p = Start-Process msiexec.exe -ArgumentList '/i', "`"$msi`"", '/qn', '/l*v', 'install.log' -Wait -PassThru
|
||||
if ($p.ExitCode -ne 0) { Get-Content install.log -Tail 60; throw "msiexec /i exited $($p.ExitCode)" }
|
||||
$shortcuts = @(
|
||||
|
||||
@@ -418,6 +418,15 @@ The honest assessment by area, what's lacking and where we're going are in **[RO
|
||||
|
||||
Installers and executables are code-signed.
|
||||
|
||||
The MSI installs for all users and requires administrator privileges. For unattended deployment
|
||||
without a desktop shortcut, run from an elevated terminal:
|
||||
|
||||
```powershell
|
||||
msiexec /i "pdfcraft-<ver>-windows-x64.msi" /qn /norestart INSTALLDESKTOPSHORTCUT=0
|
||||
```
|
||||
|
||||
Use the MSI for your architecture. Per-user installation overrides are not supported.
|
||||
|
||||
### macOS
|
||||
|
||||
| Build | File | Notes |
|
||||
|
||||
+2
-1
@@ -131,6 +131,8 @@ Newest first. One line per session: the date, what moved, and the new overall pe
|
||||
|
||||
- **2026-10-09 (M14, Simplified Chinese About):** The About tabs, contributor controls and statistics, and model table now have Simplified Chinese translations, along with multiline link-privacy and cut-and-stack guidance. Coverage checks include direct `i18n::t` calls, multiline literals and dynamic credit labels; UI control checks exercise the translated About tabs and tables. Chinese font delivery remains incomplete; overall ≈ 30–35% (unchanged).
|
||||
|
||||
- **2026-10-09 (M14, #305):** Windows MSI rejects per-user installation overrides while allowing removal of legacy per-user installs and keeps its progress message visible. README documents unattended deployment without a desktop shortcut. Compiled x64 MSI checks pass; ARM64 install regressions added for CI. Overall estimate unchanged (about 30–35%).
|
||||
|
||||
- **2026-10-08 (M14, French):** Français (`fr`) is a complete interface catalog (about 1,880 entries), selected automatically for `fr`, `fr_FR`, `fr_CA` and other French locales, and from Preferences. Coverage tests check commands, All tools labels and every `tl!` literal; history labels keep captured names. Latin UI faces cover the catalog. Localization remains partial for Czech and Brazilian Portuguese; overall ≈ 30–35% (unchanged).
|
||||
|
||||
- **2026-10-08 (M14, right-to-left file names):** A tab with an Arabic or Hebrew file name showed replacement boxes. The interface now takes an `Arab` face from craft-fonts when the build input has one, and on desktop falls back to one font installed on the machine for characters no embedded face has (`PDFCRAFT_SYSTEM_FONTS=0` turns that off; README screenshots do). File names and paths in the tab strip, the recent-files list and Properties are put in display order, since egui joins the letters but does not reorder runs. Still missing: multi-line text (bookmarks, comments, search results), text input and a mirrored layout, so `misc.rtl-ui` stays planned; the craft-fonts revision pinned for releases predates its Arabic face, so release builds rely on the installed font until the pin moves. Overall estimate unchanged (about 30–35%).
|
||||
@@ -236,4 +238,3 @@ Newest first. One line per session: the date, what moved, and the new overall pe
|
||||
- Robustness sweep (963 of 983 files open, 0 crashes), text layer, find and select, tiles, web build, polish.
|
||||
- Overall ≈ 3–4%.
|
||||
- **2026-09-30 (session 1):** planning complete; viewer vertical slice.
|
||||
|
||||
@@ -42,9 +42,8 @@
|
||||
|
||||
<Dialog Id="InstallProgress" Width="370" Height="180" Title="[ProductName] Setup" Modeless="yes">
|
||||
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\InstallerTitle}Setting up [ProductName]" />
|
||||
<Control Id="Status" Type="Text" X="20" Y="60" Width="330" Height="40" Text="Please wait while setup completes.">
|
||||
<Subscribe Event="ActionText" Attribute="Text" />
|
||||
</Control>
|
||||
<!-- No ActionText table is authored: subscribing would blank this message (#305). -->
|
||||
<Control Id="Status" Type="Text" X="20" Y="60" Width="330" Height="40" Text="Please wait while setup completes." />
|
||||
<Control Id="Progress" Type="ProgressBar" X="20" Y="110" Width="330" Height="15" ProgressBlocks="yes">
|
||||
<Subscribe Event="SetProgress" Attribute="Progress" />
|
||||
</Control>
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
Compressed="yes"
|
||||
Language="1033">
|
||||
<SummaryInformation Description="PdfCraft PDF workbench" Manufacturer="Learning Machines LLC" />
|
||||
<!-- HKLM registrations must never point into a per-user installation (#305). Allow removal
|
||||
of an existing installation even if an older MSI let it install per-user. -->
|
||||
<Launch Condition="ALLUSERS = 1 OR (Installed AND REMOVE = "ALL")"
|
||||
Message="PdfCraft must be installed for all users. Run setup with administrator privileges and ALLUSERS=1; per-user installation is not supported." />
|
||||
<!-- Same-version upgrades let successive pre-releases of one X.Y.Z replace each other
|
||||
(MSI versions are numeric, so 1.2.3-rc.1 and 1.2.3-rc.2 are both 1.2.3). -->
|
||||
<MajorUpgrade AllowSameVersionUpgrades="yes"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check the compiled MSI's shortcuts, desktop-shortcut checkbox and full-UI outcome wiring (#143), without installing it.
|
||||
Check the compiled MSI's install scope, publisher, shortcuts and native UI, without installing it.
|
||||
.EXAMPLE
|
||||
pwsh packaging/windows/test-msi.ps1 dist/release/pdfcraft-0.2.1-windows-x64.msi
|
||||
#>
|
||||
@@ -24,6 +24,55 @@ function Assert-Equal($Actual, $Expected, [string] $What) {
|
||||
if ($Actual -cne $Expected) { throw "$What`: expected '$Expected', got '$Actual'" }
|
||||
}
|
||||
|
||||
function Assert-NoRow([string] $Sql, [string] $What) {
|
||||
$view = $Database.OpenView($Sql)
|
||||
try {
|
||||
[void] $view.Execute()
|
||||
if ($view.Fetch()) { throw "Unexpected MSI row ($What): $Sql" }
|
||||
} finally { [void] $view.Close() }
|
||||
}
|
||||
|
||||
# Test the compiled condition with Windows Installer's evaluator, in a restricted session that
|
||||
# cannot change machine state. Normal installs/repairs work, per-user overrides fail, and removal
|
||||
# of an older incorrectly scoped installation remains possible (#305).
|
||||
$scopeMessage = 'PdfCraft must be installed for all users. Run setup with administrator privileges and ALLUSERS=1; per-user installation is not supported.'
|
||||
$scopeCondition = Read-Row ('SELECT `Condition` FROM `LaunchCondition` WHERE `Description` = ''' + $scopeMessage + '''') 1
|
||||
$Installer.UILevel = 2
|
||||
$session = $null
|
||||
try {
|
||||
$session = $Installer.OpenPackage((Resolve-Path -LiteralPath $Path).Path, 1)
|
||||
foreach ($case in @(
|
||||
@('machine install', '1', '', '', '', 1),
|
||||
@('machine repair', '1', '', '1', '', 1),
|
||||
@('forced per-user install', '2', '1', '', '', 0),
|
||||
@('empty ALLUSERS', '', '', '', '', 0),
|
||||
@('resolved per-user install', '', '1', '', '', 0),
|
||||
@('forced per-user repair', '2', '1', '1', '', 0),
|
||||
@('machine uninstall', '1', '', '1', 'ALL', 1),
|
||||
@('legacy per-user uninstall', '', '1', '1', 'ALL', 1))) {
|
||||
$session.Property('ALLUSERS') = $case[1]
|
||||
$session.Property('MSIINSTALLPERUSER') = $case[2]
|
||||
$session.Property('Installed') = $case[3]
|
||||
$session.Property('REMOVE') = $case[4]
|
||||
Assert-Equal ($session.EvaluateCondition($scopeCondition[0])) $case[5] $case[0]
|
||||
}
|
||||
} finally {
|
||||
if ($session) { [void] [Runtime.InteropServices.Marshal]::FinalReleaseComObject($session) }
|
||||
}
|
||||
foreach ($sequence in @('InstallUISequence', 'InstallExecuteSequence')) {
|
||||
$launch = Read-Row ('SELECT `Condition`, `Sequence` FROM `' + $sequence + '` WHERE `Action` = ''LaunchConditions''') 2
|
||||
Assert-Equal $launch[0] '' "$sequence launch conditions are unconditional"
|
||||
$cost = Read-Row ('SELECT `Sequence` FROM `' + $sequence + '` WHERE `Action` = ''CostInitialize''') 1
|
||||
if ([int] $launch[1] -le 0 -or [int] $launch[1] -ge [int] $cost[0]) {
|
||||
throw "$sequence must reject per-user overrides before costing"
|
||||
}
|
||||
}
|
||||
$manufacturer = Read-Row 'SELECT `Value` FROM `Property` WHERE `Property` = ''Manufacturer''' 1
|
||||
Assert-Equal $manufacturer[0] 'Learning Machines LLC' 'MSI manufacturer'
|
||||
$status = Read-Row 'SELECT `Text` FROM `Control` WHERE `Dialog_` = ''InstallProgress'' AND `Control` = ''Status''' 1
|
||||
Assert-Equal $status[0] 'Please wait while setup completes.' 'Persistent progress message'
|
||||
Assert-NoRow 'SELECT `Event` FROM `EventMapping` WHERE `Dialog_` = ''InstallProgress'' AND `Control_` = ''Status''' 'progress text subscription'
|
||||
|
||||
# Plain (non-advertised) shortcuts to pdfcraft.exe, each in its own component; the desktop one is
|
||||
# gated by INSTALLDESKTOPSHORTCUT, which defaults to 1 and is secure so the UI choice reaches the
|
||||
# elevated install.
|
||||
@@ -86,4 +135,4 @@ $rm = Read-Row 'SELECT `Dialog` FROM `Dialog` WHERE `Dialog` = ''MsiRMFilesInUse
|
||||
Assert-Equal $rm[0] 'MsiRMFilesInUse' 'Files-in-use dialog'
|
||||
[void] [Runtime.InteropServices.Marshal]::FinalReleaseComObject($Database)
|
||||
[void] [Runtime.InteropServices.Marshal]::FinalReleaseComObject($Installer)
|
||||
Write-Output 'ok MSI: Start Menu shortcut, optional desktop shortcut (default on, checkbox), icon/key path, full-UI success/cancel/error and Finish controls, files-in-use dialog'
|
||||
Write-Output 'ok MSI: per-machine scope guard, publisher, persistent progress text, Start Menu shortcut, optional desktop shortcut (default on, checkbox), icon/key path, full-UI success/cancel/error and Finish controls, files-in-use dialog'
|
||||
Reference in new issue
Block a user