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
88 changes: 88 additions & 0 deletions .github/workflows/store-msix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Microsoft Store MSIX

on:
pull_request:
paths:
- ".github/workflows/store-msix.yml"
- "packaging/msix/**"
- "scripts/build-msix.ps1"
- "src-tauri/**"
- "crates/**"
- "Cargo.toml"
- "Cargo.lock"
- "Trunk.toml"
- "assets/icon.png"
workflow_dispatch:
inputs:
version:
description: "Version from src-tauri/tauri.conf.json (for example 0.9.1)"
required: true
type: string
without_service:
description: "Omit the packaged Everything Windows service"
required: false
default: false
type: boolean

permissions:
contents: read

jobs:
build-msix:
runs-on: windows-latest

steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
Comment thread
StephanOrgiazzi marked this conversation as resolved.
with:
fetch-depth: 0

- name: Validate Store release ref
if: github.event_name == 'workflow_dispatch'
shell: pwsh
run: |
$expectedTag = "v${{ inputs.version }}"
if ($env:GITHUB_REF_TYPE -ne "tag" -or $env:GITHUB_REF_NAME -ne $expectedTag) {
throw "Microsoft Store builds must run from release tag '$expectedTag' (selected ref: '$env:GITHUB_REF_NAME')."
}
git merge-base --is-ancestor $env:GITHUB_SHA origin/master
if ($LASTEXITCODE -ne 0) {
throw "Store release tag '$expectedTag' must point to a commit on master."
}

- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
targets: wasm32-unknown-unknown

- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
workspaces: ". -> target"

- name: Prepare toolchain and bundled Everything runtime
shell: pwsh
run: .\scripts\setup.ps1 -SkipFormat

- name: Build unsigned Store MSIX package
shell: pwsh
env:
# Pull requests use harmless placeholders to validate the package layout.
# Manual Store builds use the exact values copied from Partner Center.
MSIX_IDENTITY_NAME: ${{ vars.MSIX_IDENTITY_NAME || 'EverythingNext' }}
MSIX_PUBLISHER: ${{ vars.MSIX_PUBLISHER || 'CN=Everything Next' }}
MSIX_PUBLISHER_DISPLAY_NAME: ${{ vars.MSIX_PUBLISHER_DISPLAY_NAME || 'Stephan Orgiazzi' }}
run: |
$arguments = @()
if ('${{ inputs.version }}') {
$arguments += @('-Version', '${{ inputs.version }}')
}
if ('${{ inputs.without_service }}' -eq 'true') {
$arguments += '-WithoutService'
}
& .\scripts\build-msix.ps1 @arguments

- name: Upload MSIX package
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: everything-next-msix-${{ inputs.version }}
path: artifacts/msix/*.msix
if-no-files-found: error
18 changes: 18 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ Installed release builds query:

When a newer version is available, Everything Next offers **Install** or **Later**. The updater uses the NSIS installer; on Windows the app exits before installation, and the existing installer hooks stop/recreate the private Everything service during the upgrade.

## Microsoft Store MSIX package

The repository also contains a separate MSIX packaging workflow for the Microsoft Store. It does not change the GitHub/NSIS release channel.

Microsoft re-signs MSIX packages submitted through the Store, so this workflow intentionally produces an unsigned `.msix` artifact. No Authenticode certificate is needed for that Store package.

Before running `.github/workflows/store-msix.yml`, reserve the app in Partner Center and create these repository variables with the exact values supplied by Microsoft:

- `MSIX_IDENTITY_NAME`
- `MSIX_PUBLISHER`
- `MSIX_PUBLISHER_DISPLAY_NAME`

Run the workflow from the release tag and enter the same three-part version as `src-tauri/tauri.conf.json`. Download the resulting artifact and upload the `.msix` package to the Store submission.

The default package declares the private `Everything Service (EverythingNext)` as a packaged LocalSystem service. Microsoft may require approval for the `packagedServices` and `localSystemServices` restricted capabilities. If that capability request is not accepted, rerun the workflow with `without_service` enabled; the application will start its own bundled Everything process when it is launched, but it will not maintain the index while the application is closed.

The Store build does not receive the Tauri updater signing key. Store updates are managed by Microsoft; GitHub releases continue to use the existing Tauri updater and NSIS hooks.

## First WinGet submission

The package identifier is `StephanOrgiazzi.EverythingNext`. The first submission must be made after the GitHub Release exists because WinGet requires a public, version-specific installer URL and its exact SHA-256 hash.
Expand Down
63 changes: 63 additions & 0 deletions packaging/msix/AppxManifest.xml.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap desktop desktop6 rescap">
<Identity
Name="__IDENTITY_NAME__"
Publisher="__PUBLISHER__"
Version="__VERSION__"
ProcessorArchitecture="x64" />

<Properties>
<DisplayName>Everything Next</DisplayName>
<PublisherDisplayName>__PUBLISHER_DISPLAY_NAME__</PublisherDisplayName>
<Description>Fast, native-style file search for Windows 11.</Description>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>

<Resources>
<Resource Language="en-us" />
</Resources>

<Dependencies>
<TargetDeviceFamily
Name="Windows.Desktop"
MinVersion="10.0.19041.0"
MaxVersionTested="10.0.26100.0" />
</Dependencies>

<Applications>
<Application
Id="EverythingNext"
Executable="EverythingNext.exe"
EntryPoint="Windows.FullTrustApplication">
Comment thread
StephanOrgiazzi marked this conversation as resolved.
<uap:VisualElements
DisplayName="Everything Next"
Description="Fast, native-style file search for Windows 11."
BackgroundColor="#202020"
Square44x44Logo="Assets\Square44x44Logo.png"
Square150x150Logo="Assets\Square150x150Logo.png" />
<Extensions>
<desktop:Extension
Category="windows.startupTask"
Executable="EverythingNextAutostart.exe"
EntryPoint="Windows.FullTrustApplication">
<desktop:StartupTask
TaskId="EverythingNextStartup"
Enabled="true"
DisplayName="Everything Next" />
</desktop:Extension>
__SERVICE_EXTENSION__
</Extensions>
</Application>
</Applications>

<Capabilities>
<rescap:Capability Name="runFullTrust" />
__SERVICE_CAPABILITIES__
</Capabilities>
</Package>
213 changes: 213 additions & 0 deletions scripts/build-msix.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
[CmdletBinding()]
param(
[string]$IdentityName = $env:MSIX_IDENTITY_NAME,
[string]$Publisher = $env:MSIX_PUBLISHER,
[string]$PublisherDisplayName = $env:MSIX_PUBLISHER_DISPLAY_NAME,
[string]$Version,
[string]$OutputDirectory = (Join-Path $PSScriptRoot '..\artifacts\msix'),
[switch]$SkipBuild,
[switch]$WithoutService
)

$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
$projectRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
Set-Location $projectRoot

function Require-Value([string]$Name, [string]$Value) {
if ([string]::IsNullOrWhiteSpace($Value)) {
throw "$Name is required. Set it as a parameter or repository variable."
}
if ($Value.Contains('"') -or $Value.Contains('<') -or $Value.Contains('>')) {
throw "$Name contains characters that cannot be used in the MSIX manifest."
}
}

Require-Value 'IdentityName' $IdentityName
Require-Value 'Publisher' $Publisher
Require-Value 'PublisherDisplayName' $PublisherDisplayName

$tauriConfig = Get-Content 'src-tauri\tauri.conf.json' -Raw | ConvertFrom-Json
$manifestVersion = [string]$tauriConfig.version
if ([string]::IsNullOrWhiteSpace($Version)) {
$Version = $manifestVersion
} elseif ($Version -ne $manifestVersion) {
throw "Requested MSIX version '$Version' does not match tauri.conf.json version '$manifestVersion'."
}

if ($Version -notmatch '^\d+\.\d+\.\d+$') {
throw "Version '$Version' must contain exactly three numeric components, such as 0.9.1."
}
$packageVersion = "$Version.0"

if (-not $SkipBuild) {
# Store packages use the manifest startup task and Microsoft Store updates.
# Do not compile the registry autostart path or private updater signing material into this build.
$savedStoreBuild = $env:TAURI_STORE_BUILD
$savedUpdaterKey = $env:TAURI_UPDATER_PUBLIC_KEY
$savedSigningKey = $env:TAURI_SIGNING_PRIVATE_KEY
try {
$env:TAURI_STORE_BUILD = '1'
Remove-Item Env:TAURI_UPDATER_PUBLIC_KEY -ErrorAction SilentlyContinue
Remove-Item Env:TAURI_SIGNING_PRIVATE_KEY -ErrorAction SilentlyContinue

& cargo tauri build --no-bundle --config src-tauri/tauri.release.conf.json
if ($LASTEXITCODE -ne 0) {
throw "Tauri build failed with exit code $LASTEXITCODE."
}

& cargo build --release -p everything-next --bin everything-next-autostart
if ($LASTEXITCODE -ne 0) {
throw "Autostart launcher build failed with exit code $LASTEXITCODE."
}
} finally {
if ($null -eq $savedStoreBuild) {
Remove-Item Env:TAURI_STORE_BUILD -ErrorAction SilentlyContinue
} else {
$env:TAURI_STORE_BUILD = $savedStoreBuild
}
if ($null -eq $savedUpdaterKey) {
Remove-Item Env:TAURI_UPDATER_PUBLIC_KEY -ErrorAction SilentlyContinue
} else {
$env:TAURI_UPDATER_PUBLIC_KEY = $savedUpdaterKey
}
if ($null -eq $savedSigningKey) {
Remove-Item Env:TAURI_SIGNING_PRIVATE_KEY -ErrorAction SilentlyContinue
} else {
$env:TAURI_SIGNING_PRIVATE_KEY = $savedSigningKey
}
}
}

$makeAppxCommand = Get-Command makeappx.exe -ErrorAction SilentlyContinue | Select-Object -First 1
if ($makeAppxCommand) {
$makeAppx = $makeAppxCommand.Source
} else {
$makeAppx = Get-ChildItem `
-Path (Join-Path ${env:ProgramFiles(x86)} 'Windows Kits\10\bin') `
-Filter makeappx.exe `
-Recurse `
-File `
-ErrorAction SilentlyContinue |
Where-Object { $_.Directory.Name -eq 'x64' } |
Sort-Object FullName |
Select-Object -Last 1 -ExpandProperty FullName
}
if (-not $makeAppx) {
throw 'makeappx.exe was not found in the installed Windows SDK.'
}

$binary = Join-Path $projectRoot 'target\release\EverythingNext.exe'
$autostartLauncher = Join-Path $projectRoot 'target\release\everything-next-autostart.exe'
$sdk = Join-Path $projectRoot 'src-tauri\Everything3_x64.dll'
$engine = Join-Path $projectRoot 'src-tauri\engine\Everything.exe'
$license = Join-Path $projectRoot 'src-tauri\engine\THIRD-PARTY-LICENSES.txt'
$manifestTemplate = Join-Path $projectRoot 'packaging\msix\AppxManifest.xml.in'

foreach ($requiredFile in @($binary, $autostartLauncher, $sdk, $engine, $license, $manifestTemplate)) {
if (-not (Test-Path -LiteralPath $requiredFile -PathType Leaf)) {
throw "Required MSIX input is missing: $requiredFile"
}
}

if ([System.IO.Path]::IsPathRooted($OutputDirectory)) {
$outputDirectory = [System.IO.Path]::GetFullPath($OutputDirectory)
} else {
$outputDirectory = [System.IO.Path]::GetFullPath((Join-Path $projectRoot $OutputDirectory))
}
New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
$packagePath = Join-Path $outputDirectory "Everything.Next_$Version`_x64.msix"

$stagingRoot = Join-Path ([System.IO.Path]::GetTempPath()) "everything-next-msix-$([guid]::NewGuid().ToString('N'))"
$stagingEngine = Join-Path $stagingRoot 'engine'
$stagingAssets = Join-Path $stagingRoot 'Assets'

try {
New-Item -ItemType Directory -Path $stagingEngine, $stagingAssets -Force | Out-Null

Copy-Item -LiteralPath $binary -Destination (Join-Path $stagingRoot 'EverythingNext.exe')
Copy-Item -LiteralPath $autostartLauncher -Destination (Join-Path $stagingRoot 'EverythingNextAutostart.exe')
Copy-Item -LiteralPath $sdk -Destination (Join-Path $stagingRoot 'Everything3_x64.dll')
Copy-Item -LiteralPath $engine -Destination (Join-Path $stagingEngine 'Everything.exe')
Copy-Item -LiteralPath $license -Destination (Join-Path $stagingEngine 'THIRD-PARTY-LICENSES.txt')

$icon = Join-Path $projectRoot 'assets\icon.png'
if (-not (Test-Path -LiteralPath $icon -PathType Leaf)) {
throw "The MSIX logo source is missing: $icon"
}
Add-Type -AssemblyName System.Drawing
function Write-ScaledPng([string]$SourcePath, [string]$DestinationPath, [int]$Width, [int]$Height) {
$sourceImage = [System.Drawing.Image]::FromFile($SourcePath)
$bitmap = [System.Drawing.Bitmap]::new($Width, $Height)
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
try {
$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
$graphics.DrawImage($sourceImage, 0, 0, $Width, $Height)
$bitmap.Save($DestinationPath, [System.Drawing.Imaging.ImageFormat]::Png)
} finally {
$graphics.Dispose()
$bitmap.Dispose()
$sourceImage.Dispose()
}
}

Write-ScaledPng $icon (Join-Path $stagingAssets 'StoreLogo.png') 50 50
Write-ScaledPng $icon (Join-Path $stagingAssets 'Square44x44Logo.png') 44 44
Write-ScaledPng $icon (Join-Path $stagingAssets 'Square150x150Logo.png') 150 150

$serviceExtension = ''
$serviceCapabilities = ''
if (-not $WithoutService) {
$serviceExtension = @'
<desktop6:Extension
Category="windows.service"
Executable="engine\Everything.exe"
EntryPoint="Windows.FullTrustApplication">
<desktop6:Service
Name="Everything Service (EverythingNext)"
StartupType="auto"
Comment thread
StephanOrgiazzi marked this conversation as resolved.
StartAccount="localSystem"
Arguments="-svc -instance EverythingNext -svc-pipe-name &quot;\\.\PIPE\Everything Service (EverythingNext)&quot;" />
</desktop6:Extension>
'@
$serviceCapabilities = @'
<rescap:Capability Name="packagedServices" />
<rescap:Capability Name="localSystemServices" />
'@
}

$manifest = Get-Content -LiteralPath $manifestTemplate -Raw
$replacements = @{
'__IDENTITY_NAME__' = [System.Security.SecurityElement]::Escape($IdentityName)
'__PUBLISHER__' = [System.Security.SecurityElement]::Escape($Publisher)
'__PUBLISHER_DISPLAY_NAME__' = [System.Security.SecurityElement]::Escape($PublisherDisplayName)
'__VERSION__' = $packageVersion
'__SERVICE_EXTENSION__' = $serviceExtension.TrimEnd()
'__SERVICE_CAPABILITIES__' = $serviceCapabilities.TrimEnd()
}
foreach ($token in $replacements.Keys) {
$manifest = $manifest.Replace($token, $replacements[$token])
}
Set-Content -LiteralPath (Join-Path $stagingRoot 'AppxManifest.xml') -Value $manifest -Encoding utf8NoBOM

if (Test-Path -LiteralPath $packagePath) {
Remove-Item -LiteralPath $packagePath -Force
}
& $makeAppx pack /d $stagingRoot /p $packagePath /o
if ($LASTEXITCODE -ne 0) {
throw "makeappx.exe failed with exit code $LASTEXITCODE."
}

Write-Host "Unsigned MSIX package written to $packagePath" -ForegroundColor Green
Write-Host "Identity: $IdentityName"
Write-Host "Publisher: $Publisher"
if ($WithoutService) {
Write-Host 'Packaged Everything service: disabled (the app starts its own engine process).' -ForegroundColor Yellow
} else {
Write-Host 'Packaged Everything service: enabled (the Store submission may require restricted capability approval).' -ForegroundColor Yellow
}
} finally {
Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction SilentlyContinue
}
Loading
Loading