Skip to content
Open
Show file tree
Hide file tree
Changes from all 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