Files
codeg/src-tauri/src/commands/folder_commands.rs
2026-03-29 18:55:47 +08:00

166 lines
5.1 KiB
Rust

#[cfg(feature = "tauri-runtime")]
use crate::db::error::DbError;
#[cfg(feature = "tauri-runtime")]
use crate::db::service::folder_command_service;
#[cfg(feature = "tauri-runtime")]
use crate::db::AppDatabase;
#[cfg(feature = "tauri-runtime")]
use crate::models::FolderCommandInfo;
use std::path::Path;
#[cfg(feature = "tauri-runtime")]
use tokio::sync::Mutex;
#[cfg(feature = "tauri-runtime")]
static BOOTSTRAP_FOLDER_COMMANDS_LOCK: Mutex<()> = Mutex::const_new(());
pub(crate) fn load_package_scripts_as_commands(folder_path: &str) -> Vec<(String, String)> {
let mut has_package_json = false;
let mut has_pnpm_lock = false;
let mut has_yarn_lock = false;
let mut has_bun_lock = false;
let entries = match std::fs::read_dir(folder_path) {
Ok(entries) => entries,
Err(_) => return Vec::new(),
};
for entry in entries.flatten() {
let Some(file_name) = entry.file_name().to_str().map(|s| s.to_string()) else {
continue;
};
match file_name.as_str() {
"package.json" => has_package_json = true,
"pnpm-lock.yaml" => has_pnpm_lock = true,
"yarn.lock" => has_yarn_lock = true,
"bun.lockb" | "bun.lock" => has_bun_lock = true,
_ => {}
}
}
if !has_package_json {
return Vec::new();
}
let package_json_path = Path::new(folder_path).join("package.json");
let package_json_content = match std::fs::read_to_string(package_json_path) {
Ok(content) => content,
Err(_) => return Vec::new(),
};
let package_json: serde_json::Value = match serde_json::from_str(&package_json_content) {
Ok(value) => value,
Err(_) => return Vec::new(),
};
let package_manager = if has_pnpm_lock {
"pnpm"
} else if has_yarn_lock {
"yarn"
} else if has_bun_lock {
"bun"
} else {
"npm"
};
let mut commands = Vec::new();
if let Some(scripts) = package_json.get("scripts").and_then(|s| s.as_object()) {
for (script_name, script_value) in scripts {
if script_name.trim().is_empty() || script_value.as_str().is_none() {
continue;
}
commands.push((
script_name.to_string(),
format!("{package_manager} run {script_name}"),
));
}
}
commands
}
#[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn list_folder_commands(
db: tauri::State<'_, AppDatabase>,
folder_id: i32,
) -> Result<Vec<FolderCommandInfo>, DbError> {
folder_command_service::list_by_folder(&db.conn, folder_id).await
}
#[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn create_folder_command(
db: tauri::State<'_, AppDatabase>,
folder_id: i32,
name: String,
command: String,
) -> Result<FolderCommandInfo, DbError> {
folder_command_service::create(&db.conn, folder_id, &name, &command).await
}
#[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn update_folder_command(
db: tauri::State<'_, AppDatabase>,
id: i32,
name: Option<String>,
command: Option<String>,
sort_order: Option<i32>,
) -> Result<FolderCommandInfo, DbError> {
folder_command_service::update(&db.conn, id, name, command, sort_order).await
}
#[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn delete_folder_command(
db: tauri::State<'_, AppDatabase>,
id: i32,
) -> Result<(), DbError> {
folder_command_service::delete(&db.conn, id).await
}
#[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn reorder_folder_commands(
db: tauri::State<'_, AppDatabase>,
folder_id: i32,
ids: Vec<i32>,
) -> Result<(), DbError> {
folder_command_service::reorder(&db.conn, folder_id, ids).await
}
#[cfg(feature = "tauri-runtime")]
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
pub async fn bootstrap_folder_commands_from_package_json(
db: tauri::State<'_, AppDatabase>,
folder_id: i32,
folder_path: String,
) -> Result<Vec<FolderCommandInfo>, DbError> {
let existing = folder_command_service::list_by_folder(&db.conn, folder_id).await?;
if !existing.is_empty() {
return Ok(existing);
}
let path_for_task = folder_path;
let commands_to_create =
tokio::task::spawn_blocking(move || load_package_scripts_as_commands(&path_for_task))
.await
.map_err(|e| DbError::Migration(format!("bootstrap task failed: {e}")))?;
if commands_to_create.is_empty() {
return Ok(existing);
}
// Serialize bootstrap so concurrent calls do not create duplicate commands.
let _bootstrap_guard = BOOTSTRAP_FOLDER_COMMANDS_LOCK.lock().await;
let latest = folder_command_service::list_by_folder(&db.conn, folder_id).await?;
if !latest.is_empty() {
return Ok(latest);
}
folder_command_service::create_many(&db.conn, folder_id, &commands_to_create).await?;
folder_command_service::list_by_folder(&db.conn, folder_id).await
}