From c64f54fe0f75658d478f0bbb945a0853be8e3bb5 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Wed, 23 Sep 2026 00:12:32 +0000 Subject: [PATCH] Strip Destination Select page from macOS pkg installer Collapse the Velopack-generated distribution.xml to a single install domain so macOS Installer skips Destination Select, leaving Apple's minimum pages (Introduction, Install, Summary). Welcome/license/readme/ conclusion pages are already absent (no --inst* options passed). --- scripts/guibuild.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/scripts/guibuild.py b/scripts/guibuild.py index 86d76a6..172f070 100755 --- a/scripts/guibuild.py +++ b/scripts/guibuild.py @@ -21,6 +21,7 @@ This script: import io import os import platform +import re import shutil import stat import subprocess @@ -249,8 +250,7 @@ def _dotnet_has_runtime(exe: Path) -> bool: def fetch_dotnet() -> str: - """Resolve a dotnet host with a modern (>= 8) runtime, bootstrapping one - into build/dotnet/ if none is found. + """Resolve a dotnet host with a modern (>= 8) runtime, bootstrapping one into build/dotnet/ if none is found. A plain `dotnet` may resolve to a runtime-only installation, and long-running services (CI runners) may carry a stale environment without @@ -376,6 +376,8 @@ def build_velopack(version: str) -> Path: setup = next(iter(sorted(releases_dir.glob(f"*{artifact_ext}"))), None) if setup is None: raise RuntimeError(f"vpk produced no *{artifact_ext} in {releases_dir}") + if sys.platform == "darwin": + minimize_macos_installer(setup) artifact = ( _REPO_ROOT / "build" @@ -386,6 +388,28 @@ def build_velopack(version: str) -> Path: return artifact +def minimize_macos_installer(pkg: Path) -> None: + """Strip the Destination Select page from the Velopack-generated pkg. + + Velopack hardcodes two install domains (currentUserHome + localSystem) in + the distribution XML, which makes macOS Installer show a Destination + Select page. With a single domain that page is skipped, leaving Apple's + minimum: Introduction, Install, Summary. Installs go to /Applications as + before (the component pkg is non-relocatable with a fixed location). + """ + expanded = pkg.with_name(pkg.stem + "-expanded") + shutil.rmtree(expanded, ignore_errors=True) + subprocess.run(["pkgutil", "--expand", str(pkg), str(expanded)], check=True) + dist_xml = expanded / "Distribution" + xml = dist_xml.read_text() + new_xml, count = re.subn(r"]*/>", '', xml) + if count != 1: + raise RuntimeError("Unexpected distribution.xml: not found") + dist_xml.write_text(new_xml) + subprocess.run(["pkgutil", "--flatten", str(expanded), str(pkg)], check=True) + shutil.rmtree(expanded) + + def read_version() -> str: """Read version via setuptools_scm (same logic as hatch-vcs).""" return setuptools_scm.get_version(root=str(_REPO_ROOT))