feat: sort out desktop app base

This commit is contained in:
LynithDev 2025-05-01 15:06:17 +02:00
parent cb1c8d54de
commit c81d7a6eeb
26 changed files with 266 additions and 395 deletions

1
Cargo.lock generated
View file

@ -6070,6 +6070,7 @@ dependencies = [
"serde",
"serde_json",
"specta",
"specta-typescript",
"tauri",
"tauri-specta-macros",
"thiserror 2.0.12",

View file

@ -63,7 +63,10 @@ specta = { version = "=2.0.0-rc.22", features = [
"tokio"
] }
specta-typescript = { version = "=0.0.9" }
tauri-specta = { version = "=2.0.0-rc.21", features = [ "derive" ] }
tauri-specta = { version = "=2.0.0-rc.21", features = [
"derive",
"typescript"
] }
# logging
tracing = { version = "0.1" }

View file

@ -0,0 +1,141 @@
use interpulse::api::minecraft::Version;
use tauri::{AppHandle, Manager};
mod cluster;
pub use crate::api::commands::cluster::*;
mod users;
pub use crate::api::commands::users::*;
mod processor;
pub use crate::api::commands::processor::*;
mod package;
pub use crate::api::commands::package::*;
#[macro_export]
macro_rules! collect_commands {
() => {{
use $crate::api::commands::*;
use $crate::ext::updater::*;
tauri_specta::collect_commands![
// User
// auth_login,
// get_users,
// get_user,
// get_default_user,
// set_default_user,
// remove_user,
// Cluster
// create_cluster,
// edit_game_settings,
// edit_cluster_meta,
// remove_cluster,
// get_cluster,
// get_clusters,
// get_clusters_grouped,
// run_cluster,
// get_cluster_logs,
// get_cluster_log,
// upload_log,
// get_screenshots,
// get_worlds,
// get_optimal_java_version,
// repair_cluster,
// Processor
// get_running_clusters,
// get_processes_by_path,
// kill_process,
// is_cluster_running,
// get_pid_by_uuid,
// get_user_by_process,
// get_process_started_at,
// get_processes_detailed_by_path,
// get_process_detailed_by_id,
// Settings
// get_settings,
// set_settings,
// Metadata
// get_minecraft_versions,
// Launcher Packages (Instances)
// get_launcher_instances,
// import_instances,
// Provider Packages
// get_provider_package,
// get_provider_packages,
// get_all_provider_package_versions,
// get_provider_package_versions,
// search_provider_packages,
// get_provider_authors,
// get_package_body,
// download_provider_package,
// Cluster Packages
// get_cluster_package,
// get_cluster_packages,
// add_cluster_package,
// remove_cluster_package,
// set_cluster_package_enabled,
// sync_cluster_packages,
// sync_cluster_packages_by_type,
// updater
// check_for_update,
// install_update,
// other
// set_window_style,
// get_program_info,
// get_featured_packages,
// get_zulu_packages,
// install_java_from_package,
// open_dev_tools,
]
}};
}
#[onelauncher_core::command]
pub fn open_dev_tools(_webview: tauri::WebviewWindow) {
#[cfg(feature = "devtools")]
_webview.open_devtools();
}
// #[onelauncher_core::command]
// pub async fn get_zulu_packages() -> Result<Vec<onelauncher::java::JavaZuluPackage>, String> {
// Ok(onelauncher::java::get_zulu_packages().await?)
// }
#[onelauncher_core::command]
pub async fn install_java_from_package(download: onelauncher::java::JavaZuluPackage) -> Result<std::path::PathBuf, String> {
Ok(onelauncher::java::install_java_from_package(download).await?)
}
#[onelauncher_core::command]
pub async fn get_featured_packages() -> Result<Vec<onelauncher::package::content::FeaturedPackage>, String> {
Ok(onelauncher::package::content::get_featured_packages().await?)
}
#[onelauncher_core::command]
pub fn get_program_info() -> Result<super::statics::ProgramInfo, String> {
Ok(super::statics::get_program_info())
}
#[onelauncher_core::command]
pub async fn get_minecraft_versions() -> Result<Vec<Version>, String> {
Ok(onelauncher::api::metadata::get_minecraft_versions()
.await?
.versions)
}
#[onelauncher_core::command]
pub async fn get_settings() -> Result<Settings, String> {
Ok(settings::get().await?)
}
#[onelauncher_core::command]
pub async fn set_settings(settings: Settings) -> Result<(), String> {
Ok(settings::set(settings).await?)
}
#[onelauncher_core::command]
pub fn set_window_style(handle: AppHandle, custom: bool) -> Result<(), String> {
let window = handle.get_webview_window("main").unwrap();
onelauncher::utils::window::set_window_styling(&window, custom).map_err(|e| e.to_string())
}

View file

@ -2,13 +2,13 @@ use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use onelauncher::cluster::content::logger;
use onelauncher::cluster::{self};
use onelauncher::data::{Loader, PackageData};
use onelauncher::processor::DetailedProcess;
use onelauncher::store::{Cluster, ClusterPath};
use onelauncher::utils::java::JavaVersion;
use onelauncher::State;
use onelauncher_core::cluster::content::logger;
use onelauncher_core::cluster::{self};
use onelauncher_core::data::{Loader, PackageData};
use onelauncher_core::processor::DetailedProcess;
use onelauncher_core::store::{Cluster, ClusterPath};
use onelauncher_core::api::java::JavaInfo;
use onelauncher_core::store::State;
use serde::{Deserialize, Serialize};
use specta::Type;
use uuid::Uuid;

View file

@ -1,152 +0,0 @@
use interpulse::api::minecraft::Version;
use onelauncher::data::Settings;
use onelauncher::settings;
use tauri::{AppHandle, Manager};
mod cluster;
pub use crate::api::commands::cluster::*;
mod users;
pub use crate::api::commands::users::*;
mod processor;
pub use crate::api::commands::processor::*;
mod package;
pub use crate::api::commands::package::*;
#[macro_export]
macro_rules! collect_commands {
() => {{
use $crate::api::commands::*;
use $crate::ext::updater::*;
tauri_specta::collect_commands![
// User
auth_login,
get_users,
get_user,
get_default_user,
set_default_user,
remove_user,
// Cluster
create_cluster,
edit_game_settings,
edit_cluster_meta,
remove_cluster,
get_cluster,
get_clusters,
get_clusters_grouped,
run_cluster,
get_cluster_logs,
get_cluster_log,
upload_log,
get_screenshots,
get_worlds,
get_optimal_java_version,
repair_cluster,
// Processor
get_running_clusters,
get_processes_by_path,
kill_process,
is_cluster_running,
get_pid_by_uuid,
get_user_by_process,
get_process_started_at,
get_processes_detailed_by_path,
get_process_detailed_by_id,
// Settings
get_settings,
set_settings,
// Metadata
get_minecraft_versions,
// Launcher Packages (Instances)
get_launcher_instances,
import_instances,
// Provider Packages
get_provider_package,
get_provider_packages,
get_all_provider_package_versions,
get_provider_package_versions,
search_provider_packages,
get_provider_authors,
get_package_body,
download_provider_package,
// Cluster Packages
get_cluster_package,
get_cluster_packages,
add_cluster_package,
remove_cluster_package,
set_cluster_package_enabled,
sync_cluster_packages,
sync_cluster_packages_by_type,
// Updater
check_for_update,
install_update,
// Other
set_window_style,
get_program_info,
get_featured_packages,
get_zulu_packages,
install_java_from_package,
open_dev_tools,
]
}};
}
#[specta::specta]
#[tauri::command]
pub fn open_dev_tools(_webview: tauri::WebviewWindow) {
#[cfg(feature = "devtools")]
_webview.open_devtools();
}
#[specta::specta]
#[tauri::command]
pub async fn get_zulu_packages() -> Result<Vec<onelauncher::java::JavaZuluPackage>, String> {
Ok(onelauncher::java::get_zulu_packages().await?)
}
#[specta::specta]
#[tauri::command]
pub async fn install_java_from_package(download: onelauncher::java::JavaZuluPackage) -> Result<std::path::PathBuf, String> {
Ok(onelauncher::java::install_java_from_package(download).await?)
}
#[specta::specta]
#[tauri::command]
pub async fn get_featured_packages() -> Result<Vec<onelauncher::package::content::FeaturedPackage>, String> {
Ok(onelauncher::package::content::get_featured_packages().await?)
}
#[specta::specta]
#[tauri::command]
pub fn get_program_info() -> Result<super::statics::ProgramInfo, String> {
Ok(super::statics::get_program_info())
}
#[specta::specta]
#[tauri::command]
pub async fn get_minecraft_versions() -> Result<Vec<Version>, String> {
Ok(onelauncher::api::metadata::get_minecraft_versions()
.await?
.versions)
}
#[specta::specta]
#[tauri::command]
pub async fn get_settings() -> Result<Settings, String> {
Ok(settings::get().await?)
}
#[specta::specta]
#[tauri::command]
pub async fn set_settings(settings: Settings) -> Result<(), String> {
Ok(settings::set(settings).await?)
}
#[specta::specta]
#[tauri::command]
pub fn set_window_style(handle: AppHandle, custom: bool) -> Result<(), String> {
let window = handle.get_webview_window("main").unwrap();
onelauncher::utils::window::set_window_styling(&window, custom).map_err(|e| e.to_string())
}

View file

@ -1,14 +1,8 @@
#[macro_export]
macro_rules! collect_events {
() => {{
use onelauncher::api::proxy::*;
tauri_specta::collect_events![
ClusterPayload,
IngressPayload,
MessagePayload,
ProcessPayload,
InternetPayload,
OfflinePayload,
]
tauri_specta::collect_events! {
onelauncher_core::api::proxy::event::LauncherEvent
}
}};
}
}

