feat(desktop): Windows Postgres lifecycle — quit prompt and uninstaller cleanup
Closing the Windows desktop app now asks whether to also shut down the
embedded PostgreSQL server ('leave it running' keeps serving other
Fusion processes; programmatic restarts never prompt and keep the full
stop). The NSIS uninstaller stops the postmaster from postmaster.pid,
removes materialized runtime binaries, and asks before deleting the
database cluster — silent and auto-update uninstalls never touch data.
build/uninstaller.nsh force-added like entitlements.mac.plist (the
build/ ignore targets generated icons).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
7
.changeset/windows-quit-postgres-prompt.md
Normal file
7
.changeset/windows-quit-postgres-prompt.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Closing the Windows desktop app now asks whether to also shut down the embedded PostgreSQL server.
|
||||
category: feature
|
||||
dev: "User-initiated window close on win32 shows a sync dialog (default: shut down); 'leave it running' skips only the embedded-cluster teardown in stopLocal({keepEmbeddedPostgres}) so pools/runtime still close. Programmatic quits (dashboard restart) never prompt and keep the full stop."
|
||||
19
packages/desktop/build/uninstaller.nsh
Normal file
19
packages/desktop/build/uninstaller.nsh
Normal file
@@ -0,0 +1,19 @@
|
||||
; FNXC:WindowsDesktopPackaging 2026-07-18-05:20:
|
||||
; Operator request: uninstalling Fusion on Windows must also shut down the
|
||||
; embedded PostgreSQL server and remove Fusion's materialized runtime binaries
|
||||
; (~\.fusion\embedded-postgres\runtime-bin, recreated on demand). The database
|
||||
; cluster (~\.fusion\embedded-postgres\default) is USER DATA: interactive
|
||||
; uninstalls ask before deleting it, silent uninstalls always keep it, and
|
||||
; auto-update reinstalls (${isUpdated}) touch nothing so updates never kill a
|
||||
; running server or prompt.
|
||||
!macro customUnInstall
|
||||
${ifNot} ${isUpdated}
|
||||
; Stop the postmaster recorded in postmaster.pid (best effort; the PID is
|
||||
; the file's first line). usebackq tolerates spaces in the profile path.
|
||||
nsExec::ExecToLog 'cmd /c for /f "usebackq" %i in ("$PROFILE\.fusion\embedded-postgres\default\postmaster.pid") do taskkill /PID %i /F /T'
|
||||
RMDir /r "$PROFILE\.fusion\embedded-postgres\runtime-bin"
|
||||
IfSilent +3 0
|
||||
MessageBox MB_YESNO|MB_ICONQUESTION "Also delete the embedded PostgreSQL database (all local Fusion data) at $PROFILE\.fusion\embedded-postgres?" IDNO +2
|
||||
RMDir /r "$PROFILE\.fusion\embedded-postgres"
|
||||
${endIf}
|
||||
!macroend
|
||||
@@ -205,6 +205,11 @@ nsis:
|
||||
perMachine: false
|
||||
allowElevation: false
|
||||
allowToChangeInstallationDirectory: true
|
||||
# FNXC:WindowsDesktopPackaging 2026-07-18-05:20:
|
||||
# Uninstall must stop the embedded postmaster and remove Fusion-materialized
|
||||
# runtime binaries; the database is user data and is only deleted after an
|
||||
# interactive YES (never on silent or auto-update uninstalls).
|
||||
include: build/uninstaller.nsh
|
||||
|
||||
portable:
|
||||
artifactName: "${productName}-${version}-${os}-${arch}-portable.${ext}"
|
||||
|
||||
@@ -567,12 +567,12 @@ export class LocalRuntimeManager {
|
||||
}
|
||||
}
|
||||
|
||||
async stopLocal(): Promise<DesktopRuntimeStatus> {
|
||||
async stopLocal(options: { keepEmbeddedPostgres?: boolean } = {}): Promise<DesktopRuntimeStatus> {
|
||||
if (this.stopPromise) {
|
||||
return this.stopPromise;
|
||||
}
|
||||
|
||||
this.stopPromise = this.stopInternal();
|
||||
this.stopPromise = this.stopInternal(options);
|
||||
try {
|
||||
return await this.stopPromise;
|
||||
} finally {
|
||||
@@ -580,7 +580,7 @@ export class LocalRuntimeManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async stopInternal(): Promise<DesktopRuntimeStatus> {
|
||||
private async stopInternal(options: { keepEmbeddedPostgres?: boolean } = {}): Promise<DesktopRuntimeStatus> {
|
||||
if (this.runtime) {
|
||||
const runtime = this.runtime;
|
||||
this.runtime = null;
|
||||
@@ -595,8 +595,15 @@ export class LocalRuntimeManager {
|
||||
// Release the backend connection pool / embedded PG cluster if the store
|
||||
// was booted via the startup factory. store.close() already closes the
|
||||
// AsyncDataLayer pool; this adds embedded-cluster teardown. Best-effort.
|
||||
/*
|
||||
FNXC:DesktopClosePolicy 2026-07-18-05:00:
|
||||
keepEmbeddedPostgres is the operator's Windows quit-prompt answer: close
|
||||
the pools and the Fusion runtime but leave the embedded postmaster
|
||||
running for other Fusion processes. Default (false) preserves the full
|
||||
teardown for programmatic restarts and every non-prompted path.
|
||||
*/
|
||||
const backendShutdown = (runtime.store as TaskStoreLike & { __backendShutdown?: () => Promise<void> }).__backendShutdown;
|
||||
if (backendShutdown) {
|
||||
if (backendShutdown && !options.keepEmbeddedPostgres) {
|
||||
await backendShutdown().catch(() => undefined);
|
||||
} else {
|
||||
runtime.store.close();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app, BrowserWindow, nativeImage, screen, shell, Tray } from "electron";
|
||||
import { dialog, app, BrowserWindow, nativeImage, screen, shell, Tray } from "electron";
|
||||
import { join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import os from "node:os";
|
||||
@@ -130,6 +130,13 @@ function isInternalDesktopNavigation(url: URL, rendererOrigin: string | null): b
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:DesktopClosePolicy 2026-07-18-05:00:
|
||||
Answer from the Windows close prompt, consumed once by before-quit teardown.
|
||||
false (default) = full stop including the embedded PostgreSQL cluster.
|
||||
*/
|
||||
let keepEmbeddedPostgresOnQuit = false;
|
||||
|
||||
async function resetLaunchModeAndReload(window: BrowserWindow): Promise<void> {
|
||||
try {
|
||||
const settings = await readShellSettings();
|
||||
@@ -237,6 +244,30 @@ export function createMainWindow(state?: WindowState, launchTargetUrl?: string):
|
||||
Windows Desktop close is a shutdown request, not a tray-minimize request. Let the BrowserWindow close so Electron emits window-all-closed and before-quit, which stops the embedded local Fusion runtime; keep macOS/non-Windows close-to-tray semantics for dock/tray restoration.
|
||||
*/
|
||||
if (process.platform === "win32") {
|
||||
/*
|
||||
FNXC:DesktopClosePolicy 2026-07-18-05:00:
|
||||
Operator request: shutting down Fusion on Windows must ASK whether to also
|
||||
shut down the embedded PostgreSQL server (it can serve other Fusion
|
||||
processes such as the CLI). Asked only on a USER-initiated window close —
|
||||
programmatic quits (dashboard-requested restart via app.relaunch/app.quit)
|
||||
never pass through this handler, so automation stays prompt-free and
|
||||
defaults to a full stop. The synchronous dialog is required: the close
|
||||
event cannot await, and the answer must exist before before-quit tears the
|
||||
runtime down.
|
||||
*/
|
||||
if (localRuntimeManager?.getStatus().state === "running") {
|
||||
const choice = dialog.showMessageBoxSync(window, {
|
||||
type: "question",
|
||||
title: "Fusion",
|
||||
message: "Also shut down the embedded PostgreSQL server?",
|
||||
detail: "Other Fusion processes (like the fn CLI) can keep using it if you leave it running. It will be reused on the next start either way.",
|
||||
buttons: ["Shut down PostgreSQL", "Leave it running"],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
noLink: true,
|
||||
});
|
||||
keepEmbeddedPostgresOnQuit = choice === 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -482,7 +513,7 @@ export function run(): void {
|
||||
}
|
||||
|
||||
if (localRuntimeManager) {
|
||||
void localRuntimeManager.stopLocal();
|
||||
void localRuntimeManager.stopLocal({ keepEmbeddedPostgres: keepEmbeddedPostgresOnQuit });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user