windows: Publish nightly (#24800)

The installer, uninstaller, and the Zed binary files are all signed
using Microsoft’s newly launched Trusted Signing service. For
demonstration purposes, I have used my own account for the signing
process.

For more information about Trusted Signing, you can refer to the
following links:
- [Microsoft Security Blog: Trusted Signing is in Public
Preview](https://techcommunity.microsoft.com/blog/microsoft-security-blog/trusted-signing-is-in-public-preview/4103457)
- [Overview of Azure Trusted
Signing](https://learn.microsoft.com/en-us/azure/trusted-signing/overview)

**TODO:**

- [x] `InnoSetup` script to setup an installer
- [x] Signing process
- [x] `Open with Zed` in right click context menu (by using sparse
package)
- [x] Integrate with `cli`
  - [x] Implement `cli` (#25412)
  - [x] Pack `cli.exe` into installer
- [x] Implement auto updating (#25734)
  - [x] Pack autoupdater helper into installer
- [x] Implement dock menus
  - [x] Add `Recent Documents` entries (#26369)
  - [x] Make `zed.exe` aware of sigle instance (#25412)
  - [x] Properly handle dock menu events (#26010)
- [x] Handle `zed://***` uri

**Materials needed:**

- [ ] Icons
  - [ ] App icon for all channels (#9571)
- [ ] Associated file icons, at minimum a default icon
([example](https://github.com/microsoft/vscode/tree/main/resources/win32))
  - [ ] Logos for installer wizard
  - [ ] Icons for appx
- [x] Code signing
- [x] Secrets: AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET,
ACCOUNT_NAME, CERT_PROFILE_NAME
- [x] Other constants: ENDPOINT, Identity Signature (i.e. `CN=Junkui
Zhang, O=Junkui Zhang, L=Wuhan, S=Hubei, C=CN`)





![屏幕截图 2025-02-13
205132](https://github.com/user-attachments/assets/925ec5b2-c8f4-4f0e-8666-26e30278eb3d)



https://github.com/user-attachments/assets/4f1092b4-90fc-4a47-a868-8f2f1a5d8ad8



Release Notes:

- N/A

---------

Co-authored-by: Kate <kate@zed.dev>
Co-authored-by: localcc <work@localcc.cc>
Co-authored-by: Peter Tripp <peter@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
张小白 2025-07-09 08:57:03 +08:00 committed by GitHub
parent 3a247ee947
commit df57754baf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 3040 additions and 19 deletions

263
script/bundle-windows.ps1 Normal file
View file

@ -0,0 +1,263 @@
[CmdletBinding()]
Param(
[Parameter()][Alias('i')][switch]$Install,
[Parameter()][Alias('h')][switch]$Help,
[Parameter()][string]$Name
)
. "$PSScriptRoot/lib/blob-store.ps1"
. "$PSScriptRoot/lib/workspace.ps1"
# https://stackoverflow.com/questions/57949031/powershell-script-stops-if-program-fails-like-bash-set-o-errexit
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
$buildSuccess = $false
if ($Help) {
Write-Output "Usage: test.ps1 [-Install] [-Help]"
Write-Output "Build the installer for Windows.\n"
Write-Output "Options:"
Write-Output " -Install, -i Run the installer after building."
Write-Output " -Help, -h Show this help message."
exit 0
}
Push-Location -Path crates/zed
$channel = Get-Content "RELEASE_CHANNEL"
$env:ZED_RELEASE_CHANNEL = $channel
Pop-Location
function CheckEnvironmentVariables {
$requiredVars = @(
'ZED_WORKSPACE', 'RELEASE_VERSION', 'ZED_RELEASE_CHANNEL',
'AZURE_TENANT_ID', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET',
'ACCOUNT_NAME', 'CERT_PROFILE_NAME', 'ENDPOINT',
'FILE_DIGEST', 'TIMESTAMP_DIGEST', 'TIMESTAMP_SERVER'
)
foreach ($var in $requiredVars) {
if (-not (Test-Path "env:$var")) {
Write-Error "$var is not set"
exit 1
}
}
}
$innoDir = "$env:ZED_WORKSPACE\inno"
function PrepareForBundle {
if (Test-Path "$innoDir") {
Remove-Item -Path "$innoDir" -Recurse -Force
}
New-Item -Path "$innoDir" -ItemType Directory -Force
Copy-Item -Path "$env:ZED_WORKSPACE\crates\zed\resources\windows\*" -Destination "$innoDir" -Recurse -Force
New-Item -Path "$innoDir\make_appx" -ItemType Directory -Force
New-Item -Path "$innoDir\appx" -ItemType Directory -Force
New-Item -Path "$innoDir\bin" -ItemType Directory -Force
New-Item -Path "$innoDir\tools" -ItemType Directory -Force
}
function BuildZedAndItsFriends {
Write-Output "Building Zed and its friends, for channel: $channel"
# Build zed.exe, cli.exe and auto_update_helper.exe
cargo build --release --package zed --package cli --package auto_update_helper
Copy-Item -Path ".\target\release\zed.exe" -Destination "$innoDir\Zed.exe" -Force
Copy-Item -Path ".\target\release\cli.exe" -Destination "$innoDir\cli.exe" -Force
Copy-Item -Path ".\target\release\auto_update_helper.exe" -Destination "$innoDir\auto_update_helper.exe" -Force
# Build explorer_command_injector.dll
switch ($channel) {
"stable" {
cargo build --release --features stable --no-default-features --package explorer_command_injector
}
"preview" {
cargo build --release --features preview --no-default-features --package explorer_command_injector
}
default {
cargo build --release --package explorer_command_injector
}
}
Copy-Item -Path ".\target\release\explorer_command_injector.dll" -Destination "$innoDir\zed_explorer_command_injector.dll" -Force
}
function ZipZedAndItsFriendsDebug {
$items = @(
".\target\release\zed.pdb",
".\target\release\cli.pdb",
".\target\release\auto_update_helper.pdb",
".\target\release\explorer_command_injector.pdb"
)
Compress-Archive -Path $items -DestinationPath ".\target\release\zed-$env:RELEASE_VERSION-$env:ZED_RELEASE_CHANNEL.dbg.zip" -Force
}
function MakeAppx {
switch ($channel) {
"stable" {
$manifestFile = "$env:ZED_WORKSPACE\crates\explorer_command_injector\AppxManifest.xml"
}
"preview" {
$manifestFile = "$env:ZED_WORKSPACE\crates\explorer_command_injector\AppxManifest-Preview.xml"
}
default {
$manifestFile = "$env:ZED_WORKSPACE\crates\explorer_command_injector\AppxManifest-Nightly.xml"
}
}
Copy-Item -Path "$manifestFile" -Destination "$innoDir\make_appx\AppxManifest.xml"
# Add makeAppx.exe to Path
$sdk = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64"
$env:Path += ';' + $sdk
makeAppx.exe pack /d "$innoDir\make_appx" /p "$innoDir\zed_explorer_command_injector.appx" /nv
}
function SignZedAndItsFriends {
$files = "$innoDir\Zed.exe,$innoDir\cli.exe,$innoDir\auto_update_helper.exe,$innoDir\zed_explorer_command_injector.dll,$innoDir\zed_explorer_command_injector.appx"
& "$innoDir\sign.ps1" $files
}
function CollectFiles {
Move-Item -Path "$innoDir\zed_explorer_command_injector.appx" -Destination "$innoDir\appx\zed_explorer_command_injector.appx" -Force
Move-Item -Path "$innoDir\zed_explorer_command_injector.dll" -Destination "$innoDir\appx\zed_explorer_command_injector.dll" -Force
Move-Item -Path "$innoDir\cli.exe" -Destination "$innoDir\bin\zed.exe" -Force
Move-Item -Path "$innoDir\auto_update_helper.exe" -Destination "$innoDir\tools\auto_update_helper.exe" -Force
}
function BuildInstaller {
$issFilePath = "$innoDir\zed.iss"
switch ($channel) {
"stable" {
$appId = "{{2DB0DA96-CA55-49BB-AF4F-64AF36A86712}"
$appIconName = "app-icon"
$appName = "Zed Editor"
$appDisplayName = "Zed Editor"
$appSetupName = "ZedEditorUserSetup-x64-$env:RELEASE_VERSION"
# The mutex name here should match the mutex name in crates\zed\src\zed\windows_only_instance.rs
$appMutex = "Zed-Editor-Stable-Instance-Mutex"
$appExeName = "Zed"
$regValueName = "ZedEditor"
$appUserId = "ZedIndustries.Zed"
$appShellNameShort = "Z&ed Editor"
$appAppxFullName = "ZedIndustries.Zed_1.0.0.0_neutral__japxn1gcva8rg"
}
"preview" {
$appId = "{{F70E4811-D0E2-4D88-AC99-D63752799F95}"
$appIconName = "app-icon-preview"
$appName = "Zed Editor Preview"
$appDisplayName = "Zed Editor Preview"
$appSetupName = "ZedEditorUserSetup-x64-$env:RELEASE_VERSION-preview"
# The mutex name here should match the mutex name in crates\zed\src\zed\windows_only_instance.rs
$appMutex = "Zed-Editor-Preview-Instance-Mutex"
$appExeName = "Zed"
$regValueName = "ZedEditorPreview"
$appUserId = "ZedIndustries.Zed.Preview"
$appShellNameShort = "Z&ed Editor Preview"
$appAppxFullName = "ZedIndustries.Zed.Preview_1.0.0.0_neutral__japxn1gcva8rg"
}
"nightly" {
$appId = "{{1BDB21D3-14E7-433C-843C-9C97382B2FE0}"
$appIconName = "app-icon-nightly"
$appName = "Zed Editor Nightly"
$appDisplayName = "Zed Editor Nightly"
$appSetupName = "ZedEditorUserSetup-x64-$env:RELEASE_VERSION-nightly"
# The mutex name here should match the mutex name in crates\zed\src\zed\windows_only_instance.rs
$appMutex = "Zed-Editor-Nightly-Instance-Mutex"
$appExeName = "Zed"
$regValueName = "ZedEditorNightly"
$appUserId = "ZedIndustries.Zed.Nightly"
$appShellNameShort = "Z&ed Editor Nightly"
$appAppxFullName = "ZedIndustries.Zed.Nightly_1.0.0.0_neutral__japxn1gcva8rg"
}
"dev" {
$appId = "{{8357632E-24A4-4F32-BA97-E575B4D1FE5D}"
$appIconName = "app-icon-nightly"
$appName = "Zed Editor Dev"
$appDisplayName = "Zed Editor Dev"
$appSetupName = "ZedEditorUserSetup-x64-$env:RELEASE_VERSION-dev"
# The mutex name here should match the mutex name in crates\zed\src\zed\windows_only_instance.rs
$appMutex = "Zed-Editor-Dev-Instance-Mutex"
$appExeName = "Zed"
$regValueName = "ZedEditorDev"
$appUserId = "ZedIndustries.Zed.Dev"
$appShellNameShort = "Z&ed Editor Dev"
$appAppxFullName = "ZedIndustries.Zed.Dev_1.0.0.0_neutral__japxn1gcva8rg"
}
default {
Write-Error "can't bundle installer for $channel."
exit 1
}
}
# Windows runner 2022 default has iscc in PATH, https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-Readme.md
# Currently, we are using Windows 2022 runner.
# Windows runner 2025 doesn't have iscc in PATH for now, https://github.com/actions/runner-images/issues/11228
# $innoSetupPath = "iscc.exe"
$innoSetupPath = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
$definitions = @{
"AppId" = $appId
"AppIconName" = $appIconName
"OutputDir" = "$env:ZED_WORKSPACE\target"
"AppSetupName" = $appSetupName
"AppName" = $appName
"AppDisplayName" = $appDisplayName
"RegValueName" = $regValueName
"AppMutex" = $appMutex
"AppExeName" = $appExeName
"ResourcesDir" = "$innoDir"
"ShellNameShort" = $appShellNameShort
"AppUserId" = $appUserId
"Version" = "$env:RELEASE_VERSION"
"SourceDir" = "$env:ZED_WORKSPACE"
"AppxFullName" = $appAppxFullName
}
$signTool = "powershell.exe -ExecutionPolicy Bypass -File $innoDir\sign.ps1 `$f"
$defs = @()
foreach ($key in $definitions.Keys) {
$defs += "/d$key=`"$($definitions[$key])`""
}
$innoArgs = @($issFilePath) + $defs + "/sDefaultsign=`"$signTool`""
# Execute Inno Setup
Write-Host "🚀 Running Inno Setup: $innoSetupPath $innoArgs"
$process = Start-Process -FilePath $innoSetupPath -ArgumentList $innoArgs -NoNewWindow -Wait -PassThru
if ($process.ExitCode -eq 0) {
Write-Host "✅ Inno Setup successfully compiled the installer"
Write-Output "SETUP_PATH=target/$appSetupName.exe" >> $env:GITHUB_ENV
$script:buildSuccess = $true
}
else {
Write-Host "❌ Inno Setup failed: $($process.ExitCode)"
$script:buildSuccess = $false
}
}
ParseZedWorkspace
CheckEnvironmentVariables
PrepareForBundle
BuildZedAndItsFriends
MakeAppx
SignZedAndItsFriends
ZipZedAndItsFriendsDebug
CollectFiles
BuildInstaller
$debugArchive = ".\target\release\zed-$env:RELEASE_VERSION-$env:ZED_RELEASE_CHANNEL.dbg.zip"
$debugStoreKey = "$env:ZED_RELEASE_CHANNEL/zed-$env:RELEASE_VERSION-$env:ZED_RELEASE_CHANNEL.dbg.zip"
UploadToBlobStorePublic -BucketName "zed-debug-symbols" -FileToUpload $debugArchive -BlobStoreKey $debugStoreKey
if ($buildSuccess) {
Write-Output "Build successful"
if ($Install) {
Write-Output "Installing Zed..."
Start-Process -FilePath "$env:ZED_WORKSPACE/target/ZedEditorUserSetup-x64-$env:RELEASE_VERSION.exe"
}
exit 0
}
else {
Write-Output "Build failed"
exit 1
}

View file

@ -18,5 +18,5 @@ Write-Host "target directory size: ${current_size_gb}GB. max size: ${MAX_SIZE_IN
if ($current_size_gb -gt $MAX_SIZE_IN_GB) {
Write-Host "clearing target directory"
Remove-Item -Recurse -Force -Path "target\*"
Remove-Item -Recurse -Force -Path "target\*" -ErrorAction SilentlyContinue
}

View file

@ -0,0 +1,37 @@
$ErrorActionPreference = "Stop"
if (-not $env:GITHUB_ACTIONS) {
Write-Error "Error: This script must be run in a GitHub Actions environment"
exit 1
}
if (-not $env:GITHUB_REF) {
Write-Error "Error: GITHUB_REF is not set"
exit 1
}
$version = & "script/get-crate-version.ps1" "zed"
$channel = Get-Content "crates/zed/RELEASE_CHANNEL"
Write-Host "Publishing version: $version on release channel $channel"
Write-Output "RELEASE_CHANNEL=$channel" >> $env:GITHUB_ENV
Write-Output "RELEASE_VERSION=$version" >> $env:GITHUB_ENV
$expectedTagName = ""
switch ($channel) {
"stable" {
$expectedTagName = "v$version"
}
"preview" {
$expectedTagName = "v$version-pre"
}
default {
Write-Error "can't publish a release on channel $channel"
exit 1
}
}
if ($env:GITHUB_REF_NAME -ne $expectedTagName) {
Write-Error "invalid release tag $($env:GITHUB_REF_NAME). expected $expectedTagName"
exit 1
}

View file

@ -0,0 +1,16 @@
if ($args.Length -ne 1) {
Write-Error "Usage: $($MyInvocation.MyCommand.Name) <crate_name>"
exit 1
}
$crateName = $args[0]
$metadata = cargo metadata --no-deps --format-version=1 | ConvertFrom-Json
$package = $metadata.packages | Where-Object { $_.name -eq $crateName }
if ($package) {
$package.version
}
else {
Write-Error "Crate '$crateName' not found."
}

68
script/lib/blob-store.ps1 Normal file
View file

@ -0,0 +1,68 @@
function UploadToBlobStoreWithACL {
param (
[string]$BucketName,
[string]$FileToUpload,
[string]$BlobStoreKey,
[string]$ACL
)
# Format date to match AWS requirements
$Date = (Get-Date).ToUniversalTime().ToString("r")
# Note: Original script had a bug where it overrode the ACL parameter
# I'm keeping the same behavior for compatibility
$ACL = "public-read"
$ContentType = "application/octet-stream"
$StorageClass = "STANDARD"
# Create string to sign (AWS S3 compatible format)
$StringToSign = "PUT`n`n${ContentType}`n${Date}`nx-amz-acl:${ACL}`nx-amz-storage-class:${StorageClass}`n/${BucketName}/${BlobStoreKey}"
# Generate HMAC-SHA1 signature
$HMACSHA1 = New-Object System.Security.Cryptography.HMACSHA1
$HMACSHA1.Key = [System.Text.Encoding]::UTF8.GetBytes($env:DIGITALOCEAN_SPACES_SECRET_KEY)
$Signature = [System.Convert]::ToBase64String($HMACSHA1.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($StringToSign)))
# Upload file using Invoke-WebRequest (equivalent to curl)
$Headers = @{
"Host" = "${BucketName}.nyc3.digitaloceanspaces.com"
"Date" = $Date
"Content-Type" = $ContentType
"x-amz-storage-class" = $StorageClass
"x-amz-acl" = $ACL
"Authorization" = "AWS ${env:DIGITALOCEAN_SPACES_ACCESS_KEY}:$Signature"
}
$Uri = "https://${BucketName}.nyc3.digitaloceanspaces.com/${BlobStoreKey}"
# Read file content
$FileContent = Get-Content $FileToUpload -Raw -AsByteStream
try {
Invoke-WebRequest -Uri $Uri -Method PUT -Headers $Headers -Body $FileContent -ContentType $ContentType -Verbose
Write-Host "Successfully uploaded $FileToUpload to $Uri" -ForegroundColor Green
}
catch {
Write-Error "Failed to upload file: $_"
throw $_
}
}
function UploadToBlobStorePublic {
param (
[string]$BucketName,
[string]$FileToUpload,
[string]$BlobStoreKey
)
UploadToBlobStoreWithACL -BucketName $BucketName -FileToUpload $FileToUpload -BlobStoreKey $BlobStoreKey -ACL "public-read"
}
function UploadToBlobStore {
param (
[string]$BucketName,
[string]$FileToUpload,
[string]$BlobStoreKey
)
UploadToBlobStoreWithACL -BucketName $BucketName -FileToUpload $FileToUpload -BlobStoreKey $BlobStoreKey -ACL "private"
}

6
script/lib/workspace.ps1 Normal file
View file

@ -0,0 +1,6 @@
function ParseZedWorkspace {
$metadata = cargo metadata --no-deps --offline | ConvertFrom-Json
$env:ZED_WORKSPACE = $metadata.workspace_root
$env:RELEASE_VERSION = $metadata.packages | Where-Object { $_.name -eq "zed" } | Select-Object -ExpandProperty version
}

60
script/upload-nightly.ps1 Normal file
View file

@ -0,0 +1,60 @@
# Based on the template in: https://docs.digitalocean.com/reference/api/spaces-api/
$ErrorActionPreference = "Stop"
. "$PSScriptRoot\lib\blob-store.ps1"
. "$PSScriptRoot\lib\workspace.ps1"
$allowedTargets = @("windows")
function Test-AllowedTarget {
param (
[string]$Target
)
return $allowedTargets -contains $Target
}
# Process arguments
if ($args.Count -gt 0) {
$target = $args[0]
if (Test-AllowedTarget $target) {
# Valid target
} else {
Write-Error "Error: Target '$target' is not allowed.`nUsage: $($MyInvocation.MyCommand.Name) [$($allowedTargets -join ', ')]"
exit 1
}
} else {
Write-Error "Error: Target is not specified.`nUsage: $($MyInvocation.MyCommand.Name) [$($allowedTargets -join ', ')]"
exit 1
}
ParseZedWorkspace
Write-Host "Uploading nightly for target: $target"
$bucketName = "zed-nightly-host"
# Get current git SHA
$sha = git rev-parse HEAD
$sha | Out-File -FilePath "target/latest-sha" -NoNewline
# TODO:
# Upload remote server files
# $remoteServerFiles = Get-ChildItem -Path "target" -Filter "zed-remote-server-*.gz" -Recurse -File
# foreach ($file in $remoteServerFiles) {
# Upload-ToBlobStore -BucketName $bucketName -FileToUpload $file.FullName -BlobStoreKey "nightly/$($file.Name)"
# Remove-Item -Path $file.FullName
# }
switch ($target) {
"windows" {
UploadToBlobStore -BucketName $bucketName -FileToUpload $env:SETUP_PATH -BlobStoreKey "nightly/zed_editor_installer_x86_64.exe"
UploadToBlobStore -BucketName $bucketName -FileToUpload "target/latest-sha" -BlobStoreKey "nightly/latest-sha-windows"
Remove-Item -Path $env:SETUP_PATH -ErrorAction SilentlyContinue
Remove-Item -Path "target/latest-sha" -ErrorAction SilentlyContinue
}
default {
Write-Error "Error: Unknown target '$target'"
exit 1
}
}