raz_core/providers/
tauri.rs

1//! Tauri framework provider for desktop app development commands
2//!
3//! Provides intelligent command suggestions for Tauri projects including
4//! development server, building for different platforms, and app distribution.
5
6use crate::{
7    Command, CommandBuilder, CommandCategory, CommandProvider, ProjectContext, ProjectType,
8    RazResult, SymbolKind,
9};
10use async_trait::async_trait;
11
12/// Provider for Tauri desktop app framework commands
13pub struct TauriProvider {
14    priority: u8,
15}
16
17impl TauriProvider {
18    pub fn new() -> Self {
19        Self {
20            priority: 88, // High priority for Tauri app projects
21        }
22    }
23}
24
25#[async_trait]
26impl CommandProvider for TauriProvider {
27    fn name(&self) -> &str {
28        "tauri"
29    }
30
31    fn priority(&self) -> u8 {
32        self.priority
33    }
34
35    fn can_handle(&self, context: &ProjectContext) -> bool {
36        // Check if this is a Tauri project
37        match &context.project_type {
38            ProjectType::Tauri => true,
39            ProjectType::Mixed(frameworks) => frameworks.contains(&ProjectType::Tauri),
40            _ => {
41                // Also check dependencies for tauri
42                context.dependencies.iter().any(|dep|
43                    dep.name == "tauri" ||
44                    dep.name == "tauri-build" ||
45                    dep.name.starts_with("tauri-")
46                ) ||
47                // Check for tauri.conf.json or src-tauri directory
48                context.workspace_root.join("src-tauri").exists() ||
49                context.workspace_root.join("tauri.conf.json").exists()
50            }
51        }
52    }
53
54    async fn commands(&self, context: &ProjectContext) -> RazResult<Vec<Command>> {
55        let mut commands = Vec::new();
56
57        // Development commands
58        commands.extend(self.development_commands(context));
59
60        // Build commands for different platforms
61        commands.extend(self.build_commands(context));
62
63        // Testing commands
64        commands.extend(self.test_commands(context));
65
66        // Context-aware commands
67        commands.extend(self.context_aware_commands(context));
68
69        // Distribution and signing commands
70        commands.extend(self.distribution_commands(context));
71
72        Ok(commands)
73    }
74}
75
76impl TauriProvider {
77    /// Development server and debugging commands
78    fn development_commands(&self, _context: &ProjectContext) -> Vec<Command> {
79        vec![
80            CommandBuilder::new("tauri-dev", "cargo")
81                .label("Tauri Dev Server")
82                .description("Start Tauri development server with hot reload")
83                .arg("tauri")
84                .arg("dev")
85                .category(CommandCategory::Run)
86                .priority(95)
87                .tag("dev")
88                .tag("serve")
89                .tag("tauri")
90                .estimated_duration(8)
91                .build(),
92            CommandBuilder::new("tauri-dev-debug", "cargo")
93                .label("Tauri Debug Mode")
94                .description("Start development with debugging enabled")
95                .arg("tauri")
96                .arg("dev")
97                .arg("--debug")
98                .category(CommandCategory::Run)
99                .priority(90)
100                .tag("dev")
101                .tag("debug")
102                .tag("tauri")
103                .estimated_duration(10)
104                .build(),
105            CommandBuilder::new("tauri-dev-release", "cargo")
106                .label("Tauri Dev Release")
107                .description("Run in development mode with release build")
108                .arg("tauri")
109                .arg("dev")
110                .arg("--release")
111                .category(CommandCategory::Run)
112                .priority(85)
113                .tag("dev")
114                .tag("release")
115                .tag("tauri")
116                .estimated_duration(30)
117                .build(),
118            CommandBuilder::new("tauri-dev-no-watch", "cargo")
119                .label("Tauri Dev (No Watch)")
120                .description("Start development without file watching")
121                .arg("tauri")
122                .arg("dev")
123                .arg("--no-watch")
124                .category(CommandCategory::Run)
125                .priority(80)
126                .tag("dev")
127                .tag("no-watch")
128                .tag("tauri")
129                .estimated_duration(6)
130                .build(),
131            CommandBuilder::new("tauri-info", "cargo")
132                .label("Tauri System Info")
133                .description("Show Tauri environment and system information")
134                .arg("tauri")
135                .arg("info")
136                .category(CommandCategory::Generate)
137                .priority(75)
138                .tag("info")
139                .tag("system")
140                .tag("tauri")
141                .estimated_duration(2)
142                .build(),
143            CommandBuilder::new("tauri-init", "cargo")
144                .label("Initialize Tauri")
145                .description("Add Tauri to existing project")
146                .arg("tauri")
147                .arg("init")
148                .category(CommandCategory::Generate)
149                .priority(70)
150                .tag("init")
151                .tag("setup")
152                .tag("tauri")
153                .estimated_duration(5)
154                .build(),
155        ]
156    }
157
158    /// Build commands for different platforms and targets
159    fn build_commands(&self, context: &ProjectContext) -> Vec<Command> {
160        let mut commands = vec![
161            CommandBuilder::new("tauri-build", "cargo")
162                .label("Build Tauri App")
163                .description("Build the Tauri application for current platform")
164                .arg("tauri")
165                .arg("build")
166                .category(CommandCategory::Build)
167                .priority(85)
168                .tag("build")
169                .tag("tauri")
170                .estimated_duration(60)
171                .build(),
172            CommandBuilder::new("tauri-build-debug", "cargo")
173                .label("Build Debug")
174                .description("Build debug version of the app")
175                .arg("tauri")
176                .arg("build")
177                .arg("--debug")
178                .category(CommandCategory::Build)
179                .priority(80)
180                .tag("build")
181                .tag("debug")
182                .tag("tauri")
183                .estimated_duration(45)
184                .build(),
185        ];
186
187        // Add platform-specific builds based on features or environment
188        let is_cross_platform = context.dependencies.iter().any(|d| {
189            d.features
190                .iter()
191                .any(|f| f.contains("updater") || f.contains("api-all"))
192        });
193
194        if is_cross_platform {
195            commands.extend(vec![
196                CommandBuilder::new("tauri-build-windows", "cargo")
197                    .label("Build for Windows")
198                    .description("Cross-compile for Windows")
199                    .arg("tauri")
200                    .arg("build")
201                    .arg("--target")
202                    .arg("x86_64-pc-windows-msvc")
203                    .category(CommandCategory::Build)
204                    .priority(75)
205                    .tag("build")
206                    .tag("windows")
207                    .tag("cross")
208                    .tag("tauri")
209                    .estimated_duration(90)
210                    .build(),
211                CommandBuilder::new("tauri-build-macos", "cargo")
212                    .label("Build for macOS")
213                    .description("Cross-compile for macOS")
214                    .arg("tauri")
215                    .arg("build")
216                    .arg("--target")
217                    .arg("x86_64-apple-darwin")
218                    .category(CommandCategory::Build)
219                    .priority(75)
220                    .tag("build")
221                    .tag("macos")
222                    .tag("cross")
223                    .tag("tauri")
224                    .estimated_duration(100)
225                    .build(),
226                CommandBuilder::new("tauri-build-linux", "cargo")
227                    .label("Build for Linux")
228                    .description("Cross-compile for Linux")
229                    .arg("tauri")
230                    .arg("build")
231                    .arg("--target")
232                    .arg("x86_64-unknown-linux-gnu")
233                    .category(CommandCategory::Build)
234                    .priority(75)
235                    .tag("build")
236                    .tag("linux")
237                    .tag("cross")
238                    .tag("tauri")
239                    .estimated_duration(85)
240                    .build(),
241            ]);
242        }
243
244        commands
245    }
246
247    /// Testing commands
248    fn test_commands(&self, _context: &ProjectContext) -> Vec<Command> {
249        vec![
250            CommandBuilder::new("tauri-test", "cargo")
251                .label("Run Tauri Tests")
252                .description("Run unit and integration tests")
253                .arg("test")
254                .category(CommandCategory::Test)
255                .priority(75)
256                .tag("test")
257                .tag("tauri")
258                .estimated_duration(25)
259                .build(),
260            CommandBuilder::new("tauri-test-core", "cargo")
261                .label("Test Core Logic")
262                .description("Test Rust core logic without UI")
263                .arg("test")
264                .arg("--package")
265                .arg("src-tauri")
266                .category(CommandCategory::Test)
267                .priority(80)
268                .tag("test")
269                .tag("core")
270                .tag("tauri")
271                .estimated_duration(15)
272                .build(),
273            CommandBuilder::new("tauri-test-webdriver", "cargo")
274                .label("WebDriver Tests")
275                .description("Run end-to-end tests with WebDriver")
276                .arg("tauri")
277                .arg("build")
278                .arg("--debug")
279                .args(vec![
280                    "&&".to_string(),
281                    "cargo".to_string(),
282                    "test".to_string(),
283                    "--test".to_string(),
284                    "webdriver".to_string(),
285                ])
286                .category(CommandCategory::Test)
287                .priority(70)
288                .tag("test")
289                .tag("e2e")
290                .tag("webdriver")
291                .tag("tauri")
292                .estimated_duration(60)
293                .build(),
294        ]
295    }
296
297    /// Context-aware commands based on cursor position
298    fn context_aware_commands(&self, context: &ProjectContext) -> Vec<Command> {
299        let mut commands = Vec::new();
300
301        if let Some(file_context) = &context.current_file {
302            if let Some(symbol) = &file_context.cursor_symbol {
303                match symbol.kind {
304                    SymbolKind::Function => {
305                        // Command-related functions
306                        if symbol.name.starts_with("cmd_")
307                            || symbol.name.contains("command")
308                            || symbol.modifiers.contains(&"tauri::command".to_string())
309                        {
310                            commands.push(
311                                CommandBuilder::new("tauri-test-command", "cargo")
312                                    .label("Test Tauri Command")
313                                    .description(format!("Test command function: {}", symbol.name))
314                                    .arg("test")
315                                    .arg(format!("test_{}", symbol.name))
316                                    .category(CommandCategory::Test)
317                                    .priority(85)
318                                    .tag("command")
319                                    .tag("test")
320                                    .tag("tauri")
321                                    .estimated_duration(8)
322                                    .build(),
323                            );
324                        }
325
326                        // Event handler functions
327                        if symbol.name.contains("event") || symbol.name.contains("handler") {
328                            commands.push(
329                                CommandBuilder::new("tauri-dev-events", "cargo")
330                                    .label("Debug Event Handlers")
331                                    .description(format!(
332                                        "Run with event debugging: {}",
333                                        symbol.name
334                                    ))
335                                    .arg("tauri")
336                                    .arg("dev")
337                                    .arg("--debug")
338                                    .category(CommandCategory::Run)
339                                    .priority(80)
340                                    .tag("events")
341                                    .tag("debug")
342                                    .tag("tauri")
343                                    .estimated_duration(12)
344                                    .build(),
345                            );
346                        }
347
348                        // Test function commands
349                        if symbol.name.starts_with("test_")
350                            || symbol.modifiers.contains(&"test".to_string())
351                        {
352                            commands.push(
353                                CommandBuilder::new("tauri-test-current", "cargo")
354                                    .label("Test Current Function")
355                                    .description(format!("Run test: {}", symbol.name))
356                                    .arg("test")
357                                    .arg(&symbol.name)
358                                    .arg("--")
359                                    .arg("--nocapture")
360                                    .category(CommandCategory::Test)
361                                    .priority(90)
362                                    .tag("test")
363                                    .tag("current")
364                                    .tag("tauri")
365                                    .estimated_duration(5)
366                                    .build(),
367                            );
368                        }
369                    }
370                    SymbolKind::Struct => {
371                        // State management structs
372                        if symbol.name.ends_with("State") || symbol.name.contains("Config") {
373                            commands.push(
374                                CommandBuilder::new("tauri-check-state", "cargo")
375                                    .label("Check State Management")
376                                    .description(format!("Validate state struct: {}", symbol.name))
377                                    .arg("check")
378                                    .category(CommandCategory::Lint)
379                                    .priority(75)
380                                    .tag("state")
381                                    .tag("check")
382                                    .tag("tauri")
383                                    .estimated_duration(10)
384                                    .build(),
385                            );
386                        }
387                    }
388                    _ => {}
389                }
390            }
391
392            // File-based commands
393            if file_context.path.to_string_lossy().contains("main.rs")
394                && file_context.path.to_string_lossy().contains("src-tauri")
395            {
396                commands.push(
397                    CommandBuilder::new("tauri-check-main", "cargo")
398                        .label("Check Main App")
399                        .description("Check main Tauri application logic")
400                        .arg("check")
401                        .arg("--package")
402                        .arg("src-tauri")
403                        .category(CommandCategory::Lint)
404                        .priority(85)
405                        .tag("main")
406                        .tag("check")
407                        .tag("tauri")
408                        .estimated_duration(8)
409                        .build(),
410                );
411            }
412
413            if file_context.path.to_string_lossy().contains("command") {
414                commands.push(
415                    CommandBuilder::new("tauri-test-commands", "cargo")
416                        .label("Test All Commands")
417                        .description("Test all Tauri command handlers")
418                        .arg("test")
419                        .arg("commands")
420                        .category(CommandCategory::Test)
421                        .priority(80)
422                        .tag("commands")
423                        .tag("test")
424                        .tag("tauri")
425                        .estimated_duration(20)
426                        .build(),
427                );
428            }
429        }
430
431        commands
432    }
433
434    /// Distribution and signing commands
435    fn distribution_commands(&self, context: &ProjectContext) -> Vec<Command> {
436        let mut commands = vec![
437            CommandBuilder::new("tauri-bundle", "cargo")
438                .label("Create App Bundle")
439                .description("Create platform-specific app bundle")
440                .arg("tauri")
441                .arg("build")
442                .arg("--bundles")
443                .arg("all")
444                .category(CommandCategory::Deploy)
445                .priority(75)
446                .tag("bundle")
447                .tag("deploy")
448                .tag("tauri")
449                .estimated_duration(90)
450                .build(),
451            CommandBuilder::new("tauri-icon", "cargo")
452                .label("Generate App Icons")
453                .description("Generate app icons from source image")
454                .arg("tauri")
455                .arg("icon")
456                .category(CommandCategory::Generate)
457                .priority(65)
458                .tag("icon")
459                .tag("generate")
460                .tag("tauri")
461                .estimated_duration(5)
462                .build(),
463            CommandBuilder::new("tauri-add", "cargo")
464                .label("Add Tauri Plugin")
465                .description("Add a Tauri plugin to the project")
466                .arg("tauri")
467                .arg("add")
468                .category(CommandCategory::Generate)
469                .priority(60)
470                .tag("plugin")
471                .tag("add")
472                .tag("tauri")
473                .estimated_duration(10)
474                .build(),
475            CommandBuilder::new("tauri-migrate", "cargo")
476                .label("Migrate to Tauri v2")
477                .description("Migrate project from Tauri v1 to v2")
478                .arg("tauri")
479                .arg("migrate")
480                .category(CommandCategory::Generate)
481                .priority(55)
482                .tag("migrate")
483                .tag("upgrade")
484                .tag("tauri")
485                .estimated_duration(20)
486                .build(),
487        ];
488
489        // Add mobile development commands if mobile features are detected
490        let has_mobile = context.dependencies.iter().any(|d| {
491            d.features
492                .iter()
493                .any(|f| f.contains("mobile") || f.contains("android") || f.contains("ios"))
494        });
495
496        if has_mobile {
497            commands.extend(vec![
498                CommandBuilder::new("tauri-android-dev", "cargo")
499                    .label("Android Dev")
500                    .description("Run Tauri app on Android device/emulator")
501                    .arg("tauri")
502                    .arg("android")
503                    .arg("dev")
504                    .category(CommandCategory::Run)
505                    .priority(70)
506                    .tag("mobile")
507                    .tag("android")
508                    .tag("dev")
509                    .tag("tauri")
510                    .estimated_duration(60)
511                    .build(),
512                CommandBuilder::new("tauri-ios-dev", "cargo")
513                    .label("iOS Dev")
514                    .description("Run Tauri app on iOS device/simulator")
515                    .arg("tauri")
516                    .arg("ios")
517                    .arg("dev")
518                    .category(CommandCategory::Run)
519                    .priority(70)
520                    .tag("mobile")
521                    .tag("ios")
522                    .tag("dev")
523                    .tag("tauri")
524                    .estimated_duration(60)
525                    .build(),
526            ]);
527        }
528
529        // Add signing commands if we detect signing configuration
530        if context
531            .dependencies
532            .iter()
533            .any(|d| d.features.iter().any(|f| f.contains("updater")))
534        {
535            commands.extend(vec![
536                CommandBuilder::new("tauri-sign", "cargo")
537                    .label("Sign Application")
538                    .description("Sign the application for distribution")
539                    .arg("tauri")
540                    .arg("signer")
541                    .arg("sign")
542                    .category(CommandCategory::Deploy)
543                    .priority(70)
544                    .tag("sign")
545                    .tag("security")
546                    .tag("deploy")
547                    .tag("tauri")
548                    .estimated_duration(15)
549                    .build(),
550                CommandBuilder::new("tauri-updater", "cargo")
551                    .label("Generate Update")
552                    .description("Generate update package for auto-updater")
553                    .arg("tauri")
554                    .arg("build")
555                    .arg("--config")
556                    .arg("updater.active=true")
557                    .category(CommandCategory::Deploy)
558                    .priority(70)
559                    .tag("updater")
560                    .tag("deploy")
561                    .tag("tauri")
562                    .estimated_duration(75)
563                    .build(),
564            ]);
565        }
566
567        commands
568    }
569}
570
571impl Default for TauriProvider {
572    fn default() -> Self {
573        Self::new()
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580    use crate::{BuildTarget, Dependency, ProjectType, TargetType, WorkspaceMember};
581    use std::collections::HashMap;
582    use std::path::PathBuf;
583
584    fn create_tauri_context() -> ProjectContext {
585        ProjectContext {
586            workspace_root: PathBuf::from("/test"),
587            current_file: None,
588            cursor_position: None,
589            project_type: ProjectType::Tauri,
590            dependencies: vec![Dependency {
591                name: "tauri".to_string(),
592                version: "1.5".to_string(),
593                features: vec!["api-all".to_string()],
594                optional: false,
595                dev_dependency: false,
596            }],
597            workspace_members: vec![WorkspaceMember {
598                name: "tauri-app".to_string(),
599                path: PathBuf::from("/test"),
600                package_type: ProjectType::Tauri,
601            }],
602            build_targets: vec![BuildTarget {
603                name: "main".to_string(),
604                target_type: TargetType::Binary,
605                path: PathBuf::from("/test/src-tauri/src/main.rs"),
606            }],
607            active_features: vec!["api-all".to_string()],
608            env_vars: HashMap::new(),
609        }
610    }
611
612    #[tokio::test]
613    async fn test_tauri_provider_can_handle() {
614        let provider = TauriProvider::new();
615        let context = create_tauri_context();
616
617        assert!(provider.can_handle(&context));
618        assert_eq!(provider.name(), "tauri");
619        assert_eq!(provider.priority(), 88);
620    }
621
622    #[tokio::test]
623    async fn test_tauri_commands_generation() {
624        let provider = TauriProvider::new();
625        let context = create_tauri_context();
626
627        let commands = provider.commands(&context).await.unwrap();
628
629        assert!(!commands.is_empty());
630
631        // Should have development commands
632        assert!(commands.iter().any(|c| c.id == "tauri-dev"));
633        assert!(commands.iter().any(|c| c.id == "tauri-dev-debug"));
634
635        // Should have build commands
636        assert!(commands.iter().any(|c| c.id == "tauri-build"));
637        assert!(commands.iter().any(|c| c.id == "tauri-build-debug"));
638
639        // Should have test commands
640        assert!(commands.iter().any(|c| c.id == "tauri-test"));
641        assert!(commands.iter().any(|c| c.id == "tauri-test-core"));
642
643        // Should have distribution commands
644        assert!(commands.iter().any(|c| c.id == "tauri-bundle"));
645        assert!(commands.iter().any(|c| c.id == "tauri-icon"));
646    }
647
648    #[tokio::test]
649    async fn test_tauri_cross_platform_builds() {
650        let provider = TauriProvider::new();
651        let mut context = create_tauri_context();
652
653        // Ensure cross-platform features are detected
654        context.dependencies[0].features.push("updater".to_string());
655
656        let commands = provider.commands(&context).await.unwrap();
657
658        // Should have cross-platform build commands
659        assert!(commands.iter().any(|c| c.id == "tauri-build-windows"));
660        assert!(commands.iter().any(|c| c.id == "tauri-build-macos"));
661        assert!(commands.iter().any(|c| c.id == "tauri-build-linux"));
662    }
663
664    #[tokio::test]
665    async fn test_tauri_updater_commands() {
666        let provider = TauriProvider::new();
667        let mut context = create_tauri_context();
668
669        // Add updater feature
670        context.dependencies[0].features.push("updater".to_string());
671
672        let commands = provider.commands(&context).await.unwrap();
673
674        // Should have updater and signing commands
675        assert!(commands.iter().any(|c| c.id == "tauri-sign"));
676        assert!(commands.iter().any(|c| c.id == "tauri-updater"));
677    }
678
679    #[tokio::test]
680    async fn test_tauri_command_priorities() {
681        let provider = TauriProvider::new();
682        let context = create_tauri_context();
683
684        let commands = provider.commands(&context).await.unwrap();
685
686        // Dev command should have highest priority
687        let dev_cmd = commands.iter().find(|c| c.id == "tauri-dev").unwrap();
688        assert_eq!(dev_cmd.priority, 95);
689
690        // All commands should have tauri tag
691        assert!(
692            commands
693                .iter()
694                .all(|c| c.tags.contains(&"tauri".to_string()))
695        );
696    }
697
698    #[tokio::test]
699    async fn test_tauri_dependency_detection() {
700        let provider = TauriProvider::new();
701        let mut context = create_tauri_context();
702        context.project_type = ProjectType::Binary; // Not explicitly App
703        // But has tauri dependency (set in create_tauri_context)
704
705        assert!(provider.can_handle(&context));
706    }
707}