From da335734d33b90e38a639971f17c472111fab744 Mon Sep 17 00:00:00 2001 From: TypoGenie Date: Thu, 29 Jan 2026 18:36:48 +0200 Subject: [PATCH] feat: Make app fully portable - Add tauri-plugin-store for portable data storage - Implement portable data directory (TypoGenie-Data/ next to EXE) - Configure Rust backend to use EXE-relative paths - Add store permissions for persistent settings - Update README with portable badges and documentation - Document how to build and use the portable EXE - Zero registry, zero AppData, fully self-contained --- README.md | 51 +++++++++++++++++--- package-lock.json | 10 ++++ package.json | 1 + src-tauri/Cargo.lock | 41 ++++++++++++++++ src-tauri/Cargo.toml | 3 +- src-tauri/capabilities/default.json | 15 +++++- src-tauri/src/lib.rs | 75 +++++++++++++++++++++-------- src-tauri/tauri.conf.json | 11 +++-- 8 files changed, 173 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 12b2831..e7787b9 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,11 @@ Desktop Apps

+

+ Portable + No Registry +

+

License PRs Welcome @@ -146,12 +151,29 @@ Every style includes meticulously configured: ## 🚀 Quick Start -### Option 1: Download Desktop App (Recommended) +### Option 1: Portable Download (Recommended) 🎒 -Pre-built binaries coming soon for: -- 🪟 **Windows** (.msi, .exe) -- 🍎 **macOS** (.dmg, .app) -- 🐧 **Linux** (.deb, .rpm, .AppImage) +**TypoGenie is fully portable** — no installation, no registry entries, no files scattered across your system. + +Just download and run: +- 🪟 **Windows**: `TypoGenie.exe` — Single executable, runs immediately +- 🍎 **macOS**: `TypoGenie.app` — Drag and run +- 🐧 **Linux**: `TypoGenie.AppImage` — Make executable and run + +**How it works:** +``` +📁 Your Folder/ +├── 🚀 TypoGenie.exe ← Run this +└── 📂 TypoGenie-Data/ ← Auto-created on first run + ├── config.json ← Your settings + └── cache/ ← Temporary files +``` + +✅ **No installer** — Just double-click the EXE +✅ **No registry** — Windows registry untouched +✅ **No AppData** — Everything stays in the same folder +✅ **USB-friendly** — Run from a thumb drive anywhere +✅ **Easy backup** — Copy the whole folder, done ### Option 2: Build from Source @@ -189,7 +211,24 @@ npm run desktop npm run desktop:build ``` -The built apps will be in `src-tauri/target/release/bundle/` +**Build outputs:** + +| Platform | Output Location | Result | +|----------|----------------|--------| +| **Windows** | `src-tauri/target/release/typogenie.exe` | ⚡ **Portable EXE** — Run immediately, no install | +| **Windows** | `src-tauri/target/release/bundle/nsis/*.exe` | 📦 NSIS Installer (optional) | +| **macOS** | `src-tauri/target/release/bundle/dmg/*.dmg` | 🍎 DMG with App bundle | +| **Linux** | `src-tauri/target/release/bundle/appimage/*.AppImage` | 🐧 Portable AppImage | + +**For the truly portable Windows experience:** +```bash +# After building, grab the raw EXE: +copy src-tauri\target\release\typogenie.exe "C:\Path\To\Your\Portable\Folder\" + +# Run it: +"C:\Path\To\Your\Portable\Folder\typogenie.exe" +# Creates TypoGenie-Data/ folder next to EXE automatically +``` ### Usage diff --git a/package-lock.json b/package-lock.json index ae9f53c..8fedf7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@tauri-apps/api": "^2.0.0", "@tauri-apps/plugin-dialog": "^2.0.0", "@tauri-apps/plugin-fs": "^2.0.0", + "@tauri-apps/plugin-store": "^2.4.2", "docx": "^8.5.0", "lucide-react": "^0.563.0", "marked": "12.0.0", @@ -1401,6 +1402,15 @@ "@tauri-apps/api": "^2.8.0" } }, + "node_modules/@tauri-apps/plugin-store": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-store/-/plugin-store-2.4.2.tgz", + "integrity": "sha512-0ClHS50Oq9HEvLPhNzTNFxbWVOqoAp3dRvtewQBeqfIQ0z5m3JRnOISIn2ZVPCrQC0MyGyhTS9DWhHjpigQE7A==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", diff --git a/package.json b/package.json index 6694e70..9d2e175 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@tauri-apps/api": "^2.0.0", "@tauri-apps/plugin-dialog": "^2.0.0", "@tauri-apps/plugin-fs": "^2.0.0", + "@tauri-apps/plugin-store": "^2.4.2", "docx": "^8.5.0", "lucide-react": "^0.563.0", "marked": "12.0.0", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 76a4136..c9b9716 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3577,6 +3577,22 @@ dependencies = [ "time", ] +[[package]] +name = "tauri-plugin-store" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca1a8ff83c269b115e98726ffc13f9e548a10161544a92ad121d6d0a96e16ea" +dependencies = [ + "dunce", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "tokio", + "tracing", +] + [[package]] name = "tauri-runtime" version = "2.9.2" @@ -3798,9 +3814,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -3962,9 +3990,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -4026,6 +4066,7 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-fs", "tauri-plugin-log", + "tauri-plugin-store", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 2d9f5db..c4fc40d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "typogenie" version = "1.0.0" -description = "TypoGenie - Markdown to Word document converter" +description = "TypoGenie - Portable Markdown to Word document converter" authors = ["TypoGenie Contributors"] license = "MIT" repository = "https://git.lashman.live/lashman/typogenie" @@ -23,3 +23,4 @@ tauri = { version = "2.9.5" } tauri-plugin-log = "2" tauri-plugin-dialog = "2" tauri-plugin-fs = "2" +tauri-plugin-store = "2" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 090208e..ece6eb2 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -1,7 +1,7 @@ { "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", - "description": "Default capabilities for TypoGenie", + "description": "Default capabilities for TypoGenie portable app", "windows": [ "main" ], @@ -19,9 +19,22 @@ "fs:allow-rename", "fs:allow-exists", "fs:allow-mkdir", + "fs:allow-applocaldata-read", + "fs:allow-applocaldata-write", + "store:default", + "store:allow-get", + "store:allow-set", + "store:allow-save", + "store:allow-load", { "identifier": "fs:scope", "allow": [ + { + "path": "$EXE/**" + }, + { + "path": "$EXE/../**" + }, { "path": "$HOME" }, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 05525f2..b57ad54 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,26 +1,59 @@ -use tauri::Manager; +use std::path::PathBuf; +use tauri::{Manager, path::BaseDirectory}; + +/// Gets the portable data directory (next to the executable) +fn get_portable_data_dir(app: &tauri::App) -> PathBuf { + // Get the directory where the EXE is located + if let Ok(exe_dir) = app.path().resolve("", BaseDirectory::Executable) { + let portable_dir = exe_dir.join("TypoGenie-Data"); + // Create the directory if it doesn't exist + let _ = std::fs::create_dir_all(&portable_dir); + portable_dir + } else { + // Fallback to app data directory (shouldn't happen) + app.path().app_data_dir().unwrap_or_else(|_| PathBuf::from(".")) + } +} #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_dialog::init()) - .plugin(tauri_plugin_fs::init()) - .setup(|app| { - // Show the main window after setup - if let Some(window) = app.get_webview_window("main") { - let _ = window.show(); - let _ = window.set_focus(); - } + tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_store::Builder::default().build()) + .setup(|app| { + // Set up portable data directory + let portable_dir = get_portable_data_dir(app); + println!("Portable data directory: {:?}", portable_dir); + + // Store the portable path in app state for frontend access + app.manage(PortableDataDir(portable_dir)); - if cfg!(debug_assertions) { - app.handle().plugin( - tauri_plugin_log::Builder::default() - .level(log::LevelFilter::Info) - .build(), - )?; - } - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + // Show the main window after setup + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + } + + if cfg!(debug_assertions) { + app.handle().plugin( + tauri_plugin_log::Builder::default() + .level(log::LevelFilter::Info) + .build(), + )?; + } + Ok(()) + }) + .invoke_handler(tauri::generate_handler![get_data_dir]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} + +/// Structure to hold the portable data directory path +struct PortableDataDir(PathBuf); + +/// Command to get the portable data directory from the frontend +#[tauri::command] +fn get_data_dir(state: tauri::State) -> String { + state.0.to_string_lossy().to_string() } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index adbb70c..f53119b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -32,7 +32,7 @@ }, "bundle": { "active": true, - "targets": ["msi", "nsis", "dmg", "app", "deb", "rpm", "appimage"], + "targets": "all", "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -41,15 +41,16 @@ "icons/icon.ico" ], "category": "Productivity", - "shortDescription": "Markdown to Word document converter", - "longDescription": "TypoGenie is a free, open-source typesetting engine that transforms Markdown into beautifully formatted Microsoft Word documents with 40+ professional styles.", + "shortDescription": "Portable Markdown to Word document converter", + "longDescription": "TypoGenie is a free, open-source, portable typesetting engine. No installation required - just run the EXE. All data stays in the same folder.", "publisher": "TypoGenie Contributors", "copyright": "Copyright (c) 2026 TypoGenie Contributors", "license": "MIT", "homepage": "https://git.lashman.live/lashman/typogenie", "windows": { - "webviewInstallMode": { - "type": "embedBootstrapper" + "nsis": { + "installMode": "currentUser", + "installerIcon": "icons/icon.ico" } } }