Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FRONTEND_SKIP_BUILD: '1'
OPENVCS_UPDATE_CHANNEL: nightly
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_PRIVATE_KEY_PASSWORD }}
with:
projectPath: Backend
tagName: openvcs-nightly
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/publish-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# We already built the Frontend; tell Backend/Tauri to skip any beforeBuildCommand
FRONTEND_SKIP_BUILD: '1'
OPENVCS_UPDATE_CHANNEL: stable
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_PRIVATE_KEY_PASSWORD }}
with:
# Point the action at your Tauri project (Backend/)
projectPath: Backend
Expand Down
2 changes: 2 additions & 0 deletions Backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]

[build-dependencies]
tauri-build = { version = "2.4", default-features = false, features = [] }
serde_json = "1.0"

[features]
default = ["with-git", "with-git-libgit2"] # enable both by default
Expand All @@ -34,6 +35,7 @@ tauri = { version = "2.8", features = [] }
tauri-plugin-opener = "2.5"
serde = { version = "1", features = ["derive"] }
tauri-plugin-dialog = "2.4"
tauri-plugin-updater = "2.9"
tokio = "1.47"
dirs = "6"
regex = "1.11"
Expand Down
95 changes: 54 additions & 41 deletions Backend/build.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,60 @@
use std::env;
use std::path::PathBuf;
use std::process::Command;
use std::{env, fs, path::PathBuf, process::Command};

