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
|
# `npm run make` keeps the premake daemon build; --targets restricts to this
|
||||||
# platform's maker.
|
# platform's maker.
|
||||||
run: npm run make -- --targets ${{ matrix.target }}
|
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
|
- name: Publish to a 0.0.0-testing-<sha> prerelease
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
"lucide-react": "^1.17.0",
|
"lucide-react": "^1.17.0",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
"posthog-js": "^1.390.2",
|
"posthog-js": "^1.390.2",
|
||||||
|
|
@ -8518,6 +8519,30 @@
|
||||||
"node": ">=10"
|
"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": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.371",
|
"version": "1.5.371",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.371.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.371.tgz",
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
"lucide-react": "^1.17.0",
|
"lucide-react": "^1.17.0",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
"posthog-js": "^1.390.2",
|
"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,
|
type OpenDialogOptions,
|
||||||
} from "electron";
|
} from "electron";
|
||||||
import { updateElectronApp } from "update-electron-app";
|
import { updateElectronApp } from "update-electron-app";
|
||||||
|
import squirrelStartup from "electron-squirrel-startup";
|
||||||
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||||
import { existsSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
|
|
@ -48,6 +49,18 @@ const ignoreStdStreamError = (err: NodeJS.ErrnoException): void => {
|
||||||
process.stdout.on("error", ignoreStdStreamError);
|
process.stdout.on("error", ignoreStdStreamError);
|
||||||
process.stderr.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.
|
// Must run before app ready so the About panel and default-menu role labels use it.
|
||||||
app.setName("Agent Orchestrator");
|
app.setName("Agent Orchestrator");
|
||||||
|
|
||||||
|
|
@ -678,6 +691,9 @@ function initAutoUpdates(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
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();
|
registerRendererProtocol();
|
||||||
createWindow();
|
createWindow();
|
||||||
void startDaemon();
|
void startDaemon();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue