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",
|
"fatih",
|
||||||
"gcflags",
|
"gcflags",
|
||||||
"GOARCH",
|
"GOARCH",
|
||||||
|
"IMAGENAME",
|
||||||
"ldflags",
|
"ldflags",
|
||||||
"LOCALAPPDATA",
|
"LOCALAPPDATA",
|
||||||
|
"mktemp",
|
||||||
|
"msys",
|
||||||
|
"pgrep",
|
||||||
"pkill",
|
"pkill",
|
||||||
"runas",
|
"runas",
|
||||||
"setlocal",
|
"setlocal",
|
||||||
"taskkill",
|
"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
|
go 1.21
|
||||||
|
|
||||||
require (
|
require github.com/fatih/color v1.15.0
|
||||||
github.com/fatih/color v1.15.0
|
|
||||||
golang.org/x/sys v0.13.0
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.17 // 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
|
#!/bin/bash
|
||||||
|
|
||||||
# Error handling function / 错误处理函数
|
# Version / 版本号
|
||||||
|
VERSION="v2.0.0"
|
||||||
|
|
||||||
|
# Bilingual message functions / 双语消息函数
|
||||||
error() {
|
error() {
|
||||||
echo "Error/错误: $1" >&2
|
echo "❌ Error: $1"
|
||||||
|
echo "❌ 错误:$2"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info() {
|
||||||
|
echo "ℹ️ $1"
|
||||||
|
echo "ℹ️ $2"
|
||||||
|
}
|
||||||
|
|
||||||
|
success() {
|
||||||
|
echo "✅ $1"
|
||||||
|
echo "✅ $2"
|
||||||
|
}
|
||||||
|
|
||||||
# Detect OS and architecture / 检测操作系统和架构
|
# Detect OS and architecture / 检测操作系统和架构
|
||||||
detect_platform() {
|
detect_platform() {
|
||||||
# Get lowercase OS name and architecture / 获取小写操作系统名称和架构
|
|
||||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
|
|
||||||
# Set binary name based on platform / 根据平台设置二进制文件名
|
|
||||||
case "$OS" in
|
case "$OS" in
|
||||||
linux*)
|
linux*)
|
||||||
case "$ARCH" in
|
case "$ARCH" in
|
||||||
x86_64) BINARY_NAME="cursor_id_modifier_v2.0.0_linux_amd64" ;;
|
x86_64) BINARY_NAME="cursor_id_modifier_${VERSION}_linux_amd64" ;;
|
||||||
*) error "Unsupported Linux architecture/不支持的Linux架构: $ARCH" ;;
|
*) error "Unsupported Linux architecture: $ARCH" "不支持的Linux架构:$ARCH" ;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
darwin*)
|
darwin*)
|
||||||
case "$ARCH" in
|
case "$ARCH" in
|
||||||
x86_64) BINARY_NAME="cursor_id_modifier_v2.0.0_mac_intel" ;;
|
x86_64) BINARY_NAME="cursor_id_modifier_${VERSION}_darwin_amd64_intel" ;;
|
||||||
arm64) BINARY_NAME="cursor_id_modifier_v2.0.0_mac_m1" ;;
|
arm64) BINARY_NAME="cursor_id_modifier_${VERSION}_darwin_arm64_m1" ;;
|
||||||
*) error "Unsupported macOS architecture/不支持的macOS架构: $ARCH" ;;
|
*) 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
|
esac
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
error "Unsupported operating system/不支持的操作系统: $OS"
|
error "Unsupported operating system: $OS" "不支持的操作系统:$OS"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check root privileges / 检查root权限
|
# Check system requirements / 检查系统要求
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
check_requirements() {
|
||||||
error "This script must be run with sudo or as root/此脚本必须使用sudo或root权限运行"
|
info "Checking system requirements..." "正在检查系统要求..."
|
||||||
fi
|
|
||||||
|
# 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 / 初始化安装
|
# Verify binary / 验证二进制文件
|
||||||
detect_platform
|
verify_binary() {
|
||||||
INSTALL_DIR="/usr/local/bin"
|
info "Verifying binary..." "正在验证二进制文件..."
|
||||||
[ -d "$INSTALL_DIR" ] || mkdir -p "$INSTALL_DIR"
|
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 / 下载二进制文件
|
# Main installation process / 主安装流程
|
||||||
echo "Downloading cursor-id-modifier for/正在下载 $OS ($ARCH)..."
|
main() {
|
||||||
TEMP_DIR=$(mktemp -d)
|
info "Starting installation of cursor-id-modifier ${VERSION}..." \
|
||||||
DOWNLOAD_URL="https://github.com/yuaotian/go-cursor-help/raw/main/bin/$BINARY_NAME"
|
"开始安装 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
|
# Start installation / 开始安装
|
||||||
error "Failed to download binary/下载二进制文件失败"
|
main
|
||||||
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'。"
|
|
346
main.go
346
main.go
@ -1,4 +1,3 @@
|
|||||||
// 主程序包 / Main package
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// 导入所需的包 / Import required packages
|
// 导入所需的包 / Import required packages
|
||||||
@ -9,6 +8,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -16,9 +16,10 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"golang.org/x/sys/windows"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"runtime/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 语言类型和常量 / Language type and constants
|
// 语言类型和常量 / Language type and constants
|
||||||
@ -30,6 +31,12 @@ const (
|
|||||||
|
|
||||||
// Version constant
|
// Version constant
|
||||||
Version = "1.0.1"
|
Version = "1.0.1"
|
||||||
|
|
||||||
|
// 定义错误类型常量
|
||||||
|
ErrPermission = "permission_error"
|
||||||
|
ErrConfig = "config_error"
|
||||||
|
ErrProcess = "process_error"
|
||||||
|
ErrSystem = "system_error"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TextResource 存储多语言文本 / TextResource stores multilingual text
|
// TextResource 存储多语言文本 / TextResource stores multilingual text
|
||||||
@ -57,9 +64,11 @@ type StorageConfig struct {
|
|||||||
|
|
||||||
// AppError 定义错误类型 / AppError defines error types
|
// AppError 定义错误类型 / AppError defines error types
|
||||||
type AppError struct {
|
type AppError struct {
|
||||||
Op string
|
Type string
|
||||||
Path string
|
Op string
|
||||||
Err error
|
Path string
|
||||||
|
Err error
|
||||||
|
Context map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProgressSpinner 用于显示进度动画 / ProgressSpinner for showing progress animation
|
// ProgressSpinner 用于显示进度动画 / ProgressSpinner for showing progress animation
|
||||||
@ -69,6 +78,14 @@ type ProgressSpinner struct {
|
|||||||
message string
|
message string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SpinnerConfig 定义进度条配置
|
||||||
|
type SpinnerConfig struct {
|
||||||
|
Frames []string
|
||||||
|
Delay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 全局变量 / Global variables
|
// 全局变量 / Global variables
|
||||||
var (
|
var (
|
||||||
currentLanguage = CN // 默认为中文 / Default to Chinese
|
currentLanguage = CN // 默认为中文 / Default to Chinese
|
||||||
@ -103,13 +120,13 @@ var (
|
|||||||
|
|
||||||
// Error implementation for AppError
|
// Error implementation for AppError
|
||||||
func (e *AppError) Error() string {
|
func (e *AppError) Error() string {
|
||||||
if e.Path != "" {
|
if e.Context != nil {
|
||||||
return fmt.Sprintf("%s: %v [Path: %s]", e.Op, e.Err, e.Path)
|
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 {
|
func NewStorageConfig() *StorageConfig {
|
||||||
return &StorageConfig{
|
return &StorageConfig{
|
||||||
TelemetryMacMachineId: generateMachineId(),
|
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 {
|
func generateMachineId() string {
|
||||||
data := make([]byte, 32)
|
data := make([]byte, 32)
|
||||||
if _, err := rand.Read(data); err != nil {
|
if _, err := rand.Read(data); err != nil {
|
||||||
@ -168,6 +185,11 @@ func (s *ProgressSpinner) Stop() {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start starts the spinner animation
|
||||||
|
func (s *ProgressSpinner) Start() {
|
||||||
|
s.current = 0
|
||||||
|
}
|
||||||
|
|
||||||
// File and system operations
|
// File and system operations
|
||||||
|
|
||||||
func getConfigPath() (string, error) {
|
func getConfigPath() (string, error) {
|
||||||
@ -196,17 +218,32 @@ func getConfigPath() (string, error) {
|
|||||||
func safeWriteFile(path string, data []byte, perm os.FileMode) error {
|
func safeWriteFile(path string, data []byte, perm os.FileMode) error {
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
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"
|
tmpPath := path + ".tmp"
|
||||||
if err := os.WriteFile(tmpPath, data, perm); err != nil {
|
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 {
|
if err := os.Rename(tmpPath, path); err != nil {
|
||||||
os.Remove(tmpPath)
|
os.Remove(tmpPath)
|
||||||
return &AppError{"rename file", path, err}
|
return &AppError{
|
||||||
|
Type: ErrSystem,
|
||||||
|
Op: "rename file",
|
||||||
|
Path: path,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -218,22 +255,34 @@ func setFilePermissions(filePath string) error {
|
|||||||
|
|
||||||
// Process management functions
|
// Process management functions
|
||||||
|
|
||||||
func killCursorProcesses() error {
|
type ProcessManager struct {
|
||||||
if runtime.GOOS == "windows" {
|
config *SystemConfig
|
||||||
// First try graceful shutdown
|
}
|
||||||
exec.Command("taskkill", "/IM", "Cursor.exe").Run()
|
|
||||||
exec.Command("taskkill", "/IM", "cursor.exe").Run()
|
|
||||||
|
|
||||||
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
|
for attempt := 0; attempt < pm.config.RetryAttempts; attempt++ {
|
||||||
exec.Command("taskkill", "/F", "/IM", "Cursor.exe").Run()
|
if err := pm.killProcess(ctx); err != nil {
|
||||||
exec.Command("taskkill", "/F", "/IM", "cursor.exe").Run()
|
time.Sleep(pm.config.RetryDelay)
|
||||||
} else {
|
continue
|
||||||
exec.Command("pkill", "-f", "Cursor").Run()
|
}
|
||||||
exec.Command("pkill", "-f", "cursor").Run()
|
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 {
|
func checkCursorRunning() bool {
|
||||||
@ -278,11 +327,11 @@ func printCyberpunkBanner() {
|
|||||||
|
|
||||||
banner := `
|
banner := `
|
||||||
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
|
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
|
||||||
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
|
██╔════╝██║ ██║██╔══██╗██╔════╝█╔═══██╗██╔══██╗
|
||||||
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
|
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
|
||||||
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
|
██║ ██║ ██║██╔══██╗╚════██ ██║ ██║██╔══██╗
|
||||||
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
|
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
|
||||||
╚════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
|
╚════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚════╝ ╚═╝ ╚═╝
|
||||||
`
|
`
|
||||||
cyan.Println(banner)
|
cyan.Println(banner)
|
||||||
yellow.Println("\t\t>> Cursor ID Modifier v1.0 <<")
|
yellow.Println("\t\t>> Cursor ID Modifier v1.0 <<")
|
||||||
@ -350,11 +399,21 @@ func saveConfig(config *StorageConfig) error {
|
|||||||
|
|
||||||
content, err := json.MarshalIndent(config, "", " ")
|
content, err := json.MarshalIndent(config, "", " ")
|
||||||
if err != nil {
|
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) {
|
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 {
|
if err := safeWriteFile(configPath, content, 0666); err != nil {
|
||||||
@ -386,18 +445,23 @@ func readExistingConfig() (*StorageConfig, error) {
|
|||||||
return &config, nil
|
return &config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAndUpdateConfig() (*StorageConfig, error) {
|
func loadAndUpdateConfig(ui *UI) (*StorageConfig, error) {
|
||||||
configPath, err := getConfigPath()
|
configPath, err := getConfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
text := texts[currentLanguage]
|
text := texts[currentLanguage]
|
||||||
showProgress(text.ReadingConfig)
|
ui.showProgress(text.ReadingConfig)
|
||||||
|
|
||||||
_, err = os.ReadFile(configPath)
|
_, err = os.ReadFile(configPath)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
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)
|
showProgress(text.GeneratingIds)
|
||||||
@ -409,49 +473,26 @@ func loadAndUpdateConfig() (*StorageConfig, error) {
|
|||||||
func checkAdminPrivileges() (bool, error) {
|
func checkAdminPrivileges() (bool, error) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
cmd := exec.Command("net", "session")
|
cmd := exec.Command("whoami", "/groups")
|
||||||
err := cmd.Run()
|
output, err := cmd.Output()
|
||||||
return err == nil, nil
|
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":
|
case "darwin", "linux":
|
||||||
currentUser, err := user.Current()
|
currentUser, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to get current user: %v", err)
|
return false, fmt.Errorf("failed to get current user: %v", err)
|
||||||
}
|
}
|
||||||
return currentUser.Uid == "0", nil
|
return currentUser.Uid == "0", nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
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
|
// Utility functions
|
||||||
|
|
||||||
func detectLanguage() Language {
|
func detectLanguage() Language {
|
||||||
@ -472,31 +513,57 @@ func waitExit() {
|
|||||||
bufio.NewReader(os.Stdin).ReadString('\n')
|
bufio.NewReader(os.Stdin).ReadString('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleError(msg string, err error) {
|
// 错误处理函数
|
||||||
if appErr, ok := err.(*AppError); ok {
|
func handleError(err error) {
|
||||||
color.Red("%s: %v", msg, appErr)
|
if err == nil {
|
||||||
} else {
|
return
|
||||||
color.Red("%s: %v", msg, err)
|
}
|
||||||
|
|
||||||
|
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
|
// Main program entry
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// 初始化错误恢复
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
color.Red(texts[currentLanguage].ErrorPrefix, r)
|
log.Printf("Panic recovered: %v\n", r)
|
||||||
fmt.Println("\nAn error occurred! / 发生错误!")
|
debug.PrintStack()
|
||||||
waitExit()
|
waitExit()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// 始化配置
|
||||||
|
config := initConfig()
|
||||||
|
|
||||||
|
// 初始化组件
|
||||||
|
ui := NewUI(&config.UI)
|
||||||
|
pm := &ProcessManager{
|
||||||
|
config: &SystemConfig{
|
||||||
|
RetryAttempts: 3,
|
||||||
|
RetryDelay: time.Second,
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
os.Stdout.Sync()
|
os.Stdout.Sync()
|
||||||
currentLanguage = detectLanguage()
|
currentLanguage = detectLanguage()
|
||||||
|
|
||||||
isAdmin, err := checkAdminPrivileges()
|
isAdmin, err := checkAdminPrivileges()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError("permission check failed", err)
|
handleError(err)
|
||||||
waitExit()
|
waitExit()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -504,7 +571,7 @@ func main() {
|
|||||||
if !isAdmin && runtime.GOOS == "windows" {
|
if !isAdmin && runtime.GOOS == "windows" {
|
||||||
fmt.Println("Requesting administrator privileges... / 请求管理员权限...")
|
fmt.Println("Requesting administrator privileges... / 请求管理员权限...")
|
||||||
if err := selfElevate(); err != nil {
|
if err := selfElevate(); err != nil {
|
||||||
handleError("failed to elevate privileges", err)
|
handleError(err)
|
||||||
showPrivilegeError()
|
showPrivilegeError()
|
||||||
waitExit()
|
waitExit()
|
||||||
return
|
return
|
||||||
@ -518,7 +585,7 @@ func main() {
|
|||||||
|
|
||||||
if checkCursorRunning() {
|
if checkCursorRunning() {
|
||||||
fmt.Println("\nDetected running Cursor instance(s). Closing... / 检测到正在运行的 Cursor 实例,正在关闭...")
|
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 实例,请手动关闭。")
|
fmt.Println("Warning: Could not close all Cursor instances. Please close them manually. / 警告:无法关闭所有 Cursor 实例,请手动关闭。")
|
||||||
waitExit()
|
waitExit()
|
||||||
return
|
return
|
||||||
@ -540,17 +607,17 @@ func main() {
|
|||||||
oldConfig = nil
|
oldConfig = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := loadAndUpdateConfig()
|
storageConfig, err := loadAndUpdateConfig(ui)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError("configuration update failed", err)
|
handleError(err)
|
||||||
waitExit()
|
waitExit()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
showIdComparison(oldConfig, config)
|
showIdComparison(oldConfig, storageConfig)
|
||||||
|
|
||||||
if err := saveConfig(config); err != nil {
|
if err := saveConfig(storageConfig); err != nil {
|
||||||
handleError("failed to save configuration", err)
|
handleError(err)
|
||||||
waitExit()
|
waitExit()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -559,3 +626,118 @@ func main() {
|
|||||||
fmt.Println("\nOperation completed! / 操作完成!")
|
fmt.Println("\nOperation completed! / 操作完成!")
|
||||||
waitExit()
|
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 "BUILDMODE=pie"
|
||||||
set "GCFLAGS=-N -l"
|
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
|
:: 设置 CGO
|
||||||
set CGO_ENABLED=0
|
set CGO_ENABLED=0
|
||||||
|
|
||||||
@ -32,31 +23,38 @@ echo %YELLOW%开始构建 version %VERSION%%RESET%
|
|||||||
echo %YELLOW%使用优化标志: LDFLAGS=%LDFLAGS%, BUILDMODE=%BUILDMODE%%RESET%
|
echo %YELLOW%使用优化标志: LDFLAGS=%LDFLAGS%, BUILDMODE=%BUILDMODE%%RESET%
|
||||||
echo %YELLOW%CGO_ENABLED=%CGO_ENABLED%%RESET%
|
echo %YELLOW%CGO_ENABLED=%CGO_ENABLED%%RESET%
|
||||||
|
|
||||||
:: 仅在必要时清理旧文件
|
:: 清理旧的构建文件
|
||||||
if "%1"=="clean" (
|
echo %YELLOW%清理旧的构建文件...%RESET%
|
||||||
echo 清理旧构建文件...
|
if exist "..\bin" (
|
||||||
if exist "..\bin" rd /s /q "..\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].os=windows
|
||||||
set platforms[0].arch=amd64
|
set platforms[0].arch=amd64
|
||||||
set platforms[0].ext=.exe
|
set platforms[0].ext=.exe
|
||||||
|
set platforms[0].suffix=
|
||||||
|
|
||||||
set platforms[1].os=darwin
|
set platforms[1].os=darwin
|
||||||
set platforms[1].arch=amd64
|
set platforms[1].arch=amd64
|
||||||
set platforms[1].ext=
|
set platforms[1].ext=
|
||||||
|
set platforms[1].suffix=_intel
|
||||||
|
|
||||||
set platforms[2].os=darwin
|
set platforms[2].os=darwin
|
||||||
set platforms[2].arch=arm64
|
set platforms[2].arch=arm64
|
||||||
set platforms[2].ext=
|
set platforms[2].ext=
|
||||||
|
set platforms[2].suffix=_m1
|
||||||
|
|
||||||
set platforms[3].os=linux
|
set platforms[3].os=linux
|
||||||
set platforms[3].arch=amd64
|
set platforms[3].arch=amd64
|
||||||
set platforms[3].ext=
|
set platforms[3].ext=
|
||||||
|
set platforms[3].suffix=
|
||||||
|
|
||||||
:: 设置开始时间
|
:: 设置开始时间
|
||||||
set start_time=%time%
|
set start_time=%time%
|
||||||
@ -68,6 +66,7 @@ for /L %%i in (0,1,3) do (
|
|||||||
set "os=!platforms[%%i].os!"
|
set "os=!platforms[%%i].os!"
|
||||||
set "arch=!platforms[%%i].arch!"
|
set "arch=!platforms[%%i].arch!"
|
||||||
set "ext=!platforms[%%i].ext!"
|
set "ext=!platforms[%%i].ext!"
|
||||||
|
set "suffix=!platforms[%%i].suffix!"
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo Building for !os! !arch!...
|
echo Building for !os! !arch!...
|
||||||
@ -75,38 +74,22 @@ for /L %%i in (0,1,3) do (
|
|||||||
set GOOS=!os!
|
set GOOS=!os!
|
||||||
set GOARCH=!arch!
|
set GOARCH=!arch!
|
||||||
|
|
||||||
:: 为 darwin 系统设置特殊编译参数和文件名
|
:: 构建输出文件名
|
||||||
if "!os!"=="darwin" (
|
set "outfile=..\bin\cursor_id_modifier_v%VERSION%_!os!_!arch!!suffix!!ext!"
|
||||||
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!"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 (
|
if !errorlevel! equ 0 (
|
||||||
echo %GREEN%Build successful: !outfile!%RESET%
|
echo %GREEN%Build successful: !outfile!%RESET%
|
||||||
) else (
|
) else (
|
||||||
echo %RED%Build failed for !os! !arch!%RESET%
|
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 end_time=%time%
|
||||||
set options="tokens=1-4 delims=:.,"
|
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%)
|
||||||
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)
|
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo %GREEN%所有构建完成! 总耗时: %duration% 秒%RESET%
|
echo %GREEN%所有构建完成! 总耗时: %duration% 秒%RESET%
|
||||||
|
Loading…
x
Reference in New Issue
Block a user