raz_core/providers/
leptos.rs1use crate::{
7 Command, CommandBuilder, CommandCategory, CommandProvider, ProjectContext, ProjectType,
8 RazResult, SymbolKind,
9};
10use async_trait::async_trait;
11
12pub struct LeptosProvider {
14 priority: u8,
15}
16
17impl LeptosProvider {
18 pub fn new() -> Self {
19 Self {
20 priority: 90, }
22 }
23}
24
25#[async_trait]
26impl CommandProvider for LeptosProvider {
27 fn name(&self) -> &str {
28 "leptos"
29 }
30
31 fn priority(&self) -> u8 {
32 self.priority
33 }
34
35 fn can_handle(&self, context: &ProjectContext) -> bool {
36 match &context.project_type {
38 ProjectType::Leptos => true,
39 ProjectType::Mixed(frameworks) => frameworks.contains(&ProjectType::Leptos),
40 _ => {
41 context.dependencies.iter().any(|dep| {
43 dep.name == "leptos" || dep.name == "leptos_axum" || dep.name == "leptos_actix"
44 })
45 }
46 }
47 }
48
49 async fn commands(&self, context: &ProjectContext) -> RazResult<Vec<Command>> {
50 let mut commands = Vec::new();
51
52 commands.extend(self.development_commands(context));
54
55 commands.extend(self.build_commands(context));
57
58 commands.extend(self.test_commands(context));
60
61 commands.extend(self.context_aware_commands(context));
63
64 commands.extend(self.deployment_commands(context));
66
67 Ok(commands)
68 }
69}
70
71impl LeptosProvider {
72 fn development_commands(&self, context: &ProjectContext) -> Vec<Command> {
74 vec![
75 CommandBuilder::new("leptos-watch", "cargo-leptos")
76 .label("Leptos Dev Watch")
77 .description("Development server with auto-reload (recommended for development)")
78 .arg("watch")
79 .category(CommandCategory::Run)
80 .priority(95)
81 .tag("dev")
82 .tag("watch")
83 .tag("leptos")
84 .cwd(context.workspace_root.clone())
85 .estimated_duration(5)
86 .build(),
87 CommandBuilder::new("leptos-watch-hot", "cargo-leptos")
88 .label("Leptos Hot Reload")
89 .description("Development with hot-reload (requires nightly)")
90 .arg("watch")
91 .arg("--hot-reload")
92 .category(CommandCategory::Run)
93 .priority(92)
94 .tag("dev")
95 .tag("hot-reload")
96 .tag("leptos")
97 .cwd(context.workspace_root.clone())
98 .estimated_duration(5)
99 .build(),
100 CommandBuilder::new("leptos-serve", "cargo-leptos")
101 .label("Leptos Serve")
102 .description("Serve in hydrate mode (production-like, no auto-reload)")
103 .arg("serve")
104 .category(CommandCategory::Run)
105 .priority(85)
106 .tag("serve")
107 .tag("leptos")
108 .cwd(context.workspace_root.clone())
109 .estimated_duration(5)
110 .build(),
111 CommandBuilder::new("leptos-serve-release", "cargo-leptos")
112 .label("Leptos Serve Release")
113 .description("Serve optimized release build")
114 .arg("serve")
115 .arg("--release")
116 .category(CommandCategory::Run)
117 .priority(80)
118 .tag("serve")
119 .tag("release")
120 .tag("leptos")
121 .cwd(context.workspace_root.clone())
122 .estimated_duration(8)
123 .build(),
124 ]
125 }
126
127 fn build_commands(&self, context: &ProjectContext) -> Vec<Command> {
129 vec![
130 CommandBuilder::new("leptos-build", "cargo-leptos")
131 .label("Leptos Build")
132 .description("Build Leptos app (both server SSR and client WASM)")
133 .arg("build")
134 .category(CommandCategory::Build)
135 .priority(85)
136 .tag("build")
137 .tag("production")
138 .tag("leptos")
139 .cwd(context.workspace_root.clone())
140 .estimated_duration(30)
141 .build(),
142 CommandBuilder::new("leptos-build-release", "cargo-leptos")
143 .label("Leptos Release Build")
144 .description("Build optimized release version")
145 .arg("build")
146 .arg("--release")
147 .category(CommandCategory::Build)
148 .priority(82)
149 .tag("build")
150 .tag("release")
151 .tag("leptos")
152 .cwd(context.workspace_root.clone())
153 .estimated_duration(60)
154 .build(),
155 CommandBuilder::new("leptos-build-precompress", "cargo-leptos")
156 .label("Leptos Build Compressed")
157 .description("Build release with precompressed assets (gzip & brotli)")
158 .arg("build")
159 .arg("--release")
160 .arg("--precompress")
161 .category(CommandCategory::Build)
162 .priority(80)
163 .tag("build")
164 .tag("release")
165 .tag("production")
166 .tag("leptos")
167 .cwd(context.workspace_root.clone())
168 .estimated_duration(70)
169 .build(),
170 ]
171 }
172
173 fn test_commands(&self, context: &ProjectContext) -> Vec<Command> {
175 vec![
176 CommandBuilder::new("leptos-test", "cargo-leptos")
177 .label("Leptos Test")
178 .description("Run tests for app, client and server")
179 .arg("test")
180 .category(CommandCategory::Test)
181 .priority(75)
182 .tag("test")
183 .tag("leptos")
184 .cwd(context.workspace_root.clone())
185 .estimated_duration(15)
186 .build(),
187 CommandBuilder::new("leptos-end-to-end", "cargo-leptos")
188 .label("Leptos E2E Tests")
189 .description("Start server and run end-to-end tests")
190 .arg("end-to-end")
191 .category(CommandCategory::Test)
192 .priority(72)
193 .tag("test")
194 .tag("e2e")
195 .tag("leptos")
196 .cwd(context.workspace_root.clone())
197 .estimated_duration(90)
198 .build(),
199 CommandBuilder::new("cargo-test-workspace", "cargo")
200 .label("Workspace Tests")
201 .description("Run all tests in the workspace")
202 .arg("test")
203 .arg("--workspace")
204 .category(CommandCategory::Test)
205 .priority(70)
206 .tag("test")
207 .tag("workspace")
208 .tag("leptos")
209 .cwd(context.workspace_root.clone())
210 .estimated_duration(30)
211 .build(),
212 ]
213 }
214
215 fn context_aware_commands(&self, context: &ProjectContext) -> Vec<Command> {
217 let mut commands = Vec::new();
218
219 if let Some(file_context) = &context.current_file {
220 if let Some(symbol) = &file_context.cursor_symbol {
222 match symbol.kind {
223 SymbolKind::Function => {
224 if symbol.name.starts_with("test_")
225 || symbol.modifiers.contains(&"test".to_string())
226 {
227 commands.push(
229 CommandBuilder::new("cargo-test-current", "cargo")
230 .label("Test Current Function")
231 .description(format!("Run test function: {}", symbol.name))
232 .arg("test")
233 .arg(&symbol.name)
234 .arg("--")
235 .arg("--nocapture")
236 .category(CommandCategory::Test)
237 .priority(90)
238 .tag("test")
239 .tag("current")
240 .tag("leptos")
241 .cwd(context.workspace_root.clone())
242 .estimated_duration(5)
243 .build(),
244 );
245 }
246
247 if symbol.name.chars().next().is_some_and(|c| c.is_uppercase()) {
249 commands.push(
250 CommandBuilder::new("leptos-dev-component", "cargo-leptos")
251 .label("Dev with Component Focus")
252 .description(format!(
253 "Develop focusing on component: {}",
254 symbol.name
255 ))
256 .arg("serve")
257 .category(CommandCategory::Run)
258 .priority(85)
259 .tag("component")
260 .tag("dev")
261 .tag("leptos")
262 .cwd(context.workspace_root.clone())
263 .estimated_duration(10)
264 .build(),
265 );
266 }
267 }
268 SymbolKind::Struct => {
269 commands.push(
271 CommandBuilder::new("leptos-check-struct", "cargo")
272 .label("Check Struct Usage")
273 .description(format!("Check usage of struct: {}", symbol.name))
274 .arg("check")
275 .arg("--message-format=json")
276 .category(CommandCategory::Lint)
277 .priority(70)
278 .tag("check")
279 .tag("struct")
280 .tag("leptos")
281 .cwd(context.workspace_root.clone())
282 .estimated_duration(8)
283 .build(),
284 );
285 }
286 _ => {}
287 }
288 }
289
290 if file_context.path.to_string_lossy().contains("component") {
292 commands.push(
293 CommandBuilder::new("leptos-build-check", "cargo-leptos")
294 .label("Build & Check")
295 .description("Build and check component compilation")
296 .arg("build")
297 .category(CommandCategory::Build)
298 .priority(80)
299 .tag("components")
300 .tag("build")
301 .tag("leptos")
302 .cwd(context.workspace_root.clone())
303 .estimated_duration(20)
304 .build(),
305 );
306 }
307 }
308
309 commands
310 }
311
312 fn deployment_commands(&self, context: &ProjectContext) -> Vec<Command> {
314 vec![
315 CommandBuilder::new("leptos-new", "cargo-leptos")
316 .label("New Leptos Project")
317 .description("Create a new Leptos project with wizard")
318 .arg("new")
319 .category(CommandCategory::Generate)
320 .priority(70)
321 .tag("new")
322 .tag("generate")
323 .tag("leptos")
324 .cwd(context.workspace_root.clone())
325 .estimated_duration(30)
326 .build(),
327 CommandBuilder::new("leptos-clean", "cargo")
328 .label("Clean Leptos Build")
329 .description("Clean build artifacts and temp files")
330 .arg("clean")
331 .category(CommandCategory::Clean)
332 .priority(60)
333 .tag("clean")
334 .tag("leptos")
335 .cwd(context.workspace_root.clone())
336 .estimated_duration(5)
337 .build(),
338 ]
339 }
340}
341
342impl Default for LeptosProvider {
343 fn default() -> Self {
344 Self::new()
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use crate::{BuildTarget, Dependency, ProjectType, TargetType, WorkspaceMember};
352 use std::collections::HashMap;
353 use std::path::PathBuf;
354
355 fn create_leptos_context() -> ProjectContext {
356 ProjectContext {
357 workspace_root: PathBuf::from("/test"),
358 current_file: None,
359 cursor_position: None,
360 project_type: ProjectType::Leptos,
361 dependencies: vec![Dependency {
362 name: "leptos".to_string(),
363 version: "0.6".to_string(),
364 features: vec!["csr".to_string()],
365 optional: false,
366 dev_dependency: false,
367 }],
368 workspace_members: vec![WorkspaceMember {
369 name: "leptos-app".to_string(),
370 path: PathBuf::from("/test"),
371 package_type: ProjectType::Leptos,
372 }],
373 build_targets: vec![BuildTarget {
374 name: "main".to_string(),
375 target_type: TargetType::Binary,
376 path: PathBuf::from("/test/src/main.rs"),
377 }],
378 active_features: vec!["csr".to_string()],
379 env_vars: HashMap::new(),
380 }
381 }
382
383 #[tokio::test]
384 async fn test_leptos_provider_can_handle() {
385 let provider = LeptosProvider::new();
386 let context = create_leptos_context();
387
388 assert!(provider.can_handle(&context));
389 assert_eq!(provider.name(), "leptos");
390 assert_eq!(provider.priority(), 90);
391 }
392
393 #[tokio::test]
394 async fn test_leptos_commands_generation() {
395 let provider = LeptosProvider::new();
396 let context = create_leptos_context();
397
398 let commands = provider.commands(&context).await.unwrap();
399
400 assert!(!commands.is_empty());
401
402 assert!(commands.iter().any(|c| c.id == "leptos-watch"));
404 assert!(commands.iter().any(|c| c.id == "leptos-serve"));
405
406 assert!(commands.iter().any(|c| c.id == "leptos-build"));
408 assert!(commands.iter().any(|c| c.id == "leptos-build-release"));
409
410 assert!(commands.iter().any(|c| c.id == "leptos-test"));
412
413 assert!(commands.iter().any(|c| c.id == "leptos-new"));
415 }
416
417 #[tokio::test]
418 async fn test_leptos_command_priorities() {
419 let provider = LeptosProvider::new();
420 let context = create_leptos_context();
421
422 let commands = provider.commands(&context).await.unwrap();
423
424 let watch_cmd = commands.iter().find(|c| c.id == "leptos-watch").unwrap();
426 assert_eq!(watch_cmd.priority, 95);
427
428 assert!(
430 commands
431 .iter()
432 .all(|c| c.tags.contains(&"leptos".to_string()))
433 );
434 }
435
436 #[tokio::test]
437 async fn test_leptos_non_leptos_project() {
438 let provider = LeptosProvider::new();
439 let mut context = create_leptos_context();
440 context.project_type = ProjectType::Binary;
441 context.dependencies.clear();
442
443 assert!(!provider.can_handle(&context));
444 }
445
446 #[tokio::test]
447 async fn test_leptos_dependency_detection() {
448 let provider = LeptosProvider::new();
449 let mut context = create_leptos_context();
450 context.project_type = ProjectType::Binary; assert!(provider.can_handle(&context));
454 }
455}