166 lines
5.1 KiB
Rust
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
|
|
}
|