From fb66a279286d6810acfde039776b93042a037322 Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 29 May 2026 23:29:06 +0530 Subject: [PATCH 1/2] feat: implement bridge process termination before updates to prevent file handle errors --- src-tauri/nsis/installer-hooks.nsi | 18 +++++++++++++++--- src-tauri/src/bridge/commands.rs | 15 +++++++++++++++ src-tauri/src/bridge/mod.rs | 2 +- src-tauri/src/main.rs | 3 ++- src/features/settings/hooks/useUpdater.ts | 13 +++++++++++++ 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src-tauri/nsis/installer-hooks.nsi b/src-tauri/nsis/installer-hooks.nsi index b69cfd2..56b3355 100644 --- a/src-tauri/nsis/installer-hooks.nsi +++ b/src-tauri/nsis/installer-hooks.nsi @@ -1,9 +1,21 @@ ; RelWave NSIS Installer Hooks -; Kill bridge.exe before install/update to prevent "Error opening file for writing" errors +; Kill bridge and Node processes before install/update to prevent +; "Error opening file for writing" errors caused by open file handles on +; better_sqlite3.node and other bundled native resources. !macro NSIS_HOOK_PREINSTALL - ; Kill any running bridge process (ignore errors if not running) + ; 1. Kill the bridge binary (pkg-compiled production binary) nsExec::ExecToLog 'taskkill /F /IM "bridge-x86_64-pc-windows-msvc.exe" /T' + + ; 2. Kill node.exe in case the bridge is running as a Node.js script + ; (dev mode or script-mode fallback). /T kills child processes too. + nsExec::ExecToLog 'taskkill /F /IM "node.exe" /T' + + ; 3. Kill the main app window (covers the case where the user launched + ; the installer without closing RelWave first) nsExec::ExecToLog 'taskkill /F /IM "RelWave.exe" /T' - Sleep 1000 + + ; 4. Give the OS time to fully release all file handles before the + ; installer tries to overwrite better_sqlite3.node and other resources. + Sleep 1500 !macroend \ No newline at end of file diff --git a/src-tauri/src/bridge/commands.rs b/src-tauri/src/bridge/commands.rs index ea33086..461b018 100644 --- a/src-tauri/src/bridge/commands.rs +++ b/src-tauri/src/bridge/commands.rs @@ -30,6 +30,21 @@ pub fn bridge_write(data: String, state: State<'_, BridgeProcess>) -> Result<(), Ok(()) } +/// Kill the bridge process and wait for it to fully exit. +/// Called before applying an update so the bridge releases file handles on +/// bundled resources (e.g. better_sqlite3.node) before the installer overwrites them. +#[tauri::command] +pub fn bridge_kill(state: State<'_, BridgeProcess>) -> Result<(), String> { + let mut guard = state.0.lock().unwrap(); + if let Some(mut child) = guard.take() { + child.kill().map_err(|e| format!("failed to kill bridge: {}", e))?; + // wait() is essential — it ensures the OS fully closes all file handles + // before we return, giving the installer a clean shot at the files. + child.wait().map_err(|e| format!("failed to wait for bridge exit: {}", e))?; + } + Ok(()) +} + /// Restart the bridge process #[tauri::command] pub fn bridge_restart( diff --git a/src-tauri/src/bridge/mod.rs b/src-tauri/src/bridge/mod.rs index 48a619e..39671c2 100644 --- a/src-tauri/src/bridge/mod.rs +++ b/src-tauri/src/bridge/mod.rs @@ -2,7 +2,7 @@ mod process; mod commands; pub use process::BridgeProcess; -pub use commands::{bridge_write, bridge_restart, bridge_status}; +pub use commands::{bridge_write, bridge_kill, bridge_restart, bridge_status}; use tauri::AppHandle; use std::process::Child; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 25b03cc..bbfdedb 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,7 +3,7 @@ mod bridge; mod devtools; -use bridge::{bridge_restart, bridge_status, bridge_write, BridgeProcess}; +use bridge::{bridge_restart, bridge_kill, bridge_status, bridge_write, BridgeProcess}; use devtools::{close_devtools, is_devtools_open, navigate_back, navigate_forward, open_devtools, reload_webview}; use tauri::Manager; @@ -21,6 +21,7 @@ fn main() { }) .invoke_handler(tauri::generate_handler![ bridge_write, + bridge_kill, bridge_restart, bridge_status, open_devtools, diff --git a/src/features/settings/hooks/useUpdater.ts b/src/features/settings/hooks/useUpdater.ts index c078692..9420777 100644 --- a/src/features/settings/hooks/useUpdater.ts +++ b/src/features/settings/hooks/useUpdater.ts @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback } from "react"; import { check, Update } from "@tauri-apps/plugin-updater"; import { relaunch } from "@tauri-apps/plugin-process"; +import { invoke } from "@tauri-apps/api/core"; const LAST_INSTALLED_UPDATE_KEY = "relwave:last-installed-update"; const RELEASES_URL = "https://github.com/Relwave/relwave-app/releases/latest"; @@ -109,6 +110,18 @@ export function useUpdater(): UseUpdaterReturn { try { setStatus("downloading"); setDownloadProgress(0); + + // Kill the bridge process BEFORE the installer runs so that it releases + // its file-handle on better_sqlite3.node (and other bundled resources). + // Without this, the NSIS installer throws "Error opening file for writing" + // because the native module is still memory-mapped by the bridge process. + try { + await invoke("bridge_kill"); + // Give the OS a moment to fully release the file handles. + await new Promise((r) => setTimeout(r, 800)); + } catch { + // Non-fatal: if the bridge is already dead, continue with the update. + } let downloaded = 0; let contentLength = 0; From d99e1ceaaf9696bd65755d3ef2deecfa53ef3fc2 Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 29 May 2026 23:29:46 +0530 Subject: [PATCH 2/2] feat: enhance release process with auto-generated changelog and update version badge in README --- .github/workflows/release.yml | 7 ++++++- README.md | 4 ++-- src/components/shared/WhatsNewDialog.tsx | 12 ++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cbc7467..c95962f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,7 +66,12 @@ jobs: with: tagName: v__VERSION__ releaseName: "RelWave v__VERSION__" - releaseBody: "See the assets to download this version and install." + # Let GitHub auto-generate the changelog from merged PRs and commits + # since the last release tag. The generated markdown is injected into + # latest.json as the `notes` field, which the in-app updater reads and + # displays in the What's New dialog after update. + releaseBody: "" + generateReleaseNotes: true releaseDraft: true prerelease: false args: ${{ matrix.args }} diff --git a/README.md b/README.md index b3c5078..d16e63f 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,12 @@ _A high-performance, cross-platform desktop suite for developers who demand more from their database tools._ -[![Version](https://img.shields.io/badge/version-0.6.0--beta.1-0066ff?style=for-the-badge&logo=semver)](https://github.com/Relwave/relwave-app/releases) +[![Version](https://img.shields.io/badge/version-0.6.0--beta.5-0066ff?style=for-the-badge&logo=semver)](https://github.com/Relwave/relwave-app/releases) [![License](https://img.shields.io/badge/license-MIT-00cc66?style=for-the-badge)](LICENSE) [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgray?style=for-the-badge&logo=windows)](https://github.com/Relwave/relwave-app/releases) [![Tauri](https://img.shields.io/badge/built%20with-Tauri-FFC131?style=for-the-badge&logo=tauri)](https://tauri.app/) -[**🚀 Quick Start**](#quick-start) • [**📥 Download**](https://github.com/Relwave/relwave-app/releases) +[**🚀 Quick Start**](INSTALLATION.md) • [**📥 Download**](https://github.com/Relwave/relwave-app/releases) --- diff --git a/src/components/shared/WhatsNewDialog.tsx b/src/components/shared/WhatsNewDialog.tsx index 666c372..6c83b3d 100644 --- a/src/components/shared/WhatsNewDialog.tsx +++ b/src/components/shared/WhatsNewDialog.tsx @@ -62,6 +62,18 @@ function parseReleaseBody(body?: string): NoteSection[] { return FALLBACK_SECTIONS; } + // GitHub generates a default `notes` value when no release notes are written. + // Treat it (and similar content-free strings) as "no notes" so we fall back + // to the generic highlights instead of surfacing a useless placeholder. + const KNOWN_PLACEHOLDERS = [ + "see the assets to download this version and install", + "no release notes provided", + ]; + const normalized = body.trim().toLowerCase(); + if (KNOWN_PLACEHOLDERS.some((p) => normalized === p || normalized.startsWith(p))) { + return FALLBACK_SECTIONS; + } + const lines = body .split(/\r?\n/) .map((line) => line.trim())