fix(desktop): package Windows via NSIS instead of Squirrel (#403)
* fix(desktop): package Windows via NSIS instead of Squirrel (#401) Squirrel.Windows is a poor fit: per-user install only, no custom install directory, no proper add/remove-programs uninstaller, and fragile updates. Replace it with a real NSIS installer (per-user or per-machine, custom install dir, uninstaller), matching recordly's working Windows setup. Electron Forge ships no first-party NSIS maker, so add a thin MakerBase subclass (makers/maker-nsis.ts) that bridges to electron-builder's buildForge, the same engine electron-builder uses, scoped to win32. The maker exposes the NSIS knobs the issue calls for (oneClick:false, allowToChangeInstallationDirectory, per-machine) and defaults to an assisted installer. - forge.config.ts: drop maker-squirrel, add the NSIS maker instance. - testing-build.yml: target "nsis"; smoke-install via /S under out/make; drop the Squirrel-specific log capture. - Rename the package "agent-orchestrator-frontend" -> "agent-orchestrator": this repo is the full app, not just a frontend. User-facing naming was already "Agent Orchestrator" (productName) / agent-orchestrator.exe. Deferred (per issue, separate follow-ups): bundling zellij.exe so a fresh Windows install needs no manual zellij, an actionable "zellij not found" error at session-create, and Windows code-signing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: format with prettier [skip ci] * fix(desktop): disable electron-builder publish + drop electron-squirrel-startup CI fix: the NSIS maker's electron-builder run inferred a GitHub publish target from package.json `repository` and tried to upload (to emit auto-update info), failing with "GitHub Personal Access Token is not set". Forge owns publishing (the workflow uploads via `gh release`), so set `config.publish = null` to disable electron-builder's upload entirely. Also remove `electron-squirrel-startup`: it only handled Squirrel.Windows install/update hooks (--squirrel-* flags) and is dead weight under NSIS. Drop the dependency, its import, the startup quit-block, the whenReady guard, and the type shim. The EPIPE std-stream guard stays (it covers any windowless Windows GUI launch, e.g. from a shortcut). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: gitignore electron-builder's builder-debug.yml dump Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
f15021c19d
commit
e970f72d3e
|
|
@ -6,7 +6,7 @@ name: Desktop testing build
|
||||||
#
|
#
|
||||||
# Per OS the current electron-forge makers produce:
|
# Per OS the current electron-forge makers produce:
|
||||||
# - macOS → .zip (the .dmg maker is a follow-up)
|
# - macOS → .zip (the .dmg maker is a follow-up)
|
||||||
# - Windows → Setup.exe + .nupkg + RELEASES (Squirrel)
|
# - Windows → NSIS installer (.exe)
|
||||||
# - Linux → .deb and .rpm
|
# - Linux → .deb and .rpm
|
||||||
#
|
#
|
||||||
# Each OS builds on its own native runner because build-daemon.mjs compiles the
|
# Each OS builds on its own native runner because build-daemon.mjs compiles the
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ name: Testing build (all platforms)
|
||||||
#
|
#
|
||||||
# Per OS:
|
# Per OS:
|
||||||
# Linux -> .deb
|
# Linux -> .deb
|
||||||
# Windows -> Setup.exe (+ .nupkg / RELEASES)
|
# Windows -> NSIS installer (.exe)
|
||||||
# macOS -> .zip (arm64; dmg + signing are follow-ups)
|
# macOS -> .zip (arm64; dmg + signing are follow-ups)
|
||||||
#
|
#
|
||||||
# Unsigned: macOS is quarantined/Gatekeeper-blocked once downloaded
|
# Unsigned: macOS is quarantined/Gatekeeper-blocked once downloaded
|
||||||
|
|
@ -29,7 +29,9 @@ jobs:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
target: "@electron-forge/maker-deb"
|
target: "@electron-forge/maker-deb"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
target: "@electron-forge/maker-squirrel"
|
# Our custom NSIS maker's `name` (see makers/maker-nsis.ts); forge
|
||||||
|
# `--targets` matches the configured maker instance by this name.
|
||||||
|
target: "nsis"
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
target: "@electron-forge/maker-zip"
|
target: "@electron-forge/maker-zip"
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
@ -58,10 +60,10 @@ 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.
|
# Smoke-install the NSIS installer on a clean, native x64 Windows runner.
|
||||||
# This is a build-vs-host verdict: if it installs here it proves the artifact
|
# 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
|
# 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
|
# fails here the build/NSIS config is wrong. continue-on-error so a failed
|
||||||
# install never blocks publishing the artifacts. The runner has no real-time
|
# install never blocks publishing the artifacts. The runner has no real-time
|
||||||
# AV blocking, so a clean install here does NOT prove SmartScreen/Defender
|
# AV blocking, so a clean install here does NOT prove SmartScreen/Defender
|
||||||
# won't reject the unsigned binaries on end-user machines.
|
# won't reject the unsigned binaries on end-user machines.
|
||||||
|
|
@ -71,33 +73,26 @@ jobs:
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$setup = Get-ChildItem -Path out/make/squirrel.windows -Recurse -Filter '*Setup.exe' | Select-Object -First 1
|
$setup = Get-ChildItem -Path out/make -Recurse -Filter '*.exe' |
|
||||||
if (-not $setup) { Write-Host '::error::no Setup.exe produced under out/make/squirrel.windows'; exit 1 }
|
Where-Object { $_.Name -like '*Setup*' } | Select-Object -First 1
|
||||||
Write-Host "Running $($setup.FullName) --silent"
|
if (-not $setup) { $setup = Get-ChildItem -Path out/make -Recurse -Filter '*.exe' | Select-Object -First 1 }
|
||||||
$proc = Start-Process -FilePath $setup.FullName -ArgumentList '--silent' -PassThru -Wait
|
if (-not $setup) { Write-Host '::error::no NSIS installer (.exe) produced under out/make'; exit 1 }
|
||||||
Write-Host "Setup.exe exit code: $($proc.ExitCode)"
|
Write-Host "Running $($setup.FullName) /S (silent)"
|
||||||
$installDir = Join-Path $env:LOCALAPPDATA 'AgentOrchestrator'
|
# electron-builder NSIS (assisted installer): /S installs silently.
|
||||||
# Update.exe writes SquirrelSetup.log into the app root dir; the Setup.exe
|
$proc = Start-Process -FilePath $setup.FullName -ArgumentList '/S' -PassThru -Wait
|
||||||
# bootstrapper only logs to SquirrelTemp for the earliest extraction stage.
|
Write-Host "Installer exit code: $($proc.ExitCode)"
|
||||||
# Grab whichever exists and copy it into the workspace for upload.
|
# Per-user assisted install lands under %LOCALAPPDATA%\Programs; a
|
||||||
$log = @(
|
# per-machine install would land under Program Files.
|
||||||
(Join-Path $installDir 'SquirrelSetup.log'),
|
$installDir = @(
|
||||||
(Join-Path $env:LOCALAPPDATA 'SquirrelTemp\SquirrelSetup.log')
|
(Join-Path $env:LOCALAPPDATA 'Programs\Agent Orchestrator'),
|
||||||
|
(Join-Path ${env:ProgramFiles} 'Agent Orchestrator')
|
||||||
) | Where-Object { Test-Path $_ } | Select-Object -First 1
|
) | 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 ($installDir) {
|
||||||
if (Test-Path $installDir) {
|
|
||||||
Write-Host "INSTALL OK: $installDir created"
|
Write-Host "INSTALL OK: $installDir created"
|
||||||
Get-ChildItem $installDir | Select-Object Name | Format-Table -AutoSize
|
Get-ChildItem $installDir | Select-Object Name | Format-Table -AutoSize
|
||||||
} else {
|
} else {
|
||||||
Write-Host "::warning::INSTALL FAILED: $installDir was not created"
|
Write-Host "::warning::INSTALL: no known install dir found (checked LOCALAPPDATA\Programs and Program Files)"
|
||||||
}
|
}
|
||||||
- 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:
|
||||||
|
|
@ -114,7 +109,7 @@ jobs:
|
||||||
# "already exists" which is fine (|| true). Distinct asset names + --clobber
|
# "already exists" which is fine (|| true). Distinct asset names + --clobber
|
||||||
# make uploads idempotent across re-runs.
|
# make uploads idempotent across re-runs.
|
||||||
gh release create "$TAG" --prerelease --target "$GITHUB_SHA" --title "$TAG" \
|
gh release create "$TAG" --prerelease --target "$GITHUB_SHA" --title "$TAG" \
|
||||||
--notes "Unsigned testing build (Linux .deb / Windows Setup.exe / macOS .zip). Not signed; for testing only." \
|
--notes "Unsigned testing build (Linux .deb / Windows NSIS .exe / macOS .zip). Not signed; for testing only." \
|
||||||
|| true
|
|| true
|
||||||
# NUL-delimited to survive spaces in the app name ("Agent Orchestrator-...").
|
# NUL-delimited to survive spaces in the app name ("Agent Orchestrator-...").
|
||||||
find out/make -type f -print0 | while IFS= read -r -d '' f; do
|
find out/make -type f -print0 | while IFS= read -r -d '' f; do
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,8 @@ Thumbs.db
|
||||||
# electron-forge / vite build output
|
# electron-forge / vite build output
|
||||||
.vite/
|
.vite/
|
||||||
dist-electron/
|
dist-electron/
|
||||||
|
# electron-builder debug dump, written to the cwd on every NSIS build
|
||||||
|
builder-debug.yml
|
||||||
|
|
||||||
# playwright artifacts
|
# playwright artifacts
|
||||||
frontend/test-results/
|
frontend/test-results/
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,9 @@ checklist.
|
||||||
- `GITHUB_TOKEN` is provided automatically; the workflow already grants
|
- `GITHUB_TOKEN` is provided automatically; the workflow already grants
|
||||||
`contents: write` to publish the Release.
|
`contents: write` to publish the Release.
|
||||||
3. **(Optional) Windows / Linux** — the `forge.config.ts` makers already include
|
3. **(Optional) Windows / Linux** — the `forge.config.ts` makers already include
|
||||||
Squirrel (Windows), deb, and rpm. To publish them, add the matching matrix
|
NSIS (Windows, via `makers/maker-nsis.ts`), deb, and rpm. To publish them, add
|
||||||
runners to `frontend-release.yml`; Windows signing needs its own certificate.
|
the matching matrix runners to `frontend-release.yml`; Windows code-signing
|
||||||
|
needs its own certificate (still a follow-up, see issue #401).
|
||||||
|
|
||||||
## Cutting a release
|
## Cutting a release
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { ForgeConfig } from "@electron-forge/shared-types";
|
import type { ForgeConfig } from "@electron-forge/shared-types";
|
||||||
import { VitePlugin } from "@electron-forge/plugin-vite";
|
import { VitePlugin } from "@electron-forge/plugin-vite";
|
||||||
|
import MakerNSIS from "./makers/maker-nsis";
|
||||||
|
|
||||||
const config: ForgeConfig = {
|
const config: ForgeConfig = {
|
||||||
packagerConfig: {
|
packagerConfig: {
|
||||||
|
|
@ -27,24 +28,25 @@ const config: ForgeConfig = {
|
||||||
},
|
},
|
||||||
rebuildConfig: {},
|
rebuildConfig: {},
|
||||||
makers: [
|
makers: [
|
||||||
{
|
// Windows installer: NSIS via electron-builder (see makers/maker-nsis.ts).
|
||||||
name: "@electron-forge/maker-squirrel",
|
// Replaces Squirrel.Windows, which only does per-user installs with no
|
||||||
config: {
|
// custom install dir or proper uninstaller (issue #401).
|
||||||
name: "AgentOrchestrator",
|
new MakerNSIS(
|
||||||
// NuGet requires a non-empty <authors>; without it `nuget pack`
|
{
|
||||||
// exits 1 and the Squirrel maker fails. Mirror package.json.author.
|
appId: "dev.agent-orchestrator.desktop",
|
||||||
authors: "Agent Orchestrator",
|
productName: "Agent Orchestrator",
|
||||||
setupIcon: "assets/icon.ico",
|
icon: "assets/icon.ico",
|
||||||
},
|
},
|
||||||
},
|
["win32"],
|
||||||
|
),
|
||||||
{ name: "@electron-forge/maker-zip", platforms: ["darwin"], config: {} },
|
{ name: "@electron-forge/maker-zip", platforms: ["darwin"], config: {} },
|
||||||
{
|
{
|
||||||
name: "@electron-forge/maker-deb",
|
name: "@electron-forge/maker-deb",
|
||||||
config: {
|
config: {
|
||||||
options: {
|
options: {
|
||||||
// Must match packagerConfig.executableName; otherwise the deb
|
// Must match packagerConfig.executableName, or the deb maker
|
||||||
// maker looks for `agent-orchestrator-frontend` (the package name)
|
// looks for the package name and fails with "could not find
|
||||||
// and fails with "could not find the Electron app binary".
|
// the Electron app binary". (Both are "agent-orchestrator".)
|
||||||
bin: "agent-orchestrator",
|
bin: "agent-orchestrator",
|
||||||
icon: "assets/icon.png",
|
icon: "assets/icon.png",
|
||||||
maintainer: "Agent Orchestrator",
|
maintainer: "Agent Orchestrator",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
// Capture buildForge's args without pulling in electron-builder's real machinery.
|
||||||
|
const buildForge = vi.fn<(forge: { dir: string }, options: any) => Promise<string[]>>(async () => [
|
||||||
|
"/out/make/Agent Orchestrator Setup.exe",
|
||||||
|
]);
|
||||||
|
vi.mock("app-builder-lib", () => ({ buildForge }));
|
||||||
|
|
||||||
|
import MakerNSIS from "./maker-nsis";
|
||||||
|
|
||||||
|
const makeOptions = {
|
||||||
|
dir: "/tmp/app/Agent Orchestrator-win32-x64",
|
||||||
|
makeDir: "/tmp/app/make",
|
||||||
|
appName: "Agent Orchestrator",
|
||||||
|
targetPlatform: "win32" as const,
|
||||||
|
targetArch: "x64" as const,
|
||||||
|
forgeConfig: {} as never,
|
||||||
|
packageJSON: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("MakerNSIS", () => {
|
||||||
|
it("targets win32 and is supported anywhere (cross-build allowed)", () => {
|
||||||
|
const maker = new MakerNSIS();
|
||||||
|
expect(maker.name).toBe("nsis");
|
||||||
|
expect(maker.defaultPlatforms).toEqual(["win32"]);
|
||||||
|
expect(maker.isSupportedOnCurrentPlatform()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds an nsis target for the requested arch and forwards config", async () => {
|
||||||
|
const maker = new MakerNSIS({ appId: "dev.agent-orchestrator.desktop", icon: "assets/icon.ico" }, ["win32"]);
|
||||||
|
// Forge resolves the (possibly arch-dependent) config before make().
|
||||||
|
await maker.prepareConfig(makeOptions.targetArch);
|
||||||
|
const artifacts = await maker.make(makeOptions);
|
||||||
|
|
||||||
|
expect(artifacts).toEqual(["/out/make/Agent Orchestrator Setup.exe"]);
|
||||||
|
const [forgeOptions, options] = buildForge.mock.calls[0];
|
||||||
|
expect(forgeOptions).toEqual({ dir: makeOptions.dir });
|
||||||
|
expect(options.win).toEqual(["nsis:x64"]);
|
||||||
|
// electron-builder must not try to publish; the workflow does that.
|
||||||
|
expect(options.config.publish).toBeNull();
|
||||||
|
expect(options.config.appId).toBe("dev.agent-orchestrator.desktop");
|
||||||
|
// productName falls back to appName when not set on the maker config.
|
||||||
|
expect(options.config.productName).toBe("Agent Orchestrator");
|
||||||
|
expect(options.config.win).toEqual({ icon: "assets/icon.ico" });
|
||||||
|
// A real installer: not Squirrel's silent one-click per-user drop.
|
||||||
|
expect(options.config.nsis.oneClick).toBe(false);
|
||||||
|
expect(options.config.nsis.allowToChangeInstallationDirectory).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import path from "node:path";
|
||||||
|
import { MakerBase, type MakerOptions } from "@electron-forge/maker-base";
|
||||||
|
import type { ForgePlatform } from "@electron-forge/shared-types";
|
||||||
|
|
||||||
|
// Electron Forge has no first-party NSIS maker, so we bridge to electron-builder's
|
||||||
|
// `buildForge`, the same engine recordly's working Windows installer uses. We drop
|
||||||
|
// Squirrel.Windows (per-user only, no custom install dir, fragile updates) for a
|
||||||
|
// real NSIS installer: per-user or per-machine, custom install directory, and a
|
||||||
|
// proper uninstaller. See https://github.com/aoagents/ReverbCode/issues/401.
|
||||||
|
//
|
||||||
|
// `buildForge` speaks Forge's legacy v5 function API, which Forge 7's class-based
|
||||||
|
// maker loader cannot resolve, so this thin MakerBase subclass adapts it.
|
||||||
|
|
||||||
|
export type MakerNSISConfig = {
|
||||||
|
// electron-builder appId; required for a well-formed NSIS installer.
|
||||||
|
appId?: string;
|
||||||
|
// Display name for the installer + Start menu shortcut. Defaults to appName.
|
||||||
|
productName?: string;
|
||||||
|
// Path to the Windows .ico used for the app and installer.
|
||||||
|
icon?: string;
|
||||||
|
// Any extra electron-builder `nsis` options, merged over our defaults.
|
||||||
|
nsis?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class MakerNSIS extends MakerBase<MakerNSISConfig> {
|
||||||
|
name = "nsis";
|
||||||
|
defaultPlatforms: ForgePlatform[] = ["win32"];
|
||||||
|
|
||||||
|
isSupportedOnCurrentPlatform(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async make({ dir, targetArch, appName }: MakerOptions): Promise<string[]> {
|
||||||
|
const { buildForge } = await import("app-builder-lib");
|
||||||
|
const cfg = this.config ?? {};
|
||||||
|
// Mirror buildForge's own output layout (<dir>/../make) so artifacts land
|
||||||
|
// where Forge's publisher expects them.
|
||||||
|
const output = path.join(path.dirname(path.resolve(dir)), "make");
|
||||||
|
return buildForge(
|
||||||
|
{ dir },
|
||||||
|
{
|
||||||
|
win: [`nsis:${targetArch}`],
|
||||||
|
config: {
|
||||||
|
appId: cfg.appId,
|
||||||
|
productName: cfg.productName ?? appName,
|
||||||
|
directories: { output },
|
||||||
|
// Forge owns publishing (the workflow uploads via `gh release`).
|
||||||
|
// `null` stops electron-builder from inferring a GitHub publish
|
||||||
|
// target from package.json `repository` and trying to upload,
|
||||||
|
// which fails in CI with no GH_TOKEN set.
|
||||||
|
publish: null,
|
||||||
|
...(cfg.icon ? { win: { icon: cfg.icon } } : {}),
|
||||||
|
nsis: {
|
||||||
|
// A real installer, not Squirrel's silent per-user drop.
|
||||||
|
oneClick: false,
|
||||||
|
perMachine: false,
|
||||||
|
allowToChangeInstallationDirectory: true,
|
||||||
|
createDesktopShortcut: true,
|
||||||
|
createStartMenuShortcut: true,
|
||||||
|
...cfg.nsis,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { MakerNSIS };
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "agent-orchestrator-frontend",
|
"name": "agent-orchestrator",
|
||||||
"productName": "Agent Orchestrator",
|
"productName": "Agent Orchestrator",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|
@ -28,9 +28,9 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron-forge/cli": "^7.8.0",
|
"@electron-forge/cli": "^7.8.0",
|
||||||
|
"@electron-forge/maker-base": "^7.8.0",
|
||||||
"@electron-forge/maker-deb": "^7.8.0",
|
"@electron-forge/maker-deb": "^7.8.0",
|
||||||
"@electron-forge/maker-rpm": "^7.8.0",
|
"@electron-forge/maker-rpm": "^7.8.0",
|
||||||
"@electron-forge/maker-squirrel": "^7.8.0",
|
|
||||||
"@electron-forge/maker-zip": "^7.8.0",
|
"@electron-forge/maker-zip": "^7.8.0",
|
||||||
"@electron-forge/plugin-vite": "^7.8.0",
|
"@electron-forge/plugin-vite": "^7.8.0",
|
||||||
"@electron-forge/publisher-github": "^7.8.0",
|
"@electron-forge/publisher-github": "^7.8.0",
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
"@types/react": "^19.2.17",
|
"@types/react": "^19.2.17",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.2",
|
"@vitejs/plugin-react": "^6.0.2",
|
||||||
|
"app-builder-lib": "^26.15.3",
|
||||||
"electron": "^33.0.0",
|
"electron": "^33.0.0",
|
||||||
"jsdom": "^29.1.1",
|
"jsdom": "^29.1.1",
|
||||||
"openapi-typescript": "^7.13.0",
|
"openapi-typescript": "^7.13.0",
|
||||||
|
|
@ -68,7 +69,6 @@
|
||||||
"@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",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
// 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,7 +12,6 @@ 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";
|
||||||
|
|
@ -38,8 +37,9 @@ import { createBrowserViewHost, type BrowserViewHost } from "./main/browser-view
|
||||||
declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string | undefined;
|
declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string | undefined;
|
||||||
declare const MAIN_WINDOW_VITE_NAME: string;
|
declare const MAIN_WINDOW_VITE_NAME: string;
|
||||||
|
|
||||||
// Windows GUI/Squirrel launches have no attached console, so process.stdout and
|
// Windows GUI launches (e.g. from a Start-menu/desktop shortcut) have no attached
|
||||||
// process.stderr are dead pipes. The daemon-output console.log/console.error calls
|
// console, so process.stdout and process.stderr are dead pipes. The daemon-output
|
||||||
|
// console.log/console.error calls
|
||||||
// below then fail with EPIPE, and with no "error" listener that surfaces as an
|
// below then fail with EPIPE, and with no "error" listener that surfaces as an
|
||||||
// uncaught exception that crashes the main process. Swallow broken-pipe write
|
// uncaught exception that crashes the main process. Swallow broken-pipe write
|
||||||
// errors on the std streams: a dropped log line is harmless, the crash is not.
|
// errors on the std streams: a dropped log line is harmless, the crash is not.
|
||||||
|
|
@ -49,18 +49,6 @@ 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");
|
||||||
|
|
||||||
|
|
@ -691,9 +679,6 @@ 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();
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"src/**/*.tsx",
|
"src/**/*.tsx",
|
||||||
"forge.config.ts",
|
"forge.config.ts",
|
||||||
|
"makers/**/*.ts",
|
||||||
"vite.main.config.ts",
|
"vite.main.config.ts",
|
||||||
"vite.preload.config.ts",
|
"vite.preload.config.ts",
|
||||||
"vite.renderer.config.ts",
|
"vite.renderer.config.ts",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue