Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions scripts/dev-macos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
set -euo pipefail

# ========================================
# MD++ macOS 开发环境启动脚本
# Usage: bash scripts/dev-macos.sh [options]
# --skip-install 跳过依赖安装(假定 node_modules 已存在)
# --check-only 仅检查环境,不启动开发服务
# -h, --help 显示帮助
# ========================================

# 切到仓库根目录
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$REPO_ROOT"

DEV_URL="http://127.0.0.1:14300"
PORT=14300

SKIP_INSTALL=0
CHECK_ONLY=0

while [[ $# -gt 0 ]]; do
case "$1" in
--skip-install) SKIP_INSTALL=1; shift ;;
--check-only) CHECK_ONLY=1; shift ;;
-h|--help)
sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*)
echo "未知参数: $1 (使用 -h 查看帮助)"
exit 1
;;
esac
done

# --- 步骤标题 ---
step() {
echo ""
echo "==> $1"
}

# --- 命令检查 ---
assert_cmd() {
local name="$1"
local hint="$2"
if ! command -v "$name" &>/dev/null; then
echo " ✗ 缺少命令 '$name'。$hint"
exit 1
fi
}

# --- 环境检查 ---
step "检查开发工具"
assert_cmd "node" "请安装 Node.js 20 或更新版本。"
assert_cmd "pnpm" "请安装 pnpm: npm install -g pnpm"
assert_cmd "cargo" "请安装 Rust stable: https://www.rust-lang.org/tools/install"

echo " ✓ Node: $(node --version)"
echo " ✓ pnpm: $(pnpm --version)"
echo " ✓ Cargo: $(cargo --version)"

# macOS 特定:检查 Xcode Command Line Tools
if [[ "$(uname)" == "Darwin" ]]; then
if xcode-select -p &>/dev/null; then
echo " ✓ Xcode Command Line Tools"
else
echo " ✗ Xcode Command Line Tools 未安装,请运行: xcode-select --install"
exit 1
fi
fi

# --- 依赖安装 ---
if [[ ! -d "$REPO_ROOT/node_modules" ]]; then
if [[ "$SKIP_INSTALL" -eq 1 ]]; then
echo ""
echo "node_modules 缺失。请去掉 --skip-install 参数,或先手动运行 'pnpm install'。"
exit 1
fi
step "安装前端依赖"
pnpm install
else
echo ""
echo "node_modules 已存在,跳过依赖安装。"
fi

step "使用 Tauri dev URL: $DEV_URL"

if [[ "$CHECK_ONLY" -eq 1 ]]; then
echo "检查完成。运行 scripts/dev-macos.sh 以开发模式启动 md++。"
exit 0
fi

# --- 端口检查 / 释放 ---
step "检查端口 $PORT"
PORT_PID="$(lsof -ti tcp:"$PORT" -sTCP:LISTEN 2>/dev/null || true)"
if [[ -n "$PORT_PID" ]]; then
echo "端口 $PORT 被 PID $PORT_PID 占用 —— 终止僵尸进程..."
if kill -9 "$PORT_PID" 2>/dev/null; then
sleep 1
echo "端口已释放。"
else
echo "无法终止 PID $PORT_PID(权限不足?),请手动处理后再试。"
exit 1
fi
else
echo "端口 $PORT 空闲。"
fi

# --- 启动开发服务 ---
step "启动 md++ 开发应用"
pnpm tauri dev
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ tauri-plugin-dialog = "2"
tauri-plugin-fs = "2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# file:// URL → 本地路径解析(macOS 通过 Apple Event 传入的文件路径是 URL 形式)
url = "2.5"
log = "0.4"
tauri-plugin-log = "2"
notify = "6"
Expand Down
41 changes: 38 additions & 3 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod commands;
mod watcher;

use tauri::Emitter;
use tauri::{Emitter, RunEvent};
use watcher::WatcherState;

/// 从 argv 中提取 .md / .markdown 文件路径(跳过 argv[0] 即 exe 路径)。
Expand All @@ -12,6 +12,22 @@ fn collect_md_files(args: Vec<String>) -> Vec<String> {
.collect()
}

/// 把 macOS Apple Event 传来的 file:// URL 转成本地路径,并只保留 .md / .markdown。
/// macOS 访达打开文件时,文件路径以 `file://...%20...` 形式经 `application:openURLs:`
/// 传入,既不在 argv 里,也需要 percent-decode 才能拿到真实路径。
fn urls_to_md_paths(urls: Vec<url::Url>) -> Vec<String> {
urls.into_iter()
.filter(|u| u.scheme() == "file")
.filter_map(|u| u.to_file_path().ok())
.filter(|p| {
let s = p.to_string_lossy();
s.ends_with(".md") || s.ends_with(".markdown")
})
.map(|p| p.to_string_lossy().to_string())
.collect()
}


#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let mut builder = tauri::Builder::default();
Expand Down Expand Up @@ -74,6 +90,25 @@ pub fn run() {
commands::recent::add_recent,
watcher::watch_files,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|app_handle, event| {
// macOS:访达双击打开 .md 时,文件路径经 Apple Event
// `application:openURLs:` 传入,既不在 argv 里(导致 argv 方案失效),
// 也需把 file:// URL 解析为本地路径。这里统一捕获并转发给前端。
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
if let RunEvent::Opened { urls } = event {
let files = urls_to_md_paths(urls);
if !files.is_empty() {
// 已运行实例的前端早已挂载监听;冷启动时前端可能尚未就绪,
// 延迟一小段以覆盖两种情形(前端 openTab 按路径去重,重复也无害)。
let handle = app_handle.clone();
tauri::async_runtime::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
let _ = handle.emit("open-on-startup", &files);
});
}
}
let _ = app_handle; // 非 macOS 平台占位,避免未使用告警
});
}
Loading
Loading