fn main() {
let git = std::process::Command::new("git")
.args(["describe", "--tags", "--always", "--dirty=-modified"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "dev".into());

println!("cargo:rustc-env=GIT_DESCRIBE={}", git);

build_frontend();
tauri_build::build();
}
// Base config path (in the Backend crate)
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let base = manifest_dir.join("tauri.conf.json");

fn build_frontend() {
if env::var_os("FRONTEND_SKIP_BUILD").is_some() {
return;
}

let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let frontend_dir = manifest_dir.join("..").join("Frontend");

// Run `npm run build` in Frontend/
let status = Command::new("npm")
.args(["run", "build"])
.current_dir(&frontend_dir)
.status()
.expect("failed to spawn npm (is Node installed?)");

if !status.success() {
panic!("frontend build failed in {}", frontend_dir.display());
let data = fs::read_to_string(&base).expect("read tauri.conf.json");
let mut json: serde_json::Value = serde_json::from_str(&data).expect("parse tauri.conf.json");

// Compute channel based on environment; default to stable
let chan = env::var("OPENVCS_UPDATE_CHANNEL").unwrap_or_else(|_| "stable".into());

// Locations
let stable = serde_json::Value::String(
"https://github.com/Jordonbc/OpenVCS/releases/latest/download/latest.json".into(),
);
let nightly = serde_json::Value::String(
"https://github.com/Jordonbc/OpenVCS/releases/download/openvcs-nightly/latest.json".into(),
);

// Navigate: plugins.updater.endpoints
if let Some(plugins) = json.get_mut("plugins") {
if let Some(updater) = plugins.get_mut("updater") {
let endpoints = match chan.as_str() {
// Nightly: check nightly first, then stable
"nightly" | "beta" => serde_json::Value::Array(vec![nightly.clone(), stable.clone()]),
// Stable: stable only
_ => serde_json::Value::Array(vec![stable.clone()]),
};
updater["endpoints"] = endpoints;
}
}

// Optional: tell Cargo to rerun if frontend sources change
println!("cargo:rerun-if-changed={}", frontend_dir.join("index.html").display());
println!("cargo:rerun-if-changed={}", frontend_dir.join("src").display());
let assets = frontend_dir.join("assets");
if assets.exists() {
println!("cargo:rerun-if-changed={}", assets.display());
// Provide the generated config via inline JSON env var (must be single-line)
let inline = serde_json::to_string(&json).unwrap();
println!("cargo:rustc-env=TAURI_CONFIG={}", inline);

// Also persist a copy alongside OUT_DIR for debugging (non-fatal if it fails)
if let Ok(out_dir) = env::var("OUT_DIR") {
let out_path = PathBuf::from(out_dir).join("tauri.generated.conf.json");
let _ = fs::write(&out_path, serde_json::to_string_pretty(&json).unwrap());
}
}

// Re-run if the base config changes
println!("cargo:rerun-if-changed={}", base.display());

// Export a GIT_DESCRIBE string for About dialog and diagnostics
let describe = Command::new("git")
.args(["describe", "--always", "--dirty", "--tags"])
.output()
.ok()
.and_then(|o| if o.status.success() { Some(String::from_utf8_lossy(&o.stdout).trim().to_string()) } else { None })
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "dev".into());
println!("cargo:rustc-env=GIT_DESCRIBE={}", describe);

// Proceed with tauri build steps
tauri_build::build();
}
61 changes: 44 additions & 17 deletions Backend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use tauri::{Emitter, Manager};
use std::sync::Arc;
use openvcs_core::{backend_descriptor, backend_id, BackendId};
use openvcs_core::{backend_id, BackendId};
use tauri_plugin_updater::UpdaterExt;

mod utilities;
mod tauri_commands;
Expand All @@ -24,32 +25,32 @@ pub const GIT_SYSTEM_ID: BackendId = backend_id!("git-system");

/// Attempt to reopen the most recent repository at startup if the
/// global setting `general.reopen_last_repos` is enabled.
fn try_reopen_last_repo<R: tauri::Runtime>(app: &tauri::App<R>) {
fn try_reopen_last_repo<R: tauri::Runtime>(app_handle: &tauri::AppHandle<R>) {
use openvcs_core::{backend_descriptor::get_backend, Repo};
use std::path::Path;

let state = app.state::<state::AppState>();
let cfg = state.config();
if !cfg.general.reopen_last_repos { return; }
let state = app_handle.state::<state::AppState>();
let app_config = state.config();
if !app_config.general.reopen_last_repos { return; }

let recents = state.recents();
if let Some(path) = recents.into_iter().find(|p| p.exists()) {
let backend: BackendId = match cfg.git.backend {
let backend: BackendId = match app_config.git.backend {
settings::GitBackend::System => GIT_SYSTEM_ID,
settings::GitBackend::Libgit2 => backend_id!("libgit2"),
};

let path_str = path.to_string_lossy().to_string();
match get_backend(&backend) {
Some(desc) => match (desc.open)(Path::new(&path_str)) {
Ok(handle) => {
let repo = Arc::new(Repo::new(handle));
state.set_current_repo(repo);
if let Err(e) = app.emit("repo:selected", &path_str) {
log::warn!("startup reopen: failed to emit repo:selected: {}", e);
Some(description) => match (description.open)(Path::new(&path)) {
Ok(backend_handle) => {
let existing_repo = Arc::new(Repo::new(backend_handle));
state.set_current_repo(existing_repo);
if let Err(error) = app_handle.emit("repo:selected", &path_str) {
log::warn!("startup reopen: failed to emit repo:selected: {}", error);
}
}
Err(e) => log::warn!("startup reopen: failed to open repo: {}", e),
Err(error) => log::warn!("startup reopen: failed to open repo: {}", error),
},
None => log::warn!("startup reopen: unknown backend `{}`", backend),
}
Expand All @@ -58,12 +59,17 @@ fn try_reopen_last_repo<R: tauri::Runtime>(app: &tauri::App<R>) {

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {

// Initialize logging
logging::init();

// (Optional) prove the registry is populated at startup
for b in backend_descriptor::list_backends() {
log::info!("backend loaded: {} ({})", b.id, b.name);
{
use openvcs_core::backend_descriptor;

// (Optional) prove the registry is populated at startup
for backend in backend_descriptor::list_backends() {
log::info!("backend loaded: {} ({})", backend.id, backend.name);
}
}

workarounds::apply_linux_nvidia_workaround();
Expand All @@ -76,14 +82,34 @@ pub fn run() {
menus::build_and_attach_menu(app)?;

// On startup, optionally reopen the last repository if enabled in settings.
try_reopen_last_repo(app);
try_reopen_last_repo(&app.handle());

// Optionally check for updates on launch and show custom dialog when available.
let app_handle = app.handle().clone();
let check_updates = {
let s = app_handle.state::<state::AppState>();
s.config().general.checks_on_launch
};
if check_updates {
tauri::async_runtime::spawn(async move {
if let Ok(updater) = app_handle.updater() {
match updater.check().await {
Ok(Some(_u)) => {
let _ = app_handle.emit("ui:update-available", serde_json::json!({"source":"startup"}));
}
_ => {}
}
}
});
}

Ok(())
})
.on_window_event(handle_window_event::<_>)
.on_menu_event(menus::handle_menu_event::<_>)
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.invoke_handler(build_invoke_handler::<_>())
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down Expand Up @@ -129,6 +155,7 @@ fn build_invoke_handler<R: tauri::Runtime>() -> impl Fn(tauri::ipc::Invoke<R>) -
tauri_commands::set_global_settings,
tauri_commands::get_repo_settings,
tauri_commands::set_repo_settings,
tauri_commands::updater_install_now,
]
}

Expand Down
Loading
Loading