mirror of
https://github.com/yuaotian/go-cursor-help.git
synced 2025-06-08 12:32:06 +08:00
feat: Update installation script and improve error handling
- Updated `install.sh` to include versioning and enhanced bilingual messages for better user feedback. - Refactored platform detection logic to dynamically set binary names based on the version. - Added checks for system requirements, including curl installation and write permissions. - Improved error handling with context-specific messages for better debugging. - Cleaned up the `go.mod` file by consolidating dependency requirements. This commit enhances the installation process and user experience by providing clearer feedback and ensuring necessary prerequisites are met.
This commit is contained in:
parent
2e27634c8d
commit
af6c9f05de
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@ -6,12 +6,18 @@
|
||||
"fatih",
|
||||
"gcflags",
|
||||
"GOARCH",
|
||||
"IMAGENAME",
|
||||
"ldflags",
|
||||
"LOCALAPPDATA",
|
||||
"mktemp",
|
||||
"msys",
|
||||
"pgrep",
|
||||
"pkill",
|
||||
"runas",
|
||||
"setlocal",
|
||||
"taskkill",
|
||||
"trimpath"
|
||||
"tasklist",
|
||||
"trimpath",
|
||||
"xattr"
|
||||
]
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
6
go.mod
6
go.mod
@ -2,12 +2,10 @@ module cursor-id-modifier
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.15.0
|
||||
golang.org/x/sys v0.13.0
|
||||
)
|
||||
require github.com/fatih/color v1.15.0
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
)
|
||||
|
160
install.sh
160
install.sh
@ -1,75 +1,141 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Error handling function / 错误处理函数
|
||||
# Version / 版本号
|
||||
VERSION="v2.0.0"
|
||||
|
||||
# Bilingual message functions / 双语消息函数
|
||||
error() {
|
||||
echo "Error/错误: $1" >&2
|
||||
echo "❌ Error: $1"
|
||||
echo "❌ 错误:$2"
|
||||
exit 1
|
||||
}
|
||||
|
||||
info() {
|
||||
echo "ℹ️ $1"
|
||||
echo "ℹ️ $2"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo "✅ $1"
|
||||
echo "✅ $2"
|
||||
}
|
||||
|
||||
# Detect OS and architecture / 检测操作系统和架构
|
||||
detect_platform() {
|
||||
# Get lowercase OS name and architecture / 获取小写操作系统名称和架构
|
||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
ARCH=$(uname -m)
|
||||
|
||||
# Set binary name based on platform / 根据平台设置二进制文件名
|
||||
case "$OS" in
|
||||
linux*)
|
||||
case "$ARCH" in
|
||||
x86_64) BINARY_NAME="cursor_id_modifier_v2.0.0_linux_amd64" ;;
|
||||
*) error "Unsupported Linux architecture/不支持的Linux架构: $ARCH" ;;
|
||||
x86_64) BINARY_NAME="cursor_id_modifier_${VERSION}_linux_amd64" ;;
|
||||
*) error "Unsupported Linux architecture: $ARCH" "不支持的Linux架构:$ARCH" ;;
|
||||
esac
|
||||
;;
|
||||
darwin*)
|
||||
case "$ARCH" in
|
||||
x86_64) BINARY_NAME="cursor_id_modifier_v2.0.0_mac_intel" ;;
|
||||
arm64) BINARY_NAME="cursor_id_modifier_v2.0.0_mac_m1" ;;
|
||||
*) error "Unsupported macOS architecture/不支持的macOS架构: $ARCH" ;;
|
||||
x86_64) BINARY_NAME="cursor_id_modifier_${VERSION}_darwin_amd64_intel" ;;
|
||||
arm64) BINARY_NAME="cursor_id_modifier_${VERSION}_darwin_arm64_m1" ;;
|
||||
*) error "Unsupported macOS architecture: $ARCH" "不支持的macOS架构:$ARCH" ;;
|
||||
esac
|
||||
;;
|
||||
msys*|mingw*|cygwin*)
|
||||
case "$ARCH" in
|
||||
x86_64) BINARY_NAME="cursor_id_modifier_${VERSION}_windows_amd64.exe" ;;
|
||||
*) error "Unsupported Windows architecture: $ARCH" "不支持的Windows架构:$ARCH" ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
error "Unsupported operating system/不支持的操作系统: $OS"
|
||||
error "Unsupported operating system: $OS" "不支持的操作系统:$OS"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check root privileges / 检查root权限
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
error "This script must be run with sudo or as root/此脚本必须使用sudo或root权限运行"
|
||||
fi
|
||||
# Check system requirements / 检查系统要求
|
||||
check_requirements() {
|
||||
info "Checking system requirements..." "正在检查系统要求..."
|
||||
|
||||
# Check curl
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
error "curl is required. Please install curl first." \
|
||||
"需要安装 curl。请先安装 curl 后再运行此脚本。"
|
||||
fi
|
||||
|
||||
# Check write permissions / 检查写入权限
|
||||
if [ ! -w "$INSTALL_DIR" ]; then
|
||||
error "No write permission for $INSTALL_DIR. Please run with sudo." \
|
||||
"没有 $INSTALL_DIR 的写入权限。请使用 sudo 运行此脚本。"
|
||||
}
|
||||
}
|
||||
|
||||
# Initialize installation / 初始化安装
|
||||
detect_platform
|
||||
INSTALL_DIR="/usr/local/bin"
|
||||
[ -d "$INSTALL_DIR" ] || mkdir -p "$INSTALL_DIR"
|
||||
# Verify binary / 验证二进制文件
|
||||
verify_binary() {
|
||||
info "Verifying binary..." "正在验证二进制文件..."
|
||||
if [ ! -f "$TEMP_DIR/$BINARY_NAME" ]; then
|
||||
error "Binary file download failed or does not exist" \
|
||||
"二进制文件下载失败或不存在"
|
||||
fi
|
||||
|
||||
# Check file size / 检查文件大小
|
||||
local size=$(wc -c < "$TEMP_DIR/$BINARY_NAME")
|
||||
if [ "$size" -lt 1000000 ]; then # At least 1MB / 至少1MB
|
||||
error "Downloaded file size is abnormal, download might be incomplete" \
|
||||
"下载的文件大小异常,可能下载不完整"
|
||||
}
|
||||
}
|
||||
|
||||
# Download binary / 下载二进制文件
|
||||
echo "Downloading cursor-id-modifier for/正在下载 $OS ($ARCH)..."
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
DOWNLOAD_URL="https://github.com/yuaotian/go-cursor-help/raw/main/bin/$BINARY_NAME"
|
||||
# Main installation process / 主安装流程
|
||||
main() {
|
||||
info "Starting installation of cursor-id-modifier ${VERSION}..." \
|
||||
"开始安装 cursor-id-modifier ${VERSION}..."
|
||||
|
||||
# Initialize installation / 初始化安装
|
||||
detect_platform
|
||||
INSTALL_DIR="/usr/local/bin"
|
||||
[ -d "$INSTALL_DIR" ] || mkdir -p "$INSTALL_DIR"
|
||||
|
||||
# Check requirements / 检查要求
|
||||
check_requirements
|
||||
|
||||
# Create temp directory / 创建临时目录
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||
|
||||
# Download binary / 下载二进制文件
|
||||
info "Downloading cursor-id-modifier ($OS-$ARCH)..." \
|
||||
"正在下载 cursor-id-modifier ($OS-$ARCH)..."
|
||||
DOWNLOAD_URL="https://github.com/yuaotian/go-cursor-help/raw/main/bin/$BINARY_NAME"
|
||||
|
||||
if ! curl -fsSL "$DOWNLOAD_URL" -o "$TEMP_DIR/$BINARY_NAME"; then
|
||||
error "Failed to download binary" "下载二进制文件失败"
|
||||
fi
|
||||
|
||||
# Verify download / 验证下载
|
||||
verify_binary
|
||||
|
||||
# Set permissions / 设置权限
|
||||
info "Setting execution permissions..." "正在设置执行权限..."
|
||||
if ! chmod +x "$TEMP_DIR/$BINARY_NAME"; then
|
||||
error "Failed to set executable permissions" "无法设置可执行权限"
|
||||
fi
|
||||
|
||||
# Handle macOS security / 处理macOS安全设置
|
||||
if [ "$OS" = "darwin" ]; then
|
||||
info "Handling macOS security settings..." "正在处理macOS安全设置..."
|
||||
xattr -d com.apple.quarantine "$TEMP_DIR/$BINARY_NAME" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Install binary / 安装二进制文件
|
||||
info "Installing binary..." "正在安装二进制文件..."
|
||||
if ! mv "$TEMP_DIR/$BINARY_NAME" "$INSTALL_DIR/cursor-id-modifier"; then
|
||||
error "Failed to install binary" "安装二进制文件失败"
|
||||
fi
|
||||
|
||||
success "Installation successful! You can now run 'cursor-id-modifier' from anywhere." \
|
||||
"安装成功!现在可以在任何位置运行 'cursor-id-modifier'。"
|
||||
success "For help, run 'cursor-id-modifier --help'" \
|
||||
"如需帮助,请运行 'cursor-id-modifier --help'"
|
||||
}
|
||||
|
||||
if ! curl -fsSL "$DOWNLOAD_URL" -o "$TEMP_DIR/$BINARY_NAME"; then
|
||||
error "Failed to download binary/下载二进制文件失败"
|
||||
fi
|
||||
|
||||
# Set permissions / 设置权限
|
||||
if ! chmod +x "$TEMP_DIR/$BINARY_NAME"; then
|
||||
error "Failed to make binary executable/无法设置可执行权限"
|
||||
fi
|
||||
|
||||
# Handle macOS security / 处理macOS安全设置
|
||||
if [ "$OS" = "darwin" ]; then
|
||||
echo "Removing macOS quarantine attribute/移除macOS隔离属性..."
|
||||
xattr -d com.apple.quarantine "$TEMP_DIR/$BINARY_NAME" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Install binary / 安装二进制文件
|
||||
if ! mv "$TEMP_DIR/$BINARY_NAME" "$INSTALL_DIR/cursor-id-modifier"; then
|
||||
error "Failed to install binary/安装二进制文件失败"
|
||||
fi
|
||||
|
||||
# Cleanup / 清理
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
echo "✅ Installation successful! You can now run 'cursor-id-modifier' from anywhere."
|
||||
echo "✅ 安装成功!现在可以在任何位置运行 'cursor-id-modifier'。"
|
||||
# Start installation / 开始安装
|
||||
main
|
346
main.go
346
main.go
@ -1,4 +1,3 @@
|
||||
// 主程序包 / Main package
|
||||
package main
|
||||
|
||||
// 导入所需的包 / Import required packages
|
||||
@ -9,6 +8,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
@ -16,9 +16,10 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"golang.org/x/sys/windows"
|
||||
"context"
|
||||
"errors"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// 语言类型和常量 / Language type and constants
|
||||
@ -30,6 +31,12 @@ const (
|
||||
|
||||
// Version constant
|
||||
Version = "1.0.1"
|
||||
|
||||
// 定义错误类型常量
|
||||
ErrPermission = "permission_error"
|
||||
ErrConfig = "config_error"
|
||||
ErrProcess = "process_error"
|
||||
ErrSystem = "system_error"
|
||||
)
|
||||
|
||||
// TextResource 存储多语言文本 / TextResource stores multilingual text
|
||||
@ -57,9 +64,11 @@ type StorageConfig struct {
|
||||
|
||||
// AppError 定义错误类型 / AppError defines error types
|
||||
type AppError struct {
|
||||
Op string
|
||||
Path string
|
||||
Err error
|
||||
Type string
|
||||
Op string
|
||||
Path string
|
||||
Err error
|
||||
Context map[string]interface{}
|
||||
}
|
||||
|
||||
// ProgressSpinner 用于显示进度动画 / ProgressSpinner for showing progress animation
|
||||
@ -69,6 +78,14 @@ type ProgressSpinner struct {
|
||||
message string
|
||||
}
|
||||
|
||||
// SpinnerConfig 定义进度条配置
|
||||
type SpinnerConfig struct {
|
||||
Frames []string
|
||||
Delay time.Duration
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 全局变量 / Global variables
|
||||
var (
|
||||
currentLanguage = CN // 默认为中文 / Default to Chinese
|
||||
@ -103,13 +120,13 @@ var (
|
||||
|
||||
// Error implementation for AppError
|
||||
func (e *AppError) Error() string {
|
||||
if e.Path != "" {
|
||||
return fmt.Sprintf("%s: %v [Path: %s]", e.Op, e.Err, e.Path)
|
||||
if e.Context != nil {
|
||||
return fmt.Sprintf("[%s] %s: %v (context: %v)", e.Type, e.Op, e.Err, e.Context)
|
||||
}
|
||||
return fmt.Sprintf("%s: %v", e.Op, e.Err)
|
||||
return fmt.Sprintf("[%s] %s: %v", e.Type, e.Op, e.Err)
|
||||
}
|
||||
|
||||
// NewStorageConfig 创建新的配置实例 / Creates a new configuration instance
|
||||
// NewStorageConfig 创建新的实例 / Creates a new configuration instance
|
||||
func NewStorageConfig() *StorageConfig {
|
||||
return &StorageConfig{
|
||||
TelemetryMacMachineId: generateMachineId(),
|
||||
@ -120,7 +137,7 @@ func NewStorageConfig() *StorageConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// 生成类似原始machineId的字符串(64位小写十六进制) / Generate a string similar to the original machineId (64-bit lowercase hex)
|
||||
// 生成类似原始machineId的字符串(64位小十六进制) / Generate a string similar to the original machineId (64-bit lowercase hex)
|
||||
func generateMachineId() string {
|
||||
data := make([]byte, 32)
|
||||
if _, err := rand.Read(data); err != nil {
|
||||
@ -168,6 +185,11 @@ func (s *ProgressSpinner) Stop() {
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Start starts the spinner animation
|
||||
func (s *ProgressSpinner) Start() {
|
||||
s.current = 0
|
||||
}
|
||||
|
||||
// File and system operations
|
||||
|
||||
func getConfigPath() (string, error) {
|
||||
@ -196,17 +218,32 @@ func getConfigPath() (string, error) {
|
||||
func safeWriteFile(path string, data []byte, perm os.FileMode) error {
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return &AppError{"create directory", dir, err}
|
||||
return &AppError{
|
||||
Type: ErrSystem,
|
||||
Op: "create directory",
|
||||
Path: dir,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
tmpPath := path + ".tmp"
|
||||
if err := os.WriteFile(tmpPath, data, perm); err != nil {
|
||||
return &AppError{"write temporary file", tmpPath, err}
|
||||
return &AppError{
|
||||
Type: ErrSystem,
|
||||
Op: "write temporary file",
|
||||
Path: tmpPath,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Rename(tmpPath, path); err != nil {
|
||||
os.Remove(tmpPath)
|
||||
return &AppError{"rename file", path, err}
|
||||
return &AppError{
|
||||
Type: ErrSystem,
|
||||
Op: "rename file",
|
||||
Path: path,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -218,22 +255,34 @@ func setFilePermissions(filePath string) error {
|
||||
|
||||
// Process management functions
|
||||
|
||||
func killCursorProcesses() error {
|
||||
if runtime.GOOS == "windows" {
|
||||
// First try graceful shutdown
|
||||
exec.Command("taskkill", "/IM", "Cursor.exe").Run()
|
||||
exec.Command("taskkill", "/IM", "cursor.exe").Run()
|
||||
type ProcessManager struct {
|
||||
config *SystemConfig
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
func (pm *ProcessManager) killCursorProcesses() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), pm.config.Timeout)
|
||||
defer cancel()
|
||||
|
||||
// Force kill any remaining instances
|
||||
exec.Command("taskkill", "/F", "/IM", "Cursor.exe").Run()
|
||||
exec.Command("taskkill", "/F", "/IM", "cursor.exe").Run()
|
||||
} else {
|
||||
exec.Command("pkill", "-f", "Cursor").Run()
|
||||
exec.Command("pkill", "-f", "cursor").Run()
|
||||
for attempt := 0; attempt < pm.config.RetryAttempts; attempt++ {
|
||||
if err := pm.killProcess(ctx); err != nil {
|
||||
time.Sleep(pm.config.RetryDelay)
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
||||
return &AppError{
|
||||
Type: ErrProcess,
|
||||
Op: "kill_processes",
|
||||
Err: errors.New("failed to kill all Cursor processes after retries"),
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProcessManager) killProcess(ctx context.Context) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
return pm.killWindowsProcess(ctx)
|
||||
}
|
||||
return pm.killUnixProcess(ctx)
|
||||
}
|
||||
|
||||
func checkCursorRunning() bool {
|
||||
@ -278,11 +327,11 @@ func printCyberpunkBanner() {
|
||||
|
||||
banner := `
|
||||
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
|
||||
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
|
||||
██╔════╝██║ ██║██╔══██╗██╔════╝█╔═══██╗██╔══██╗
|
||||
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
|
||||
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
|
||||
██║ ██║ ██║██╔══██╗╚════██ ██║ ██║██╔══██╗
|
||||
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
|
||||
╚════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
╚════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚════╝ ╚═╝ ╚═╝
|
||||
`
|
||||
cyan.Println(banner)
|
||||
yellow.Println("\t\t>> Cursor ID Modifier v1.0 <<")
|
||||
@ -350,11 +399,21 @@ func saveConfig(config *StorageConfig) error {
|
||||
|
||||
content, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return &AppError{"generate JSON", "", err}
|
||||
return &AppError{
|
||||
Type: ErrSystem,
|
||||
Op: "generate JSON",
|
||||
Path: "",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Chmod(configPath, 0666); err != nil && !os.IsNotExist(err) {
|
||||
return &AppError{"modify file permissions", configPath, err}
|
||||
return &AppError{
|
||||
Type: ErrSystem,
|
||||
Op: "modify file permissions",
|
||||
Path: configPath,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := safeWriteFile(configPath, content, 0666); err != nil {
|
||||
@ -386,18 +445,23 @@ func readExistingConfig() (*StorageConfig, error) {
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func loadAndUpdateConfig() (*StorageConfig, error) {
|
||||
func loadAndUpdateConfig(ui *UI) (*StorageConfig, error) {
|
||||
configPath, err := getConfigPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text := texts[currentLanguage]
|
||||
showProgress(text.ReadingConfig)
|
||||
ui.showProgress(text.ReadingConfig)
|
||||
|
||||
_, err = os.ReadFile(configPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, &AppError{"read config file", configPath, err}
|
||||
return nil, &AppError{
|
||||
Type: ErrSystem,
|
||||
Op: "read config file",
|
||||
Path: configPath,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
showProgress(text.GeneratingIds)
|
||||
@ -409,49 +473,26 @@ func loadAndUpdateConfig() (*StorageConfig, error) {
|
||||
func checkAdminPrivileges() (bool, error) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd := exec.Command("net", "session")
|
||||
err := cmd.Run()
|
||||
return err == nil, nil
|
||||
|
||||
cmd := exec.Command("whoami", "/groups")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.Contains(string(output), "S-1-16-12288") ||
|
||||
strings.Contains(string(output), "S-1-5-32-544"), nil
|
||||
|
||||
case "darwin", "linux":
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get current user: %v", err)
|
||||
}
|
||||
return currentUser.Uid == "0", nil
|
||||
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
||||
}
|
||||
}
|
||||
|
||||
func selfElevate() error {
|
||||
verb := "runas"
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 将字符串转换为UTF-16指针
|
||||
verbPtr, _ := windows.UTF16PtrFromString(verb)
|
||||
exePtr, _ := windows.UTF16PtrFromString(exe)
|
||||
cwdPtr, _ := windows.UTF16PtrFromString(cwd)
|
||||
argPtr, _ := windows.UTF16PtrFromString("")
|
||||
|
||||
var showCmd int32 = 1 //SW_NORMAL
|
||||
|
||||
err = windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
func detectLanguage() Language {
|
||||
@ -472,31 +513,57 @@ func waitExit() {
|
||||
bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
}
|
||||
|
||||
func handleError(msg string, err error) {
|
||||
if appErr, ok := err.(*AppError); ok {
|
||||
color.Red("%s: %v", msg, appErr)
|
||||
} else {
|
||||
color.Red("%s: %v", msg, err)
|
||||
// 错误处理函数
|
||||
func handleError(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
logger := log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
||||
switch e := err.(type) {
|
||||
case *AppError:
|
||||
logger.Printf("[ERROR] %v\n", e)
|
||||
if e.Type == ErrPermission {
|
||||
showPrivilegeError()
|
||||
}
|
||||
default:
|
||||
logger.Printf("[ERROR] Unexpected error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Main program entry
|
||||
|
||||
func main() {
|
||||
// 初始化错误恢复
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
color.Red(texts[currentLanguage].ErrorPrefix, r)
|
||||
fmt.Println("\nAn error occurred! / 发生错误!")
|
||||
log.Printf("Panic recovered: %v\n", r)
|
||||
debug.PrintStack()
|
||||
waitExit()
|
||||
}
|
||||
}()
|
||||
|
||||
// 始化配置
|
||||
config := initConfig()
|
||||
|
||||
// 初始化组件
|
||||
ui := NewUI(&config.UI)
|
||||
pm := &ProcessManager{
|
||||
config: &SystemConfig{
|
||||
RetryAttempts: 3,
|
||||
RetryDelay: time.Second,
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
os.Stdout.Sync()
|
||||
currentLanguage = detectLanguage()
|
||||
|
||||
isAdmin, err := checkAdminPrivileges()
|
||||
if err != nil {
|
||||
handleError("permission check failed", err)
|
||||
handleError(err)
|
||||
waitExit()
|
||||
return
|
||||
}
|
||||
@ -504,7 +571,7 @@ func main() {
|
||||
if !isAdmin && runtime.GOOS == "windows" {
|
||||
fmt.Println("Requesting administrator privileges... / 请求管理员权限...")
|
||||
if err := selfElevate(); err != nil {
|
||||
handleError("failed to elevate privileges", err)
|
||||
handleError(err)
|
||||
showPrivilegeError()
|
||||
waitExit()
|
||||
return
|
||||
@ -518,7 +585,7 @@ func main() {
|
||||
|
||||
if checkCursorRunning() {
|
||||
fmt.Println("\nDetected running Cursor instance(s). Closing... / 检测到正在运行的 Cursor 实例,正在关闭...")
|
||||
if err := killCursorProcesses(); err != nil {
|
||||
if err := pm.killCursorProcesses(); err != nil {
|
||||
fmt.Println("Warning: Could not close all Cursor instances. Please close them manually. / 警告:无法关闭所有 Cursor 实例,请手动关闭。")
|
||||
waitExit()
|
||||
return
|
||||
@ -540,17 +607,17 @@ func main() {
|
||||
oldConfig = nil
|
||||
}
|
||||
|
||||
config, err := loadAndUpdateConfig()
|
||||
storageConfig, err := loadAndUpdateConfig(ui)
|
||||
if err != nil {
|
||||
handleError("configuration update failed", err)
|
||||
handleError(err)
|
||||
waitExit()
|
||||
return
|
||||
}
|
||||
|
||||
showIdComparison(oldConfig, config)
|
||||
showIdComparison(oldConfig, storageConfig)
|
||||
|
||||
if err := saveConfig(config); err != nil {
|
||||
handleError("failed to save configuration", err)
|
||||
if err := saveConfig(storageConfig); err != nil {
|
||||
handleError(err)
|
||||
waitExit()
|
||||
return
|
||||
}
|
||||
@ -559,3 +626,118 @@ func main() {
|
||||
fmt.Println("\nOperation completed! / 操作完成!")
|
||||
waitExit()
|
||||
}
|
||||
|
||||
// 优化配置结构
|
||||
type Config struct {
|
||||
Storage StorageConfig
|
||||
UI UIConfig
|
||||
System SystemConfig
|
||||
}
|
||||
|
||||
type UIConfig struct {
|
||||
Language Language
|
||||
Theme string
|
||||
Spinner SpinnerConfig
|
||||
}
|
||||
|
||||
type SystemConfig struct {
|
||||
RetryAttempts int
|
||||
RetryDelay time.Duration
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// 配置初始化函数
|
||||
func initConfig() *Config {
|
||||
return &Config{
|
||||
Storage: StorageConfig{
|
||||
Version: Version,
|
||||
},
|
||||
UI: UIConfig{
|
||||
Language: detectLanguage(),
|
||||
Theme: "default",
|
||||
Spinner: SpinnerConfig{
|
||||
Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
||||
Delay: 100 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
System: SystemConfig{
|
||||
RetryAttempts: 3,
|
||||
RetryDelay: time.Second,
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// UI 组件<E7BB84><E4BBB6><EFBFBD>化
|
||||
type UI struct {
|
||||
config *UIConfig
|
||||
spinner *ProgressSpinner
|
||||
}
|
||||
|
||||
func NewUI(config *UIConfig) *UI {
|
||||
return &UI{
|
||||
config: config,
|
||||
spinner: NewProgressSpinner(""),
|
||||
}
|
||||
}
|
||||
|
||||
func (ui *UI) showProgress(message string) {
|
||||
ui.spinner.message = message
|
||||
ui.spinner.Start()
|
||||
defer ui.spinner.Stop()
|
||||
|
||||
ticker := time.NewTicker(ui.config.Spinner.Delay)
|
||||
defer ticker.Stop()
|
||||
|
||||
for i := 0; i < 15; i++ {
|
||||
<-ticker.C
|
||||
ui.spinner.Spin()
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProcessManager) killWindowsProcess(ctx context.Context) error {
|
||||
// 使用 taskkill 命令结束进程
|
||||
exec.CommandContext(ctx, "taskkill", "/IM", "Cursor.exe").Run()
|
||||
time.Sleep(pm.config.RetryDelay)
|
||||
exec.CommandContext(ctx, "taskkill", "/F", "/IM", "Cursor.exe").Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *ProcessManager) killUnixProcess(ctx context.Context) error {
|
||||
exec.CommandContext(ctx, "pkill", "-f", "Cursor").Run()
|
||||
exec.CommandContext(ctx, "pkill", "-f", "cursor").Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func selfElevate() error {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
// 使用 cmd 实现 Windows 下的提权
|
||||
verb := "runas"
|
||||
exe, _ := os.Executable()
|
||||
cwd, _ := os.Getwd()
|
||||
args := strings.Join(os.Args[1:], " ")
|
||||
|
||||
cmd := exec.Command("cmd", "/C", "start", verb, exe, args)
|
||||
cmd.Dir = cwd
|
||||
return cmd.Run()
|
||||
|
||||
case "darwin", "linux":
|
||||
// Unix 系统使用 sudo
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command("sudo", append([]string{exe}, os.Args[1:]...)...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -15,15 +15,6 @@ set "LDFLAGS=-s -w"
|
||||
set "BUILDMODE=pie"
|
||||
set "GCFLAGS=-N -l"
|
||||
|
||||
:: 检查是否安装了必要的交叉编译工具
|
||||
where gcc >nul 2>nul
|
||||
if %errorlevel% neq 0 (
|
||||
echo %RED%错误: 未找到 gcc,这可能会影响 Mac 系统的交叉编译%RESET%
|
||||
echo %YELLOW%请安装 MinGW-w64 或其他 gcc 工具链%RESET%
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 设置 CGO
|
||||
set CGO_ENABLED=0
|
||||
|
||||
@ -32,31 +23,38 @@ echo %YELLOW%开始构建 version %VERSION%%RESET%
|
||||
echo %YELLOW%使用优化标志: LDFLAGS=%LDFLAGS%, BUILDMODE=%BUILDMODE%%RESET%
|
||||
echo %YELLOW%CGO_ENABLED=%CGO_ENABLED%%RESET%
|
||||
|
||||
:: 仅在必要时清理旧文件
|
||||
if "%1"=="clean" (
|
||||
echo 清理旧构建文件...
|
||||
if exist "..\bin" rd /s /q "..\bin"
|
||||
:: 清理旧的构建文件
|
||||
echo %YELLOW%清理旧的构建文件...%RESET%
|
||||
if exist "..\bin" (
|
||||
rd /s /q "..\bin"
|
||||
echo %GREEN%清理完成%RESET%
|
||||
) else (
|
||||
echo %YELLOW%bin 目录不存在,无需清理%RESET%
|
||||
)
|
||||
|
||||
:: 创建输出目录
|
||||
if not exist "..\bin" mkdir "..\bin" 2>nul
|
||||
mkdir "..\bin" 2>nul
|
||||
|
||||
:: 定义目标平台数组
|
||||
set platforms[0].os=windows
|
||||
set platforms[0].arch=amd64
|
||||
set platforms[0].ext=.exe
|
||||
set platforms[0].suffix=
|
||||
|
||||
set platforms[1].os=darwin
|
||||
set platforms[1].arch=amd64
|
||||
set platforms[1].ext=
|
||||
set platforms[1].suffix=_intel
|
||||
|
||||
set platforms[2].os=darwin
|
||||
set platforms[2].arch=arm64
|
||||
set platforms[2].ext=
|
||||
set platforms[2].suffix=_m1
|
||||
|
||||
set platforms[3].os=linux
|
||||
set platforms[3].arch=amd64
|
||||
set platforms[3].ext=
|
||||
set platforms[3].suffix=
|
||||
|
||||
:: 设置开始时间
|
||||
set start_time=%time%
|
||||
@ -68,6 +66,7 @@ for /L %%i in (0,1,3) do (
|
||||
set "os=!platforms[%%i].os!"
|
||||
set "arch=!platforms[%%i].arch!"
|
||||
set "ext=!platforms[%%i].ext!"
|
||||
set "suffix=!platforms[%%i].suffix!"
|
||||
|
||||
echo.
|
||||
echo Building for !os! !arch!...
|
||||
@ -75,38 +74,22 @@ for /L %%i in (0,1,3) do (
|
||||
set GOOS=!os!
|
||||
set GOARCH=!arch!
|
||||
|
||||
:: 为 darwin 系统设置特殊编译参数和文件名
|
||||
if "!os!"=="darwin" (
|
||||
set "extra_flags=-tags ios"
|
||||
if "!arch!"=="amd64" (
|
||||
set "outfile=..\bin\cursor_id_modifier_v%VERSION%_mac_intel!ext!"
|
||||
) else (
|
||||
set "outfile=..\bin\cursor_id_modifier_v%VERSION%_mac_m1!ext!"
|
||||
)
|
||||
) else (
|
||||
set "extra_flags="
|
||||
set "outfile=..\bin\cursor_id_modifier_v%VERSION%_!os!_!arch!!ext!"
|
||||
)
|
||||
:: 构建输出文件名
|
||||
set "outfile=..\bin\cursor_id_modifier_v%VERSION%_!os!_!arch!!suffix!!ext!"
|
||||
|
||||
go build -trimpath !extra_flags! -buildmode=%BUILDMODE% -ldflags="%LDFLAGS%" -gcflags="%GCFLAGS%" -o "!outfile!" ..\main.go
|
||||
:: 执行构建
|
||||
go build -trimpath -buildmode=%BUILDMODE% -ldflags="%LDFLAGS%" -gcflags="%GCFLAGS%" -o "!outfile!" ..\main.go
|
||||
|
||||
if !errorlevel! equ 0 (
|
||||
echo %GREEN%Build successful: !outfile!%RESET%
|
||||
) else (
|
||||
echo %RED%Build failed for !os! !arch!%RESET%
|
||||
echo %YELLOW%如果是 Mac 系统编译失败,请确保:%RESET%
|
||||
echo %YELLOW%1. 已安装 MinGW-w64%RESET%
|
||||
echo %YELLOW%2. 已设置 GOARCH 和 GOOS%RESET%
|
||||
echo %YELLOW%3. CGO_ENABLED=0%RESET%
|
||||
)
|
||||
)
|
||||
|
||||
:: 计算总耗时
|
||||
set end_time=%time%
|
||||
set options="tokens=1-4 delims=:.,"
|
||||
for /f %options% %%a in ("%start_time%") do set start_s=%%a&set start_m=%%b&set start_h=%%c
|
||||
for /f %options% %%a in ("%end_time%") do set end_s=%%a&set end_m=%%b&set end_h=%%c
|
||||
set /a duration = (end_h - start_h) * 3600 + (end_m - start_m) * 60 + (end_s - start_s)
|
||||
set /a duration = %end_time:~0,2% * 3600 + %end_time:~3,2% * 60 + %end_time:~6,2% - (%start_time:~0,2% * 3600 + %start_time:~3,2% * 60 + %start_time:~6,2%)
|
||||
|
||||
echo.
|
||||
echo %GREEN%所有构建完成! 总耗时: %duration% 秒%RESET%
|
||||
|
Loading…
x
Reference in New Issue
Block a user