View file

@ -1,103 +1,3 @@
use onelauncher::State;
use serde::{Serialize, Serializer};
use tauri::plugin::TauriPlugin;
use tauri::Manager;
use tokio::sync::Mutex;
pub mod commands;
pub mod events;
pub mod statics;
#[must_use]
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
tauri::plugin::Builder::new("onelauncher")
.setup(|app, _| {
app.manage(Mutex::new(State::get()));
Ok(())
})
.build()
}
// TODO: fix the error system/rspc
pub type Result<T> = std::result::Result<T, OneLauncherSerializableError>;
#[derive(thiserror::Error, Debug, Serialize, specta::Type)]
pub enum OneLauncherSerializableError {
#[error("{0}")]
CommonError(String),
}
impl From<OneLauncherError> for OneLauncherSerializableError {
fn from(value: OneLauncherError) -> Self {
Self::CommonError(value.to_string())
}
}
impl From<onelauncher::Error> for OneLauncherSerializableError {
fn from(value: onelauncher::Error) -> Self {
Self::CommonError(value.to_string())
}
}
#[derive(thiserror::Error, Debug)]
pub enum OneLauncherError {
#[error("{0}")]
OneLauncher(#[from] onelauncher::Error),
#[error("failed to handle io management: {0}")]
IO(#[from] std::io::Error),
#[error("failed to handle tauri management: {0}")]
Tauri(#[from] tauri::Error),
#[cfg(target_os = "macos")]
#[error("failed to handle callback: {0}")]
Callback(String),
}
// serialization code from https://github.com/modrinth/theseus/blob/master/theseus_gui/src-tauri/src/api/mod.rs
// (yoinked under MIT license)
macro_rules! impl_serialize_err {
($($variant:ident),* $(,)?) => {
impl Serialize for OneLauncherError {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeStruct;
match self {
OneLauncherError::OneLauncher(onelauncher_error) => {
$crate::error::display_tracing_error(onelauncher_error);
let mut state = serializer.serialize_struct("OneLauncher", 2)?;
state.serialize_field("field_name", "OneLauncher")?;
state.serialize_field("message", &onelauncher_error.to_string())?;
state.end()
}
$(
OneLauncherError::$variant(message) => {
let mut state = serializer.serialize_struct(stringify!($variant), 2)?;
state.serialize_field("field_name", stringify!($variant))?;
state.serialize_field("message", &message.to_string())?;
state.end()
},
)*
}
}
}
};
}
#[cfg(target_os = "macos")]
impl_serialize_err! {
IO,
Tauri,
Callback
}
#[cfg(not(target_os = "macos"))]
impl_serialize_err! {
IO,
Tauri,
}

View file

@ -1,4 +1,4 @@
use onelauncher::constants::{NATIVE_ARCH, TARGET_OS, VERSION};
use onelauncher_core::{constants::{NATIVE_ARCH, TARGET_OS}, store::Core};
use serde::Serialize;
use specta::Type;
@ -14,15 +14,12 @@ pub struct ProgramInfo {
#[must_use]
pub fn get_program_info() -> ProgramInfo {
let webview_version = tauri::webview_version().unwrap_or_else(|_| "UNKNOWN".into());
let tauri_version = tauri::VERSION;
let dev_build = tauri::is_dev();
ProgramInfo {
launcher_version: VERSION.into(),
webview_version,
tauri_version: tauri_version.into(),
dev_build,
launcher_version: Core::get().launcher_version.clone(),
webview_version: tauri::webview_version().unwrap_or_else(|_| "UNKNOWN".into()),
tauri_version: tauri::VERSION.into(),
dev_build: tauri::is_dev(),
platform: TARGET_OS.into(),
arch: NATIVE_ARCH.into(),
}

View file

@ -0,0 +1,3 @@
// === API ===
pub const CURSEFORGE_API_KEY: &str = "$2a$10$6utA1UNSmFPrE/Lh7b7ndeeGmiOkjKNY8kpFB0fsmE/d42ZAfFgCe";
pub const DISCORD_CLIENT_ID: &str = "1274084193193955408";

View file

@ -1,18 +0,0 @@
use tracing_error::ExtractSpanTrace;
pub fn display_tracing_error(err: &onelauncher::Error) {
match get_span_trace(err) {
Some(span_trace) => {
tracing::error!(error = %err, span_trace = %span_trace);
}
None => {
tracing::error!(error = %err);
}
}
}
pub fn get_span_trace<'a>(
error: &'a (dyn std::error::Error + 'static),
) -> Option<&'a tracing_error::SpanTrace> {
error.source().and_then(|e| e.span_trace())
}

View file

@ -1,8 +0,0 @@
use serde::{Deserialize, Serialize};
// TODO: this should be a specta event
#[derive(Debug, Clone, Serialize, Deserialize, specta::Type, tauri_specta::Event)]
#[serde(rename_all = "camelCase")]
pub struct DeepLinkEvent {
data: String,
}

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1,5 +1,2 @@
pub mod deeplink;
pub mod macos;
pub mod menu;
pub mod specta;
pub mod updater;

View file

@ -1,3 +1,4 @@
use onelauncher_core::store::Core;
use tauri::plugin::TauriPlugin;
use tauri::{Emitter, Runtime};
use tauri_plugin_updater::{Update as TauriPluginUpdate, UpdaterExt};
@ -32,7 +33,6 @@ async fn get_update(app: tauri::AppHandle) -> Result<Option<TauriPluginUpdate>,
.map_err(|e| e.to_string())
}
// TODO: this should be a specta event
#[derive(Debug, Clone, serde::Serialize, specta::Type, tauri_specta::Event)]
#[serde(rename_all = "camelCase", tag = "status")]
pub enum UpdateEvent {
@ -111,7 +111,7 @@ pub fn plugin<R: Runtime>() -> TauriPlugin<R> {
})
.js_init_script(format!(
r#"window.__ONELAUNCHER_VERSION__ = "{}";"#,
onelauncher::constants::VERSION,
Core::get().launcher_version,
))
.build()
}

