- MCP 新增 plan_onboarding(readOnly)与 onboard_project(幂等写操作):schema 各自单独成函数, dispatch 一行委托到 mcp_inject 生产入口(中间只做 JSON 类型解析);全局与项目路由都可见, 项目路由强制本项目、跨项目返回 scope_violation;onboard_project required 含 expected_digest 与 confirmed;共用可选选项 bypass_permissions / skip_targets;进 STRUCTURED_TOOLS 并给一行摘要 - Tauri 新增同名 async 命令(async fn + spawn_blocking),commands.ts 同步 OnboardOptions、 OnboardingPlan、OnboardReport 类型与 planOnboarding / onboardProject 封装 - 统筹决定(P8d 审查 ⑩):L1 Codex 列仅因 codex_project_untrusted 未就绪的格不计入 done, completion 单列「Codex 信任」(pending_user),next_actions 保留 codex_trust;仓库子目录与 worktree 补 next_action repo_layout 说明为何到不了 done;非 git 维持不能 done - 契约同步:project-context.yaml、agent-button-system.md、AGENTS.md 架构速查;P8d 卡记统筹决定落地 - cargo test 413 passed / 0 failed / 6 ignored;pnpm typecheck、pnpm test 通过;三轮 10 处变异全部被杀 Feature-Confirmed: true Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
593 lines
24 KiB
Rust
593 lines
24 KiB
Rust
mod commands;
|
||
mod credential_store;
|
||
mod db;
|
||
mod envoy;
|
||
mod mcp_inject;
|
||
mod mcp_server;
|
||
mod mcp_tools;
|
||
mod mcp_webhook;
|
||
mod models;
|
||
mod onboard;
|
||
use git2;
|
||
|
||
/// Codex 的 http_headers_helper 入口;只被本机同一可执行文件的专用参数调用。
|
||
pub fn mcp_client_auth_headers_json(project_digest: &str) -> Result<String, String> {
|
||
let token = credential_store::read_mcp_auth_token_for_digest(
|
||
&credential_store::WindowsCredentialStore,
|
||
project_digest,
|
||
)?;
|
||
serde_json::to_string(&serde_json::json!({ "Authorization": format!("Bearer {token}") }))
|
||
.map_err(|_| "MCP 认证头生成失败".to_string())
|
||
}
|
||
|
||
/// 两段文本忽略行尾差异(CRLF/LF)后是否相同。
|
||
pub(crate) fn same_text_ignoring_eol(left: &str, right: &str) -> bool {
|
||
left.replace("\r\n", "\n") == right.replace("\r\n", "\n")
|
||
}
|
||
|
||
/// 内容(忽略行尾差异)未变时不重写文件,返回是否真的写入。
|
||
/// 子项目常开 core.autocrlf:用 LF 重写 CRLF 检出的同内容文件会改变大小与修改时间,
|
||
/// git status 因此误报改动,修改时间也不再能说明「最后是谁改的」(2026-10-01 enterprise 归属梳理实测)。
|
||
/// 凡向子项目受跟踪文件写入,都应经此函数(或在其之前做更强的语义比较,如 JSON)。
|
||
pub(crate) fn write_if_content_changed(
|
||
path: &std::path::Path,
|
||
content: &str,
|
||
) -> std::io::Result<bool> {
|
||
if let Ok(existing) = std::fs::read_to_string(path) {
|
||
if same_text_ignoring_eol(&existing, content) {
|
||
return Ok(false);
|
||
}
|
||
}
|
||
std::fs::write(path, content).map(|()| true)
|
||
}
|
||
|
||
/// 接入写入汇(agent-client-parity P8c):接入包、蓝图规则、项目身份与 MCP 投影这些项目文件写入
|
||
/// 只经它读写。`DiskSink` 真实写入;onboard::plan 的记录汇只记录不落盘,并让后续步骤读到前面
|
||
/// 步骤的待写内容(同一文件被两步先后改写时,计划与实际写入算出同样的结果)。
|
||
pub(crate) trait WriteSink {
|
||
/// 读取当前内容(含本汇里尚未落盘的待写内容);文件不存在返回 Ok(None)。
|
||
fn read(&self, path: &std::path::Path) -> std::io::Result<Option<String>>;
|
||
/// 内容(忽略行尾)未变返回 Ok(false);否则写入(必要时先建父目录)并返回 Ok(true)。
|
||
fn write(&self, path: &std::path::Path, content: &str) -> std::io::Result<bool>;
|
||
/// 是否存在(含本汇里的待写内容)。「存在即跳过」的分发只需判存在,不读内容——
|
||
/// 子项目自有的非 UTF-8 文件(如 PowerShell 5 重定向生成的 UTF-16 lefthook.yml)照旧跳过,不报读取失败。
|
||
fn exists(&self, path: &std::path::Path) -> bool;
|
||
}
|
||
|
||
/// 写入序列中某个文件读写失败:`path` 指向真正出错的文件(计划的阻断项据此给出目标),
|
||
/// `reason` 为原有错误文案;转成 String 时只取文案,原有返回 String 的入口行为不变。
|
||
#[derive(Debug)]
|
||
pub(crate) struct WriteFailure {
|
||
pub(crate) path: std::path::PathBuf,
|
||
pub(crate) reason: String,
|
||
}
|
||
|
||
impl WriteFailure {
|
||
pub(crate) fn at(path: &std::path::Path) -> impl FnOnce(String) -> WriteFailure + '_ {
|
||
move |reason| WriteFailure {
|
||
path: path.to_path_buf(),
|
||
reason,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<WriteFailure> for String {
|
||
fn from(failure: WriteFailure) -> String {
|
||
failure.reason
|
||
}
|
||
}
|
||
|
||
/// 真实写入:内容不变不重写(write_if_content_changed),父目录按需创建。
|
||
pub(crate) struct DiskSink;
|
||
|
||
impl WriteSink for DiskSink {
|
||
fn exists(&self, path: &std::path::Path) -> bool {
|
||
path.exists()
|
||
}
|
||
|
||
fn read(&self, path: &std::path::Path) -> std::io::Result<Option<String>> {
|
||
match std::fs::read_to_string(path) {
|
||
Ok(text) => Ok(Some(text)),
|
||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||
Err(error) => Err(error),
|
||
}
|
||
}
|
||
|
||
fn write(&self, path: &std::path::Path, content: &str) -> std::io::Result<bool> {
|
||
if let Some(existing) = self.read(path)? {
|
||
if same_text_ignoring_eol(&existing, content) {
|
||
return Ok(false);
|
||
}
|
||
}
|
||
if let Some(parent) = path.parent() {
|
||
std::fs::create_dir_all(parent)?;
|
||
}
|
||
std::fs::write(path, content).map(|()| true)
|
||
}
|
||
}
|
||
|
||
/// 创建不弹 CMD 窗口的子进程(Windows 专用 CREATE_NO_WINDOW 标志)。
|
||
/// 所有后台静默命令都应通过此函数创建,避免在 Windows 上频繁闪出黑框。
|
||
pub(crate) fn silent_cmd(program: &str) -> std::process::Command {
|
||
let mut cmd = std::process::Command::new(program);
|
||
#[cfg(target_os = "windows")]
|
||
{
|
||
use std::os::windows::process::CommandExt;
|
||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||
}
|
||
cmd
|
||
}
|
||
|
||
use commands::{
|
||
activity::*,
|
||
blueprint::*,
|
||
buff::{apply_blueprint_buff, get_buff_status, list_buffed_projects, remove_blueprint_buff},
|
||
canvas::*,
|
||
cicd::*,
|
||
claude_config::get_beast_global_status,
|
||
devtools::*,
|
||
git_ops::*,
|
||
git_repos::*,
|
||
groups::*,
|
||
mentor::{
|
||
add_project_note, get_group_mentor_report, get_mentor_context, get_project_notes,
|
||
get_startup_alerts, run_startup_health_scan,
|
||
},
|
||
network::{
|
||
clear_wsl_proxy, get_wsl_ip, get_wsl_proxy_status, sync_wsl_proxy, test_win_proxy_ip,
|
||
test_wsl_proxy_ip,
|
||
},
|
||
project_scan::*,
|
||
project_tools::*,
|
||
projects::{
|
||
batch_create_projects, create_project, delete_project, get_project, get_projects,
|
||
scan_subfolders, update_project, update_project_tags,
|
||
},
|
||
proxy::*,
|
||
publish::*,
|
||
settings::{
|
||
check_mcp_health, detect_editors, get_agent_autostart_status, get_agent_runtime_status,
|
||
get_settings, run_health_check, search_editor_path, set_agent_autostart, update_settings,
|
||
},
|
||
shell::*,
|
||
spaces::*,
|
||
tags::*,
|
||
templates::*,
|
||
toolchain::*,
|
||
};
|
||
|
||
fn has_autostart_arg(args: impl IntoIterator<Item = std::ffi::OsString>) -> bool {
|
||
args.into_iter().any(|arg| arg == "--autostart")
|
||
}
|
||
|
||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||
pub fn run() {
|
||
tauri::Builder::default()
|
||
.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
|
||
// 第二个实例启动时,激活已有窗口
|
||
use tauri::Manager;
|
||
if let Some(w) = app.get_webview_window("main") {
|
||
let _ = w.show();
|
||
let _ = w.unminimize();
|
||
let _ = w.set_focus();
|
||
}
|
||
}))
|
||
.plugin(tauri_plugin_autostart::init(
|
||
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
|
||
Some(vec!["--autostart"]),
|
||
))
|
||
.plugin(tauri_plugin_http::init())
|
||
.plugin(tauri_plugin_opener::init())
|
||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||
.plugin(tauri_plugin_process::init())
|
||
.plugin(tauri_plugin_dialog::init())
|
||
.plugin(tauri_plugin_shell::init())
|
||
.setup(|app| {
|
||
use tauri::{
|
||
menu::{Menu, MenuItem},
|
||
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||
Manager,
|
||
};
|
||
|
||
let data_dir = app.path().app_data_dir().expect("no app data dir");
|
||
db::init(data_dir.clone());
|
||
commands::blueprint::init_user_conventions_dir(data_dir);
|
||
// 禁用 libgit2 目录所有者验证,允许访问 WSL UNC 路径(\\wsl.localhost\...)
|
||
unsafe {
|
||
let _ = git2::opts::set_verify_owner_validation(false);
|
||
}
|
||
// 种植内置模板(首次启动时执行)
|
||
commands::templates::seed_builtin_templates();
|
||
|
||
// ── 启动 MCP Server ───────────────────────────────────────
|
||
let mcp_port = mcp_server::read_port();
|
||
tauri::async_runtime::spawn(mcp_server::start(mcp_port));
|
||
|
||
// ── 三端共用的稳定 helper 入口与 DSH 托管块(best-effort,失败只记日志)──
|
||
// 每次启动把 shim 指向当前 exe,换构建目录后各客户端无需逐项目重注入。
|
||
if let Err(error) = mcp_inject::ensure_header_shim() {
|
||
db::log_event(
|
||
"mcp_inject",
|
||
"warn",
|
||
&format!("MCP helper 入口写入失败: {error}"),
|
||
Some(r#"{"client":"shim"}"#),
|
||
);
|
||
}
|
||
// 刷新 DSH 白名单要读所有项目根目录的 context.json,放到后台线程,不拖慢窗口启动
|
||
std::thread::spawn(|| mcp_inject::refresh_dsh_patch(None));
|
||
|
||
// ── MCP 单一服务器化一次性迁移(settings 标记位防重复)────
|
||
std::thread::spawn(|| {
|
||
std::thread::sleep(std::time::Duration::from_secs(10)); // 等 DB 就绪
|
||
mcp_inject::migrate_to_single_server();
|
||
});
|
||
|
||
// ── Gitea 远端轮询(G6):每 10 分钟拉取绑定仓库 commit ───
|
||
// 出站 HTTPS 采集,适配公网 Gitea + 内网炼境拓扑(webhook 不可达时的降级)
|
||
std::thread::spawn(|| {
|
||
std::thread::sleep(std::time::Duration::from_secs(30)); // 等 DB 与网络就绪
|
||
loop {
|
||
let _ = commands::gitea::poll_all_repos();
|
||
std::thread::sleep(std::time::Duration::from_secs(600));
|
||
}
|
||
});
|
||
|
||
// ── 启动健康扫描(异步,不阻塞窗口渲染)────────────────
|
||
tauri::async_runtime::spawn(async {
|
||
tokio::task::spawn_blocking(run_startup_health_scan)
|
||
.await
|
||
.ok();
|
||
});
|
||
|
||
// ── 启动蓝图 Buff 后台轮询 ────────────────────────────
|
||
commands::buff::start_blueprint_watchers(app.handle().clone());
|
||
|
||
// ── 系统托盘 ──────────────────────────────────────────────
|
||
let show_i = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?;
|
||
let quit_i = MenuItem::with_id(app, "quit", "退出炼境", true, None::<&str>)?;
|
||
let menu = Menu::with_items(app, &[&show_i, &quit_i])?;
|
||
|
||
TrayIconBuilder::new()
|
||
.icon(app.default_window_icon().unwrap().clone())
|
||
.menu(&menu)
|
||
.show_menu_on_left_click(false)
|
||
.tooltip("炼境")
|
||
.on_menu_event(|app, event| match event.id.as_ref() {
|
||
"quit" => app.exit(0),
|
||
"show" => {
|
||
if let Some(w) = app.get_webview_window("main") {
|
||
let _ = w.show();
|
||
let _ = w.set_focus();
|
||
}
|
||
}
|
||
_ => {}
|
||
})
|
||
.on_tray_icon_event(|tray, event| {
|
||
// 左键单击托盘图标 → 显示/聚焦窗口
|
||
if let TrayIconEvent::Click {
|
||
button: MouseButton::Left,
|
||
button_state: MouseButtonState::Up,
|
||
..
|
||
} = event
|
||
{
|
||
let app = tray.app_handle();
|
||
if let Some(w) = app.get_webview_window("main") {
|
||
let _ = w.show();
|
||
let _ = w.set_focus();
|
||
}
|
||
}
|
||
})
|
||
.build(app)?;
|
||
|
||
// ── 关闭窗口 → 最小化到托盘 ──────────────────────────────
|
||
if let Some(window) = app.get_webview_window("main") {
|
||
let win = window.clone();
|
||
window.on_window_event(move |event| {
|
||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||
api.prevent_close();
|
||
let _ = win.hide();
|
||
}
|
||
});
|
||
if has_autostart_arg(std::env::args_os()) {
|
||
let _ = window.hide();
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
})
|
||
.invoke_handler(tauri::generate_handler![
|
||
// projects
|
||
get_projects,
|
||
get_project,
|
||
create_project,
|
||
update_project,
|
||
update_project_tags,
|
||
delete_project,
|
||
scan_subfolders,
|
||
batch_create_projects,
|
||
// activity
|
||
get_activity,
|
||
add_activity,
|
||
// groups
|
||
get_groups,
|
||
create_group,
|
||
update_group,
|
||
delete_group,
|
||
reorder_groups,
|
||
get_group_maps,
|
||
add_group_member,
|
||
remove_group_member,
|
||
// settings
|
||
get_settings,
|
||
update_settings,
|
||
detect_editors,
|
||
search_editor_path,
|
||
check_mcp_health,
|
||
get_agent_runtime_status,
|
||
get_agent_autostart_status,
|
||
set_agent_autostart,
|
||
run_health_check,
|
||
// project tools
|
||
get_project_tools,
|
||
add_project_tool,
|
||
update_project_tool,
|
||
remove_project_tool,
|
||
launch_tool,
|
||
// global quick launch
|
||
get_global_tools,
|
||
add_global_tool,
|
||
update_global_tool,
|
||
remove_global_tool,
|
||
// devtools
|
||
scan_dev_tools,
|
||
scan_wsl_dev_tools,
|
||
get_dev_tools,
|
||
save_dev_tools,
|
||
delete_dev_tool,
|
||
upsert_dev_tool,
|
||
// wsl proxy
|
||
get_wsl_ip,
|
||
sync_wsl_proxy,
|
||
get_wsl_proxy_status,
|
||
clear_wsl_proxy,
|
||
test_wsl_proxy_ip,
|
||
test_win_proxy_ip,
|
||
// v2rayn import
|
||
scan_v2rayn_nodes,
|
||
detect_v2rayn_db_path,
|
||
get_active_v2rayn_node,
|
||
// project env scan
|
||
scan_project_env,
|
||
get_project_env_tools,
|
||
save_project_env_tools,
|
||
get_toolchain_status,
|
||
prepare_toolchain,
|
||
// shell
|
||
open_editor,
|
||
open_terminal,
|
||
// git ops
|
||
git_clone,
|
||
git_status,
|
||
git_fetch,
|
||
git_pull,
|
||
git_push,
|
||
git_head_commit,
|
||
git_upstream_behind,
|
||
git_create_branch,
|
||
git_list_branches,
|
||
git_checkout_branch,
|
||
// spaces
|
||
get_spaces,
|
||
create_space,
|
||
update_space,
|
||
delete_space,
|
||
set_current_space,
|
||
get_deploy_commands,
|
||
add_deploy_command,
|
||
remove_deploy_command,
|
||
execute_deploy,
|
||
// git repos registry
|
||
get_git_repos,
|
||
import_git_repos,
|
||
update_git_repo_deploy,
|
||
mount_repo,
|
||
delete_git_repo,
|
||
// 本地 git 工具
|
||
scan_local_git,
|
||
git_init_and_commit,
|
||
git_remote_set,
|
||
register_and_link_repo,
|
||
write_gitignore,
|
||
resolve_wsl_path,
|
||
get_wsl_distros,
|
||
// project templates
|
||
list_templates,
|
||
get_template,
|
||
save_template,
|
||
delete_template,
|
||
create_project_from_template,
|
||
// tags
|
||
get_tags,
|
||
create_tag,
|
||
update_tag,
|
||
delete_tag,
|
||
get_project_tags,
|
||
set_project_tags,
|
||
get_all_project_tag_maps,
|
||
// cicd
|
||
get_cicd_config,
|
||
save_cicd_config,
|
||
generate_workflow,
|
||
plan_cicd_workflow,
|
||
apply_cicd_workflow,
|
||
// blueprint
|
||
get_blueprint,
|
||
generate_blueprint_prompt,
|
||
get_blueprint_status,
|
||
sync_blueprint_rules,
|
||
get_flywheel_stats,
|
||
generate_muhe_prompt,
|
||
archive_project_docs,
|
||
get_user_conventions_status,
|
||
save_user_conventions,
|
||
reset_conventions_to_builtin,
|
||
// canvas state
|
||
get_canvas_state,
|
||
save_canvas_state,
|
||
// blueprint buff
|
||
apply_blueprint_buff,
|
||
remove_blueprint_buff,
|
||
get_buff_status,
|
||
list_buffed_projects,
|
||
// onboarding pack
|
||
commands::onboarding::apply_onboarding_pack,
|
||
commands::onboarding::probe_client_parity,
|
||
commands::onboarding::plan_onboarding,
|
||
commands::onboarding::onboard_project,
|
||
// agent infrastructure
|
||
commands::agent_infra::scan_agent_infra_stack,
|
||
commands::agent_infra::generate_agent_infra_files,
|
||
commands::agent_infra::scan_doc_freshness,
|
||
commands::agent_infra::get_doc_freshness_cache,
|
||
commands::agent_infra::get_agent_health,
|
||
commands::commit_metrics::get_commit_stats,
|
||
commands::commit_metrics::ingest_full_git_history,
|
||
commands::commit_metrics::get_project_commit_health,
|
||
commands::commit_metrics::get_all_commit_health,
|
||
commands::risk::get_high_risk_scopes,
|
||
// mentor
|
||
get_mentor_context,
|
||
get_project_notes,
|
||
add_project_note,
|
||
get_group_mentor_report,
|
||
get_startup_alerts,
|
||
// user todos
|
||
commands::user_todos::get_user_todos,
|
||
commands::user_todos::add_user_todo,
|
||
commands::user_todos::toggle_user_todo,
|
||
commands::user_todos::delete_user_todo,
|
||
// servers
|
||
commands::servers::get_servers,
|
||
commands::servers::add_server,
|
||
commands::servers::edit_server,
|
||
commands::servers::delete_server,
|
||
commands::servers::link_server,
|
||
commands::servers::unlink_server,
|
||
commands::servers::get_project_server_links,
|
||
commands::servers::get_server_project_links,
|
||
// ssh config & vscode remote
|
||
commands::ssh_config::sync_ssh_config,
|
||
commands::ssh_config::get_ssh_config_status,
|
||
commands::ssh_config::check_vscode_status,
|
||
commands::ssh_config::open_vscode_remote,
|
||
commands::ssh_config::open_server_terminal,
|
||
commands::ssh_config::parse_ssh_config_entries,
|
||
commands::ssh_config::import_ssh_config_entries,
|
||
// server software & mirrors
|
||
commands::server_software::get_github_mirrors,
|
||
commands::server_software::add_github_mirror,
|
||
commands::server_software::delete_github_mirror,
|
||
commands::server_software::test_mirror_local,
|
||
commands::server_software::get_server_network_config,
|
||
commands::server_software::set_server_mirror,
|
||
commands::server_software::get_server_software_list,
|
||
commands::server_software::add_server_software,
|
||
commands::server_software::update_server_software,
|
||
commands::server_software::delete_server_software,
|
||
commands::server_software::generate_install_command,
|
||
commands::server_software::get_software_categories,
|
||
commands::server_software::add_software_category,
|
||
commands::server_software::update_software_category,
|
||
commands::server_software::delete_software_category,
|
||
// api keys
|
||
commands::api_keys::get_api_keys,
|
||
commands::api_keys::add_api_key,
|
||
commands::api_keys::edit_api_key,
|
||
commands::api_keys::delete_api_key,
|
||
commands::api_keys::reveal_api_key,
|
||
// project development variables (human filling; no reveal command)
|
||
commands::dev_variables::list_project_dev_variables,
|
||
commands::dev_variables::save_project_dev_variable,
|
||
commands::dev_variables::save_project_test_account,
|
||
commands::dev_variables::reveal_project_dev_variable,
|
||
commands::dev_variables::revoke_project_dev_variable,
|
||
// host apps
|
||
commands::host_apps::get_host_apps,
|
||
commands::host_apps::add_host_app,
|
||
commands::host_apps::edit_host_app,
|
||
commands::host_apps::delete_host_app,
|
||
// server service configs
|
||
commands::service_config::get_service_configs,
|
||
commands::service_config::add_service_config,
|
||
commands::service_config::edit_service_config,
|
||
commands::service_config::delete_service_config,
|
||
// gitea integration
|
||
commands::gitea::gitea_save_instance,
|
||
commands::gitea::gitea_list_instances,
|
||
commands::gitea::gitea_delete_instance,
|
||
commands::gitea::gitea_update_instance,
|
||
commands::gitea::gitea_test_connection,
|
||
commands::gitea::get_project_resource_status,
|
||
commands::gitea::gitea_list_linked_repos,
|
||
commands::gitea::gitea_get_repo_link,
|
||
commands::gitea::gitea_create_repo,
|
||
commands::gitea::gitea_migrate_repo,
|
||
commands::gitea::gitea_link_repo,
|
||
commands::gitea::gitea_setup_webhook,
|
||
commands::gitea::gitea_unlink_repo,
|
||
commands::gitea::gitea_poll_now,
|
||
// beast status (read-only)
|
||
get_beast_global_status,
|
||
// source library
|
||
commands::source_library::get_source_categories,
|
||
commands::source_library::create_source_category,
|
||
commands::source_library::update_source_category,
|
||
commands::source_library::delete_source_category,
|
||
commands::source_library::get_source_projects,
|
||
commands::source_library::create_source_project,
|
||
commands::source_library::update_source_project,
|
||
commands::source_library::delete_source_project,
|
||
commands::source_library::get_source_versions,
|
||
commands::source_library::add_source_version,
|
||
commands::source_library::set_primary_version,
|
||
commands::source_library::delete_source_version,
|
||
commands::source_library::update_version_notes,
|
||
commands::source_library::scan_source_dir_tree,
|
||
commands::source_library::reveal_source_in_explorer,
|
||
commands::source_library::copy_source_to,
|
||
// cloud products
|
||
commands::cloud_products::get_cloud_products,
|
||
commands::cloud_products::add_cloud_product,
|
||
commands::cloud_products::edit_cloud_product,
|
||
commands::cloud_products::delete_cloud_product,
|
||
// cloud db databases
|
||
commands::cloud_db_databases::get_cloud_databases,
|
||
commands::cloud_db_databases::add_cloud_database,
|
||
commands::cloud_db_databases::edit_cloud_database,
|
||
commands::cloud_db_databases::delete_cloud_database,
|
||
// docker apps
|
||
commands::docker_apps::get_docker_apps,
|
||
commands::docker_apps::add_docker_app,
|
||
commands::docker_apps::edit_docker_app,
|
||
commands::docker_apps::delete_docker_app,
|
||
])
|
||
.run(tauri::generate_context!())
|
||
.expect("error while running tauri application");
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
#[test]
|
||
fn autostart_launch_argument_is_explicit() {
|
||
assert!(super::has_autostart_arg([
|
||
"dev-manager-tauri.exe".into(),
|
||
"--autostart".into(),
|
||
]));
|
||
assert!(!super::has_autostart_arg([
|
||
"dev-manager-tauri.exe".into(),
|
||
"--inspect".into(),
|
||
]));
|
||
}
|
||
}
|