Skip to content

Commit 04aef44

Browse files
authored
chore(desktop): integrate tauri-specta (#11740)
1 parent c02dd06 commit 04aef44

File tree

10 files changed

+289
-73
lines changed

10 files changed

+289
-73
lines changed

packages/desktop/src-tauri/Cargo.lock

Lines changed: 207 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/desktop/src-tauri/Cargo.toml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
1818
tauri-build = { version = "2", features = [] }
1919

2020
[dependencies]
21-
tauri = { version = "2", features = ["macos-private-api", "devtools"] }
21+
tauri = { version = "2.9.5", features = ["macos-private-api", "devtools"] }
2222
tauri-plugin-opener = "2"
2323
tauri-plugin-deep-link = "2.4.6"
2424
tauri-plugin-shell = "2"
@@ -43,10 +43,13 @@ reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"
4343
uuid = { version = "1.19.0", features = ["v4"] }
4444
tauri-plugin-decorum = "1.1.1"
4545
comrak = { version = "0.50", default-features = false }
46+
specta = "=2.0.0-rc.22"
47+
specta-typescript = "0.0.9"
48+
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
4649

4750
[target.'cfg(target_os = "linux")'.dependencies]
4851
gtk = "0.18.2"
49-
webkit2gtk = "=2.0.1"
52+
webkit2gtk = "=2.0.2"
5053

5154
[target.'cfg(target_os = "macos")'.dependencies]
5255
objc2 = "0.6"
@@ -59,3 +62,10 @@ windows = { version = "0.61", features = [
5962
"Win32_System_Threading",
6063
"Win32_Security"
6164
] }
65+
66+
[patch.crates-io]
67+
specta = { git = "https://github.com/specta-rs/specta", rev = "106425eac4964d8ff34d3a02f1612e33117b08bb" }
68+
specta-typescript = { git = "https://github.com/specta-rs/specta", rev = "106425eac4964d8ff34d3a02f1612e33117b08bb" }
69+
tauri-specta = { git = "https://github.com/specta-rs/tauri-specta", rev = "6720b2848eff9a3e40af54c48d65f6d56b640c0b" }
70+
# TODO: https://github.com/tauri-apps/tauri/pull/14812
71+
tauri = { git = "https://github.com/tauri-apps/tauri", rev = "4d5d78daf636feaac20c5bc48a6071491c4291ee" }

packages/desktop/src-tauri/src/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ fn is_cli_installed() -> bool {
5151
const INSTALL_SCRIPT: &str = include_str!("../../../../install");
5252

5353
#[tauri::command]
54+
#[specta::specta]
5455
pub fn install_cli(app: tauri::AppHandle) -> Result<String, String> {
5556
if cfg!(not(unix)) {
5657
return Err("CLI installation is only supported on macOS & Linux".to_string());

packages/desktop/src-tauri/src/lib.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ use std::{
1616
time::{Duration, Instant},
1717
};
1818
use tauri::{AppHandle, LogicalSize, Manager, RunEvent, State, WebviewWindowBuilder};
19-
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
20-
use tauri_plugin_deep_link::DeepLinkExt;
2119
#[cfg(windows)]
2220
use tauri_plugin_decorum::WebviewWindowExt;
21+
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
22+
use tauri_plugin_deep_link::DeepLinkExt;
2323
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogResult};
2424
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
2525
use tauri_plugin_store::StoreExt;
@@ -30,7 +30,7 @@ use crate::window_customizer::PinchZoomDisablePlugin;
3030
const SETTINGS_STORE: &str = "opencode.settings.dat";
3131
const DEFAULT_SERVER_URL_KEY: &str = "defaultServerUrl";
3232

33-
#[derive(Clone, serde::Serialize)]
33+
#[derive(Clone, serde::Serialize, specta::Type)]
3434
struct ServerReadyData {
3535
url: String,
3636
password: Option<String>,
@@ -64,6 +64,7 @@ struct LogState(Arc<Mutex<VecDeque<String>>>);
6464
const MAX_LOG_ENTRIES: usize = 200;
6565

6666
#[tauri::command]
67+
#[specta::specta]
6768
fn kill_sidecar(app: AppHandle) {
6869
let Some(server_state) = app.try_state::<ServerState>() else {
6970
println!("Server not running");
@@ -97,6 +98,7 @@ async fn get_logs(app: AppHandle) -> Result<String, String> {
9798
}
9899

99100
#[tauri::command]
101+
#[specta::specta]
100102
async fn ensure_server_ready(state: State<'_, ServerState>) -> Result<ServerReadyData, String> {
101103
state
102104
.status
@@ -106,6 +108,7 @@ async fn ensure_server_ready(state: State<'_, ServerState>) -> Result<ServerRead
106108
}
107109

108110
#[tauri::command]
111+
#[specta::specta]
109112
fn get_default_server_url(app: AppHandle) -> Result<Option<String>, String> {
110113
let store = app
111114
.store(SETTINGS_STORE)
@@ -119,6 +122,7 @@ fn get_default_server_url(app: AppHandle) -> Result<Option<String>, String> {
119122
}
120123

121124
#[tauri::command]
125+
#[specta::specta]
122126
async fn set_default_server_url(app: AppHandle, url: Option<String>) -> Result<(), String> {
123127
let store = app
124128
.store(SETTINGS_STORE)
@@ -252,6 +256,26 @@ async fn check_server_health(url: &str, password: Option<&str>) -> bool {
252256
pub fn run() {
253257
let updater_enabled = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some();
254258

259+
let builder = tauri_specta::Builder::<tauri::Wry>::new()
260+
// Then register them (separated by a comma)
261+
.commands(tauri_specta::collect_commands![
262+
kill_sidecar,
263+
install_cli,
264+
ensure_server_ready,
265+
get_default_server_url,
266+
set_default_server_url,
267+
markdown::parse_markdown_command
268+
])
269+
.error_handling(tauri_specta::ErrorHandlingMode::Throw);
270+
271+
#[cfg(debug_assertions)] // <- Only export on non-release builds
272+
builder
273+
.export(
274+
specta_typescript::Typescript::default(),
275+
"../src/bindings.ts",
276+
)
277+
.expect("Failed to export typescript bindings");
278+
255279
#[cfg(all(target_os = "macos", not(debug_assertions)))]
256280
let _ = std::process::Command::new("killall")
257281
.arg("opencode-cli")
@@ -285,15 +309,10 @@ pub fn run() {
285309
.plugin(tauri_plugin_notification::init())
286310
.plugin(PinchZoomDisablePlugin)
287311
.plugin(tauri_plugin_decorum::init())
288-
.invoke_handler(tauri::generate_handler![
289-
kill_sidecar,
290-
install_cli,
291-
ensure_server_ready,
292-
get_default_server_url,
293-
set_default_server_url,
294-
markdown::parse_markdown_command
295-
])
312+
.invoke_handler(builder.invoke_handler())
296313
.setup(move |app| {
314+
builder.mount_events(app);
315+
297316
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
298317
app.deep_link().register_all().ok();
299318

packages/desktop/src-tauri/src/markdown.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use comrak::{create_formatter, parse_document, Arena, Options, html::ChildRendering, nodes::NodeValue};
1+
use comrak::{
2+
Arena, Options, create_formatter, html::ChildRendering, nodes::NodeValue, parse_document,
3+
};
24
use std::fmt::Write;
35

46
create_formatter!(ExternalLinkFormatter, {
@@ -55,6 +57,7 @@ pub fn parse_markdown(input: &str) -> String {
5557
}
5658

5759
#[tauri::command]
60+
#[specta::specta]
5861
pub async fn parse_markdown_command(markdown: String) -> Result<String, String> {
5962
Ok(parse_markdown(&markdown))
6063
}

packages/desktop/src/bindings.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This file has been generated by Tauri Specta. Do not edit this file manually.
2+
3+
import { invoke as __TAURI_INVOKE, Channel } from '@tauri-apps/api/core';
4+
5+
/** Commands */
6+
export const commands = {
7+
killSidecar: () => __TAURI_INVOKE<void>("kill_sidecar"),
8+
installCli: () => __TAURI_INVOKE<string>("install_cli"),
9+
ensureServerReady: () => __TAURI_INVOKE<ServerReadyData>("ensure_server_ready"),
10+
getDefaultServerUrl: () => __TAURI_INVOKE<string | null>("get_default_server_url"),
11+
setDefaultServerUrl: (url: string | null) => __TAURI_INVOKE<null>("set_default_server_url", { url }),
12+
parseMarkdownCommand: (markdown: string) => __TAURI_INVOKE<string>("parse_markdown_command", { markdown }),
13+
};
14+
15+
/* Types */
16+
export type ServerReadyData = {
17+
url: string,
18+
password: string | null,
19+
};
20+

packages/desktop/src/cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { invoke } from "@tauri-apps/api/core"
21
import { message } from "@tauri-apps/plugin-dialog"
32

43
import { initI18n, t } from "./i18n"
4+
import { commands } from "./bindings"
55

66
export async function installCli(): Promise<void> {
77
await initI18n()
88

99
try {
10-
const path = await invoke<string>("install_cli")
10+
const path = await commands.installCli()
1111
await message(t("desktop.cli.installed.message", { path }), { title: t("desktop.cli.installed.title") })
1212
} catch (e) {
1313
await message(t("desktop.cli.failed.message", { error: String(e) }), { title: t("desktop.cli.failed.title") })

packages/desktop/src/index.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link"
77
import { open as shellOpen } from "@tauri-apps/plugin-shell"
88
import { type as ostype } from "@tauri-apps/plugin-os"
99
import { check, Update } from "@tauri-apps/plugin-updater"
10-
import { invoke } from "@tauri-apps/api/core"
1110
import { getCurrentWindow } from "@tauri-apps/api/window"
1211
import { isPermissionGranted, requestPermission } from "@tauri-apps/plugin-notification"
1312
import { relaunch } from "@tauri-apps/plugin-process"
@@ -22,6 +21,7 @@ import { createMenu } from "./menu"
2221
import { initI18n, t } from "./i18n"
2322
import pkg from "../package.json"
2423
import "./styles.css"
24+
import { commands } from "./bindings"
2525

2626
const root = document.getElementById("root")
2727
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
@@ -274,12 +274,12 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
274274

275275
update: async () => {
276276
if (!UPDATER_ENABLED || !update) return
277-
if (ostype() === "windows") await invoke("kill_sidecar").catch(() => undefined)
277+
if (ostype() === "windows") await commands.killSidecar().catch(() => undefined)
278278
await update.install().catch(() => undefined)
279279
},
280280

281281
restart: async () => {
282-
await invoke("kill_sidecar").catch(() => undefined)
282+
await commands.killSidecar().catch(() => undefined)
283283
await relaunch()
284284
},
285285

@@ -335,17 +335,13 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
335335
},
336336

337337
getDefaultServerUrl: async () => {
338-
const result = await invoke<string | null>("get_default_server_url").catch(() => null)
338+
const result = await commands.getDefaultServerUrl().catch(() => null)
339339
return result
340340
},
341341

342-
setDefaultServerUrl: async (url: string | null) => {
343-
await invoke("set_default_server_url", { url })
344-
},
342+
setDefaultServerUrl: async (url: string | null) => { await commands.setDefaultServerUrl(url) },
345343

346-
parseMarkdown: async (markdown: string) => {
347-
return invoke<string>("parse_markdown_command", { markdown })
348-
},
344+
parseMarkdown: (markdown: string) => commands.parseMarkdownCommand(markdown),
349345

350346
webviewZoom,
351347
})
@@ -394,7 +390,7 @@ type ServerReadyData = { url: string; password: string | null }
394390
// Gate component that waits for the server to be ready
395391
function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.Element }) {
396392
const [serverData] = createResource<ServerReadyData>(() =>
397-
invoke("ensure_server_ready").then((v) => {
393+
commands.ensureServerReady().then((v) => {
398394
return new Promise((res) => setTimeout(() => res(v as ServerReadyData), 2000))
399395
}),
400396
)
@@ -408,7 +404,7 @@ function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.
408404
}
409405

410406
const restartApp = async () => {
411-
await invoke("kill_sidecar").catch(() => undefined)
407+
await commands.killSidecar().catch(() => undefined)
412408
await relaunch().catch(() => undefined)
413409
}
414410

packages/desktop/src/menu.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Menu, MenuItem, PredefinedMenuItem, Submenu } from "@tauri-apps/api/menu"
22
import { type as ostype } from "@tauri-apps/plugin-os"
3-
import { invoke } from "@tauri-apps/api/core"
43
import { relaunch } from "@tauri-apps/plugin-process"
54

65
import { runUpdater, UPDATER_ENABLED } from "./updater"
76
import { installCli } from "./cli"
87
import { initI18n, t } from "./i18n"
8+
import { commands } from "./bindings"
99

1010
export async function createMenu() {
1111
if (ostype() !== "macos") return
@@ -35,7 +35,7 @@ export async function createMenu() {
3535
}),
3636
await MenuItem.new({
3737
action: async () => {
38-
await invoke("kill_sidecar").catch(() => undefined)
38+
await commands.killSidecar().catch(() => undefined)
3939
await relaunch().catch(() => undefined)
4040
},
4141
text: t("desktop.menu.restart"),

packages/desktop/src/updater.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { check } from "@tauri-apps/plugin-updater"
22
import { relaunch } from "@tauri-apps/plugin-process"
33
import { ask, message } from "@tauri-apps/plugin-dialog"
4-
import { invoke } from "@tauri-apps/api/core"
54
import { type as ostype } from "@tauri-apps/plugin-os"
65

76
import { initI18n, t } from "./i18n"
7+
import { commands } from "./bindings"
88

99
export const UPDATER_ENABLED = window.__OPENCODE__?.updaterEnabled ?? false
1010

@@ -39,13 +39,13 @@ export async function runUpdater({ alertOnFail }: { alertOnFail: boolean }) {
3939
if (!shouldUpdate) return
4040

4141
try {
42-
if (ostype() === "windows") await invoke("kill_sidecar")
42+
if (ostype() === "windows") await commands.killSidecar()
4343
await update.install()
4444
} catch {
4545
await message(t("desktop.updater.installFailed.message"), { title: t("desktop.updater.installFailed.title") })
4646
return
4747
}
4848

49-
await invoke("kill_sidecar")
49+
await commands.killSidecar()
5050
await relaunch()
5151
}

0 commit comments

Comments
 (0)