Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
Condition="'$(OutputType)' == 'Library' and '$(CopyLocalLockFileAssemblies)' == 'true' and $(AssemblyName.EndsWith('Tests')) == 'false' ">
<ItemGroup>
<ProjectReferenceWithConfiguration Update="@(ProjectReferenceWithConfiguration)" >
<Private>false</Private>
<Private>true</Private>
</ProjectReferenceWithConfiguration>
<ProjectReference Update="@(ProjectReference)" >
<Private>false</Private>
<Private>true</Private>
</ProjectReference>
</ItemGroup>
</Target>
Expand Down
9 changes: 8 additions & 1 deletion Scripts/flowlauncher.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@
<version>$version$</version>
<authors>Flow-Launcher Team</authors>
<projectUrl>https://github.com/Flow-Launcher/Flow.Launcher</projectUrl>
<iconUrl>https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher/master/Flow.Launcher/Images/app.png</iconUrl>
<icon>images\app.png</icon>
<iconUrl>https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher/master/Flow.Launcher/Resources/app.ico</iconUrl>
<readme>README.md</readme>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Flow Launcher - Quick file search and app launcher for Windows with community-made plugins</description>
<dependencies>
<group targetFramework="net9.0" />
</dependencies>
</metadata>
<files>
<file src="**\*.*" target="lib\net9.0\" exclude="Flow.Launcher.vshost.exe;Flow.Launcher.vshost.exe.config;Flow.Launcher.vshost.exe.manifest;*.nupkg;Setup.exe;RELEASES"/>
<file src="images\app.png" target="images\"/>
<file src="..\..\README.md" target="\"/>
</files>
</package>
182 changes: 143 additions & 39 deletions Scripts/post_build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Write-Host "Config: $config"
function Build-Version {
if ([string]::IsNullOrEmpty($env:flowVersion)) {
$targetPath = Join-Path $solution "Output/Release/Flow.Launcher.dll" -Resolve
$v = (Get-Command ${targetPath}).FileVersionInfo.FileVersion
# Use Get-Item for reliability and ProductVersion to align with AssemblyInformationalVersion.
$v = (Get-Item $targetPath).VersionInfo.ProductVersion
} else {
Comment on lines +10 to 12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Normalize ProductVersion to a Squirrel/NuGet‑safe version (strip +build metadata).

Build metadata (e.g., 1.2.3-beta+abc) can break folder matching (app-$version) and sometimes NuGet/Squirrel expectations. Strip anything after “+”.

Apply this diff:

-        $v = (Get-Item $targetPath).VersionInfo.ProductVersion
+        $v = (Get-Item $targetPath).VersionInfo.ProductVersion
+        # Normalize for NuGet/Squirrel: remove +build metadata to match app-<version> folder naming.
+        $v = $v -replace '\+.*$', ''

Evidence that Squirrel uses app- conventions and Release feed versions. (github.com)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Use Get-Item for reliability and ProductVersion to align with AssemblyInformationalVersion.
$v = (Get-Item $targetPath).VersionInfo.ProductVersion
} else {
# Use Get-Item for reliability and ProductVersion to align with AssemblyInformationalVersion.
$v = (Get-Item $targetPath).VersionInfo.ProductVersion
# Normalize for NuGet/Squirrel: remove +build metadata to match app-<version> folder naming.
$v = $v -replace '\+.*$', ''
} else {
🤖 Prompt for AI Agents
In Scripts/post_build.ps1 around lines 10 to 12, the ProductVersion from
Get-Item may include +build metadata (e.g. 1.2.3-beta+abc) which breaks
Squirrel/NuGet folder/version conventions; after assigning $v from
VersionInfo.ProductVersion, normalize it by stripping any '+' and all following
characters (so only the semver+prerelease part remains) and reassign the cleaned
value to $v before it is used for folder naming or packaging.

$v = $env:flowVersion
}
Expand All @@ -32,56 +33,73 @@ function Build-Path {
}

function Copy-Resources ($path) {
# making version static as multiple versions can exist in the nuget folder and in the case a breaking change is introduced.
Copy-Item -Force $env:USERPROFILE\.nuget\packages\squirrel.windows\1.5.2\tools\Squirrel.exe $path\Output\Update.exe
$squirrelExe = (Get-ChildItem -Path "$env:USERPROFILE\.nuget\packages\squirrel.windows\*" -Directory | Sort-Object Name -Descending | Select-Object -First 1).FullName + "\tools\Squirrel.exe"
if (Test-Path $squirrelExe) {
Copy-Item -Force $squirrelExe $path\Output\Update.exe
} else {
Write-Host "Warning: Squirrel.exe could not be found in the NuGet cache." -ForegroundColor Yellow
}
}

function Delete-Unused ($path, $config) {
function Remove-UnusedFiles ($path, $config) {
$target = "$path\Output\$config"
$included = Get-ChildItem $target -Filter "*.dll"
foreach ($i in $included){
$deleteList = Get-ChildItem $target\Plugins -Include $i -Recurse | Where { $_.VersionInfo.FileVersion -eq $i.VersionInfo.FileVersion -And $_.Name -eq "$i" }
$deleteList | ForEach-Object{ Write-Host Deleting duplicated $_.Name with version $_.VersionInfo.FileVersion at location $_.Directory.FullName }
$deleteList | Remove-Item
$deleteList = Get-ChildItem $target\Plugins -Include $i.Name -Recurse | Where-Object { $_.VersionInfo.FileVersion -eq $i.VersionInfo.FileVersion }
foreach ($fileToDelete in $deleteList) {
# A plugin's main DLL has the same name as its parent directory. We must not delete it.
if ($fileToDelete.Directory.Name -ne $fileToDelete.BaseName) {
Remove-Item $fileToDelete.FullName
}
}
}
Remove-Item -Path $target -Include "*.xml" -Recurse
}

function Remove-CreateDumpExe ($path, $config) {
$target = "$path\Output\$config"

$depjson = Get-Content $target\Flow.Launcher.deps.json -raw
$depjson -replace '(?s)(.createdump.exe": {.*?}.*?\n)\s*', "" | Out-File $target\Flow.Launcher.deps.json -Encoding UTF8
$depjsonPath = (Get-Item "$target\Flow.Launcher.deps.json").FullName
if (Test-Path $depjsonPath) {
$depjson = Get-Content $depjsonPath -raw
$depjson -replace '(?s)(.createdump.exe": {.*?}.*?\n)\s*', "" | Out-File $depjsonPath -Encoding UTF8
}
Remove-Item -Path $target -Include "*createdump.exe" -Recurse
Comment on lines +62 to 67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix: Get-Item runs before the existence check and will throw if the file is missing.

Swap to Test-Path before Get-Item.

Apply this diff:

-    $depjsonPath = (Get-Item "$target\Flow.Launcher.deps.json").FullName
-    if (Test-Path $depjsonPath) {
-        $depjson = Get-Content $depjsonPath -raw
+    $depjsonPath = Join-Path $target "Flow.Launcher.deps.json"
+    if (Test-Path $depjsonPath) {
+        $depjson = Get-Content $depjsonPath -Raw
         $depjson -replace '(?s)(.createdump.exe": {.*?}.*?\n)\s*', "" | Out-File $depjsonPath -Encoding UTF8
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$depjsonPath = (Get-Item "$target\Flow.Launcher.deps.json").FullName
if (Test-Path $depjsonPath) {
$depjson = Get-Content $depjsonPath -raw
$depjson -replace '(?s)(.createdump.exe": {.*?}.*?\n)\s*', "" | Out-File $depjsonPath -Encoding UTF8
}
Remove-Item -Path $target -Include "*createdump.exe" -Recurse
$depjsonPath = Join-Path $target "Flow.Launcher.deps.json"
if (Test-Path $depjsonPath) {
$depjson = Get-Content $depjsonPath -Raw
$depjson -replace '(?s)(.createdump.exe": {.*?}.*?\n)\s*', "" | Out-File $depjsonPath -Encoding UTF8
}
Remove-Item -Path $target -Include "*createdump.exe" -Recurse
🤖 Prompt for AI Agents
In Scripts/post_build.ps1 around lines 62 to 67 the script calls Get-Item on
"$target\Flow.Launcher.deps.json" before checking the file exists, which will
throw if the file is missing; change the order to build the path string first,
use Test-Path on that path and only then call Get-Item to obtain FullName (or
directly operate on the path string), then continue with the content
read/replace and removal steps.

}


function Validate-Directory ($output) {
New-Item $output -ItemType Directory -Force
function Initialize-Directory ($output) {
if (Test-Path $output) {
Remove-Item -Recurse -Force $output
}
New-Item $output -ItemType Directory -Force | Out-Null
}


function Pack-Squirrel-Installer ($path, $version, $output) {
function New-SquirrelInstallerPackage ($path, $version, $output) {
# msbuild based installer generation is not working in appveyor, not sure why
Write-Host "Begin pack squirrel installer"

$spec = "$path\Scripts\flowlauncher.nuspec"
$input = "$path\Output\Release"
$inputPath = "$path\Output\Release"

Write-Host "Packing: $spec"
Write-Host "Input path: $input"
Write-Host "Input path: $inputPath"

# dotnet pack is not used because ran into issues, need to test installation and starting up if to use it.
nuget pack $spec -Version $version -BasePath $input -OutputDirectory $output -Properties Configuration=Release
nuget pack $spec -Version $version -BasePath $inputPath -OutputDirectory $output -Properties Configuration=Release

$nupkg = "$output\FlowLauncher.$version.nupkg"
Write-Host "nupkg path: $nupkg"
$icon = "$path\Flow.Launcher\Resources\app.ico"
Write-Host "icon: $icon"
# Squirrel.com: https://github.com/Squirrel/Squirrel.Windows/issues/369
New-Alias Squirrel $env:USERPROFILE\.nuget\packages\squirrel.windows\1.5.2\tools\Squirrel.exe -Force
# why we need Write-Output: https://github.com/Squirrel/Squirrel.Windows/issues/489#issuecomment-156039327
# directory of releaseDir in squirrel can't be same as directory ($nupkg) in releasify

$squirrelExe = (Get-ChildItem -Path "$env:USERPROFILE\.nuget\packages\squirrel.windows\*" -Directory | Sort-Object Name -Descending | Select-Object -First 1).FullName + "\tools\Squirrel.exe"
if (-not (Test-Path $squirrelExe)) {
Write-Host "FATAL: Squirrel.exe could not be found, aborting installer creation." -ForegroundColor Red
exit 1
}
New-Alias Squirrel $squirrelExe -Force

$temp = "$output\Temp"

Squirrel --releasify $nupkg --releaseDir $temp --setupIcon $icon --no-msi | Write-Output
Expand All @@ -96,41 +114,127 @@ function Pack-Squirrel-Installer ($path, $version, $output) {
Write-Host "End pack squirrel installer"
}

function Publish-Self-Contained ($p) {
function Build-Solution ($p) {
Write-Host "Building solution..."
$solutionFile = Join-Path $p "Flow.Launcher.sln"
dotnet build $solutionFile -c Release
if ($LASTEXITCODE -ne 0) { return $false }
return $true
}

function Publish-SelfContainedToTemp($p, $outputPath) {
Write-Host "Publishing self-contained application to temporary directory..."
$csproj = Join-Path "$p" "Flow.Launcher/Flow.Launcher.csproj" -Resolve
$profile = Join-Path "$p" "Flow.Launcher/Properties/PublishProfiles/Net9.0-SelfContained.pubxml" -Resolve

# We publish to a temporary directory first to ensure a clean, self-contained build
# without interfering with the main /Output/Release folder, which contains plugins.
# Let publish do its own build to ensure all self-contained dependencies are correctly resolved.
dotnet publish -c Release $csproj -r win-x64 --self-contained true -o $outputPath
if ($LASTEXITCODE -ne 0) { return $false }
return $true
}

# we call dotnet publish on the main project.
# The other projects should have been built in Release at this point.
dotnet publish -c Release $csproj /p:PublishProfile=$profile
function Merge-PublishToRelease($publishPath, $releasePath) {
Write-Host "Merging published files into release directory..."
Copy-Item -Path "$publishPath\*" -Destination $releasePath -Recurse -Force
Remove-Item -Recurse -Force $publishPath
}

function Publish-Portable ($outputLocation, $version) {
function Publish-Portable ($outputLocation, $version, $path) {
# The portable version is created by silently running the installer to a temporary location,
# then packaging the result. This ensures the structure is identical to a real installation
# and can be updated by Squirrel.
& "$outputLocation\Flow-Launcher-Setup.exe" --silent | Out-Null

$installRoot = Join-Path $env:LocalAppData "FlowLauncher"
$appPath = Join-Path $installRoot "app-$version"
$appExePath = Join-Path $appPath "Flow.Launcher.exe"

# Wait for silent installation to complete
$waitTime = 0
$maxWaitTime = 60 # 60 seconds timeout
while (-not (Test-Path $appExePath) -and $waitTime -lt $maxWaitTime) {
Start-Sleep -Seconds 1
$waitTime++
}

if (-not (Test-Path $appExePath)) {
Write-Host "Error: Timed out waiting for silent installation to complete." -ForegroundColor Red
return
}

Comment on lines +144 to 165
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Portable packaging flow: two risks and one hardening suggestion.

  • Risk 1: Silent install to %LocalAppData%\FlowLauncher will overwrite an existing user install on dev machines, then uninstall it later. Gate this behind CI env checks or install to an isolated temp root.
  • Risk 2: Waiting 60s may be flaky on slow agents; increase timeout or wait on the spawned process.
  • Hardening: Don’t rely on $version for app- path; pick the newest app-* folder to tolerate version normalization differences.

Apply this diff:

-    & "$outputLocation\Flow-Launcher-Setup.exe" --silent | Out-Null
+    # Avoid clobbering a developer's local install; allow override via env var
+    if (-not $env:CI -and -not $env:APPVEYOR) {
+        Write-Host "Warning: Skipping silent install outside CI to avoid overwriting a local install." -ForegroundColor Yellow
+        return
+    }
+    & "$outputLocation\Flow-Launcher-Setup.exe" --silent | Out-Null
@@
-    $appPath = Join-Path $installRoot "app-$version"
-    $appExePath = Join-Path $appPath "Flow.Launcher.exe"
+    # Prefer discovering the latest app-* folder to avoid version string mismatches
+    $latestAppDir = Get-ChildItem -Path $installRoot -Directory -Filter "app-*" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
+    $appPath = $latestAppDir?.FullName
+    $appExePath = if ($appPath) { Join-Path $appPath "Flow.Launcher.exe" } else { $null }
@@
-    $maxWaitTime = 60 # 60 seconds timeout
+    $maxWaitTime = 180 # allow slower agents
@@
-        Write-Host "Error: Timed out waiting for silent installation to complete." -ForegroundColor Red
+        Write-Host "Error: Timed out waiting for silent installation to complete." -ForegroundColor Red
         return

On removing “packages” from the portable zip: this is safe for update functionality; Update.exe downloads RELEASES and nupkg files from the configured feed on demand, and the local packages cache is recreated as needed. (gist.github.com)

Also applies to: 167-185, 186-198

🤖 Prompt for AI Agents
In Scripts/post_build.ps1 around lines 144-165 (also apply same changes to lines
167-185 and 186-198), the portable packaging flow risks overwriting a
developer's existing install, can time out waiting for a silent install, and
brittlely assumes app-$version exists; fix by: 1) gate the silent installer run
so it only executes in CI (e.g., check for a CI-specific env var) or change the
install target to an isolated temporary install root under $env:TEMP instead of
$env:LocalAppData\FlowLauncher; 2) stop using a fixed 60s loop—either capture
the installer process and Wait-Process for it or increase the timeout and
backoff retries to be robust on slow agents; 3) replace the hardcoded Join-Path
$installRoot "app-$version" logic with code that enumerates app-* directories
under the install root and selects the newest (highest semver or newest
LastWriteTime) folder to find Flow.Launcher.exe; also remove local “packages”
from the portable zip packaging per the suggested change so updates rely on the
feed.

& $outputLocation\Flow-Launcher-Setup.exe --silent | Out-Null
mkdir "$env:LocalAppData\FlowLauncher\app-$version\UserData"
Compress-Archive -Path $env:LocalAppData\FlowLauncher -DestinationPath $outputLocation\Flow-Launcher-Portable.zip
# Create a temporary staging directory for the portable package
$stagingDir = Join-Path $outputLocation "PortableStaging"
Initialize-Directory $stagingDir

try {
# Copy installed files to staging directory
Copy-Item -Path "$installRoot\*" -Destination $stagingDir -Recurse -Force

# Create the UserData folder inside app-<version> to enable portable mode.
New-Item -Path (Join-Path $stagingDir "app-$version" "UserData") -ItemType Directory -Force | Out-Null

# Remove the unnecessary 'packages' directory before creating the archive
$packagesPath = Join-Path $stagingDir "packages"
if (Test-Path $packagesPath) {
Remove-Item -Path $packagesPath -Recurse -Force
}

# Create the zip from the staging directory's contents
Compress-Archive -Path "$stagingDir\*" -DestinationPath "$outputLocation\Flow-Launcher-Portable.zip" -Force
}
finally {
if (Test-Path $stagingDir) {
Remove-Item -Recurse -Force $stagingDir
}
}

# Uninstall after packaging
$uninstallExe = Join-Path $installRoot "Update.exe"
if (Test-Path $uninstallExe) {
Write-Host "Uninstalling temporary application..."
Start-Process -FilePath $uninstallExe -ArgumentList "--uninstall -s" -Wait
}
}

function Main {
$p = Build-Path
$v = Build-Version
Copy-Resources $p

if ($config -eq "Release"){

Delete-Unused $p $config

Publish-Self-Contained $p

if (-not (Build-Solution $p)) {
Write-Host "dotnet build failed. Aborting post-build script." -ForegroundColor Red
exit 1
}

$tempPublishPath = Join-Path $p "Output\PublishTemp"
Initialize-Directory $tempPublishPath
if (-not (Publish-SelfContainedToTemp $p $tempPublishPath)) {
Write-Host "dotnet publish failed. Aborting." -ForegroundColor Red
exit 1
}

Merge-PublishToRelease $tempPublishPath (Join-Path $p "Output\Release")

$v = Build-Version
if ([string]::IsNullOrEmpty($v)) {
Write-Host "Could not determine build version. Aborting." -ForegroundColor Red
exit 1
}

Copy-Resources $p
Remove-UnusedFiles $p $config
Remove-CreateDumpExe $p $config

$o = "$p\Output\Packages"
Validate-Directory $o
Pack-Squirrel-Installer $p $v $o

Publish-Portable $o $v
Initialize-Directory $o
New-SquirrelInstallerPackage $p $v $o
if ($LASTEXITCODE -ne 0) {
Write-Host "Squirrel packaging failed. Aborting." -ForegroundColor Red
exit 1
}

Publish-Portable $o $v $p
}
}

Expand Down
Loading