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
This commit is contained in:
TypoGenie
2026-01-29 18:36:48 +02:00
parent 2c22b0fce6
commit a2631ac473
8 changed files with 173 additions and 34 deletions

View File

@@ -25,6 +25,11 @@
<img src="https://img.shields.io/badge/Desktop_App-Windows%20%7C%20macOS%20%7C%20Linux-blue?style=for-the-badge" alt="Desktop Apps" />
</p>
<p>
<img src="https://img.shields.io/badge/Portable-No_Installation_Required-success?style=for-the-badge&logo=windows-terminal&logoColor=white" alt="Portable" />
<img src="https://img.shields.io/badge/Zero_Registry-Leave_No_Trace-orange?style=for-the-badge" alt="No Registry" />
</p>
<p>
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License" />
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square" alt="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

10
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

41
src-tauri/Cargo.lock generated
View File

@@ -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]]

View File

@@ -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"

View File

@@ -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"
},

View File

@@ -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);
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");
// Store the portable path in app state for frontend access
app.manage(PortableDataDir(portable_dir));
// 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<PortableDataDir>) -> String {
state.0.to_string_lossy().to_string()
}

View File

@@ -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"
}
}
}