【新增】插件git同步模块,用于同步项目内容,加速项目开发

【调整】前端暗色问题
This commit is contained in:
chudong
2025-05-14 16:50:54 +08:00
parent 819ffe8d99
commit 9af06c8780
215 changed files with 19918 additions and 9710 deletions

View File

@@ -0,0 +1,589 @@
#!/bin/bash
# ===================================================
# 项目编译处理脚本 - build-operations.sh
# 用于处理项目编译、工作区选择和编译结果检查
# ===================================================
# 依赖文件操作脚本的函数
source "$(dirname "$0")/file-operations.sh"
# 检查依赖
check_dependencies() {
local deps=("pnpm" "git")
local missing_deps=()
# 检查基本依赖
for dep in "${deps[@]}"; do
if ! command -v "$dep" &> /dev/null; then
missing_deps+=("$dep")
fi
done
# 检查 yq
if ! command -v yq &> /dev/null; then
log_error "未安装 yq这是必需的依赖"
log_info "请按照以下步骤安装 yq"
case "$(uname -s)" in
"Darwin")
log_info "1. 使用 Homebrew 安装:"
log_info " brew install yq"
;;
"Linux")
log_info "1. 使用包管理器安装:"
log_info " # Ubuntu/Debian"
log_info " sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64"
log_info " sudo chmod a+x /usr/local/bin/yq"
log_info " # CentOS/RHEL"
log_info " sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64"
log_info " sudo chmod a+x /usr/local/bin/yq"
;;
"MINGW"*|"MSYS"*)
log_info "1. 使用 Chocolatey 安装:"
log_info " choco install yq"
;;
esac
log_info "2. 安装完成后重新运行此脚本"
exit 1
fi
# 检查其他缺失的依赖
if [[ ${#missing_deps[@]} -gt 0 ]]; then
log_error "未找到必要的依赖: ${missing_deps[*]}"
exit 1
fi
}
# 检测操作系统
detect_os() {
case "$(uname -s)" in
"Darwin")
OS="macos"
;;
"Linux")
OS="linux"
;;
"MINGW"*|"MSYS"*)
OS="windows"
;;
*)
log_error "不支持的操作系统"
exit 1
;;
esac
log_info "检测到操作系统: $OS"
}
# 解析工作区
parse_workspaces() {
if [[ ! -f "$PROJECT_ROOT/pnpm-workspace.yaml" ]]; then
log_error "未找到工作区配置文件"
return 1
fi
# 检查 apps 目录
if [[ ! -d "$PROJECT_ROOT/apps" ]]; then
log_error "未找到 apps 目录"
return 1
fi
# 扫描 apps 目录下的子目录作为工作区
local workspaces=()
for dir in "$PROJECT_ROOT/apps"/*/; do
if [[ -d "$dir" ]]; then
local rel_path="${dir#$PROJECT_ROOT/apps/}"
rel_path="${rel_path%/}"
workspaces+=("$rel_path")
fi
done
# 检查是否找到工作区
if [[ ${#workspaces[@]} -eq 0 ]]; then
log_error "未在 apps 目录下找到任何工作区"
return 1
fi
# 初始化选择
local selected_index=0
local max_index=$((${#workspaces[@]}-1))
# 设置当前步骤
CURRENT_STEP=1
# 显示工作区列表
while true; do
# 显示步骤状态
show_steps
# 打印工作区列表
printf "%s%s选择当前项目工作区%s\n\n" "${BOLD}" "${CYAN}" "${NC}"
for i in "${!workspaces[@]}"; do
if [[ $i -eq $selected_index ]]; then
printf "%s%s %s%s\n" \
"${BOLD}" "${GREEN}" "${workspaces[$i]}" "${NC}"
else
printf " %s%s%s\n" \
"${DIM}" "${workspaces[$i]}" "${NC}"
fi
done
# 显示操作提示
printf "\n%s%s使用上下箭头选择回车确认q键退出%s\n" \
"${BOLD}" "${CYAN}" "${NC}"
# 读取用户输入 - 统一处理方式
local key_pressed=""
local key
read -r -n 1 key
# 获取ASCII码用于调试
if [[ -z "$key" ]]; then
[[ "$DEBUG_MODE" == "true" ]] && printf "空字符,可能是回车键\n"
key_pressed="ENTER" # 空字符通常是回车键
else
[[ "$DEBUG_MODE" == "true" ]] && printf "按键ASCII码: %d\n" "'$key"
# 处理其他按键
case "$key" in
$'\x1b') # ESC 序列,包括方向键
read -r -n 2 seq
[[ "$DEBUG_MODE" == "true" ]] && printf "ESC序列: %s\n" "$seq"
case "$seq" in
"[A") key_pressed="UP" ;; # 上箭头
"[B") key_pressed="DOWN" ;; # 下箭头
*) key_pressed="ESC" ;; # 其他ESC序列
esac
;;
"q"|"Q") # q键退出
key_pressed="QUIT"
;;
*) # 其他按键忽略
key_pressed="OTHER"
;;
esac
fi
# 调试信息
[[ "$DEBUG_MODE" == "true" ]] && log_debug "按键被解析为: $key_pressed"
# 处理操作
case "$key_pressed" in
"UP") # 上箭头
if [[ $selected_index -gt 0 ]]; then
selected_index=$((selected_index-1))
else
# 如果已经是第一项,跳到最后一项
selected_index=$max_index
fi
;;
"DOWN") # 下箭头
if [[ $selected_index -lt $max_index ]]; then
selected_index=$((selected_index+1))
else
# 如果已经是最后一项,回到第一项
selected_index=0
fi
;;
"ENTER") # 回车
SELECTED_WORKSPACE="${workspaces[$selected_index]}"
STEP_WORKSPACE="$SELECTED_WORKSPACE"
CURRENT_STEP=2
return 0
;;
"QUIT") # 退出
log_error "操作已取消"
return 1
;;
esac
done
}
# 显示步骤状态
show_steps() {
clear
printf "\n%s%s项目同步向导%s\n\n" "${BOLD}" "${MAGENTA}" "${NC}"
# 定义灰色文本样式,用于未到达的步骤
local GRAY="${DIM}"
# 步骤一:选择工作区
if [[ $CURRENT_STEP -eq 1 ]]; then
printf "%s%s▶ 第一步:选择工作区%s\n" "${BOLD}" "${GREEN}" "${NC}"
elif [[ $CURRENT_STEP -gt 1 ]]; then
printf "%s%s✓ 第一步:选择工作区%s %s- %s%s%s\n" \
"${DIM}" "${GREEN}" "${NC}" \
"${DIM}" "${CYAN}" "$STEP_WORKSPACE" "${NC}"
else
printf "%s%s○ 第一步:选择工作区%s\n" "${GRAY}" "${GRAY}" "${NC}"
fi
# 步骤二选择Git仓库
if [[ $CURRENT_STEP -eq 2 ]]; then
printf "%s%s▶ 第二步选择Git仓库%s\n" "${BOLD}" "${GREEN}" "${NC}"
elif [[ $CURRENT_STEP -gt 2 ]]; then
printf "%s%s✓ 第二步选择Git仓库%s %s- %s%s%s\n" \
"${DIM}" "${GREEN}" "${NC}" \
"${DIM}" "${CYAN}" "$STEP_GIT_REPOS" "${NC}"
else
printf "%s%s○ 第二步选择Git仓库%s\n" "${GRAY}" "${GRAY}" "${NC}"
fi
# 步骤三:选择同步方式
if [[ $CURRENT_STEP -eq 3 ]]; then
printf "%s%s▶ 第三步:选择同步方式%s\n" "${BOLD}" "${GREEN}" "${NC}"
elif [[ $CURRENT_STEP -gt 3 ]]; then
printf "%s%s✓ 第三步:选择同步方式%s %s- %s%s%s\n" \
"${DIM}" "${GREEN}" "${NC}" \
"${DIM}" "${CYAN}" "$STEP_SYNC_MODE" "${NC}"
else
printf "%s%s○ 第三步:选择同步方式%s\n" "${GRAY}" "${GRAY}" "${NC}"
fi
# 步骤四:执行同步
if [[ $CURRENT_STEP -eq 4 ]]; then
printf "%s%s▶ 第四步:执行同步%s\n" "${BOLD}" "${GREEN}" "${NC}"
elif [[ $CURRENT_STEP -gt 4 ]]; then
printf "%s%s✓ 第四步:执行同步%s\n" "${DIM}" "${GREEN}" "${NC}"
else
printf "%s%s○ 第四步:执行同步%s\n" "${GRAY}" "${GRAY}" "${NC}"
fi
# 分隔线
printf "\n"
show_separator
printf "\n"
}
# 编译执行模块
build_workspace() {
log_info "开始编译工作区: $SELECTED_WORKSPACE"
# 切换到项目根目录
cd "$PROJECT_ROOT" || {
log_error "无法切换到项目根目录"
return 1
}
# 执行编译命令
log_info "执行编译命令: pnpm build --filter $SELECTED_WORKSPACE"
if pnpm build --filter "$SELECTED_WORKSPACE"; then
log_info "编译成功"
return 0
else
log_error "编译失败"
return 1
fi
}
# 检查编译结果
check_build_result() {
local workspace_path="$PROJECT_ROOT/apps/$SELECTED_WORKSPACE"
local dist_path="$workspace_path/dist"
if [[ ! -d "$dist_path" ]]; then
log_error "未找到编译输出目录: $dist_path"
return 1
fi
if [[ -z "$(ls -A "$dist_path")" ]]; then
log_error "编译输出目录为空"
return 1
fi
log_info "编译结果检查通过"
return 0
}
# 并行编译功能
parallel_build_workspaces() {
local workspaces=("$@")
local pids=()
local results=()
# 检查参数
if [[ ${#workspaces[@]} -eq 0 ]]; then
log_error "未指定工作区"
return 1
fi
log_info "开始并行编译 ${#workspaces[@]} 个工作区..."
# 为每个工作区启动编译进程
for workspace in "${workspaces[@]}"; do
(
log_info "开始编译工作区: $workspace"
if pnpm build --filter "$workspace"; then
echo "$workspace|success" > "/tmp/build_${workspace}.result"
else
echo "$workspace|failed" > "/tmp/build_${workspace}.result"
fi
) &
pids+=($!)
done
# 等待所有编译进程完成
for pid in "${pids[@]}"; do
wait "$pid" || {
log_error "编译进程异常退出"
return 1
}
done
# 收集编译结果
local success=true
for workspace in "${workspaces[@]}"; do
if [[ -f "/tmp/build_${workspace}.result" ]]; then
local result=$(cat "/tmp/build_${workspace}.result")
local status=$(echo "$result" | cut -d'|' -f2)
if [[ "$status" == "failed" ]]; then
log_error "工作区 $workspace 编译失败"
success=false
else
log_info "工作区 $workspace 编译成功"
fi
rm -f "/tmp/build_${workspace}.result"
else
log_error "工作区 $workspace 编译结果文件丢失"
success=false
fi
done
if [[ "$success" == "true" ]]; then
log_info "所有工作区编译完成"
return 0
else
log_error "部分工作区编译失败"
return 1
fi
}
# 显示选项列表
show_option_list() {
local title="$1"
shift
local selected_index="${!#}" # 取最后一个参数
local items=("${@:1:$(($#-1))}") # 除最后一个参数外的所有参数
local padding=2 # 选中标识的宽度
# 显示标题
show_title "$title"
printf "%s%s%s\n" "${DIM}" "使用方向键选择回车确认q 退出" "${NC}"
show_separator
# 显示列表
for i in "${!items[@]}"; do
if [[ $i -eq $selected_index ]]; then
# 选中项:使用固定宽度的选中标识
printf "%s%s%s%*s%s%s%s\n" \
"${BOLD}" "${CYAN}" "" \
"$padding" "" \
"${GREEN}" "${items[$i]}" "${NC}"
else
# 未选中项:使用相同的缩进保持对齐
printf "%*s%s%s%s\n" \
"$((padding + 1))" "" \
"${DIM}" "${items[$i]}" "${NC}"
fi
done
show_separator
}
# 显示多选列表
show_multi_select_list() {
local title="$1"
shift
local items=()
local i=0
# 收集所有项目,直到遇到特殊标记 "--INDICES--"
while [[ $i -lt $# && "$1" != "--INDICES--" ]]; do
items+=("$1")
shift
((i++))
done
shift # 跳过 "--INDICES--" 标记
local selected_indices=($@) # 剩余的参数都是选中的索引
local selected_index="${selected_indices[0]}" # 第一个是当前光标位置
# 移除当前索引,只保留选中项索引
selected_indices=("${selected_indices[@]:1}")
local padding=2 # 选中标识的宽度
# 显示标题
show_title "$title"
printf "%s%s%s\n" "${DIM}" "使用数字键1选中/0取消回车确认q 退出" "${NC}"
show_separator
# 显示列表
for i in "${!items[@]}"; do
# 检查当前索引是否在选中列表中
local is_selected=false
for sel_idx in "${selected_indices[@]}"; do
if [[ $i -eq $sel_idx ]]; then
is_selected=true
break
fi
done
if [[ $i -eq $selected_index ]]; then
# 当前光标位置项 - 使用青色箭头标识
if [[ "$is_selected" == "true" ]]; then
# 选中项 - 绿色文本,带复选框
printf "%s%s%s%*s%s%s%s\n" \
"${BOLD}" "${CYAN}" "" \
"$padding" "" \
"${GREEN}" "[✓] ${items[$i]}" "${NC}"
else
# 未选中项 - 灰色文本,不带复选框
printf "%s%s%s%*s%s%s%s\n" \
"${BOLD}" "${CYAN}" "" \
"$padding" "" \
"${DIM}" "[ ] ${items[$i]}" "${NC}"
fi
else
# 非当前光标位置项
if [[ "$is_selected" == "true" ]]; then
# 选中项 - 绿色文本,带复选框
printf "%*s%s%s%s\n" \
"$((padding + 1))" "" \
"${GREEN}" "[✓] ${items[$i]}" "${NC}"
else
# 未选中项 - 灰色文本,不带复选框
printf "%*s%s%s%s\n" \
"$((padding + 1))" "" \
"${DIM}" "[ ] ${items[$i]}" "${NC}"
fi
fi
done
show_separator
}
# 显示帮助信息
show_help() {
show_title "使用帮助"
printf "%s%s用法:%s %s [选项]\n\n" "${BOLD}" "${GREEN}" "${NC}" "$0"
printf "%s%s选项:%s\n" "${BOLD}" "${BLUE}" "${NC}"
printf " %s-w, --workspace%s WORKSPACE %s指定工作区%s\n" "${GREEN}" "${NC}" "${CYAN}" "${NC}"
printf " %s-t, --target%s DIR %s指定目标 Git 仓库路径%s\n" "${GREEN}" "${NC}" "${CYAN}" "${NC}"
printf " %s-b, --branch%s BRANCH %s指定分支名称%s\n" "${GREEN}" "${NC}" "${CYAN}" "${NC}"
printf " %s-s, --sync-structure%s %s同步项目结构%s\n" "${GREEN}" "${NC}" "${CYAN}" "${NC}"
printf " %s-p, --parallel%s %s并行编译%s\n" "${GREEN}" "${NC}" "${CYAN}" "${NC}"
printf " %s-d, --dry-run%s %s干运行模式%s\n" "${GREEN}" "${NC}" "${CYAN}" "${NC}"
printf " %s--debug%s %s调试模式显示详细日志%s\n" "${GREEN}" "${NC}" "${CYAN}" "${NC}"
printf " %s-h, --help%s %s显示帮助信息%s\n" "${GREEN}" "${NC}" "${CYAN}" "${NC}"
show_separator
}
# 命令行参数解析
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-w|--workspace)
SELECTED_WORKSPACE="$2"
shift 2
;;
-t|--target)
TARGET_GIT_DIR="$2"
shift 2
;;
-b|--branch)
BRANCH="$2"
shift 2
;;
-s|--sync-structure)
SYNC_STRUCTURE=true
shift
;;
-p|--parallel)
PARALLEL_BUILD=true
shift
;;
-d|--dry-run)
DRY_RUN=true
shift
;;
--debug)
DEBUG_MODE=true
shift
;;
-h|--help)
show_help
exit 0
;;
*)
log_error "未知参数: $1"
show_help
exit 1
;;
esac
done
}
# 初始化插件目录
init_plugins() {
if [[ -z "$PLUGINS_DIR" ]]; then
log_error "插件目录未初始化,请确保已找到项目根目录"
return 1
fi
if [[ ! -d "$PLUGINS_DIR" ]]; then
mkdir -p "$PLUGINS_DIR" || {
log_error "创建插件目录失败: $PLUGINS_DIR"
return 1
}
log_info "已创建插件目录: $PLUGINS_DIR"
fi
}
# 加载插件
load_plugins() {
if [[ -z "$PLUGINS_DIR" ]]; then
log_error "插件目录未初始化,请确保已找到项目根目录"
return 1
fi
if [[ -d "$PLUGINS_DIR" ]]; then
for plugin in "$PLUGINS_DIR"/*.sh; do
if [[ -f "$plugin" ]]; then
if ! source "$plugin"; then
log_error "加载插件失败: $(basename "$plugin")"
continue
fi
log_info "已加载插件: $(basename "$plugin")"
fi
done
fi
}
# 插件钩子函数
run_hook() {
local hook_name="$1"
shift
# 检查是否存在对应的钩子函数
if declare -F "hook_${hook_name}" > /dev/null; then
"hook_${hook_name}" "$@"
fi
}
# 导出函数
export -f check_dependencies
export -f detect_os
export -f parse_workspaces
export -f show_steps
export -f build_workspace
export -f check_build_result
export -f parallel_build_workspaces
export -f show_option_list
export -f show_multi_select_list
export -f show_help
export -f parse_args
export -f init_plugins
export -f load_plugins
export -f run_hook

View File

@@ -0,0 +1,7 @@
#!/bin/sh
# 查找并删除当前目录及子目录下所有 node_modules 文件夹和pnpm-lock.yaml文件 和 .turbo 文件夹
find . -name "node_modules" -type d -prune -exec rm -rf {} +
find . -name "pnpm-lock.yaml" -type f -delete
find . -name "dist" -type d -prune -exec rm -rf {} +
find . -name ".turbo" -type d -prune -exec rm -rf {} +
echo "删除成功"

View File

@@ -0,0 +1,620 @@
#!/bin/bash
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 检测操作系统并设置相关变量
if [[ "$OSTYPE" == "darwin"* ]]; then
OS="macOS"
SETTINGS_DIR="$HOME/Library/Application Support/Cursor"
EXTENSIONS_DIR="$HOME/.cursor"
USER_DIR="$SETTINGS_DIR/User"
TEMP_ROOT="/tmp"
PATH_SEP="/"
STAT_CMD="stat -f"
STAT_TIME_FORMAT="%Sm"
STAT_SIZE_FORMAT="%z"
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" || "$OSTYPE" == "cygwin" ]]; then
OS="Windows"
# Windows 下使用 APPDATA 环境变量
if [ -n "$APPDATA" ]; then
SETTINGS_DIR="$APPDATA/Cursor"
EXTENSIONS_DIR="$APPDATA/Cursor"
else
SETTINGS_DIR="$HOME/AppData/Roaming/Cursor"
EXTENSIONS_DIR="$HOME/AppData/Roaming/Cursor"
fi
USER_DIR="$SETTINGS_DIR/User"
TEMP_ROOT="$TEMP"
[ -z "$TEMP_ROOT" ] && TEMP_ROOT="$TMP"
[ -z "$TEMP_ROOT" ] && TEMP_ROOT="$HOME/AppData/Local/Temp"
PATH_SEP="/"
STAT_CMD="stat -c"
STAT_TIME_FORMAT="%y"
STAT_SIZE_FORMAT="%s"
else
echo -e "${RED}错误: 不支持的操作系统${NC}"
exit 1
fi
# 规范化路径
normalize_path() {
local path="$1"
echo "$path" | sed 's/\\/\//g'
}
# 备份目录
BACKUP_DIR="$(normalize_path "$HOME/cursor_backups")"
# 检查目录是否存在
if [ ! -d "$SETTINGS_DIR" ] && [ ! -d "$EXTENSIONS_DIR" ]; then
echo -e "${RED}错误: 未找到 Cursor 目录${NC}"
echo -e "${YELLOW}请确保 Cursor 编辑器已经安装并运行过至少一次${NC}"
exit 1
fi
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 获取文件修改时间
get_file_time() {
local file="$1"
if [[ "$OS" == "Windows" ]]; then
# 对于 Windows使用兼容的时间格式
$STAT_CMD "$STAT_TIME_FORMAT" "$file" 2>/dev/null || echo "Unknown"
else
$STAT_CMD "$STAT_TIME_FORMAT" "$file"
fi
}
# 获取文件大小
get_file_size() {
local file="$1"
if [[ "$OS" == "Windows" ]]; then
# 对于 Windows使用兼容的大小获取方式
$STAT_CMD "$STAT_SIZE_FORMAT" "$file" 2>/dev/null || echo "0"
else
$STAT_CMD "$STAT_SIZE_FORMAT" "$file"
fi
}
# 创建临时目录
create_temp_dir() {
local prefix="$1"
local temp_dir
if [[ "$OS" == "Windows" ]]; then
temp_dir="$(normalize_path "$TEMP_ROOT/$prefix")"
else
temp_dir="$TEMP_ROOT/$prefix"
fi
mkdir -p "$temp_dir"
echo "$temp_dir"
}
# 获取下一个可用的序号
get_next_sequence() {
local date=$1
local max_seq=0
# 查找同一天的备份,获取最大序号
find "$BACKUP_DIR" -maxdepth 1 -type d -name "cursor_${date}_*" | while read -r backup; do
if [ -d "$backup" ]; then
backup_name=$(basename "$backup")
# 提取日期和序号
if [[ $backup_name =~ ^cursor_${date}_([0-9]+)$ ]]; then
seq_num=${BASH_REMATCH[1]}
if (( seq_num > max_seq )); then
max_seq=$seq_num
fi
fi
fi
done
# 返回下一个序号
echo $((max_seq + 1))
}
# 获取插件列表
get_extensions_list() {
local extensions_dir="$(normalize_path "$1")"
local output_file="$(normalize_path "$2")"
if [ ! -d "$extensions_dir/extensions" ]; then
echo -e "${YELLOW}! 未找到插件目录${NC}"
return 1
fi
echo -e "${YELLOW}正在检查插件列表...${NC}"
# 创建一个临时文件来存储插件信息
local temp_list="$(create_temp_dir "cursor_ext_list")/extensions.tmp"
# 遍历插件目录
if [[ "$OS" == "Windows" ]]; then
# Windows 环境使用 dir /b 命令
(cd "$extensions_dir/extensions" && cmd //c "dir /b /ad" 2>/dev/null) | while read -r ext_name; do
local package_json="$extensions_dir/extensions/$ext_name/package.json"
package_json="$(normalize_path "$package_json")"
if [ -f "$package_json" ]; then
# 尝试从 package.json 中提取版本信息
local version=$(grep -o '"version": *"[^"]*"' "$package_json" 2>/dev/null | cut -d'"' -f4)
if [ -n "$version" ]; then
echo "$ext_name@$version" >> "$temp_list"
else
echo "$ext_name" >> "$temp_list"
fi
else
echo "$ext_name" >> "$temp_list"
fi
done
else
# Unix 环境使用 find 命令
find "$extensions_dir/extensions" -maxdepth 1 -type d | while read -r ext_dir; do
if [ "$ext_dir" != "$extensions_dir/extensions" ]; then
local ext_name=$(basename "$ext_dir")
local package_json="$ext_dir/package.json"
if [ -f "$package_json" ]; then
local version=$(grep -o '"version": *"[^"]*"' "$package_json" 2>/dev/null | cut -d'"' -f4)
if [ -n "$version" ]; then
echo "$ext_name@$version" >> "$temp_list"
else
echo "$ext_name" >> "$temp_list"
fi
else
echo "$ext_name" >> "$temp_list"
fi
fi
done
fi
# 排序插件列表
if [ -f "$temp_list" ]; then
sort "$temp_list" > "$output_file"
rm -f "$temp_list"
return 0
fi
rm -f "$temp_list"
return 1
}
# 显示插件列表差异
show_extensions_diff() {
local backup_list="$1"
local current_list="$2"
if [ ! -f "$backup_list" ] || [ ! -f "$current_list" ]; then
return 1
fi
echo -e "\n${YELLOW}插件对比:${NC}"
# 找出新增的插件
echo -e "\n${GREEN}新增的插件:${NC}"
comm -13 "$backup_list" "$current_list" | while read -r ext; do
echo -e "${GREEN}+ $ext${NC}"
done
# 找出删除的插件
echo -e "\n${RED}删除的插件:${NC}"
comm -23 "$backup_list" "$current_list" | while read -r ext; do
echo -e "${RED}- $ext${NC}"
done
# 找出相同的插件
echo -e "\n${YELLOW}保持不变的插件:${NC}"
comm -12 "$backup_list" "$current_list" | while read -r ext; do
echo -e " $ext"
done
}
# 创建备份
create_backup() {
# 生成日期和序号
DATE=$(date +"%Y%m%d")
SEQ=$(get_next_sequence "$DATE")
# 格式化序号为两位数
printf -v SEQ_PADDED "%02d" "$SEQ"
BACKUP_NAME="cursor_${DATE}_${SEQ_PADDED}"
BACKUP_PATH="$BACKUP_DIR/$BACKUP_NAME"
TEMP_PATH="/tmp/$BACKUP_NAME"
echo -e "${YELLOW}正在创建备份...${NC}"
echo -e "${YELLOW}操作系统: $OS${NC}"
echo -e "${YELLOW}设置目录: $SETTINGS_DIR${NC}"
echo -e "${YELLOW}插件目录: $EXTENSIONS_DIR${NC}"
# 创建临时目录
mkdir -p "$TEMP_PATH"
# 获取当前插件列表
local extensions_list="$TEMP_PATH/extensions.list"
if get_extensions_list "$EXTENSIONS_DIR" "$extensions_list"; then
echo -e "${GREEN}✓ 已保存插件列表${NC}"
echo -e "\n${YELLOW}当前安装的插件:${NC}"
cat "$extensions_list" | while read -r ext; do
echo " $ext"
done
echo
fi
# 备份设置文件
if [ -f "$USER_DIR/settings.json" ]; then
mkdir -p "$TEMP_PATH/User"
cp "$USER_DIR/settings.json" "$TEMP_PATH/User/"
echo -e "${GREEN}✓ 已备份设置文件${NC}"
else
echo -e "${YELLOW}! 未找到设置文件${NC}"
fi
# 备份扩展目录
if [ -d "$EXTENSIONS_DIR/extensions" ]; then
cp -r "$EXTENSIONS_DIR/extensions" "$TEMP_PATH/"
echo -e "${GREEN}✓ 已备份扩展目录${NC}"
else
echo -e "${YELLOW}! 未找到扩展目录${NC}"
fi
# 压缩备份
echo -e "${YELLOW}正在压缩备份...${NC}"
tar -czf "${BACKUP_PATH}.tar.gz" -C "/tmp" "$BACKUP_NAME"
# 清理临时目录
rm -rf "$TEMP_PATH"
echo -e "${GREEN}备份创建成功: ${BACKUP_PATH}.tar.gz${NC}"
}
# 还原备份
restore_backup() {
if [ -z "$1" ]; then
echo -e "${RED}错误: 请指定要还原的备份名称${NC}"
echo "用法: $0 restore <backup_name>"
exit 1
fi
BACKUP_NAME="$1"
BACKUP_PATH="$BACKUP_DIR/$BACKUP_NAME"
TEMP_PATH="/tmp/$BACKUP_NAME"
if [ ! -f "${BACKUP_PATH}.tar.gz" ]; then
echo -e "${RED}错误: 未找到备份文件: ${BACKUP_PATH}.tar.gz${NC}"
exit 1
fi
echo -e "${YELLOW}正在还原备份...${NC}"
echo -e "${YELLOW}操作系统: $OS${NC}"
echo -e "${YELLOW}设置目录: $SETTINGS_DIR${NC}"
echo -e "${YELLOW}插件目录: $EXTENSIONS_DIR${NC}"
# 解压备份到临时目录
echo -e "${YELLOW}正在解压备份...${NC}"
rm -rf "$TEMP_PATH"
tar -xzf "${BACKUP_PATH}.tar.gz" -C "/tmp"
# 获取当前插件列表
local current_list=$(mktemp)
local backup_list="$TEMP_PATH/extensions.list"
if get_extensions_list "$EXTENSIONS_DIR" "$current_list"; then
if [ -f "$backup_list" ]; then
show_extensions_diff "$backup_list" "$current_list"
echo
echo -n "是否继续还原? [y/N] "
read -r confirm
if [[ ! $confirm =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}取消还原操作${NC}"
rm -f "$current_list"
rm -rf "$TEMP_PATH"
return
fi
fi
fi
rm -f "$current_list"
# 还原设置文件
if [ -f "$TEMP_PATH/User/settings.json" ]; then
mkdir -p "$USER_DIR"
cp "$TEMP_PATH/User/settings.json" "$USER_DIR/"
echo -e "${GREEN}✓ 已还原设置文件${NC}"
else
echo -e "${YELLOW}! 备份中未找到设置文件${NC}"
fi
# 还原扩展目录
if [ -d "$TEMP_PATH/extensions" ]; then
rm -rf "$EXTENSIONS_DIR/extensions"
cp -r "$TEMP_PATH/extensions" "$EXTENSIONS_DIR/"
echo -e "${GREEN}✓ 已还原扩展目录${NC}"
else
echo -e "${YELLOW}! 备份中未找到扩展目录${NC}"
fi
# 清理临时目录
rm -rf "$TEMP_PATH"
echo -e "${GREEN}备份还原成功${NC}"
echo -e "${YELLOW}请重启 Cursor 编辑器以使更改生效${NC}"
}
# 删除备份
delete_backup() {
if [ -z "$1" ]; then
echo -e "${RED}错误: 请指定要删除的备份名称${NC}"
echo "用法: $0 delete <backup_name>"
exit 1
fi
BACKUP_NAME="$1"
BACKUP_PATH="$BACKUP_DIR/$BACKUP_NAME"
if [ ! -f "${BACKUP_PATH}.tar.gz" ]; then
echo -e "${RED}错误: 未找到备份文件: ${BACKUP_PATH}.tar.gz${NC}"
exit 1
fi
echo -e "${YELLOW}即将删除备份: $BACKUP_NAME${NC}"
echo -n "确认删除? [y/N] "
read -r confirm
if [[ $confirm =~ ^[Yy]$ ]]; then
rm -f "${BACKUP_PATH}.tar.gz"
echo -e "${GREEN}备份已删除: $BACKUP_NAME${NC}"
else
echo -e "${YELLOW}取消删除操作${NC}"
fi
}
# 删除所有备份
delete_all_backups() {
# 检查是否有备份
if ! list_backups; then
return 1
fi
echo -e "${YELLOW}警告: 即将删除所有备份!${NC}"
echo -n "确认删除所有备份? [y/N] "
read -r confirm
if [[ $confirm =~ ^[Yy]$ ]]; then
rm -f "$BACKUP_DIR"/cursor_*.tar.gz
echo -e "${GREEN}已删除所有备份${NC}"
else
echo -e "${YELLOW}取消删除操作${NC}"
fi
}
# 格式化文件大小
format_size() {
local size=$1
local units=("B" "KiB" "MiB" "GiB" "TiB")
local unit=0
while (( size > 1024 && unit < 4 )); do
size=$(( (size + 512) / 1024 ))
((unit++))
done
echo "${size}${units[$unit]}"
}
# 列出备份并返回备份名称数组
list_backups() {
if [ ! -d "$BACKUP_DIR" ]; then
echo -e "${YELLOW}未找到备份${NC}"
return 1
fi
# 创建一个数组来存储备份名称
backup_names=()
echo -e "${YELLOW}可用的备份:${NC}"
id=1
if [[ "$OS" == "Windows" ]]; then
# Windows 环境使用 dir 命令
(cd "$BACKUP_DIR" && cmd //c "dir /b *.tar.gz" 2>/dev/null) | sort -r | while read -r backup_file; do
local backup="$BACKUP_DIR/$backup_file"
backup="$(normalize_path "$backup")"
if [ -f "$backup" ]; then
local backup_name=$(basename "$backup" .tar.gz)
backup_names+=("$backup_name")
local backup_time=$(get_file_time "$backup")
local backup_size=$(get_file_size "$backup")
local formatted_size=$(format_size "$backup_size")
printf "%2d) %s (%s) [%8s]\n" $id "$backup_name" "$backup_time" "$formatted_size"
((id++))
fi
done
else
# Unix 环境使用 find 命令
find "$BACKUP_DIR" -maxdepth 1 -type f -name "cursor_*.tar.gz" | sort -r | while read -r backup; do
if [ -f "$backup" ]; then
local backup_name=$(basename "$backup" .tar.gz)
backup_names+=("$backup_name")
local backup_time=$(get_file_time "$backup")
local backup_size=$(get_file_size "$backup")
local formatted_size=$(format_size "$backup_size")
printf "%2d) %s (%s) [%8s]\n" $id "$backup_name" "$backup_time" "$formatted_size"
((id++))
fi
done
fi
# 如果没有找到备份
if [ ${#backup_names[@]} -eq 0 ]; then
echo -e "${YELLOW}没有可用的备份${NC}"
return 1
fi
return 0
}
# 根据ID获取备份名称
get_backup_by_id() {
local id=$1
local -a backup_names=()
# 获取所有备份名称并排序
find "$BACKUP_DIR" -maxdepth 1 -type f -name "cursor_*.tar.gz" | sort -r | while read -r backup; do
if [ -f "$backup" ]; then
backup_name=$(basename "$backup" .tar.gz)
backup_names+=("$backup_name")
fi
done
# 检查ID是否有效
if [ "$id" -le 0 ] || [ "$id" -gt "${#backup_names[@]}" ]; then
return 1
fi
# 返回对应的备份名称
echo "${backup_names[$((id-1))]}"
return 0
}
# 显示帮助信息
show_help() {
echo -e "${GREEN}Cursor 编辑器备份工具${NC}"
echo -e "${YELLOW}当前操作系统: $OS${NC}"
if [[ "$OS" == "macOS" ]]; then
echo -e "${YELLOW}设置目录: $SETTINGS_DIR${NC}"
echo -e "${YELLOW}插件目录: $EXTENSIONS_DIR${NC}"
else
echo -e "${YELLOW}Cursor 目录: $SETTINGS_DIR${NC}"
fi
echo -e "${YELLOW}备份目录: $BACKUP_DIR${NC}"
echo
echo "用法:"
echo " 创建备份: $0 backup"
echo " 还原备份: $0 restore <backup_name>"
echo " 删除备份: $0 delete <backup_name>"
echo " 删除所有: $0 delete-all"
echo " 列出备份: $0 list"
echo " 显示帮助: $0 help"
}
# 显示菜单并获取用户选择
show_menu() {
clear
echo -e "${GREEN}Cursor 编辑器备份工具${NC}"
echo -e "${YELLOW}当前操作系统: $OS${NC}"
if [[ "$OS" == "macOS" ]]; then
echo -e "${YELLOW}设置目录: $SETTINGS_DIR${NC}"
echo -e "${YELLOW}插件目录: $EXTENSIONS_DIR${NC}"
else
echo -e "${YELLOW}Cursor 目录: $SETTINGS_DIR${NC}"
fi
echo -e "${YELLOW}备份目录: $BACKUP_DIR${NC}"
echo
echo "请选择操作:"
echo "1) 创建备份"
echo "2) 还原备份"
echo "3) 删除备份"
echo "4) 删除所有备份"
echo "5) 列出备份"
echo "0) 退出"
echo
echo -n "请输入选项 [0-5]: "
read -r choice
case $choice in
1)
create_backup
;;
2)
# 显示可用备份并获取用户选择
echo
if list_backups; then
echo
echo -n "请输入要还原的备份ID: "
read -r backup_id
if [[ "$backup_id" =~ ^[0-9]+$ ]]; then
backup_name=$(get_backup_by_id "$backup_id")
if [ -n "$backup_name" ]; then
restore_backup "$backup_name"
else
echo -e "${RED}错误: 无效的备份ID${NC}"
fi
else
echo -e "${RED}错误: 请输入有效的数字ID${NC}"
fi
fi
;;
3)
# 显示可用备份并获取用户选择
echo
if list_backups; then
echo
echo -n "请输入要删除的备份ID: "
read -r backup_id
if [[ "$backup_id" =~ ^[0-9]+$ ]]; then
backup_name=$(get_backup_by_id "$backup_id")
if [ -n "$backup_name" ]; then
delete_backup "$backup_name"
else
echo -e "${RED}错误: 无效的备份ID${NC}"
fi
else
echo -e "${RED}错误: 请输入有效的数字ID${NC}"
fi
fi
;;
4)
delete_all_backups
;;
5)
list_backups
;;
0)
echo "退出程序"
exit 0
;;
*)
echo -e "${RED}无效的选项${NC}"
;;
esac
echo
echo -n "按回车键继续..."
read -r
show_menu
}
# 主程序
if [ $# -eq 0 ]; then
# 如果没有命令行参数,显示交互式菜单
show_menu
else
# 保持原有的命令行参数支持
case "$1" in
"backup")
create_backup
;;
"restore")
restore_backup "$2"
;;
"delete")
delete_backup "$2"
;;
"delete-all")
delete_all_backups
;;
"list")
list_backups
;;
"help"|"--help"|"-h")
show_help
;;
*)
echo -e "${RED}未知命令: $1${NC}"
show_help
exit 1
;;
esac
fi

View File

@@ -0,0 +1,709 @@
#!/bin/bash
# ===================================================
# 文件操作脚本 - file-operations.sh
# 用于处理文件同步、路径处理和目录结构管理
# ===================================================
# 颜色定义
if [[ -t 1 ]]; then # 检查是否在终端中运行
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
BLUE=$(tput setaf 4)
MAGENTA=$(tput setaf 5)
CYAN=$(tput setaf 6)
BOLD=$(tput bold)
DIM=$(tput dim)
NC=$(tput sgr0) # No Color
else
RED=""
GREEN=""
YELLOW=""
BLUE=""
MAGENTA=""
CYAN=""
BOLD=""
DIM=""
NC=""
fi
# 工具函数
log_info() {
printf "%s%s[INFO]%s %s\n" "${BOLD}" "${GREEN}" "${NC}" "$1"
}
log_debug() {
if [[ "$DEBUG_MODE" == "true" ]]; then
printf "%s%s[DEBUG]%s %s\n" "${BOLD}" "${MAGENTA}" "${NC}" "$1"
fi
}
log_warn() {
printf "%s%s[WARN]%s %s\n" "${BOLD}" "${YELLOW}" "${NC}" "$1"
}
log_error() {
printf "%s%s[ERROR]%s %s\n" "${BOLD}" "${RED}" "${NC}" "$1"
}
# 显示分隔线
show_separator() {
printf "%s%s%s\n" "${DIM}" "──────────────────────────────────────────" "${NC}"
}
# 显示标题
show_title() {
printf "\n%s%s%s%s\n\n" "${BOLD}" "${CYAN}" "$1" "${NC}"
}
# 显示进度条
show_progress() {
local current=$1
local total=$2
local width=30
local percentage=$((current * 100 / total))
local completed=$((width * current / total))
local remaining=$((width - completed))
printf "\r%s%s[%s%s%s] %d%%" \
"${BOLD}" "${CYAN}" \
"$(printf '%*s' "$completed" | tr ' ' '●')" \
"$(printf '%*s' "$remaining" | tr ' ' '○')" \
"${NC}" \
"$percentage"
}
# 初始化同步目录结构
init_sync_dirs() {
if [[ -z "$PROJECT_ROOT" ]]; then
log_error "项目根目录未初始化"
return 1
fi
# 创建同步配置目录
SYNC_DIR="$PROJECT_ROOT/.sync"
if [[ ! -d "$SYNC_DIR" ]]; then
mkdir -p "$SYNC_DIR" || {
log_error "创建同步配置目录失败: $SYNC_DIR"
return 1
}
log_info "已创建同步配置目录: $SYNC_DIR"
fi
# 创建 Git 仓库目录
GIT_DIR="$PROJECT_ROOT/.git-sync"
if [[ ! -d "$GIT_DIR" ]]; then
mkdir -p "$GIT_DIR" || {
log_error "创建 Git 仓库目录失败: $GIT_DIR"
return 1
}
log_info "已创建 Git 仓库目录: $GIT_DIR"
fi
# 初始化历史记录文件
HISTORY_FILE="$SYNC_DIR/history"
if [[ ! -f "$HISTORY_FILE" ]]; then
touch "$HISTORY_FILE" || {
log_error "创建历史记录文件失败: $HISTORY_FILE"
return 1
}
log_info "已创建历史记录文件: $HISTORY_FILE"
fi
# 初始化插件目录
PLUGINS_DIR="$SYNC_DIR/plugins"
if [[ ! -d "$PLUGINS_DIR" ]]; then
mkdir -p "$PLUGINS_DIR" || {
log_error "创建插件目录失败: $PLUGINS_DIR"
return 1
}
log_info "已创建插件目录: $PLUGINS_DIR"
fi
# 初始化同步配置文件
SYNC_CONFIG_FILE="$SYNC_DIR/sync-config.yaml"
if [[ ! -f "$SYNC_CONFIG_FILE" ]]; then
cat > "$SYNC_CONFIG_FILE" << EOF
# 工具配置
config:
parallel_build: false # 是否并行编译
dry_run: false # 是否干运行
# 工作区配置
workspaces:
# 示例配置
# app-name:
# sync_mappings:
# - source:
# git_url: "https://github.com/user/repo.git"
# branch: "main"
# target:
# sync_dir: "dist" # 要同步的目录
# git_dir: "dist" # Git 仓库中的目标目录
EOF
log_info "已创建同步配置文件: $SYNC_CONFIG_FILE"
fi
return 0
}
# 查找项目根目录
find_project_root() {
local current_dir="$PWD"
while [[ "$current_dir" != "/" ]]; do
if [[ -f "$current_dir/pnpm-workspace.yaml" ]]; then
PROJECT_ROOT="$current_dir"
log_info "找到项目根目录: $PROJECT_ROOT"
# 在找到根目录后,初始化相关路径
SYNC_DIR="$PROJECT_ROOT/.sync"
GIT_DIR="$PROJECT_ROOT/.git-sync"
SYNC_CONFIG_FILE="$SYNC_DIR/sync-config.yaml"
HISTORY_FILE="$SYNC_DIR/history"
PLUGINS_DIR="$SYNC_DIR/plugins"
return 0
fi
current_dir="$(dirname "$current_dir")"
done
log_error "未找到项目根目录"
return 1
}
# 检查并创建工作区配置目录
check_workspace_config_dir() {
# 检查工作区配置目录是否存在
if [[ ! -d "$WORKSPACE_CONFIG_DIR" ]]; then
log_info "工作区配置目录不存在,正在创建..."
mkdir -p "$WORKSPACE_CONFIG_DIR" || {
log_error "创建工作区配置目录失败: $WORKSPACE_CONFIG_DIR"
return 1
}
log_info "已创建工作区配置目录: $WORKSPACE_CONFIG_DIR"
# 创建 .gitignore 文件
cat > "$WORKSPACE_CONFIG_DIR/.gitignore" << EOF
# 忽略所有文件
*
# 不忽略 .gitignore
!.gitignore
EOF
log_info "已创建 .gitignore 文件"
# 创建 README.md 文件
cat > "$WORKSPACE_CONFIG_DIR/README.md" << EOF
# 工作区配置目录
此目录用于存储各个工作区的同步配置信息。
## 配置文件格式
每个工作区对应一个 YAML 配置文件,命名格式为 \`{workspace}.yaml\`。
### 配置示例
\`\`\`yaml
# 工作区同步配置
workspace: "app-name"
sync_mappings:
- source:
git_url: "https://github.com/user/repo.git"
branch: "main"
target:
sync_dir: "dist" # 要同步的目录
git_dir: "dist" # Git 仓库中的目标目录
\`\`\`
## 注意事项
1. 此目录不应被 Git 追踪
2. 配置文件包含敏感信息,请妥善保管
3. 建议定期备份配置文件
EOF
log_info "已创建 README.md 文件"
fi
# 检查目录权限
if [[ ! -w "$WORKSPACE_CONFIG_DIR" ]]; then
log_error "工作区配置目录没有写入权限: $WORKSPACE_CONFIG_DIR"
return 1
fi
return 0
}
# 同步文件操作函数
sync_files() {
local source_path="$PROJECT_ROOT/$SELECTED_WORKSPACE"
local dist_path="$source_path/dist"
# 检查源目录
if [[ ! -d "$dist_path" ]]; then
log_error "源目录不存在: $dist_path"
return 1
fi
# 询问是否同步项目结构
read -p "是否同步项目结构?(y/n): " sync_structure
if [[ "$sync_structure" == "y" ]]; then
# 同步项目结构
log_info "开始同步项目结构..."
# 创建临时目录
local temp_dir=$(mktemp -d)
# 复制项目结构
find "$source_path" -type f -not -path "*/node_modules/*" -not -path "*/dist/*" | while IFS= read -r file; do
local rel_path="${file#$source_path/}"
local target_path="$temp_dir/$rel_path"
mkdir -p "$(dirname "$target_path")"
cp "$file" "$target_path"
done
# 移动临时目录内容到目标目录
cp -r "$temp_dir"/* "$TARGET_GIT_DIR/"
rm -rf "$temp_dir"
fi
# 同步编译结果
log_info "开始同步编译结果..."
if [[ "$OS" == "windows" ]]; then
# Windows 路径处理
local target_dist="${TARGET_GIT_DIR}\\dist"
if [[ -d "$target_dist" ]]; then
rm -rf "$target_dist"
fi
cp -r "$dist_path" "$target_dist"
else
# Unix 路径处理
local target_dist="$TARGET_GIT_DIR/dist"
if [[ -d "$target_dist" ]]; then
rm -rf "$target_dist"
fi
cp -r "$dist_path" "$target_dist"
fi
log_info "文件同步完成"
return 0
}
# 同步所有源码文件
sync_all_source_files() {
local files=("$@")
local total=${#files[@]}
local current=0
for target_dir in "${TARGET_GIT_DIRS[@]}"; do
log_info "开始同步到: $target_dir"
for file in "${files[@]}"; do
local rel_path="${file#$PROJECT_ROOT/$SELECTED_WORKSPACE/}"
local target_path="$target_dir/$rel_path"
mkdir -p "$(dirname "$target_path")"
cp "$file" "$target_path"
((current++))
show_progress "$current" "$total"
done
printf "\n"
done
log_info "源码同步完成"
return 0
}
# 仅同步配置文件
sync_config_files() {
local files=("$@")
local config_files=()
for file in "${files[@]}"; do
if [[ "$file" =~ \.(json|yaml|yml|config\.js|config\.ts)$ ]]; then
config_files+=("$file")
fi
done
if [[ ${#config_files[@]} -eq 0 ]]; then
log_error "未找到配置文件"
return 1
fi
sync_all_source_files "${config_files[@]}"
}
# 仅同步源代码
sync_source_files() {
local files=("$@")
local source_files=()
for file in "${files[@]}"; do
if [[ "$file" =~ \.(js|ts|jsx|tsx|vue|css|scss|less)$ ]]; then
source_files+=("$file")
fi
done
if [[ ${#source_files[@]} -eq 0 ]]; then
log_error "未找到源代码文件"
return 1
fi
sync_all_source_files "${source_files[@]}"
}
# 记录操作历史
record_history() {
if [[ -z "$HISTORY_FILE" ]]; then
log_error "历史记录文件未初始化,请确保已找到项目根目录"
return 1
fi
local operation="$1" # 操作
local timestamp=$(date '+%Y-%m-%d %H:%M:%S') # 时间戳
echo "$timestamp|$operation" >> "$HISTORY_FILE" # 写入历史记录文件
# 保持历史记录在最大限制内
if [[ -f "$HISTORY_FILE" ]]; then
tail -n "$MAX_HISTORY" "$HISTORY_FILE" > "${HISTORY_FILE}.tmp"
mv "${HISTORY_FILE}.tmp" "$HISTORY_FILE"
fi
}
# 显示操作历史
show_history() {
if [[ -z "$HISTORY_FILE" ]]; then
log_error "历史记录文件未初始化,请确保已找到项目根目录"
return 1
fi
if [[ -f "$HISTORY_FILE" ]]; then
log_info "最近的操作历史:"
while IFS='|' read -r timestamp operation; do
echo "$timestamp - $operation"
done < "$HISTORY_FILE"
else
log_info "暂无操作历史"
fi
}
# 同步源码
sync_source_code() {
local workspace_path="$PROJECT_ROOT/$SELECTED_WORKSPACE"
local source_files=()
# 收集需要同步的源码文件
for file in $(find "$workspace_path" -type f); do
if [[ ! "$file" =~ /(node_modules|dist|\.git)/ ]]; then
source_files+=("$file")
fi
done
if [[ ${#source_files[@]} -eq 0 ]]; then
log_error "未找到需要同步的源码文件"
return 1
fi
# 显示同步选项
show_title "同步源码选项"
printf "%s%s%s\n" "${DIM}" "选择要同步的内容" "${NC}"
show_separator
local sync_options=(
"同步所有源码文件"
"仅同步配置文件"
"仅同步源代码"
"自定义同步"
)
local selected_index=0
local max_index=$((${#sync_options[@]}-1))
local padding=2
while true; do
clear
# 显示选项列表
show_title "同步源码选项"
printf "%s%s%s\n" "${DIM}" "选择要同步的内容" "${NC}"
show_separator
for i in "${!sync_options[@]}"; do
if [[ $i -eq $selected_index ]]; then
printf "%s%s%s%*s%s%s%s\n" \
"${BOLD}" "${CYAN}" "" \
"$padding" "" \
"${GREEN}" "${sync_options[$i]}" "${NC}"
else
printf "%*s%s%s%s\n" \
"$((padding + 1))" "" \
"${DIM}" "${sync_options[$i]}" "${NC}"
fi
done
show_separator
# 读取用户输入
local key_pressed=""
local key
read -r -n 1 key
# 获取ASCII码用于调试针对回车键
if [[ -z "$key" ]]; then
[[ "$DEBUG_MODE" == "true" ]] && printf "空字符,可能是回车键\n"
key_pressed="ENTER"
else
[[ "$DEBUG_MODE" == "true" ]] && printf "按键ASCII码: %d\n" "'$key"
fi
# 处理按键
case "$key" in
$'\x1b') # ESC 序列
read -r -n 2 key
case "$key" in
"[A") # 上箭头
if [[ $selected_index -gt 0 ]]; then
selected_index=$((selected_index-1))
else
# 循环到最后一项
selected_index=$max_index
fi
;;
"[B") # 下箭头
if [[ $selected_index -lt $max_index ]]; then
selected_index=$((selected_index+1))
else
# 循环到第一项
selected_index=0
fi
;;
esac
;;
"") # 回车
case $selected_index in
0) # 同步所有源码文件
sync_all_source_files "${source_files[@]}"
;;
1) # 仅同步配置文件
sync_config_files "${source_files[@]}"
;;
2) # 仅同步源代码
sync_source_files "${source_files[@]}"
;;
3) # 自定义同步
sync_custom_files "${source_files[@]}"
;;
esac
return $?
;;
"q") # 退出
log_error "操作已取消"
return 1
;;
esac
done
}
# 自定义同步文件选择
sync_custom_files() {
local files=("$@")
local selected_files=()
local selected_indices=()
local cursor_index=0
local max_index=$((${#files[@]}-1))
local padding=2
while true; do
clear
show_title "选择要同步的文件"
printf "%s%s%s\n" "${DIM}" "使用数字键1选中/0取消回车确认q 退出" "${NC}"
show_separator
# 显示文件列表
for i in "${!files[@]}"; do
local is_selected=false
# 检查当前文件是否已被选中
for idx in "${selected_indices[@]}"; do
if [[ $i -eq $idx ]]; then
is_selected=true
break
fi
done
if [[ $i -eq $cursor_index ]]; then
# 当前光标位置
if [[ "$is_selected" == "true" ]]; then
printf "%s%s%s%*s%s%s%s\n" \
"${BOLD}" "${CYAN}" "" \
"$padding" "" \
"${GREEN}" "[✓] ${files[$i]#$PROJECT_ROOT/}" "${NC}"
else
printf "%s%s%s%*s%s%s%s\n" \
"${BOLD}" "${CYAN}" "" \
"$padding" "" \
"${DIM}" "[ ] ${files[$i]#$PROJECT_ROOT/}" "${NC}"
fi
else
# 非光标位置
if [[ "$is_selected" == "true" ]]; then
printf "%*s%s%s%s\n" \
"$((padding + 1))" "" \
"${GREEN}" "[✓] ${files[$i]#$PROJECT_ROOT/}" "${NC}"
else
printf "%*s%s%s%s\n" \
"$((padding + 1))" "" \
"${DIM}" "[ ] ${files[$i]#$PROJECT_ROOT/}" "${NC}"
fi
fi
done
show_separator
# 显示操作提示
printf "\n%s%s使用上下箭头选择数字键1选中/0取消回车确认q键退出%s\n" \
"${BOLD}" "${CYAN}" "${NC}"
# 读取用户输入 - 统一处理方式
local key_pressed=""
local key
read -r -n 1 key
# 获取ASCII码用于调试
if [[ -z "$key" ]]; then
[[ "$DEBUG_MODE" == "true" ]] && printf "空字符,可能是回车键\n"
key_pressed="ENTER" # 空字符通常是回车键
elif [[ "$key" == "1" ]]; then
printf "检测到数字键1执行选中操作\n" # 始终输出不依赖DEBUG_MODE
key_pressed="SELECT" # 选中
elif [[ "$key" == "0" ]]; then
printf "检测到数字键0执行取消选中操作\n" # 始终输出不依赖DEBUG_MODE
key_pressed="DESELECT" # 取消选中
else
[[ "$DEBUG_MODE" == "true" ]] && printf "按键ASCII码: %d\n" "'$key"
# 处理其他按键
case "$key" in
$'\x1b') # ESC 序列,包括方向键
read -r -n 2 seq
[[ "$DEBUG_MODE" == "true" ]] && printf "ESC序列: %s\n" "$seq"
case "$seq" in
"[A") key_pressed="UP" ;; # 上箭头
"[B") key_pressed="DOWN" ;; # 下箭头
*) key_pressed="ESC" ;; # 其他ESC序列
esac
;;
"q"|"Q") # q键退出
key_pressed="QUIT"
;;
*) # 其他按键忽略
key_pressed="OTHER"
;;
esac
fi
# 调试信息
[[ "$DEBUG_MODE" == "true" ]] && log_debug "按键被解析为: $key_pressed"
# 处理操作
case "$key_pressed" in
"UP") # 上箭头
if [[ $cursor_index -gt 0 ]]; then
cursor_index=$((cursor_index-1))
else
# 如果已经是第一项,跳到最后一项
cursor_index=$max_index
fi
;;
"DOWN") # 下箭头
if [[ $cursor_index -lt $max_index ]]; then
cursor_index=$((cursor_index+1))
else
# 如果已经是最后一项,回到第一项
cursor_index=0
fi
;;
"SELECT") # 数字键1 - 选中当前项
printf "处理SELECT操作 - 选中当前项: ${cursor_index}\n"
# 检查当前索引是否已在选中列表中
local found=false
for idx in "${selected_indices[@]}"; do
if [[ $idx -eq $cursor_index ]]; then
found=true
break
fi
done
# 如果未选中,则添加到选中列表
if [[ "$found" == "false" ]]; then
selected_indices+=($cursor_index)
printf "已添加索引 ${cursor_index} 到选中列表\n"
# 对选中项排序
if [[ ${#selected_indices[@]} -gt 0 ]]; then
IFS=$'\n'
selected_indices=($(sort -n <<<"${selected_indices[*]}"))
unset IFS
fi
else
printf "索引 ${cursor_index} 已在选中列表中\n"
fi
;;
"DESELECT") # 数字键0 - 取消选中当前项
printf "处理DESELECT操作 - 取消选中当前项: ${cursor_index}\n"
# 检查当前索引是否已在选中列表中
local found=false
local new_indices=()
# 创建新数组,排除当前索引
for idx in "${selected_indices[@]}"; do
if [[ $idx -ne $cursor_index ]]; then
new_indices+=($idx)
else
found=true # 标记找到了要删除的索引
fi
done
# 只有在找到并删除了索引的情况下才更新选中列表
if [[ "$found" == "true" ]]; then
selected_indices=("${new_indices[@]}")
printf "已从选中列表中移除索引 ${cursor_index}\n"
else
printf "索引 ${cursor_index} 不在选中列表中\n"
fi
;;
"ENTER") # 回车 - 确认选择
if [[ ${#selected_indices[@]} -eq 0 ]]; then
log_error "请至少选择一个文件"
sleep 2 # 暂停显示错误信息
continue
fi
# 重建选中文件列表
selected_files=()
for idx in "${selected_indices[@]}"; do
selected_files+=("${files[$idx]}")
done
sync_all_source_files "${selected_files[@]}"
return $?
;;
"QUIT") # 退出
log_error "操作已取消"
return 1
;;
esac
done
}
# 导出函数
export -f log_info
export -f log_debug
export -f log_warn
export -f log_error
export -f show_separator
export -f show_title
export -f show_progress
export -f init_sync_dirs
export -f find_project_root
export -f check_workspace_config_dir
export -f sync_files
export -f sync_all_source_files
export -f sync_config_files
export -f sync_source_files
export -f record_history
export -f show_history
export -f sync_source_code
export -f sync_custom_files

View File

@@ -0,0 +1,515 @@
#!/bin/bash
# ===================================================
# Git 操作脚本 - git-operations.sh
# 用于处理 Git 仓库管理、同步和提交相关功能
# ===================================================
# 依赖文件操作脚本的函数
source "$(dirname "$0")/file-operations.sh"
# Git 操作模块
prepare_git_repo() {
local git_url="$1"
local branch="$2"
local alias="$3"
local target_dir="$GIT_DIR/${alias:-$(basename "$git_url" .git)}"
# 检查目标目录是否存在
if [[ ! -d "$target_dir" ]]; then
log_info "目标目录不存在,尝试克隆仓库"
if ! git clone "$git_url" "$target_dir"; then
log_error "克隆仓库失败"
return 1
fi
fi
# 切换到目标目录
cd "$target_dir" || {
log_error "无法切换到目标目录"
return 1
}
# 检查是否是 Git 仓库
if [[ ! -d ".git" ]]; then
log_error "目标目录不是有效的 Git 仓库"
return 1
}
# 清理未提交的更改
if [[ -n "$(git status --porcelain)" ]]; then
log_warn "发现未提交的更改,正在清理..."
git reset --hard HEAD
git clean -fd
fi
# 拉取最新代码
log_info "拉取最新代码..."
if ! git pull; then
log_error "拉取代码失败"
return 1
fi
# 切换到指定分支
if [[ -n "$branch" ]]; then
log_info "切换到分支: $branch"
if ! git checkout "$branch"; then
log_error "切换分支失败"
return 1
fi
fi
log_info "Git 仓库准备完成"
return 0
}
# 获取源项目的最新提交信息
get_source_commit_info() {
cd "$PROJECT_ROOT" || return 1
local commit_hash=$(git rev-parse HEAD)
local commit_msg=$(git log -1 --pretty=%B)
echo "$commit_hash|$commit_msg"
}
# 提交更改
commit_changes() {
local commit_info
commit_info=$(get_source_commit_info) || {
log_error "获取源项目提交信息失败"
return 1
}
local commit_hash=$(echo "$commit_info" | cut -d'|' -f1)
local commit_msg=$(echo "$commit_info" | cut -d'|' -f2)
# 添加所有更改
git add .
# 提交更改
if git commit -m "sync: $commit_msg (from $commit_hash)"; then
log_info "提交成功"
return 0
else
log_error "提交失败"
return 1
fi
}
# 推送更改到远程仓库
push_changes() {
local current_branch=$(git rev-parse --abbrev-ref HEAD)
# 检查是否有远程仓库
if ! git remote | grep -q origin; then
log_error "未找到远程仓库 origin"
return 1
fi
# 推送到远程仓库
log_info "正在推送更改到远程仓库..."
if git push origin "$current_branch"; then
log_info "推送成功"
return 0
else
log_error "推送失败"
return 1
fi
}
# 选择 Git 项目目录
select_git_dirs() {
local git_dirs=()
local selected_indices=()
local SYNC_MAPPINGS=()
# 检查配置文件是否存在
if [[ ! -f "$SYNC_CONFIG_FILE" ]]; then
log_error "配置文件不存在: $SYNC_CONFIG_FILE"
return 1
fi
# 检查是否安装了 yq
if ! command -v yq &> /dev/null; then
log_error "未安装 yq无法读取配置文件"
return 1
fi
# 检查工作区是否存在
if ! yq e ".workspaces.$SELECTED_WORKSPACE" "$SYNC_CONFIG_FILE" &> /dev/null; then
log_error "工作区 $SELECTED_WORKSPACE 不存在于配置文件中"
return 1
fi
# 获取工作区的所有 Git 仓库配置
local count
count=$(yq e ".workspaces.$SELECTED_WORKSPACE.sync_mappings | length" "$SYNC_CONFIG_FILE")
if [[ $count -eq 0 ]]; then
log_error "工作区 $SELECTED_WORKSPACE 未配置任何 Git 仓库"
return 1
fi
# 构建显示列表和映射信息
local all_mappings=()
for ((i=0; i<count; i++)); do
local git_url
local branch
local alias
local sync_dir
local git_dir
git_url=$(yq e ".workspaces.$SELECTED_WORKSPACE.sync_mappings[$i].source.git_url" "$SYNC_CONFIG_FILE")
branch=$(yq e ".workspaces.$SELECTED_WORKSPACE.sync_mappings[$i].source.branch" "$SYNC_CONFIG_FILE")
alias=$(yq e ".workspaces.$SELECTED_WORKSPACE.sync_mappings[$i].source.alias" "$SYNC_CONFIG_FILE")
sync_dir=$(yq e ".workspaces.$SELECTED_WORKSPACE.sync_mappings[$i].target.sync_dir" "$SYNC_CONFIG_FILE")
git_dir=$(yq e ".workspaces.$SELECTED_WORKSPACE.sync_mappings[$i].target.git_dir" "$SYNC_CONFIG_FILE")
if [[ -n "$git_url" ]]; then
# 格式化显示信息
local display_name
if [[ -n "$alias" ]]; then
display_name="[$alias] $git_url ($branch) -> $sync_dir:$git_dir"
else
display_name="$git_url ($branch) -> $sync_dir:$git_dir"
fi
git_dirs+=("$display_name")
# 存储完整映射信息,稍后使用
all_mappings+=("$git_url|$branch|$sync_dir|$git_dir|$alias")
fi
done
# 默认全选
for i in "${!git_dirs[@]}"; do
selected_indices+=($i)
done
local cursor_index=0
local max_index=$((${#git_dirs[@]}-1))
# 设置当前步骤
CURRENT_STEP=2
# 主循环 - 处理用户输入并更新选择
while true; do
# 显示步骤状态
show_steps
# 显示菜单标题
printf "%s%s选择目标 Git 仓库%s\n\n" "${BOLD}" "${CYAN}" "${NC}"
# 显示Git仓库列表
for i in "${!git_dirs[@]}"; do
# 检查当前索引是否已在选中列表中
local is_selected=false
for idx in "${selected_indices[@]}"; do
if [[ $i -eq $idx ]]; then
is_selected=true
break
fi
done
if [[ $i -eq $cursor_index ]]; then
# 当前光标位置项
if [[ "$is_selected" == "true" ]]; then
# 选中项
printf "%s%s %s[✓] %s%s\n" \
"${BOLD}" "${CYAN}" "${GREEN}" "${git_dirs[$i]}" "${NC}"
else
# 未选中项
printf "%s%s %s[ ] %s%s\n" \
"${BOLD}" "${CYAN}" "${DIM}" "${git_dirs[$i]}" "${NC}"
fi
else
# 非光标位置项
if [[ "$is_selected" == "true" ]]; then
# 选中项
printf " %s[✓] %s%s\n" \
"${GREEN}" "${git_dirs[$i]}" "${NC}"
else
# 未选中项
printf " %s[ ] %s%s\n" \
"${DIM}" "${git_dirs[$i]}" "${NC}"
fi
fi
done
# 显示操作提示
printf "\n%s%s使用上下箭头选择数字键1选中/0取消回车确认q键退出%s\n" \
"${BOLD}" "${CYAN}" "${NC}"
# 读取用户输入 - 统一处理方式
local key_pressed=""
local key
read -r -n 1 key
# 获取ASCII码用于调试
if [[ -z "$key" ]]; then
[[ "$DEBUG_MODE" == "true" ]] && printf "空字符,可能是回车键\n"
key_pressed="ENTER" # 空字符通常是回车键
elif [[ "$key" == "1" ]]; then
printf "检测到数字键1执行选中操作\n" # 始终输出不依赖DEBUG_MODE
key_pressed="SELECT" # 选中
elif [[ "$key" == "0" ]]; then
printf "检测到数字键0执行取消选中操作\n" # 始终输出不依赖DEBUG_MODE
key_pressed="DESELECT" # 取消选中
else
[[ "$DEBUG_MODE" == "true" ]] && printf "按键ASCII码: %d\n" "'$key"
# 处理其他按键
case "$key" in
$'\x1b') # ESC 序列,包括方向键
read -r -n 2 seq
[[ "$DEBUG_MODE" == "true" ]] && printf "ESC序列: %s\n" "$seq"
case "$seq" in
"[A") key_pressed="UP" ;; # 上箭头
"[B") key_pressed="DOWN" ;; # 下箭头
*) key_pressed="ESC" ;; # 其他ESC序列
esac
;;
"q"|"Q") # q键退出
key_pressed="QUIT"
;;
*) # 其他按键忽略
key_pressed="OTHER"
;;
esac
fi
# 调试信息
[[ "$DEBUG_MODE" == "true" ]] && log_debug "按键被解析为: $key_pressed"
# 处理操作
case "$key_pressed" in
"UP") # 上箭头
if [[ $cursor_index -gt 0 ]]; then
cursor_index=$((cursor_index-1))
else
# 如果已经是第一项,跳到最后一项
cursor_index=$max_index
fi
;;
"DOWN") # 下箭头
if [[ $cursor_index -lt $max_index ]]; then
cursor_index=$((cursor_index+1))
else
# 如果已经是最后一项,回到第一项
cursor_index=0
fi
;;
"SELECT") # 数字键1 - 选中当前项
printf "处理SELECT操作 - 选中当前项: ${cursor_index}\n"
# 检查当前索引是否已在选中列表中
local found=false
for idx in "${selected_indices[@]}"; do
if [[ $idx -eq $cursor_index ]]; then
found=true
break
fi
done
# 如果未选中,则添加到选中列表
if [[ "$found" == "false" ]]; then
selected_indices+=($cursor_index)
printf "已添加索引 ${cursor_index} 到选中列表\n"
# 对选中项排序
if [[ ${#selected_indices[@]} -gt 0 ]]; then
IFS=$'\n'
selected_indices=($(sort -n <<<"${selected_indices[*]}"))
unset IFS
fi
else
printf "索引 ${cursor_index} 已在选中列表中\n"
fi
;;
"DESELECT") # 数字键0 - 取消选中当前项
printf "处理DESELECT操作 - 取消选中当前项: ${cursor_index}\n"
# 检查当前索引是否已在选中列表中
local found=false
local new_indices=()
# 创建新数组,排除当前索引
for idx in "${selected_indices[@]}"; do
if [[ $idx -ne $cursor_index ]]; then
new_indices+=($idx)
else
found=true # 标记找到了要删除的索引
fi
done
# 只有在找到并删除了索引的情况下才更新选中列表
if [[ "$found" == "true" ]]; then
selected_indices=("${new_indices[@]}")
printf "已从选中列表中移除索引 ${cursor_index}\n"
else
printf "索引 ${cursor_index} 不在选中列表中\n"
fi
;;
"ENTER") # 回车 - 确认选择
if [[ ${#selected_indices[@]} -eq 0 ]]; then
log_error "请至少选择一个 Git 仓库"
sleep 2 # 暂停显示错误信息
continue
fi
# 根据选中的索引获取对应的映射信息
SYNC_MAPPINGS=()
for idx in "${selected_indices[@]}"; do
SYNC_MAPPINGS+=("${all_mappings[$idx]}")
done
# 更新步骤状态
STEP_GIT_REPOS="${#selected_indices[@]} 个仓库"
CURRENT_STEP=3
return 0
;;
"QUIT") # 退出
log_error "操作已取消"
return 1
;;
esac
done
}
# 读取工作区配置
read_workspace_config() {
local workspace="$1"
if ! command -v yq &> /dev/null; then
log_warn "未安装 yq将使用默认配置"
return 1
fi
# 使用 yq 解析 YAML
local mappings
mappings=$(yq e ".workspaces.$workspace.sync_mappings" "$SYNC_CONFIG_FILE")
if [[ "$mappings" != "null" ]]; then
# 解析每个映射
local count
count=$(yq e ".workspaces.$workspace.sync_mappings | length" "$SYNC_CONFIG_FILE")
for ((i=0; i<count; i++)); do
local git_url
local branch
local sync_dir
local git_dir
local alias
git_url=$(yq e ".workspaces.$workspace.sync_mappings[$i].source.git_url" "$SYNC_CONFIG_FILE")
branch=$(yq e ".workspaces.$workspace.sync_mappings[$i].source.branch" "$SYNC_CONFIG_FILE")
sync_dir=$(yq e ".workspaces.$workspace.sync_mappings[$i].target.sync_dir" "$SYNC_CONFIG_FILE")
git_dir=$(yq e ".workspaces.$workspace.sync_mappings[$i].target.git_dir" "$SYNC_CONFIG_FILE")
alias=$(yq e ".workspaces.$workspace.sync_mappings[$i].source.alias" "$SYNC_CONFIG_FILE")
if [[ -n "$git_url" && -n "$sync_dir" ]]; then
# 存储映射信息,添加别名
SYNC_MAPPINGS+=("$git_url|$branch|$sync_dir|$git_dir|$alias")
fi
done
return 0
fi
return 1
}
# 更新工作区配置
update_workspace_config() {
local workspace="$1"
if ! command -v yq &> /dev/null; then
log_warn "未安装 yq无法更新配置"
return 1
fi
# 创建备份
if [[ -f "$SYNC_CONFIG_FILE" ]]; then
cp "$SYNC_CONFIG_FILE" "${SYNC_CONFIG_FILE}.bak"
fi
# 清空现有映射
yq e -i ".workspaces.$workspace.sync_mappings = []" "$SYNC_CONFIG_FILE"
# 添加新的映射
for mapping in "${SYNC_MAPPINGS[@]}"; do
IFS='|' read -r git_url branch sync_dir git_dir alias <<< "$mapping"
yq e -i ".workspaces.$workspace.sync_mappings += [{\"source\": {\"git_url\": \"$git_url\", \"branch\": \"$branch\", \"alias\": \"$alias\"}, \"target\": {\"sync_dir\": \"$sync_dir\", \"git_dir\": \"$git_dir\"}}]" "$SYNC_CONFIG_FILE"
done
# 删除备份
rm -f "${SYNC_CONFIG_FILE}.bak"
log_info "工作区配置已更新"
return 0
}
# 读取配置
read_config() {
if [[ -z "$SYNC_CONFIG_FILE" ]]; then
log_error "配置文件未初始化,请确保已找到项目根目录"
return 1
fi
if [[ -f "$SYNC_CONFIG_FILE" ]]; then
# 使用 yq 解析 YAML如果安装了的话
if command -v yq &> /dev/null; then
SELECTED_WORKSPACE=$(yq e '.config.workspace' "$SYNC_CONFIG_FILE")
TARGET_GIT_DIR=$(yq e '.config.target_git_dir' "$SYNC_CONFIG_FILE")
BRANCH=$(yq e '.config.branch' "$SYNC_CONFIG_FILE")
SYNC_STRUCTURE=$(yq e '.config.sync_structure' "$SYNC_CONFIG_FILE")
PARALLEL_BUILD=$(yq e '.config.parallel_build' "$SYNC_CONFIG_FILE")
DRY_RUN=$(yq e '.config.dry_run' "$SYNC_CONFIG_FILE")
else
log_warn "未安装 yq将使用默认配置"
fi
else
log_error "配置文件不存在: $SYNC_CONFIG_FILE"
return 1
fi
}
# 保存配置
save_config() {
if [[ -z "$SYNC_CONFIG_FILE" ]]; then
log_error "配置文件未初始化,请确保已找到项目根目录"
return 1
fi
if ! command -v yq &> /dev/null; then
log_warn "未安装 yq无法保存配置"
return 1
fi
# 创建备份
if [[ -f "$SYNC_CONFIG_FILE" ]]; then
cp "$SYNC_CONFIG_FILE" "${SYNC_CONFIG_FILE}.bak"
fi
# 保存配置
if ! yq e -i ".config.workspace = \"$SELECTED_WORKSPACE\"" "$SYNC_CONFIG_FILE" \
&& yq e -i ".config.target_git_dir = \"$TARGET_GIT_DIR\"" "$SYNC_CONFIG_FILE" \
&& yq e -i ".config.branch = \"$BRANCH\"" "$SYNC_CONFIG_FILE" \
&& yq e -i ".config.sync_structure = $SYNC_STRUCTURE" "$SYNC_CONFIG_FILE" \
&& yq e -i ".config.parallel_build = $PARALLEL_BUILD" "$SYNC_CONFIG_FILE" \
&& yq e -i ".config.dry_run = $DRY_RUN" "$SYNC_CONFIG_FILE"; then
log_error "保存配置失败"
# 恢复备份
if [[ -f "${SYNC_CONFIG_FILE}.bak" ]]; then
mv "${SYNC_CONFIG_FILE}.bak" "$SYNC_CONFIG_FILE"
fi
return 1
fi
# 删除备份
rm -f "${SYNC_CONFIG_FILE}.bak"
log_info "配置已保存"
return 0
}
# 导出函数
export -f prepare_git_repo
export -f get_source_commit_info
export -f commit_changes
export -f push_changes
export -f select_git_dirs
export -f read_workspace_config
export -f update_workspace_config
export -f read_config
export -f save_config

View File

@@ -0,0 +1,306 @@
#!/bin/bash
# ===================================================
# 项目同步脚本 - sync-project.sh
# 用于同步 Turborepo 项目工作区的编译结果到 Git 仓库
# ===================================================
# 脚本目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 全局变量
PROJECT_ROOT="" # 项目根目录
SELECTED_WORKSPACE="" # 选中的工作区
PARALLEL_BUILD=false # 是否并行编译
DRY_RUN=false # 是否干运行
OS="" # 操作系统类型
DEBUG_MODE=false # 调试模式
# 相关文件和目录变量(初始化为空,后续赋值)
SYNC_DIR="" # 同步配置目录
GIT_DIR="" # Git 仓库目录
SYNC_CONFIG_FILE="" # 同步配置文件
HISTORY_FILE="" # 历史记录文件
PLUGINS_DIR="" # 插件目录
WORKSPACE_CONFIG_DIR="" # 工作区配置目录
# 步骤状态变量
CURRENT_STEP=1
STEP_WORKSPACE=""
STEP_GIT_REPOS=""
STEP_SYNC_MODE=""
# 操作历史相关
MAX_HISTORY=10
# 加载拆分后的脚本
source "$SCRIPT_DIR/file-operations.sh"
source "$SCRIPT_DIR/git-operations.sh"
source "$SCRIPT_DIR/build-operations.sh"
# 主函数
main() {
# 检查参数
if [[ $# -gt 0 ]]; then
for arg in "$@"; do
if [[ "$arg" == "--help" || "$arg" == "-h" ]]; then
show_help
exit 0
fi
done
fi
# 初始化
detect_os
check_dependencies # 检查依赖,包括 yq
# 首先查找项目根目录
find_project_root || exit 1
# 初始化同步目录结构
init_sync_dirs || exit 1
# 在找到项目根目录后,初始化插件系统
init_plugins
load_plugins
# 读取配置
read_config
# 解析命令行参数
parse_args "$@"
# 运行前置钩子
run_hook "pre_main"
# 如果是干运行模式,只显示将要执行的操作
if [[ "$DRY_RUN" == "true" ]]; then
log_info "干运行模式 - 将显示将要执行的操作"
log_info "工作区: $SELECTED_WORKSPACE"
log_info "并行编译: $PARALLEL_BUILD"
exit 0
fi
# 解析工作区(如果未通过参数指定)
if [[ -z "$SELECTED_WORKSPACE" ]]; then
parse_workspaces || {
log_error "工作区选择失败"
exit 1
}
fi
# 验证工作区是否有效
if [[ -z "$SELECTED_WORKSPACE" ]]; then
log_error "未选择有效的工作区"
exit 1
fi
# 验证工作区目录是否存在
if [[ ! -d "$PROJECT_ROOT/apps/$SELECTED_WORKSPACE" ]]; then
log_error "工作区目录不存在: $PROJECT_ROOT/apps/$SELECTED_WORKSPACE"
exit 1
fi
log_info "当前选择的工作区: $SELECTED_WORKSPACE"
# 编译工作区
build_workspace || {
log_error "编译失败"
exit 1
}
# 检查编译结果
check_build_result || {
log_error "编译结果检查失败"
exit 1
}
# 选择 Git 项目目录
select_git_dirs || {
log_error "Git 仓库选择失败"
exit 1
}
# 询问是否同步源码
show_title "同步选项"
printf "%s%s%s\n" "${DIM}" "选择要执行的操作" "${NC}"
show_separator
local sync_options=(
"仅同步编译结果"
"同步编译结果和源码"
)
local selected_index=0
local max_index=$((${#sync_options[@]}-1))
local padding=2
# 设置当前步骤
CURRENT_STEP=3
while true; do
# 显示步骤状态
show_steps
# 显示选项列表标题
printf "%s%s选择同步方式%s\n\n" "${BOLD}" "${CYAN}" "${NC}"
# 显示同步选项
for i in "${!sync_options[@]}"; do
if [[ $i -eq $selected_index ]]; then
printf "%s%s %s%s\n" \
"${BOLD}" "${GREEN}" "${sync_options[$i]}" "${NC}"
else
printf " %s%s%s\n" \
"${DIM}" "${sync_options[$i]}" "${NC}"
fi
done
# 显示操作提示
printf "\n%s%s使用上下箭头选择回车确认q键退出%s\n" \
"${BOLD}" "${CYAN}" "${NC}"
# 读取用户输入 - 统一处理方式
local key_pressed=""
local key
read -r -n 1 key
# 获取ASCII码用于调试
if [[ -z "$key" ]]; then
[[ "$DEBUG_MODE" == "true" ]] && printf "空字符,可能是回车键\n"
key_pressed="ENTER" # 空字符通常是回车键
else
[[ "$DEBUG_MODE" == "true" ]] && printf "按键ASCII码: %d\n" "'$key"
# 处理其他按键
case "$key" in
$'\x1b') # ESC 序列,包括方向键
read -r -n 2 seq
[[ "$DEBUG_MODE" == "true" ]] && printf "ESC序列: %s\n" "$seq"
case "$seq" in
"[A") key_pressed="UP" ;; # 上箭头
"[B") key_pressed="DOWN" ;; # 下箭头
*) key_pressed="ESC" ;; # 其他ESC序列
esac
;;
"q"|"Q") # q键退出
key_pressed="QUIT"
;;
"") # 回车键
key_pressed="ENTER"
;;
*) # 其他按键
key_pressed="OTHER"
;;
esac
fi
# 调试信息
[[ "$DEBUG_MODE" == "true" ]] && log_debug "按键被解析为: $key_pressed"
# 处理操作
case "$key_pressed" in
"UP") # 上箭头
if [[ $selected_index -gt 0 ]]; then
selected_index=$((selected_index-1))
else
# 如果已经是第一项,跳到最后一项
selected_index=$max_index
fi
;;
"DOWN") # 下箭头
if [[ $selected_index -lt $max_index ]]; then
selected_index=$((selected_index+1))
else
# 如果已经是最后一项,回到第一项
selected_index=0
fi
;;
"ENTER") # 回车
case $selected_index in
0) # 仅同步编译结果
STEP_SYNC_MODE="仅同步编译结果"
CURRENT_STEP=4
sync_files || {
log_error "同步编译结果失败"
exit 1
}
;;
1) # 同步编译结果和源码
STEP_SYNC_MODE="同步编译结果和源码"
CURRENT_STEP=4
sync_files || {
log_error "同步编译结果失败"
exit 1
}
sync_source_code || {
log_error "同步源码失败"
exit 1
}
;;
esac
break
;;
"QUIT") # 退出
log_error "操作已取消"
exit 1
;;
esac
done
# 运行工作区选择后钩子
run_hook "post_workspace_select" "$SELECTED_WORKSPACE"
# 设置当前步骤
CURRENT_STEP=4
# 显示当前步骤
show_steps
printf "%s%s正在执行同步操作请稍候...%s\n\n" "${BOLD}" "${CYAN}" "${NC}"
# 准备 Git 仓库
for mapping in "${SYNC_MAPPINGS[@]}"; do
IFS='|' read -r git_url branch sync_dir git_dir alias <<< "$mapping"
prepare_git_repo "$git_url" "$branch" "$alias" || {
log_error "准备 Git 仓库失败"
exit 1
}
done
# 提交和推送
for mapping in "${SYNC_MAPPINGS[@]}"; do
IFS='|' read -r git_url branch sync_dir git_dir alias <<< "$mapping"
local repo_dir="$GIT_DIR/${alias:-$(basename "$git_url" .git)}"
cd "$repo_dir" || {
log_error "无法切换到 Git 仓库目录"
exit 1
}
commit_changes || {
log_error "提交更改失败"
exit 1
}
push_changes || {
log_error "推送更改失败"
exit 1
}
done
# 保存配置
save_config || log_warn "保存配置失败,但操作已完成"
# 记录操作历史
record_history "同步工作区 $SELECTED_WORKSPACE"
# 运行完成钩子
run_hook "post_main"
# 设置最后步骤状态,显示完成
CURRENT_STEP=5
show_steps
printf "%s%s所有操作已完成%s\n" "${BOLD}" "${GREEN}" "${NC}"
}
# 执行主函数
main "$@"