View file

@ -1,10 +1,9 @@
#![allow(trivial_casts)]
use onelauncher::utils::window::set_window_styling;
use onelauncher_core::{api::proxy::ProxyTauri, error::LauncherResult, store::{proxy::ProxyState, semaphore::SemaphoreStore, Core, CoreOptions, Dirs, State}};
use tauri::{Emitter, Manager};
pub mod api;
pub mod error;
pub mod ext;
pub mod constants;
#[derive(Clone, serde::Serialize)]
pub struct SingleInstancePayload {
@ -12,37 +11,29 @@ pub struct SingleInstancePayload {
cwd: String,
}
#[tracing::instrument(skip_all)]
async fn initialize_state(handle: &tauri::AppHandle) -> api::Result<()> {
onelauncher::ProxyState::initialize(handle).await?;
let s = onelauncher::State::get().await?;
onelauncher::State::update();
s.processor.write().await.restore().await?;
#[tracing::instrument]
async fn initialize_core() -> LauncherResult<()> {
let opts = CoreOptions {
curseforge_api_key: Some(constants::CURSEFORGE_API_KEY.to_string()),
launcher_name: "OneLauncher".to_string(),
launcher_version: env!("CARGO_PKG_VERSION").to_string(),
launcher_website: "https://polyfrost.org/".to_string(),
discord_client_id: Some(constants::DISCORD_CLIENT_ID.to_string()),
fetch_attempts: 3,
..Default::default()
};
Core::initialize(opts).await?;
Dirs::get().await?;
onelauncher_core::start_logger().await;
SemaphoreStore::get().await;
tracing::info!("initialized core modules");
Ok(())
}
/// the main entrypoint to the desktop add (initializes the logger and runs the app)
///
/// if the logger fails to initialize then we will panic because
/// nothing else can be debugged once the logger fails.
///
/// the only thing that can fail before the logger should be our [`tokio::main`] loop.
pub async fn run() {
let _log_guard = onelauncher::start_logger();
tracing::info!("initialized logger. loading onelauncher/tauri...");
run_app(tauri::Builder::default(), |app| {
if let Err(err) = setup(app) {
tracing::error!("failed to setup app: {:?}", err);
}
})
.await;
}
pub async fn run_app<F: FnOnce(&tauri::AppHandle<tauri::Wry>) + Send + 'static>(
builder: tauri::Builder<tauri::Wry>,
setup: F,
) {
#[tracing::instrument(skip_all)]
async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult<tauri::App> {
let prebuild = tauri_specta::Builder::<tauri::Wry>::new()
.commands(collect_commands!())
.events(collect_events!());
@ -58,7 +49,6 @@ pub async fn run_app<F: FnOnce(&tauri::AppHandle<tauri::Wry>) + Send + 'static>(
.expect("failed to export debug bindings!");
let builder = builder
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
println!("{}, {argv:?}, {cwd}", app.package_info().name);
app.emit("single-instance", SingleInstancePayload { args: argv, cwd })
@ -70,12 +60,12 @@ pub async fn run_app<F: FnOnce(&tauri::AppHandle<tauri::Wry>) + Send + 'static>(
.manage(ext::updater::State::default())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(api::init())
// .plugin(api::init())
.menu(tauri::menu::Menu::new)
.invoke_handler(prebuild.invoke_handler())
.setup(move |app| {
prebuild.mount_events(app.handle());
setup(app.handle());
setup_window(app.handle());
Ok(())
});
@ -83,54 +73,39 @@ pub async fn run_app<F: FnOnce(&tauri::AppHandle<tauri::Wry>) + Send + 'static>(
.build(tauri::generate_context!())
.expect("failed to build tauri application");
if let Err(err) = initialize_state(app.handle()).await {
tracing::error!("{err}");
};
Ok(app)
}
#[tracing::instrument(skip_all)]
async fn initialize_state(handle: &tauri::AppHandle) -> LauncherResult<()> {
let proxy = ProxyTauri::new(handle.clone());
ProxyState::initialize(proxy).await?;
State::get().await?;
tracing::info!("initialized launcher successfully");
Ok(())
}
pub async fn run() {
initialize_core().await.expect("failed to initialize core");
let app = initialize_tauri(tauri::Builder::default()).await.expect("failed to initialize tauri");
initialize_state(app.handle()).await.expect("failed to initialize state");
app.run(|_, _| {});
}
fn setup(handle: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
fn setup_window(handle: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
let win = handle.get_webview_window("main").unwrap();
tokio::task::spawn(async move {
let state = onelauncher::State::get();
let state = match state.await {
Ok(state) => state,
Err(err) => {
tracing::error!("{err}");
return;
}
};
// tokio::task::spawn(async move {
// // let state = State::get().await.expect("failed to get state");
// // let settings = state.settings.read().await;
// // win.set_decorations(settings.);
// });
let settings = state.settings.read().await;
if let Err(err) = set_window_styling(&win, settings.custom_frame) {
tracing::error!(err);
};
win.show().unwrap();
});
win.show().unwrap();
Ok(())
}
// TODO: Add tests
// #[cfg(test)]
// mod tests {
// use tauri::Manager;
// #[tokio::test]
// async fn run_app() {
// super::run_app(tauri::test::mock_builder(), |app| {
// super::setup(app);
// let win = app.get_webview_window("main").unwrap();
// tokio::spawn(async move {
// tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
// win.close().unwrap();
// });
// })
// .await
// }
// }
}

View file

@ -1,9 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[tokio::main]
async fn main() {
onelauncher_gui::run().await;

View file

@ -66,7 +66,7 @@
"license": "GPL-3.0-only",
"homepage": "https://polyfrost.org/",
"longDescription": "Next-generation open source Minecraft launcher",
"createUpdaterArtifacts": false,
"createUpdaterArtifacts": true,
"resources": [],
"externalBin": [],
"linux": {

View file

@ -19,12 +19,14 @@ tauri = [
"dep:tauri",
"dep:tauri-specta",
"specta",
"gui"
"gui",
"onelauncher_macro/tauri",
]
specta = [
"dep:specta",
"interpulse/specta",
"onelauncher_entity/specta"
"onelauncher_entity/specta",
"onelauncher_macro/specta",
]
cli = [ "dep:indicatif" ]
gui = []

View file

@ -4,7 +4,7 @@
use api::proxy::LauncherProxy;
use store::{proxy::ProxyState, semaphore::SemaphoreStore, Core, CoreOptions, Dirs, State};
use error::LauncherResult;
use logger::start_logger;
pub use logger::start_logger;
pub mod api;
pub mod constants;
@ -14,7 +14,7 @@ pub mod utils;
mod logger;
pub use onelauncher_macro as macros;
pub use onelauncher_macro::*;
pub use onelauncher_entity as entity;
pub use onelauncher_migration as migration;

View file

@ -50,6 +50,8 @@ impl Core {
Ok::<_, LauncherError>(Self::new(options))
}).await?;
tracing::info!("core initialized successfully");
Ok(())
}

View file

@ -33,6 +33,8 @@ impl Dirs {
.or_else(|| Some(dirs::data_dir()?.join(Core::get().launcher_name.clone())))
.ok_or(DirectoryError::BaseDir)?;
tracing::info!("using base directory '{}'", base_dir.display());
Ok(Self {
base_dir
})

View file

@ -16,7 +16,11 @@ path = "src/lib.rs"
[features]
default = []
specta = [ "dep:specta", "interpulse/specta" ]
specta = [
"onelauncher_macro/specta",
"dep:specta",
"interpulse/specta"
]
[dependencies]
onelauncher_macro = { workspace = true }

View file

@ -17,6 +17,7 @@ proc-macro = true
[features]
default = []
tauri = [ "specta" ]
specta = []
[dependencies]

View file

@ -1,4 +1,9 @@
#[cfg(feature = "specta")]
mod specta;
#[cfg(feature = "tauri")]
mod tauri;
mod pin;
/// This macro is used to derive the `specta::Type` trait for the annotated item.
@ -18,10 +23,23 @@ mod pin;
/// #[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
/// ```
#[proc_macro_attribute]
#[cfg(feature = "specta")]
pub fn specta(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
specta::specta(attr, item)
}
/// This macro is used to derive the `tauri::command` as well as the `specta::specta` attribute for the annotated item.
/// #### Expands to:
/// ```
/// #[specta::specta]
/// #[tauri::command]
/// ```
#[proc_macro_attribute]
#[cfg(feature = "tauri")]
pub fn command(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
tauri::tauri_command(attr, item)
}
/// This macro Box::Pin a function
#[proc_macro_attribute]
pub fn pin(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {

View file

@ -0,0 +1,14 @@
use quote::quote;
use syn::{parse_macro_input, Item};
pub fn tauri_command(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(item as Item);
let expanded = quote! {
#[specta::specta]
#[tauri::command]
#input
};
expanded.into()
}