fix(desktop): handle Squirrel startup events + CI smoke-install the Windows build (#398)
* fix(desktop): handle Squirrel startup events + CI smoke-install the Windows build
Two Windows-install fixes.
1. Add electron-squirrel-startup handling. main.ts had no handler for the
--squirrel-{install,updated,uninstall,obsolete} flags Squirrel runs the exe
with during install/update/uninstall. Without it the install hook booted the
full app (window + daemon spawn) and never exited, so the installer hung
waiting on it. Now we create/remove shortcuts and quit immediately; it is a
no-op on macOS/Linux.
2. Add a Windows CI smoke-install step. After the build, run Setup.exe --silent
on the clean native x64 windows-latest runner, assert the install dir is
created, and upload SquirrelSetup.log as an artifact. This captures the
install log we otherwise cannot get and gives a build-vs-host verdict: a clean
install proves the artifact is good, so a failing user machine is host-side
(AV/disk/signing). The runner has no real-time AV, so it does NOT prove
SmartScreen/Defender will accept the unsigned binaries on end-user machines;
code-signing stays the durable fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci(windows): capture SquirrelSetup.log from the app dir, fix misleading warning
Update.exe writes SquirrelSetup.log into %LocalAppData%\AgentOrchestrator, not
SquirrelTemp; the smoke step looked only in SquirrelTemp and so printed
"failed before Update.exe ran" even on a successful install (exit 0, dir
created). Look in the app root dir first, fall back to SquirrelTemp, and drop
the false-failure wording.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
47e3dddff5
commit
7ac1e2caa2
|
|
@ -58,6 +58,46 @@ jobs:
|
|||
# `npm run make` keeps the premake daemon build; --targets restricts to this
|
||||
# platform's maker.
|
||||
run: npm run make -- --targets ${{ matrix.target }}
|
||||
# Smoke-install the Squirrel installer on a clean, native x64 Windows runner.
|
||||
# This is a build-vs-host verdict: if it installs here it proves the artifact
|
||||
# is good and a failing user machine is host-side (AV/disk/signing); if it
|
||||
# fails here the build/Squirrel config is wrong. continue-on-error so a failed
|
||||
# install never blocks publishing the artifacts. The runner has no real-time
|
||||
# AV blocking, so a clean install here does NOT prove SmartScreen/Defender
|
||||
# won't reject the unsigned binaries on end-user machines.
|
||||
- name: Smoke-install the Windows installer
|
||||
if: runner.os == 'Windows'
|
||||
continue-on-error: true
|
||||
timeout-minutes: 5
|
||||
shell: pwsh
|
||||
run: |
|
||||
$setup = Get-ChildItem -Path out/make/squirrel.windows -Recurse -Filter '*Setup.exe' | Select-Object -First 1
|
||||
if (-not $setup) { Write-Host '::error::no Setup.exe produced under out/make/squirrel.windows'; exit 1 }
|
||||
Write-Host "Running $($setup.FullName) --silent"
|
||||
$proc = Start-Process -FilePath $setup.FullName -ArgumentList '--silent' -PassThru -Wait
|
||||
Write-Host "Setup.exe exit code: $($proc.ExitCode)"
|
||||
$installDir = Join-Path $env:LOCALAPPDATA 'AgentOrchestrator'
|
||||
# Update.exe writes SquirrelSetup.log into the app root dir; the Setup.exe
|
||||
# bootstrapper only logs to SquirrelTemp for the earliest extraction stage.
|
||||
# Grab whichever exists and copy it into the workspace for upload.
|
||||
$log = @(
|
||||
(Join-Path $installDir 'SquirrelSetup.log'),
|
||||
(Join-Path $env:LOCALAPPDATA 'SquirrelTemp\SquirrelSetup.log')
|
||||
) | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
if ($log) { Copy-Item $log squirrel-setup.log; Write-Host "captured log: $log" } else { Write-Host '::warning::no SquirrelSetup.log found in install dir or SquirrelTemp' }
|
||||
if (Test-Path $installDir) {
|
||||
Write-Host "INSTALL OK: $installDir created"
|
||||
Get-ChildItem $installDir | Select-Object Name | Format-Table -AutoSize
|
||||
} else {
|
||||
Write-Host "::warning::INSTALL FAILED: $installDir was not created"
|
||||
}
|
||||
- name: Upload SquirrelSetup.log
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: squirrel-setup-log-${{ github.sha }}
|
||||
path: frontend/squirrel-setup.log
|
||||
if-no-files-found: warn
|
||||
- name: Publish to a 0.0.0-testing-<sha> prerelease
|
||||
shell: bash
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"@xterm/xterm": "^5.5.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"lucide-react": "^1.17.0",
|
||||
"openapi-fetch": "^0.17.0",
|
||||
"posthog-js": "^1.390.2",
|
||||
|
|
@ -8518,6 +8519,30 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-squirrel-startup": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-squirrel-startup/-/electron-squirrel-startup-1.0.1.tgz",
|
||||
"integrity": "sha512-sTfFIHGku+7PsHLJ7v0dRcZNkALrV+YEozINTW8X1nM//e5O3L+rfYuvSW00lmGHnYmUjARZulD8F2V8ISI9RA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"debug": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-squirrel-startup/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-squirrel-startup/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.371",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.371.tgz",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"@xterm/xterm": "^5.5.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"lucide-react": "^1.17.0",
|
||||
"openapi-fetch": "^0.17.0",
|
||||
"posthog-js": "^1.390.2",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
// electron-squirrel-startup ships no types. Its default export is the boolean
|
||||
// result of handling any --squirrel-* startup event (true when the app should
|
||||
// quit, false on macOS/Linux or a normal launch).
|
||||
declare module "electron-squirrel-startup" {
|
||||
const startup: boolean;
|
||||
export default startup;
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import {
|
|||
type OpenDialogOptions,
|
||||
} from "electron";
|
||||
import { updateElectronApp } from "update-electron-app";
|
||||
import squirrelStartup from "electron-squirrel-startup";
|
||||
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
|
@ -48,6 +49,18 @@ const ignoreStdStreamError = (err: NodeJS.ErrnoException): void => {
|
|||
process.stdout.on("error", ignoreStdStreamError);
|
||||
process.stderr.on("error", ignoreStdStreamError);
|
||||
|
||||
// Windows Squirrel runs this exe with a --squirrel-{install,updated,uninstall,
|
||||
// obsolete} flag during install/update/uninstall. electron-squirrel-startup
|
||||
// creates/removes the shortcuts for that event and returns true; we then quit
|
||||
// immediately instead of booting the full app. A full launch (window + daemon
|
||||
// spawn) would hang the installer, which runs this hook and waits for it to
|
||||
// exit — that hang is the Squirrel-side failure. Returns false on macOS/Linux,
|
||||
// so this is a no-op there.
|
||||
const isSquirrelStartup: boolean = squirrelStartup;
|
||||
if (isSquirrelStartup) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
// Must run before app ready so the About panel and default-menu role labels use it.
|
||||
app.setName("Agent Orchestrator");
|
||||
|
||||
|
|
@ -678,6 +691,9 @@ function initAutoUpdates(): void {
|
|||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// A Squirrel install/update hook already called app.quit() above; do no
|
||||
// startup work (no window, no daemon spawn) so the hook exits promptly.
|
||||
if (isSquirrelStartup) return;
|
||||
registerRendererProtocol();
|
||||
createWindow();
|
||||
void startDaemon();
|
||||
|
|
|
|||
Loading…
Reference in New Issue