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
|
perMachine: false
|
||||||
allowElevation: false
|
allowElevation: false
|
||||||
allowToChangeInstallationDirectory: true
|
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:
|
portable:
|
||||||
artifactName: "${productName}-${version}-${os}-${arch}-portable.${ext}"
|
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) {
|
if (this.stopPromise) {
|
||||||
return this.stopPromise;
|
return this.stopPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stopPromise = this.stopInternal();
|
this.stopPromise = this.stopInternal(options);
|
||||||
try {
|
try {
|
||||||
return await this.stopPromise;
|
return await this.stopPromise;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -580,7 +580,7 @@ export class LocalRuntimeManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async stopInternal(): Promise<DesktopRuntimeStatus> {
|
private async stopInternal(options: { keepEmbeddedPostgres?: boolean } = {}): Promise<DesktopRuntimeStatus> {
|
||||||
if (this.runtime) {
|
if (this.runtime) {
|
||||||
const runtime = this.runtime;
|
const runtime = this.runtime;
|
||||||
this.runtime = null;
|
this.runtime = null;
|
||||||
@@ -595,8 +595,15 @@ export class LocalRuntimeManager {
|
|||||||
// Release the backend connection pool / embedded PG cluster if the store
|
// Release the backend connection pool / embedded PG cluster if the store
|
||||||
// was booted via the startup factory. store.close() already closes the
|
// was booted via the startup factory. store.close() already closes the
|
||||||
// AsyncDataLayer pool; this adds embedded-cluster teardown. Best-effort.
|
// 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;
|
const backendShutdown = (runtime.store as TaskStoreLike & { __backendShutdown?: () => Promise<void> }).__backendShutdown;
|
||||||
if (backendShutdown) {
|
if (backendShutdown && !options.keepEmbeddedPostgres) {
|
||||||
await backendShutdown().catch(() => undefined);
|
await backendShutdown().catch(() => undefined);
|
||||||
} else {
|
} else {
|
||||||
runtime.store.close();
|
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 { join, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
@@ -130,6 +130,13 @@ function isInternalDesktopNavigation(url: URL, rendererOrigin: string | null): b
|
|||||||
return false;
|
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> {
|
async function resetLaunchModeAndReload(window: BrowserWindow): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const settings = await readShellSettings();
|
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.
|
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") {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +513,7 @@ export function run(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (localRuntimeManager) {
|
if (localRuntimeManager) {
|
||||||
void localRuntimeManager.stopLocal();
|
void localRuntimeManager.stopLocal({ keepEmbeddedPostgres: keepEmbeddedPostgresOnQuit });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user