Add WiX .msi installer alongside portable zip for Windows

winget currently only has a `portable` installer to offer for fzf, which
installs via a symlink into the WinGet Links folder. That link is created
by whoever runs winget, normally a non-elevated terminal, and Windows will
not let an elevated process follow a link that a less-privileged process
created ("The path cannot be traversed because it contains an untrusted
mount point"). An administrator's SSH session is elevated, so fzf, and the
shell integration that runs it at startup, fails there.

Build a real per-machine .msi with the WiX dotnet-tool CLI (this is a Go
project, so cargo-wix doesn't apply) from the goreleaser-built fzf.exe, for
amd64 and arm64 (WiX's -arch has no armv7 target). It installs to Program
Files with its own machine PATH entry, so there is no link. WiX only runs
on Windows, so the release job hands the Windows binaries to a new msi job
on windows-latest. The MSIs are built in both workflow modes, so a
workflow_dispatch validation run checks them, and uploaded alongside the
.zip on a tag push; the winget-releaser installers-regex also matches .msi.
WiX is pinned to 5.0.2, the last release that does not require accepting
its Open Source Maintenance Fee EULA.
This commit is contained in:
Marshall Porter authored and Junegunn Choi committed 2026-10-26 20:26:33 +09:00
1 parent 26ff936df0
commit 5b2936db82
3 files changed
+122 -1

No files matched your search

+66
View File
@@ -18,6 +18,8 @@ jobs:
release:
runs-on: macos-latest
environment: release
outputs:
version: ${{ steps.ver.outputs.version }}
steps:
- uses: actions/checkout@v7
with:
@@ -74,3 +76,67 @@ jobs:
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
# The Windows binaries for the msi job below.
- uses: actions/upload-artifact@v7
with:
name: windows-binaries
path: |
dist/*windows_amd64*/fzf.exe
dist/*windows_arm64*/fzf.exe
if-no-files-found: error
# Real per-machine Windows installers (.msi), alongside the portable .zip.
# winget's portable install puts a symlink in the WinGet Links folder,
# created as whoever runs winget (usually a non-elevated terminal), and
# Windows will not let an elevated process, such as an administrator's SSH
# session, follow a link a less privileged process created. The MSI installs
# fzf.exe under Program Files with a machine PATH entry instead.
# WiX only runs on Windows, hence a separate job. Built in both modes, so a
# workflow_dispatch validation run checks the MSIs too; only a tag push
# uploads them. WiX's -arch has no armv7 target, so armv7 stays zip-only.
msi:
needs: release
runs-on: windows-latest
environment: release
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
name: windows-binaries
path: dist
- uses: actions/setup-dotnet@v6
with:
dotnet-version: '8.0.x'
# WiX 5 is the last release that does not require accepting the WiX
# Open Source Maintenance Fee EULA; main.wxs uses the v4/v5 schema.
- name: Install WiX
run: dotnet tool install --global wix --version 5.0.2
- name: Build Windows MSI installers
shell: bash
run: |
set -e
V='${{ needs.release.outputs.version }}'
for pair in "amd64:x64" "arm64:arm64"; do
goarch="${pair%%:*}"
wixarch="${pair##*:}"
bindir=$(dirname "$(find dist -type f -iname fzf.exe -path "*windows_${goarch}*" | head -n1)")
wix build wix/main.wxs -arch "$wixarch" -d Version="$V" -d BinDir="$bindir" -o "dist/fzf-${V}-windows_${goarch}.msi"
done
ls -la dist/*.msi
- uses: actions/upload-artifact@v7
with:
name: windows-msi
path: dist/*.msi
- name: Upload MSI installers to release
if: github.event_name == 'push'
shell: bash
run: gh release upload "v${{ needs.release.outputs.version }}" dist/*.msi --clobber
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }}
+1 -1
View File
@@ -17,5 +17,5 @@ jobs:
with:
identifier: junegunn.fzf
release-tag: ${{ inputs.release-tag || github.event.release.tag_name }}
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
installers-regex: '-windows_(armv7|arm64|amd64)\.(zip|msi)$'
token: ${{ secrets.WINGET_TOKEN }}
+55
View File
@@ -0,0 +1,55 @@
<?xml version='1.0' encoding='utf-8'?>
<!--
WiX v4/v5 source for a real per-machine .msi installer for fzf on Windows.
Unlike the existing portable .zip distributed via winget (InstallerType:
portable), this produces a proper Program Files install with a PATH
registration, so `fzf` resolves to a real file instead of a WinGet Links
symlink. winget creates that symlink as whoever runs it, usually a
non-elevated terminal, and Windows will not let an elevated process (such
as an administrator's SSH session) follow a link created by a less
privileged one, which breaks fzf's shell integration there.
Built on Windows with the WiX v5 dotnet-tool CLI (`wix build`; WiX does
not support other platforms), not `cargo wix` (this is a Go project). Run
with `-arch x64` or `-arch arm64` to select the target Program Files
folder, and pass `-d Version=<ver> -d BinDir=<path-to-fzf.exe-dir>`.
-->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package
Name="fzf"
Manufacturer="junegunn"
Version="$(var.Version)"
UpgradeCode="C34C8C24-3E05-4D14-BD9A-439C3F6E9942"
Language="1033"
Scope="perMachine">
<SummaryInformation Description="fzf - a command-line fuzzy finder" Manufacturer="junegunn" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed. Setup will now exit." />
<MediaTemplate EmbedCab="yes" />
<StandardDirectory Id="ProgramFiles6432Folder">
<Directory Id="APPLICATIONFOLDER" Name="fzf">
<Directory Id="BinFolder" Name="bin" />
</Directory>
</StandardDirectory>
<Feature Id="Main" Title="fzf" Level="1" AllowAbsent="no">
<ComponentGroupRef Id="MainComponents" />
</Feature>
<ComponentGroup Id="MainComponents" Directory="BinFolder">
<Component Id="FzfBinary">
<File Id="FzfExe" Name="fzf.exe" Source="$(var.BinDir)\fzf.exe" KeyPath="yes" />
</Component>
<Component Id="LicenseFile" Directory="APPLICATIONFOLDER">
<File Id="License" Name="LICENSE" Source="LICENSE" KeyPath="yes" />
</Component>
<Component Id="PathEnv" Guid="8B7C2C6A-F4A0-4E3E-9E37-2E8F3E4E5B6A">
<Environment Id="PATH" Name="PATH" Value="[BinFolder]" Permanent="no" Part="last" Action="set" System="yes" />
</Component>
</ComponentGroup>
</Package>
</Wix>