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:
51
README.md
51
README.md
@@ -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" />
|
<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>
|
||||||
|
|
||||||
|
<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>
|
<p>
|
||||||
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License" />
|
<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" />
|
<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
|
## 🚀 Quick Start
|
||||||
|
|
||||||
### Option 1: Download Desktop App (Recommended)
|
### Option 1: Portable Download (Recommended) 🎒
|
||||||
|
|
||||||
Pre-built binaries coming soon for:
|
**TypoGenie is fully portable** — no installation, no registry entries, no files scattered across your system.
|
||||||
- 🪟 **Windows** (.msi, .exe)
|
|
||||||
- 🍎 **macOS** (.dmg, .app)
|
Just download and run:
|
||||||
- 🐧 **Linux** (.deb, .rpm, .AppImage)
|
- 🪟 **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
|
### Option 2: Build from Source
|
||||||
|
|
||||||
@@ -189,7 +211,24 @@ npm run desktop
|
|||||||
npm run desktop:build
|
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
|
### Usage
|
||||||
|
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"@tauri-apps/api": "^2.0.0",
|
"@tauri-apps/api": "^2.0.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.0.0",
|
"@tauri-apps/plugin-dialog": "^2.0.0",
|
||||||
"@tauri-apps/plugin-fs": "^2.0.0",
|
"@tauri-apps/plugin-fs": "^2.0.0",
|
||||||
|
"@tauri-apps/plugin-store": "^2.4.2",
|
||||||
"docx": "^8.5.0",
|
"docx": "^8.5.0",
|
||||||
"lucide-react": "^0.563.0",
|
"lucide-react": "^0.563.0",
|
||||||
"marked": "12.0.0",
|
"marked": "12.0.0",
|
||||||
@@ -1401,6 +1402,15 @@
|
|||||||
"@tauri-apps/api": "^2.8.0"
|
"@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": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@tauri-apps/api": "^2.0.0",
|
"@tauri-apps/api": "^2.0.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.0.0",
|
"@tauri-apps/plugin-dialog": "^2.0.0",
|
||||||
"@tauri-apps/plugin-fs": "^2.0.0",
|
"@tauri-apps/plugin-fs": "^2.0.0",
|
||||||
|
"@tauri-apps/plugin-store": "^2.4.2",
|
||||||
"docx": "^8.5.0",
|
"docx": "^8.5.0",
|
||||||
"lucide-react": "^0.563.0",
|
"lucide-react": "^0.563.0",
|
||||||
"marked": "12.0.0",
|
"marked": "12.0.0",
|
||||||
|
|||||||
41
src-tauri/Cargo.lock
generated
41
src-tauri/Cargo.lock
generated
@@ -3577,6 +3577,22 @@ dependencies = [
|
|||||||
"time",
|
"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]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.9.2"
|
version = "2.9.2"
|
||||||
@@ -3798,9 +3814,21 @@ dependencies = [
|
|||||||
"mio",
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.61.2",
|
"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]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.18"
|
version = "0.7.18"
|
||||||
@@ -3962,9 +3990,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
"tracing-core",
|
"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]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.36"
|
version = "0.1.36"
|
||||||
@@ -4026,6 +4066,7 @@ dependencies = [
|
|||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-fs",
|
"tauri-plugin-fs",
|
||||||
"tauri-plugin-log",
|
"tauri-plugin-log",
|
||||||
|
"tauri-plugin-store",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "typogenie"
|
name = "typogenie"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
description = "TypoGenie - Markdown to Word document converter"
|
description = "TypoGenie - Portable Markdown to Word document converter"
|
||||||
authors = ["TypoGenie Contributors"]
|
authors = ["TypoGenie Contributors"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.lashman.live/lashman/typogenie"
|
repository = "https://git.lashman.live/lashman/typogenie"
|
||||||
@@ -23,3 +23,4 @@ tauri = { version = "2.9.5" }
|
|||||||
tauri-plugin-log = "2"
|
tauri-plugin-log = "2"
|
||||||
tauri-plugin-dialog = "2"
|
tauri-plugin-dialog = "2"
|
||||||
tauri-plugin-fs = "2"
|
tauri-plugin-fs = "2"
|
||||||
|
tauri-plugin-store = "2"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "Default capabilities for TypoGenie",
|
"description": "Default capabilities for TypoGenie portable app",
|
||||||
"windows": [
|
"windows": [
|
||||||
"main"
|
"main"
|
||||||
],
|
],
|
||||||
@@ -19,9 +19,22 @@
|
|||||||
"fs:allow-rename",
|
"fs:allow-rename",
|
||||||
"fs:allow-exists",
|
"fs:allow-exists",
|
||||||
"fs:allow-mkdir",
|
"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",
|
"identifier": "fs:scope",
|
||||||
"allow": [
|
"allow": [
|
||||||
|
{
|
||||||
|
"path": "$EXE/**"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "$EXE/../**"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "$HOME"
|
"path": "$HOME"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.setup(|app| {
|
.plugin(tauri_plugin_store::Builder::default().build())
|
||||||
// Show the main window after setup
|
.setup(|app| {
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
// Set up portable data directory
|
||||||
let _ = window.show();
|
let portable_dir = get_portable_data_dir(app);
|
||||||
let _ = window.set_focus();
|
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) {
|
// Show the main window after setup
|
||||||
app.handle().plugin(
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
tauri_plugin_log::Builder::default()
|
let _ = window.show();
|
||||||
.level(log::LevelFilter::Info)
|
let _ = window.set_focus();
|
||||||
.build(),
|
}
|
||||||
)?;
|
|
||||||
}
|
if cfg!(debug_assertions) {
|
||||||
Ok(())
|
app.handle().plugin(
|
||||||
})
|
tauri_plugin_log::Builder::default()
|
||||||
.run(tauri::generate_context!())
|
.level(log::LevelFilter::Info)
|
||||||
.expect("error while running tauri application");
|
.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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": ["msi", "nsis", "dmg", "app", "deb", "rpm", "appimage"],
|
"targets": "all",
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
@@ -41,15 +41,16 @@
|
|||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
"category": "Productivity",
|
"category": "Productivity",
|
||||||
"shortDescription": "Markdown to Word document converter",
|
"shortDescription": "Portable 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.",
|
"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",
|
"publisher": "TypoGenie Contributors",
|
||||||
"copyright": "Copyright (c) 2026 TypoGenie Contributors",
|
"copyright": "Copyright (c) 2026 TypoGenie Contributors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://git.lashman.live/lashman/typogenie",
|
"homepage": "https://git.lashman.live/lashman/typogenie",
|
||||||
"windows": {
|
"windows": {
|
||||||
"webviewInstallMode": {
|
"nsis": {
|
||||||
"type": "embedBootstrapper"
|
"installMode": "currentUser",
|
||||||
|
"installerIcon": "icons/icon.ico"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user