claude-mpm 4.15.6__py3-none-any.whl → 4.21.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +272 -23
- claude_mpm/agents/PM_INSTRUCTIONS.md +49 -0
- claude_mpm/agents/agent_loader.py +4 -4
- claude_mpm/agents/templates/engineer.json +5 -1
- claude_mpm/agents/templates/php-engineer.json +10 -4
- claude_mpm/agents/templates/python_engineer.json +8 -3
- claude_mpm/agents/templates/rust_engineer.json +12 -7
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/mpm_init/__init__.py +73 -0
- claude_mpm/cli/commands/mpm_init/core.py +525 -0
- claude_mpm/cli/commands/mpm_init/display.py +341 -0
- claude_mpm/cli/commands/mpm_init/git_activity.py +427 -0
- claude_mpm/cli/commands/mpm_init/modes.py +397 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +442 -0
- claude_mpm/cli/commands/mpm_init_cli.py +396 -0
- claude_mpm/cli/commands/mpm_init_handler.py +67 -1
- claude_mpm/cli/commands/skills.py +488 -0
- claude_mpm/cli/executor.py +2 -0
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +42 -0
- claude_mpm/cli/parsers/skills_parser.py +137 -0
- claude_mpm/cli/startup.py +57 -0
- claude_mpm/commands/mpm-auto-configure.md +52 -0
- claude_mpm/commands/mpm-help.md +6 -0
- claude_mpm/commands/mpm-init.md +112 -6
- claude_mpm/commands/mpm-resume.md +372 -0
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +2 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/constants.py +12 -0
- claude_mpm/core/config.py +42 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/interfaces.py +56 -1
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/hooks/__init__.py +8 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/session_resume_hook.py +121 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/auto_config_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/recommender.py +47 -0
- claude_mpm/services/cli/resume_service.py +617 -0
- claude_mpm/services/cli/session_manager.py +87 -0
- claude_mpm/services/cli/session_pause_manager.py +504 -0
- claude_mpm/services/cli/session_resume_helper.py +372 -0
- claude_mpm/services/core/base.py +26 -11
- claude_mpm/services/core/interfaces.py +56 -1
- claude_mpm/services/core/models/agent_config.py +3 -0
- claude_mpm/services/core/models/process.py +4 -0
- claude_mpm/services/core/path_resolver.py +1 -1
- claude_mpm/services/diagnostics/models.py +21 -0
- claude_mpm/services/event_bus/relay.py +23 -7
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +2 -0
- claude_mpm/services/mcp_config_manager.py +7 -131
- claude_mpm/services/mcp_gateway/auto_configure.py +31 -25
- claude_mpm/services/mcp_gateway/core/process_pool.py +19 -10
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +26 -21
- claude_mpm/services/memory/failure_tracker.py +19 -4
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/unified/deployment_strategies/local.py +1 -1
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +21 -0
- claude_mpm/skills/agent_skills_injector.py +324 -0
- claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +157 -0
- claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +425 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +303 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +113 -0
- claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +72 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +44 -0
- claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +129 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +97 -9
- claude_mpm/skills/skills_registry.py +348 -0
- claude_mpm/skills/skills_service.py +739 -0
- claude_mpm/tools/code_tree_analyzer/__init__.py +45 -0
- claude_mpm/tools/code_tree_analyzer/analysis.py +299 -0
- claude_mpm/tools/code_tree_analyzer/cache.py +131 -0
- claude_mpm/tools/code_tree_analyzer/core.py +380 -0
- claude_mpm/tools/code_tree_analyzer/discovery.py +403 -0
- claude_mpm/tools/code_tree_analyzer/events.py +168 -0
- claude_mpm/tools/code_tree_analyzer/gitignore.py +308 -0
- claude_mpm/tools/code_tree_analyzer/models.py +39 -0
- claude_mpm/tools/code_tree_analyzer/multilang_analyzer.py +224 -0
- claude_mpm/tools/code_tree_analyzer/python_analyzer.py +284 -0
- claude_mpm/utils/agent_dependency_loader.py +2 -2
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/METADATA +211 -33
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/RECORD +206 -64
- claude_mpm/agents/INSTRUCTIONS_OLD_DEPRECATED.md +0 -602
- claude_mpm/cli/commands/mpm_init.py +0 -2008
- claude_mpm/tools/code_tree_analyzer.py +0 -1825
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.15.6.dist-info → claude_mpm-4.21.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
# Testing and Deployment
|
|
2
|
+
|
|
3
|
+
Complete guide to testing strategies, cross-platform builds, distribution, and CI/CD pipelines for Rust desktop applications.
|
|
4
|
+
|
|
5
|
+
## Testing Strategies
|
|
6
|
+
|
|
7
|
+
### Unit Testing Tauri Commands
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
// src/commands.rs
|
|
11
|
+
use serde::{Deserialize, Serialize};
|
|
12
|
+
|
|
13
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
14
|
+
pub struct User {
|
|
15
|
+
pub id: u64,
|
|
16
|
+
pub name: String,
|
|
17
|
+
pub email: String,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub struct UserService {
|
|
21
|
+
users: Vec<User>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl UserService {
|
|
25
|
+
pub fn new() -> Self {
|
|
26
|
+
Self { users: Vec::new() }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pub fn add_user(&mut self, name: String, email: String) -> Result<User, String> {
|
|
30
|
+
if name.is_empty() {
|
|
31
|
+
return Err("Name cannot be empty".to_string());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if !email.contains('@') {
|
|
35
|
+
return Err("Invalid email".to_string());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let user = User {
|
|
39
|
+
id: self.users.len() as u64 + 1,
|
|
40
|
+
name,
|
|
41
|
+
email,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
self.users.push(user.clone());
|
|
45
|
+
Ok(user)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub fn get_user(&self, id: u64) -> Option<&User> {
|
|
49
|
+
self.users.iter().find(|u| u.id == id)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
pub fn get_all_users(&self) -> &[User] {
|
|
53
|
+
&self.users
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#[cfg(test)]
|
|
58
|
+
mod tests {
|
|
59
|
+
use super::*;
|
|
60
|
+
|
|
61
|
+
#[test]
|
|
62
|
+
fn test_add_user_success() {
|
|
63
|
+
let mut service = UserService::new();
|
|
64
|
+
let result = service.add_user("John Doe".to_string(), "john@example.com".to_string());
|
|
65
|
+
|
|
66
|
+
assert!(result.is_ok());
|
|
67
|
+
let user = result.unwrap();
|
|
68
|
+
assert_eq!(user.name, "John Doe");
|
|
69
|
+
assert_eq!(user.email, "john@example.com");
|
|
70
|
+
assert_eq!(service.get_all_users().len(), 1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#[test]
|
|
74
|
+
fn test_add_user_empty_name() {
|
|
75
|
+
let mut service = UserService::new();
|
|
76
|
+
let result = service.add_user("".to_string(), "john@example.com".to_string());
|
|
77
|
+
|
|
78
|
+
assert!(result.is_err());
|
|
79
|
+
assert_eq!(result.unwrap_err(), "Name cannot be empty");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[test]
|
|
83
|
+
fn test_add_user_invalid_email() {
|
|
84
|
+
let mut service = UserService::new();
|
|
85
|
+
let result = service.add_user("John Doe".to_string(), "invalid-email".to_string());
|
|
86
|
+
|
|
87
|
+
assert!(result.is_err());
|
|
88
|
+
assert_eq!(result.unwrap_err(), "Invalid email");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#[test]
|
|
92
|
+
fn test_get_user() {
|
|
93
|
+
let mut service = UserService::new();
|
|
94
|
+
service.add_user("John Doe".to_string(), "john@example.com".to_string()).unwrap();
|
|
95
|
+
|
|
96
|
+
let user = service.get_user(1);
|
|
97
|
+
assert!(user.is_some());
|
|
98
|
+
assert_eq!(user.unwrap().name, "John Doe");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#[test]
|
|
102
|
+
fn test_get_user_not_found() {
|
|
103
|
+
let service = UserService::new();
|
|
104
|
+
let user = service.get_user(999);
|
|
105
|
+
assert!(user.is_none());
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Integration Testing with Mock State
|
|
111
|
+
|
|
112
|
+
```rust
|
|
113
|
+
#[cfg(test)]
|
|
114
|
+
mod integration_tests {
|
|
115
|
+
use super::*;
|
|
116
|
+
use std::sync::Mutex;
|
|
117
|
+
|
|
118
|
+
struct TestState {
|
|
119
|
+
service: Mutex<UserService>,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn setup_test_state() -> TestState {
|
|
123
|
+
TestState {
|
|
124
|
+
service: Mutex::new(UserService::new()),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[test]
|
|
129
|
+
fn test_user_workflow() {
|
|
130
|
+
let state = setup_test_state();
|
|
131
|
+
|
|
132
|
+
// Add multiple users
|
|
133
|
+
{
|
|
134
|
+
let mut service = state.service.lock().unwrap();
|
|
135
|
+
service.add_user("Alice".to_string(), "alice@example.com".to_string()).unwrap();
|
|
136
|
+
service.add_user("Bob".to_string(), "bob@example.com".to_string()).unwrap();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Verify users were added
|
|
140
|
+
{
|
|
141
|
+
let service = state.service.lock().unwrap();
|
|
142
|
+
assert_eq!(service.get_all_users().len(), 2);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Get specific user
|
|
146
|
+
{
|
|
147
|
+
let service = state.service.lock().unwrap();
|
|
148
|
+
let alice = service.get_user(1);
|
|
149
|
+
assert!(alice.is_some());
|
|
150
|
+
assert_eq!(alice.unwrap().name, "Alice");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Async Testing with Tokio
|
|
157
|
+
|
|
158
|
+
```rust
|
|
159
|
+
#[cfg(test)]
|
|
160
|
+
mod async_tests {
|
|
161
|
+
use super::*;
|
|
162
|
+
use tokio::test;
|
|
163
|
+
|
|
164
|
+
#[tokio::test]
|
|
165
|
+
async fn test_async_operation() {
|
|
166
|
+
let result = fetch_data_async().await;
|
|
167
|
+
assert!(result.is_ok());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#[tokio::test]
|
|
171
|
+
async fn test_concurrent_operations() {
|
|
172
|
+
let futures = vec![
|
|
173
|
+
fetch_data_async(),
|
|
174
|
+
fetch_data_async(),
|
|
175
|
+
fetch_data_async(),
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
let results = futures::future::join_all(futures).await;
|
|
179
|
+
|
|
180
|
+
assert_eq!(results.len(), 3);
|
|
181
|
+
for result in results {
|
|
182
|
+
assert!(result.is_ok());
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async fn fetch_data_async() -> Result<String, String> {
|
|
187
|
+
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
|
188
|
+
Ok("data".to_string())
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Testing with Database
|
|
194
|
+
|
|
195
|
+
```rust
|
|
196
|
+
#[cfg(test)]
|
|
197
|
+
mod database_tests {
|
|
198
|
+
use super::*;
|
|
199
|
+
use sqlx::SqlitePool;
|
|
200
|
+
|
|
201
|
+
async fn setup_test_db() -> SqlitePool {
|
|
202
|
+
let pool = SqlitePool::connect(":memory:")
|
|
203
|
+
.await
|
|
204
|
+
.expect("Failed to create in-memory database");
|
|
205
|
+
|
|
206
|
+
// Run migrations
|
|
207
|
+
sqlx::query(
|
|
208
|
+
"CREATE TABLE users (
|
|
209
|
+
id INTEGER PRIMARY KEY,
|
|
210
|
+
name TEXT NOT NULL,
|
|
211
|
+
email TEXT NOT NULL
|
|
212
|
+
)"
|
|
213
|
+
)
|
|
214
|
+
.execute(&pool)
|
|
215
|
+
.await
|
|
216
|
+
.expect("Failed to create table");
|
|
217
|
+
|
|
218
|
+
pool
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#[tokio::test]
|
|
222
|
+
async fn test_create_user() {
|
|
223
|
+
let pool = setup_test_db().await;
|
|
224
|
+
|
|
225
|
+
let result = sqlx::query(
|
|
226
|
+
"INSERT INTO users (name, email) VALUES (?, ?)"
|
|
227
|
+
)
|
|
228
|
+
.bind("John Doe")
|
|
229
|
+
.bind("john@example.com")
|
|
230
|
+
.execute(&pool)
|
|
231
|
+
.await;
|
|
232
|
+
|
|
233
|
+
assert!(result.is_ok());
|
|
234
|
+
|
|
235
|
+
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users")
|
|
236
|
+
.fetch_one(&pool)
|
|
237
|
+
.await
|
|
238
|
+
.unwrap();
|
|
239
|
+
|
|
240
|
+
assert_eq!(count.0, 1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## UI Testing Approaches
|
|
246
|
+
|
|
247
|
+
### egui Testing
|
|
248
|
+
|
|
249
|
+
```rust
|
|
250
|
+
#[cfg(test)]
|
|
251
|
+
mod ui_tests {
|
|
252
|
+
use super::*;
|
|
253
|
+
use eframe::egui;
|
|
254
|
+
|
|
255
|
+
#[test]
|
|
256
|
+
fn test_ui_state_update() {
|
|
257
|
+
let mut app = MyApp::default();
|
|
258
|
+
|
|
259
|
+
// Simulate UI interaction
|
|
260
|
+
app.counter = 0;
|
|
261
|
+
|
|
262
|
+
// Trigger button click logic
|
|
263
|
+
app.counter += 1;
|
|
264
|
+
|
|
265
|
+
assert_eq!(app.counter, 1);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#[test]
|
|
269
|
+
fn test_ui_rendering() {
|
|
270
|
+
let mut app = MyApp::default();
|
|
271
|
+
let ctx = egui::Context::default();
|
|
272
|
+
|
|
273
|
+
// Test that UI can be rendered without panic
|
|
274
|
+
app.update(&ctx, &mut eframe::Frame::new(eframe::FrameData {
|
|
275
|
+
info: eframe::IntegrationInfo::default(),
|
|
276
|
+
output: Default::default(),
|
|
277
|
+
repaint_signal: std::sync::Arc::new(eframe::RepaintSignal::default()),
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Tauri Frontend-Backend Integration Tests
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
// src/tests/integration.test.ts
|
|
287
|
+
import { invoke } from '@tauri-apps/api/core';
|
|
288
|
+
|
|
289
|
+
describe('User Management', () => {
|
|
290
|
+
beforeEach(async () => {
|
|
291
|
+
// Reset state
|
|
292
|
+
await invoke('reset_users');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test('should add user', async () => {
|
|
296
|
+
const user = await invoke<User>('add_user', {
|
|
297
|
+
name: 'John Doe',
|
|
298
|
+
email: 'john@example.com',
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
expect(user.name).toBe('John Doe');
|
|
302
|
+
expect(user.email).toBe('john@example.com');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('should reject invalid email', async () => {
|
|
306
|
+
await expect(
|
|
307
|
+
invoke('add_user', {
|
|
308
|
+
name: 'John Doe',
|
|
309
|
+
email: 'invalid',
|
|
310
|
+
})
|
|
311
|
+
).rejects.toThrow('Invalid email');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('should get all users', async () => {
|
|
315
|
+
await invoke('add_user', {
|
|
316
|
+
name: 'Alice',
|
|
317
|
+
email: 'alice@example.com',
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
await invoke('add_user', {
|
|
321
|
+
name: 'Bob',
|
|
322
|
+
email: 'bob@example.com',
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const users = await invoke<User[]>('get_all_users');
|
|
326
|
+
expect(users).toHaveLength(2);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### E2E Testing with WebDriver
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// tests/e2e/app.spec.ts
|
|
335
|
+
import { test, expect } from '@playwright/test';
|
|
336
|
+
|
|
337
|
+
test.describe('Desktop App E2E', () => {
|
|
338
|
+
test('should load application', async ({ page }) => {
|
|
339
|
+
await page.goto('http://localhost:1420'); // Tauri dev server
|
|
340
|
+
|
|
341
|
+
await expect(page.locator('h1')).toContainText('My App');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('should add user through UI', async ({ page }) => {
|
|
345
|
+
await page.goto('http://localhost:1420');
|
|
346
|
+
|
|
347
|
+
await page.fill('input[name="name"]', 'John Doe');
|
|
348
|
+
await page.fill('input[name="email"]', 'john@example.com');
|
|
349
|
+
await page.click('button:has-text("Add User")');
|
|
350
|
+
|
|
351
|
+
await expect(page.locator('.user-item')).toContainText('John Doe');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('should show error for invalid input', async ({ page }) => {
|
|
355
|
+
await page.goto('http://localhost:1420');
|
|
356
|
+
|
|
357
|
+
await page.fill('input[name="name"]', '');
|
|
358
|
+
await page.fill('input[name="email"]', 'invalid');
|
|
359
|
+
await page.click('button:has-text("Add User")');
|
|
360
|
+
|
|
361
|
+
await expect(page.locator('.error')).toBeVisible();
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Cross-Compilation and Builds
|
|
367
|
+
|
|
368
|
+
### Build Scripts
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
#!/bin/bash
|
|
372
|
+
# scripts/build.sh
|
|
373
|
+
|
|
374
|
+
set -e
|
|
375
|
+
|
|
376
|
+
TARGET_TRIPLE="${1:-}"
|
|
377
|
+
BUILD_TYPE="${2:-release}"
|
|
378
|
+
|
|
379
|
+
if [ -z "$TARGET_TRIPLE" ]; then
|
|
380
|
+
echo "Building for current platform..."
|
|
381
|
+
cargo tauri build
|
|
382
|
+
else
|
|
383
|
+
echo "Building for $TARGET_TRIPLE..."
|
|
384
|
+
|
|
385
|
+
# Install target if needed
|
|
386
|
+
rustup target add "$TARGET_TRIPLE"
|
|
387
|
+
|
|
388
|
+
# Build
|
|
389
|
+
if [ "$BUILD_TYPE" = "debug" ]; then
|
|
390
|
+
cargo tauri build --debug --target "$TARGET_TRIPLE"
|
|
391
|
+
else
|
|
392
|
+
cargo tauri build --target "$TARGET_TRIPLE"
|
|
393
|
+
fi
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
echo "Build completed successfully!"
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Cross-Compilation Setup
|
|
400
|
+
|
|
401
|
+
**For Windows from Linux/macOS:**
|
|
402
|
+
```bash
|
|
403
|
+
# Install cross-compilation tools
|
|
404
|
+
cargo install cargo-xwin
|
|
405
|
+
|
|
406
|
+
# Add Windows target
|
|
407
|
+
rustup target add x86_64-pc-windows-msvc
|
|
408
|
+
|
|
409
|
+
# Build for Windows
|
|
410
|
+
cargo xwin build --release --target x86_64-pc-windows-msvc
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**For macOS from Linux:**
|
|
414
|
+
```bash
|
|
415
|
+
# Install osxcross (complex setup, see osxcross documentation)
|
|
416
|
+
# Add macOS targets
|
|
417
|
+
rustup target add x86_64-apple-darwin
|
|
418
|
+
rustup target add aarch64-apple-darwin
|
|
419
|
+
|
|
420
|
+
# Build for macOS
|
|
421
|
+
CROSS_COMPILE=x86_64-apple-darwin- \
|
|
422
|
+
cargo build --release --target x86_64-apple-darwin
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**For Linux from macOS/Windows:**
|
|
426
|
+
```bash
|
|
427
|
+
# Install cross
|
|
428
|
+
cargo install cross
|
|
429
|
+
|
|
430
|
+
# Build for Linux
|
|
431
|
+
cross build --release --target x86_64-unknown-linux-gnu
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Universal Binaries (macOS)
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
#!/bin/bash
|
|
438
|
+
# scripts/build-universal-macos.sh
|
|
439
|
+
|
|
440
|
+
set -e
|
|
441
|
+
|
|
442
|
+
echo "Building universal macOS binary..."
|
|
443
|
+
|
|
444
|
+
# Build for both architectures
|
|
445
|
+
cargo tauri build --target x86_64-apple-darwin
|
|
446
|
+
cargo tauri build --target aarch64-apple-darwin
|
|
447
|
+
|
|
448
|
+
# Create universal binary
|
|
449
|
+
lipo -create \
|
|
450
|
+
target/x86_64-apple-darwin/release/bundle/macos/MyApp.app/Contents/MacOS/MyApp \
|
|
451
|
+
target/aarch64-apple-darwin/release/bundle/macos/MyApp.app/Contents/MacOS/MyApp \
|
|
452
|
+
-output target/universal-apple-darwin/release/MyApp
|
|
453
|
+
|
|
454
|
+
echo "Universal binary created!"
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Platform-Specific Installers
|
|
458
|
+
|
|
459
|
+
### Windows Installer (WiX)
|
|
460
|
+
|
|
461
|
+
**wix/main.wxs:**
|
|
462
|
+
```xml
|
|
463
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
464
|
+
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
|
465
|
+
<Product
|
|
466
|
+
Id="*"
|
|
467
|
+
Name="My Application"
|
|
468
|
+
Language="1033"
|
|
469
|
+
Version="1.0.0"
|
|
470
|
+
Manufacturer="My Company"
|
|
471
|
+
UpgradeCode="PUT-GUID-HERE">
|
|
472
|
+
|
|
473
|
+
<Package
|
|
474
|
+
InstallerVersion="200"
|
|
475
|
+
Compressed="yes"
|
|
476
|
+
InstallScope="perMachine" />
|
|
477
|
+
|
|
478
|
+
<MajorUpgrade
|
|
479
|
+
DowngradeErrorMessage="A newer version is already installed." />
|
|
480
|
+
|
|
481
|
+
<MediaTemplate EmbedCab="yes" />
|
|
482
|
+
|
|
483
|
+
<Feature
|
|
484
|
+
Id="MainApplication"
|
|
485
|
+
Title="My Application"
|
|
486
|
+
Level="1">
|
|
487
|
+
<ComponentGroupRef Id="AppFiles" />
|
|
488
|
+
</Feature>
|
|
489
|
+
|
|
490
|
+
<Directory Id="TARGETDIR" Name="SourceDir">
|
|
491
|
+
<Directory Id="ProgramFilesFolder">
|
|
492
|
+
<Directory Id="INSTALLFOLDER" Name="MyApp" />
|
|
493
|
+
</Directory>
|
|
494
|
+
<Directory Id="ProgramMenuFolder">
|
|
495
|
+
<Directory Id="ApplicationProgramsFolder" Name="MyApp"/>
|
|
496
|
+
</Directory>
|
|
497
|
+
</Directory>
|
|
498
|
+
|
|
499
|
+
<ComponentGroup Id="AppFiles" Directory="INSTALLFOLDER">
|
|
500
|
+
<Component Id="MainExecutable">
|
|
501
|
+
<File
|
|
502
|
+
Id="MainExe"
|
|
503
|
+
Source="$(var.SourceDir)\MyApp.exe"
|
|
504
|
+
KeyPath="yes">
|
|
505
|
+
<Shortcut
|
|
506
|
+
Id="ApplicationStartMenuShortcut"
|
|
507
|
+
Directory="ApplicationProgramsFolder"
|
|
508
|
+
Name="MyApp"
|
|
509
|
+
Description="My Application"
|
|
510
|
+
WorkingDirectory="INSTALLFOLDER"
|
|
511
|
+
Icon="AppIcon.exe"
|
|
512
|
+
IconIndex="0"
|
|
513
|
+
Advertise="yes" />
|
|
514
|
+
</File>
|
|
515
|
+
</Component>
|
|
516
|
+
</ComponentGroup>
|
|
517
|
+
</Product>
|
|
518
|
+
</Wix>
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### macOS DMG Creation
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
#!/bin/bash
|
|
525
|
+
# scripts/create-dmg.sh
|
|
526
|
+
|
|
527
|
+
set -e
|
|
528
|
+
|
|
529
|
+
APP_NAME="MyApp"
|
|
530
|
+
VERSION="1.0.0"
|
|
531
|
+
DMG_NAME="${APP_NAME}-${VERSION}.dmg"
|
|
532
|
+
SOURCE_FOLDER="target/release/bundle/macos/${APP_NAME}.app"
|
|
533
|
+
DMG_FOLDER="dmg_temp"
|
|
534
|
+
|
|
535
|
+
echo "Creating DMG for ${APP_NAME}..."
|
|
536
|
+
|
|
537
|
+
# Create temporary folder
|
|
538
|
+
mkdir -p "$DMG_FOLDER"
|
|
539
|
+
|
|
540
|
+
# Copy app
|
|
541
|
+
cp -R "$SOURCE_FOLDER" "$DMG_FOLDER/"
|
|
542
|
+
|
|
543
|
+
# Create symbolic link to Applications
|
|
544
|
+
ln -s /Applications "$DMG_FOLDER/Applications"
|
|
545
|
+
|
|
546
|
+
# Create DMG
|
|
547
|
+
hdiutil create -volname "$APP_NAME" \
|
|
548
|
+
-srcfolder "$DMG_FOLDER" \
|
|
549
|
+
-ov \
|
|
550
|
+
-format UDZO \
|
|
551
|
+
"$DMG_NAME"
|
|
552
|
+
|
|
553
|
+
# Cleanup
|
|
554
|
+
rm -rf "$DMG_FOLDER"
|
|
555
|
+
|
|
556
|
+
echo "DMG created: $DMG_NAME"
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Linux Packages
|
|
560
|
+
|
|
561
|
+
**Debian Package (.deb):**
|
|
562
|
+
```bash
|
|
563
|
+
#!/bin/bash
|
|
564
|
+
# scripts/build-deb.sh
|
|
565
|
+
|
|
566
|
+
set -e
|
|
567
|
+
|
|
568
|
+
APP_NAME="myapp"
|
|
569
|
+
VERSION="1.0.0"
|
|
570
|
+
ARCH="amd64"
|
|
571
|
+
|
|
572
|
+
DEB_DIR="target/debian"
|
|
573
|
+
mkdir -p "$DEB_DIR/DEBIAN"
|
|
574
|
+
mkdir -p "$DEB_DIR/usr/bin"
|
|
575
|
+
mkdir -p "$DEB_DIR/usr/share/applications"
|
|
576
|
+
mkdir -p "$DEB_DIR/usr/share/icons/hicolor/256x256/apps"
|
|
577
|
+
|
|
578
|
+
# Create control file
|
|
579
|
+
cat > "$DEB_DIR/DEBIAN/control" << EOF
|
|
580
|
+
Package: $APP_NAME
|
|
581
|
+
Version: $VERSION
|
|
582
|
+
Section: utils
|
|
583
|
+
Priority: optional
|
|
584
|
+
Architecture: $ARCH
|
|
585
|
+
Maintainer: Your Name <your@email.com>
|
|
586
|
+
Description: My Application
|
|
587
|
+
A desktop application built with Tauri
|
|
588
|
+
EOF
|
|
589
|
+
|
|
590
|
+
# Copy binary
|
|
591
|
+
cp "target/release/$APP_NAME" "$DEB_DIR/usr/bin/"
|
|
592
|
+
|
|
593
|
+
# Create desktop entry
|
|
594
|
+
cat > "$DEB_DIR/usr/share/applications/$APP_NAME.desktop" << EOF
|
|
595
|
+
[Desktop Entry]
|
|
596
|
+
Name=My App
|
|
597
|
+
Exec=/usr/bin/$APP_NAME
|
|
598
|
+
Icon=$APP_NAME
|
|
599
|
+
Type=Application
|
|
600
|
+
Categories=Utility;
|
|
601
|
+
EOF
|
|
602
|
+
|
|
603
|
+
# Copy icon
|
|
604
|
+
cp "icons/icon.png" "$DEB_DIR/usr/share/icons/hicolor/256x256/apps/$APP_NAME.png"
|
|
605
|
+
|
|
606
|
+
# Build package
|
|
607
|
+
dpkg-deb --build "$DEB_DIR" "${APP_NAME}_${VERSION}_${ARCH}.deb"
|
|
608
|
+
|
|
609
|
+
echo "Debian package created!"
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
**AppImage:**
|
|
613
|
+
```bash
|
|
614
|
+
#!/bin/bash
|
|
615
|
+
# scripts/build-appimage.sh
|
|
616
|
+
|
|
617
|
+
set -e
|
|
618
|
+
|
|
619
|
+
APP_NAME="MyApp"
|
|
620
|
+
APPDIR="AppDir"
|
|
621
|
+
|
|
622
|
+
# Create AppDir structure
|
|
623
|
+
mkdir -p "$APPDIR/usr/bin"
|
|
624
|
+
mkdir -p "$APPDIR/usr/share/applications"
|
|
625
|
+
mkdir -p "$APPDIR/usr/share/icons/hicolor/256x256/apps"
|
|
626
|
+
|
|
627
|
+
# Copy files
|
|
628
|
+
cp "target/release/myapp" "$APPDIR/usr/bin/"
|
|
629
|
+
cp "myapp.desktop" "$APPDIR/usr/share/applications/"
|
|
630
|
+
cp "icons/icon.png" "$APPDIR/usr/share/icons/hicolor/256x256/apps/myapp.png"
|
|
631
|
+
|
|
632
|
+
# Create AppRun
|
|
633
|
+
cat > "$APPDIR/AppRun" << 'EOF'
|
|
634
|
+
#!/bin/bash
|
|
635
|
+
SELF=$(readlink -f "$0")
|
|
636
|
+
HERE=${SELF%/*}
|
|
637
|
+
export PATH="${HERE}/usr/bin:${PATH}"
|
|
638
|
+
exec "${HERE}/usr/bin/myapp" "$@"
|
|
639
|
+
EOF
|
|
640
|
+
chmod +x "$APPDIR/AppRun"
|
|
641
|
+
|
|
642
|
+
# Download appimagetool
|
|
643
|
+
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
|
644
|
+
chmod +x appimagetool
|
|
645
|
+
|
|
646
|
+
# Build AppImage
|
|
647
|
+
./appimagetool "$APPDIR" "${APP_NAME}.AppImage"
|
|
648
|
+
|
|
649
|
+
echo "AppImage created!"
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
## Code Signing and Notarization
|
|
653
|
+
|
|
654
|
+
### macOS Code Signing
|
|
655
|
+
|
|
656
|
+
```bash
|
|
657
|
+
#!/bin/bash
|
|
658
|
+
# scripts/sign-macos.sh
|
|
659
|
+
|
|
660
|
+
set -e
|
|
661
|
+
|
|
662
|
+
APP_PATH="$1"
|
|
663
|
+
IDENTITY="Developer ID Application: Your Name (TEAMID)"
|
|
664
|
+
|
|
665
|
+
echo "Signing $APP_PATH..."
|
|
666
|
+
|
|
667
|
+
# Sign all frameworks and dylibs first
|
|
668
|
+
find "$APP_PATH/Contents" -name "*.framework" -or -name "*.dylib" | while read file; do
|
|
669
|
+
codesign --force --sign "$IDENTITY" \
|
|
670
|
+
--options runtime \
|
|
671
|
+
--timestamp \
|
|
672
|
+
"$file"
|
|
673
|
+
done
|
|
674
|
+
|
|
675
|
+
# Sign the app bundle
|
|
676
|
+
codesign --force --deep --sign "$IDENTITY" \
|
|
677
|
+
--options runtime \
|
|
678
|
+
--entitlements entitlements.plist \
|
|
679
|
+
--timestamp \
|
|
680
|
+
"$APP_PATH"
|
|
681
|
+
|
|
682
|
+
# Verify signature
|
|
683
|
+
codesign --verify --verbose=4 "$APP_PATH"
|
|
684
|
+
|
|
685
|
+
echo "Signing completed!"
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
**entitlements.plist:**
|
|
689
|
+
```xml
|
|
690
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
691
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
692
|
+
<plist version="1.0">
|
|
693
|
+
<dict>
|
|
694
|
+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
|
695
|
+
<true/>
|
|
696
|
+
<key>com.apple.security.cs.allow-jit</key>
|
|
697
|
+
<true/>
|
|
698
|
+
<key>com.apple.security.cs.disable-library-validation</key>
|
|
699
|
+
<true/>
|
|
700
|
+
</dict>
|
|
701
|
+
</plist>
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
### macOS Notarization
|
|
705
|
+
|
|
706
|
+
```bash
|
|
707
|
+
#!/bin/bash
|
|
708
|
+
# scripts/notarize-macos.sh
|
|
709
|
+
|
|
710
|
+
set -e
|
|
711
|
+
|
|
712
|
+
APP_PATH="$1"
|
|
713
|
+
BUNDLE_ID="com.yourcompany.myapp"
|
|
714
|
+
APPLE_ID="your@email.com"
|
|
715
|
+
TEAM_ID="TEAMID"
|
|
716
|
+
APP_SPECIFIC_PASSWORD="xxxx-xxxx-xxxx-xxxx"
|
|
717
|
+
|
|
718
|
+
# Create DMG
|
|
719
|
+
DMG_PATH="MyApp.dmg"
|
|
720
|
+
hdiutil create -volname "MyApp" \
|
|
721
|
+
-srcfolder "$APP_PATH" \
|
|
722
|
+
-ov \
|
|
723
|
+
-format UDZO \
|
|
724
|
+
"$DMG_PATH"
|
|
725
|
+
|
|
726
|
+
# Submit for notarization
|
|
727
|
+
echo "Submitting for notarization..."
|
|
728
|
+
xcrun notarytool submit "$DMG_PATH" \
|
|
729
|
+
--apple-id "$APPLE_ID" \
|
|
730
|
+
--team-id "$TEAM_ID" \
|
|
731
|
+
--password "$APP_SPECIFIC_PASSWORD" \
|
|
732
|
+
--wait
|
|
733
|
+
|
|
734
|
+
# Staple the ticket
|
|
735
|
+
echo "Stapling ticket..."
|
|
736
|
+
xcrun stapler staple "$DMG_PATH"
|
|
737
|
+
|
|
738
|
+
echo "Notarization completed!"
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### Windows Code Signing
|
|
742
|
+
|
|
743
|
+
```powershell
|
|
744
|
+
# scripts/sign-windows.ps1
|
|
745
|
+
|
|
746
|
+
param(
|
|
747
|
+
[string]$FilePath,
|
|
748
|
+
[string]$CertPath,
|
|
749
|
+
[string]$Password
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
# Sign executable
|
|
753
|
+
& "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" sign `
|
|
754
|
+
/f $CertPath `
|
|
755
|
+
/p $Password `
|
|
756
|
+
/tr http://timestamp.digicert.com `
|
|
757
|
+
/td sha256 `
|
|
758
|
+
/fd sha256 `
|
|
759
|
+
$FilePath
|
|
760
|
+
|
|
761
|
+
# Verify signature
|
|
762
|
+
& "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" verify /pa $FilePath
|
|
763
|
+
|
|
764
|
+
Write-Host "Signing completed!"
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
## CI/CD Pipelines
|
|
768
|
+
|
|
769
|
+
### GitHub Actions Workflow
|
|
770
|
+
|
|
771
|
+
```yaml
|
|
772
|
+
# .github/workflows/build.yml
|
|
773
|
+
name: Build and Release
|
|
774
|
+
|
|
775
|
+
on:
|
|
776
|
+
push:
|
|
777
|
+
tags:
|
|
778
|
+
- 'v*'
|
|
779
|
+
|
|
780
|
+
jobs:
|
|
781
|
+
build:
|
|
782
|
+
strategy:
|
|
783
|
+
fail-fast: false
|
|
784
|
+
matrix:
|
|
785
|
+
platform: [macos-latest, ubuntu-20.04, windows-latest]
|
|
786
|
+
|
|
787
|
+
runs-on: ${{ matrix.platform }}
|
|
788
|
+
|
|
789
|
+
steps:
|
|
790
|
+
- name: Checkout code
|
|
791
|
+
uses: actions/checkout@v4
|
|
792
|
+
|
|
793
|
+
- name: Setup Node.js
|
|
794
|
+
uses: actions/setup-node@v4
|
|
795
|
+
with:
|
|
796
|
+
node-version: 20
|
|
797
|
+
|
|
798
|
+
- name: Setup Rust
|
|
799
|
+
uses: dtolnay/rust-toolchain@stable
|
|
800
|
+
|
|
801
|
+
- name: Install dependencies (Ubuntu only)
|
|
802
|
+
if: matrix.platform == 'ubuntu-20.04'
|
|
803
|
+
run: |
|
|
804
|
+
sudo apt-get update
|
|
805
|
+
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
|
806
|
+
|
|
807
|
+
- name: Install frontend dependencies
|
|
808
|
+
run: npm install
|
|
809
|
+
|
|
810
|
+
- name: Build application
|
|
811
|
+
run: npm run tauri build
|
|
812
|
+
|
|
813
|
+
- name: Upload artifacts
|
|
814
|
+
uses: actions/upload-artifact@v4
|
|
815
|
+
with:
|
|
816
|
+
name: ${{ matrix.platform }}-build
|
|
817
|
+
path: |
|
|
818
|
+
src-tauri/target/release/bundle/*
|
|
819
|
+
|
|
820
|
+
release:
|
|
821
|
+
needs: build
|
|
822
|
+
runs-on: ubuntu-latest
|
|
823
|
+
steps:
|
|
824
|
+
- name: Download artifacts
|
|
825
|
+
uses: actions/download-artifact@v4
|
|
826
|
+
|
|
827
|
+
- name: Create Release
|
|
828
|
+
uses: softprops/action-gh-release@v1
|
|
829
|
+
with:
|
|
830
|
+
files: |
|
|
831
|
+
macos-latest-build/**/*
|
|
832
|
+
ubuntu-20.04-build/**/*
|
|
833
|
+
windows-latest-build/**/*
|
|
834
|
+
env:
|
|
835
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
### Multi-Platform Build Matrix
|
|
839
|
+
|
|
840
|
+
```yaml
|
|
841
|
+
# .github/workflows/release.yml
|
|
842
|
+
name: Release
|
|
843
|
+
|
|
844
|
+
on:
|
|
845
|
+
workflow_dispatch:
|
|
846
|
+
push:
|
|
847
|
+
tags:
|
|
848
|
+
- 'v*'
|
|
849
|
+
|
|
850
|
+
jobs:
|
|
851
|
+
build:
|
|
852
|
+
strategy:
|
|
853
|
+
matrix:
|
|
854
|
+
include:
|
|
855
|
+
- os: ubuntu-20.04
|
|
856
|
+
target: x86_64-unknown-linux-gnu
|
|
857
|
+
artifact: deb, appimage
|
|
858
|
+
- os: macos-latest
|
|
859
|
+
target: x86_64-apple-darwin
|
|
860
|
+
artifact: dmg, app
|
|
861
|
+
- os: macos-latest
|
|
862
|
+
target: aarch64-apple-darwin
|
|
863
|
+
artifact: dmg, app
|
|
864
|
+
- os: windows-latest
|
|
865
|
+
target: x86_64-pc-windows-msvc
|
|
866
|
+
artifact: msi, exe
|
|
867
|
+
|
|
868
|
+
runs-on: ${{ matrix.os }}
|
|
869
|
+
|
|
870
|
+
steps:
|
|
871
|
+
- uses: actions/checkout@v4
|
|
872
|
+
|
|
873
|
+
- name: Setup Rust
|
|
874
|
+
uses: dtolnay/rust-toolchain@stable
|
|
875
|
+
with:
|
|
876
|
+
targets: ${{ matrix.target }}
|
|
877
|
+
|
|
878
|
+
- name: Build
|
|
879
|
+
run: cargo tauri build --target ${{ matrix.target }}
|
|
880
|
+
|
|
881
|
+
- name: Upload artifacts
|
|
882
|
+
uses: actions/upload-artifact@v4
|
|
883
|
+
with:
|
|
884
|
+
name: ${{ matrix.target }}
|
|
885
|
+
path: src-tauri/target/${{ matrix.target }}/release/bundle/
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
### Auto-Update Server
|
|
889
|
+
|
|
890
|
+
```rust
|
|
891
|
+
// Simple update server
|
|
892
|
+
use actix_web::{web, App, HttpResponse, HttpServer};
|
|
893
|
+
use serde::Serialize;
|
|
894
|
+
|
|
895
|
+
#[derive(Serialize)]
|
|
896
|
+
struct UpdateManifest {
|
|
897
|
+
version: String,
|
|
898
|
+
notes: String,
|
|
899
|
+
pub_date: String,
|
|
900
|
+
platforms: Platforms,
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
#[derive(Serialize)]
|
|
904
|
+
struct Platforms {
|
|
905
|
+
#[serde(rename = "darwin-x86_64")]
|
|
906
|
+
darwin_x86_64: Platform,
|
|
907
|
+
#[serde(rename = "darwin-aarch64")]
|
|
908
|
+
darwin_aarch64: Platform,
|
|
909
|
+
#[serde(rename = "linux-x86_64")]
|
|
910
|
+
linux_x86_64: Platform,
|
|
911
|
+
#[serde(rename = "windows-x86_64")]
|
|
912
|
+
windows_x86_64: Platform,
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
#[derive(Serialize)]
|
|
916
|
+
struct Platform {
|
|
917
|
+
signature: String,
|
|
918
|
+
url: String,
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
async fn update_manifest() -> HttpResponse {
|
|
922
|
+
let manifest = UpdateManifest {
|
|
923
|
+
version: "1.0.1".to_string(),
|
|
924
|
+
notes: "Bug fixes and improvements".to_string(),
|
|
925
|
+
pub_date: "2024-01-15T00:00:00Z".to_string(),
|
|
926
|
+
platforms: Platforms {
|
|
927
|
+
darwin_x86_64: Platform {
|
|
928
|
+
signature: "BASE64_SIGNATURE".to_string(),
|
|
929
|
+
url: "https://releases.myapp.com/myapp-1.0.1-x64.dmg".to_string(),
|
|
930
|
+
},
|
|
931
|
+
darwin_aarch64: Platform {
|
|
932
|
+
signature: "BASE64_SIGNATURE".to_string(),
|
|
933
|
+
url: "https://releases.myapp.com/myapp-1.0.1-arm64.dmg".to_string(),
|
|
934
|
+
},
|
|
935
|
+
linux_x86_64: Platform {
|
|
936
|
+
signature: "BASE64_SIGNATURE".to_string(),
|
|
937
|
+
url: "https://releases.myapp.com/myapp-1.0.1-amd64.AppImage".to_string(),
|
|
938
|
+
},
|
|
939
|
+
windows_x86_64: Platform {
|
|
940
|
+
signature: "BASE64_SIGNATURE".to_string(),
|
|
941
|
+
url: "https://releases.myapp.com/myapp-1.0.1-x64-setup.exe".to_string(),
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
HttpResponse::Ok().json(manifest)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
#[actix_web::main]
|
|
950
|
+
async fn main() -> std::io::Result<()> {
|
|
951
|
+
HttpServer::new(|| {
|
|
952
|
+
App::new()
|
|
953
|
+
.route("/update-manifest.json", web::get().to(update_manifest))
|
|
954
|
+
})
|
|
955
|
+
.bind(("127.0.0.1", 8080))?
|
|
956
|
+
.run()
|
|
957
|
+
.await
|
|
958
|
+
}
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
This comprehensive testing and deployment guide covers everything from unit tests to production CI/CD pipelines, enabling reliable cross-platform distribution of Rust desktop applications.
|