From 99e31a7bba724d046173d651852a23cf7e31d18b Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 02:05:35 +0200 Subject: [PATCH 01/68] chore: Refactor build and installation scripts; update configuration and release process - Enhanced build scripts for improved parallel execution and optimization flags. - Updated installation scripts for better user experience and error handling. - Modified .gitignore to include new build artifacts and IDE configurations. - Updated .goreleaser.yml for better release management and platform support. - Removed deprecated main.go file and adjusted README for clarity on installation and usage. - Added support for multiple architectures in build process, including 32-bit and 64-bit for Windows, macOS, and Linux. These changes streamline the development workflow and enhance the overall usability of the Cursor ID Modifier tool. --- .github/workflows/release.yml | 86 ++- .gitignore | 43 +- .goreleaser.yml | 106 +++- Makefile | 21 + README.md | 174 ++---- go.mod | 9 +- go.sum | 17 + internal/config/config.go | 150 +++++ internal/lang/lang.go | 156 +++++ internal/process/manager.go | 162 +++++ internal/ui/display.go | 101 ++++ internal/ui/spinner.go | 110 ++++ main.go | 1050 --------------------------------- pkg/idgen/generator.go | 95 +++ pkg/idgen/generator_test.go | 26 + scripts/build_all.bat | 146 ++--- scripts/build_all.sh | 108 ++-- scripts/install.ps1 | 336 +++-------- scripts/install.sh | 324 ++-------- 19 files changed, 1336 insertions(+), 1884 deletions(-) create mode 100644 Makefile create mode 100644 internal/config/config.go create mode 100644 internal/lang/lang.go create mode 100644 internal/process/manager.go create mode 100644 internal/ui/display.go create mode 100644 internal/ui/spinner.go delete mode 100644 main.go create mode 100644 pkg/idgen/generator.go create mode 100644 pkg/idgen/generator_test.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9feeb88..47d5628 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,33 @@ permissions: contents: write jobs: - goreleaser: + test: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + arch: [amd64, arm64] + exclude: + - os: windows-latest + arch: arm64 + - os: ubuntu-latest + arch: arm64 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + cache: true + + - name: Run tests + run: go test -v ./... + + release: + needs: test runs-on: ubuntu-latest steps: - name: Checkout @@ -20,8 +46,13 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: + go-version: '1.21' + cache: true check-latest: true + - name: Install dependencies + run: go mod tidy + - name: Set Repository Variables run: | echo "GITHUB_REPOSITORY_OWNER=$(echo ${{ github.repository }} | cut -d '/' -f 1)" >> $GITHUB_ENV @@ -35,3 +66,56 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CGO_ENABLED: 0 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: binaries + path: dist/* + retention-days: 5 + + - name: Generate changelog + if: success() + run: | + echo "# 🚀 Cursor ID Modifier ${{ github.ref_name }}" > ${{ github.workspace }}-CHANGELOG.txt + echo "" >> ${{ github.workspace }}-CHANGELOG.txt + + echo "## ✨ Supported Platforms" >> ${{ github.workspace }}-CHANGELOG.txt + echo "" >> ${{ github.workspace }}-CHANGELOG.txt + echo "### 🪟 Windows" >> ${{ github.workspace }}-CHANGELOG.txt + echo "- Windows x64 (64-bit)" >> ${{ github.workspace }}-CHANGELOG.txt + echo "- Windows x86 (32-bit)" >> ${{ github.workspace }}-CHANGELOG.txt + echo "" >> ${{ github.workspace }}-CHANGELOG.txt + echo "### 🍎 macOS" >> ${{ github.workspace }}-CHANGELOG.txt + echo "- macOS Intel (x64)" >> ${{ github.workspace }}-CHANGELOG.txt + echo "- macOS Apple Silicon (M1/M2)" >> ${{ github.workspace }}-CHANGELOG.txt + echo "" >> ${{ github.workspace }}-CHANGELOG.txt + echo "### 🐧 Linux" >> ${{ github.workspace }}-CHANGELOG.txt + echo "- Linux x64 (64-bit)" >> ${{ github.workspace }}-CHANGELOG.txt + echo "- Linux x86 (32-bit)" >> ${{ github.workspace }}-CHANGELOG.txt + echo "- Linux ARM64" >> ${{ github.workspace }}-CHANGELOG.txt + echo "" >> ${{ github.workspace }}-CHANGELOG.txt + + echo "## 📦 Quick Start" >> ${{ github.workspace }}-CHANGELOG.txt + echo "" >> ${{ github.workspace }}-CHANGELOG.txt + echo "### Linux/macOS" >> ${{ github.workspace }}-CHANGELOG.txt + echo "\`\`\`bash" >> ${{ github.workspace }}-CHANGELOG.txt + echo "curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier" >> ${{ github.workspace }}-CHANGELOG.txt + echo "\`\`\`" >> ${{ github.workspace }}-CHANGELOG.txt + echo "" >> ${{ github.workspace }}-CHANGELOG.txt + echo "### Windows (PowerShell Admin)" >> ${{ github.workspace }}-CHANGELOG.txt + echo "\`\`\`powershell" >> ${{ github.workspace }}-CHANGELOG.txt + echo "irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier" >> ${{ github.workspace }}-CHANGELOG.txt + echo "\`\`\`" >> ${{ github.workspace }}-CHANGELOG.txt + echo "" >> ${{ github.workspace }}-CHANGELOG.txt + + echo "## 🔄 Changes" >> ${{ github.workspace }}-CHANGELOG.txt + echo "* 📦 Release version: ${{ github.ref_name }}" >> ${{ github.workspace }}-CHANGELOG.txt + echo "* 📝 Full changelog: https://github.com/${{ github.repository }}/commits/${{ github.ref_name }}" >> ${{ github.workspace }}-CHANGELOG.txt + + echo "" >> ${{ github.workspace }}-CHANGELOG.txt + echo "## 🔍 Download Files" >> ${{ github.workspace }}-CHANGELOG.txt + echo "- Windows: \`cursor-id-modifier_${{ github.ref_name }}_Windows_[x64/x86].zip\`" >> ${{ github.workspace }}-CHANGELOG.txt + echo "- macOS: \`cursor-id-modifier_${{ github.ref_name }}_macOS_[x64/arm64]_[intel/apple_silicon].tar.gz\`" >> ${{ github.workspace }}-CHANGELOG.txt + echo "- Linux: \`cursor-id-modifier_${{ github.ref_name }}_Linux_[x64/x86/arm64].tar.gz\`" >> ${{ github.workspace }}-CHANGELOG.txt diff --git a/.gitignore b/.gitignore index 54f0356..ef225bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,40 @@ -# Binary files -*.dll -*.so -*.dylib - -# Build directories -releases/ +# Compiled binary cursor-id-modifier +cursor-id-modifier.exe +# Build output directories +bin/ +dist/ # Go specific go.sum -# Temporary files -*.tmp -*~ +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo -# System files +# OS specific .DS_Store Thumbs.db -.vscode -/.idea \ No newline at end of file +# Build and release artifacts +releases/ +*.syso +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test files +*.test +*.out +coverage.txt + +# Temporary files +*.tmp +*~ +*.bak +*.log \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index 862b38d..4a01b1f 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,56 +1,110 @@ -project_name: cursor-id-modifier - before: hooks: - go mod tidy builds: - - env: + - main: ./cmd/cursor-id-modifier + binary: cursor-id-modifier + env: - CGO_ENABLED=0 - ldflags: - - -s -w -X main.version={{.Version}} goos: - linux - windows - darwin goarch: - - amd64 - - arm64 - mod_timestamp: '{{ .CommitTimestamp }}' + - amd64 # Intel 64-bit + - arm64 # Apple Silicon/ARM64 + - "386" # Intel 32-bit + ignore: + - goos: darwin + goarch: "386" # No 32-bit support for macOS + ldflags: + - -s -w + - -X main.version={{.Version}} flags: - -trimpath - binary: cursor_id_modifier_{{ .Version }}_{{ .Os }}_{{ .Arch }} + mod_timestamp: '{{ .CommitTimestamp }}' + + # Build matrix + matrix: + # Special builds for macOS + - goos: [darwin] + goarch: [amd64] + tags: ["intel"] + - goos: [darwin] + goarch: [arm64] + tags: ["apple_silicon"] + # Windows builds + - goos: [windows] + goarch: [amd64, "386"] + # Linux builds + - goos: [linux] + goarch: [amd64, arm64, "386"] archives: - - format: binary - name_template: "{{ .Binary }}" - allow_different_binary_count: true + - format: tar.gz + format_overrides: + - goos: windows + format: zip + name_template: >- + {{ .ProjectName }}_ + {{- .Version }}_ + {{- .Os }}_ + {{- .Arch }} + {{- with .Tags }}_{{ . }}{{ end }} + files: + - README.md + - LICENSE + - scripts/* # Include installation scripts + replacements: + darwin: macOS + linux: Linux + windows: Windows + 386: x86 + amd64: x64 + arm64: arm64 + +checksum: + name_template: 'checksums.txt' + algorithm: sha256 changelog: - use: github sort: asc - groups: - - title: Features - regexp: "^.*feat[(\\w)]*:+.*$" - order: 0 - - title: 'Bug Fixes' - regexp: "^.*fix[(\\w)]*:+.*$" - order: 1 - - title: Others - order: 999 filters: exclude: - '^docs:' - '^test:' - '^ci:' - - '^chore:' - Merge pull request - Merge branch release: - github: - owner: '{{ .Env.GITHUB_REPOSITORY_OWNER }}' - name: '{{ .Env.GITHUB_REPOSITORY_NAME }}' draft: false prerelease: auto mode: replace + header: | + ## Cursor ID Modifier {{ .Version }} + + ### Supported Platforms + - Windows: x64, x86 + - macOS: Intel (x64), Apple Silicon (M1/M2) + - Linux: x64, x86, ARM64 + + See [CHANGELOG](CHANGELOG.md) for more details. + footer: | + **Full Changelog**: https://github.com/dacrab/cursor-id-modifier/compare/{{ .PreviousTag }}...{{ .Tag }} + + ## Quick Installation + + **Linux/macOS**: + ```bash + curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier + ``` + + **Windows** (PowerShell Admin): + ```powershell + irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier + ``` + +snapshot: + name_template: "{{ incpatch .Version }}-next" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f60895c --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +.PHONY: build clean test vet + +# Build the application +build: + go build -v ./cmd/cursor-id-modifier + +# Clean build artifacts +clean: + rm -f cursor-id-modifier + go clean + +# Run tests +test: + go test -v ./... + +# Run go vet +vet: + go vet ./... + +# Run all checks +all: vet test build \ No newline at end of file diff --git a/README.md b/README.md index 0f3b3f6..ec98ffc 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@
-[![Release](https://img.shields.io/github/v/release/yuaotian/go-cursor-help?style=flat-square&logo=github&color=blue)](https://github.com/yuaotian/go-cursor-help/releases/latest) -[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/yuaotian/go-cursor-help/blob/main/LICENSE) -[![Stars](https://img.shields.io/github/stars/yuaotian/go-cursor-help?style=flat-square&logo=github)](https://github.com/yuaotian/go-cursor-help/stargazers) +[![Release](https://img.shields.io/github/v/release/dacrab/cursor-id-modifier?style=flat-square&logo=github&color=blue)](https://github.com/dacrab/cursor-id-modifier/releases/latest) +[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/dacrab/cursor-id-modifier/blob/main/LICENSE) +[![Stars](https://img.shields.io/github/stars/dacrab/cursor-id-modifier?style=flat-square&logo=github)](https://github.com/dacrab/cursor-id-modifier/stargazers) [English](#-english) | [中文](#-chinese) @@ -27,73 +27,59 @@ this is a mistake. ### 💻 System Support -**Windows** ✅ x64 -**macOS** ✅ Intel & M-series -**Linux** ✅ x64 & ARM64 +**Windows** ✅ +- x64 (64-bit) +- x86 (32-bit) -### 📥 Installation +**macOS** ✅ +- Intel (x64) +- Apple Silicon (M1/M2) -#### Automatic Installation (Recommended) +**Linux** ✅ +- x64 (64-bit) +- x86 (32-bit) +- ARM64 -**Linux/macOS** +### 📥 One-Click Solution + +**Linux/macOS**: Copy and paste in terminal: ```bash -curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.sh | sudo bash +curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier ``` -**Windows** (Run PowerShell as Admin) +**Windows**: Copy and paste in PowerShell (Admin): ```powershell -irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.ps1 | iex +irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier ``` -The installation script will automatically: -- Request necessary privileges (sudo/admin) -- Close any running Cursor instances -- Backup existing configuration -- Install the tool -- Add it to system PATH -- Clean up temporary files - -#### Manual Installation - -1. Download the latest release for your system from the [releases page](https://github.com/yuaotian/go-cursor-help/releases) -2. Extract and run with administrator/root privileges: - ```bash - # Linux/macOS - chmod +x ./cursor_id_modifier_* # Add execute permission - sudo ./cursor_id_modifier_* - - # Windows (PowerShell Admin) - .\cursor_id_modifier_*.exe - ``` - -#### Manual Configuration Method - -1. Close Cursor completely -2. Navigate to the configuration file location: - - Windows: `%APPDATA%\Cursor\User\globalStorage\storage.json` - - macOS: `~/Library/Application Support/Cursor/User/globalStorage/storage.json` - - Linux: `~/.config/Cursor/User/globalStorage/storage.json` -3. Create a backup of `storage.json` -4. Edit `storage.json` and update these fields with new random UUIDs: - ```json - { - "telemetry.machineId": "generate-new-uuid", - "telemetry.macMachineId": "generate-new-uuid", - "telemetry.devDeviceId": "generate-new-uuid", - "telemetry.sqmId": "generate-new-uuid", - "lastModified": "2024-01-01T00:00:00.000Z", - "version": "1.0.1" - } - ``` -5. Save the file and restart Cursor +That's it! The script will: +1. Install the tool automatically +2. Reset your Cursor trial immediately + +### 🔧 Manual Installation + +Download the appropriate file for your system from [releases](https://github.com/dacrab/cursor-id-modifier/releases/latest): + +**Windows**: +- 64-bit: `cursor-id-modifier_vX.X.X_Windows_x64.zip` +- 32-bit: `cursor-id-modifier_vX.X.X_Windows_x86.zip` + +**macOS**: +- Intel: `cursor-id-modifier_vX.X.X_macOS_x64_intel.tar.gz` +- M1/M2: `cursor-id-modifier_vX.X.X_macOS_arm64_apple_silicon.tar.gz` + +**Linux**: +- 64-bit: `cursor-id-modifier_vX.X.X_Linux_x64.tar.gz` +- 32-bit: `cursor-id-modifier_vX.X.X_Linux_x86.tar.gz` +- ARM64: `cursor-id-modifier_vX.X.X_Linux_arm64.tar.gz` ### 🔧 Technical Details #### Configuration Files The program modifies Cursor's `storage.json` config file located at: -- Windows: `%APPDATA%\Cursor\User\globalStorage\` -- macOS: `~/Library/Application Support/Cursor/User/globalStorage/` -- Linux: `~/.config/Cursor/User/globalStorage/` +- Windows: `%APPDATA%\Cursor\User\globalStorage\storage.json` +- macOS: `~/Library/Application Support/Cursor/User/globalStorage/storage.json` +- Linux: `~/.config/Cursor/User/globalStorage/storage.json` #### Modified Fields The tool generates new unique identifiers for: @@ -103,10 +89,9 @@ The tool generates new unique identifiers for: - `telemetry.sqmId` #### Safety Features -- Automatic backup of existing configuration -- Safe process termination -- Atomic file operations -- Error handling and rollback +- ✅ Safe process termination +- ✅ Atomic file operations +- ✅ Error handling and recovery --- @@ -125,65 +110,25 @@ this is a mistake. ### 💻 系统支持 -**Windows** ✅ x64 -**macOS** ✅ Intel和M系列 -**Linux** ✅ x64和ARM64 - -### 📥 安装方法 +**Windows** ✅ x64 & x86 +**macOS** ✅ Intel & M-series +**Linux** ✅ x64 & ARM64 -#### 自动安装(推荐) +### 📥 一键解决 -**Linux/macOS** +**Linux/macOS**: 在终端中复制粘贴: ```bash -curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.sh | sudo bash +curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier ``` -**Windows** (以管理员身份运行PowerShell) +**Windows**: 在PowerShell(管理员)中复制粘贴: ```powershell -irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.ps1 | iex +irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier ``` -安装脚本会自动: -- 请求必要的权限(sudo/管理员) -- 关闭所有运行中的Cursor实例 -- 备份现有配置 -- 安装工具 -- 添加到系统PATH -- 清理临时文件 - -#### 手动安装 - -1. 从[发布页面](https://github.com/yuaotian/go-cursor-help/releases)下载适合您系统的最新版本 -2. 解压并以管理员/root权限运行: - ```bash - # Linux/macOS - chmod +x ./cursor_id_modifier_* # 添加执行权限 - sudo ./cursor_id_modifier_* - - # Windows (PowerShell 管理员) - .\cursor_id_modifier_*.exe - ``` - -#### 手动配置方法 - -1. 完全关闭 Cursor -2. 找到配置文件位置: - - Windows: `%APPDATA%\Cursor\User\globalStorage\storage.json` - - macOS: `~/Library/Application Support/Cursor/User/globalStorage/storage.json` - - Linux: `~/.config/Cursor/User/globalStorage/storage.json` -3. 备份 `storage.json` -4. 编辑 `storage.json` 并更新以下字段(使用新的随机UUID): - ```json - { - "telemetry.machineId": "生成新的uuid", - "telemetry.macMachineId": "生成新的uuid", - "telemetry.devDeviceId": "生成新的uuid", - "telemetry.sqmId": "生成新的uuid", - "lastModified": "2024-01-01T00:00:00.000Z", - "version": "1.0.1" - } - ``` -5. 保存文件并重启 Cursor +就这样!脚本会: +1. 自动安装工具 +2. 立即重置Cursor试用期 ### 🔧 技术细节 @@ -201,10 +146,9 @@ irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/ins - `telemetry.sqmId` #### 安全特性 -- 自动备份现有配置 -- 安全的进程终止 -- 原子文件操作 -- 错误处理和回滚 +- ✅ 安全的进程终止 +- ✅ 原子文件操作 +- ✅ 错误处理和恢复 ## ⭐ Star History or Repobeats diff --git a/go.mod b/go.mod index 6b674f1..05e7116 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,17 @@ module cursor-id-modifier go 1.21 -require github.com/fatih/color v1.15.0 +require ( + github.com/fatih/color v1.15.0 + github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.10.0 +) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.13.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b4bb98d..75c1611 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,6 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -5,6 +8,20 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..e3f3f53 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,150 @@ +package config + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "sync" + "time" +) + +// StorageConfig represents the storage configuration +type StorageConfig struct { + TelemetryMacMachineId string `json:"telemetry.macMachineId"` + TelemetryMachineId string `json:"telemetry.machineId"` + TelemetryDevDeviceId string `json:"telemetry.devDeviceId"` + TelemetrySqmId string `json:"telemetry.sqmId"` + LastModified string `json:"lastModified"` + Version string `json:"version"` +} + +// Manager handles configuration operations +type Manager struct { + configPath string + mu sync.RWMutex +} + +// NewManager creates a new configuration manager +func NewManager(username string) (*Manager, error) { + configPath, err := getConfigPath(username) + if err != nil { + return nil, fmt.Errorf("failed to get config path: %w", err) + } + + return &Manager{ + configPath: configPath, + }, nil +} + +// ReadConfig reads the existing configuration +func (m *Manager) ReadConfig() (*StorageConfig, error) { + m.mu.RLock() + defer m.mu.RUnlock() + + data, err := os.ReadFile(m.configPath) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, fmt.Errorf("failed to read config file: %w", err) + } + + var config StorageConfig + if err := json.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("failed to parse config file: %w", err) + } + + return &config, nil +} + +// SaveConfig saves the configuration +func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error { + m.mu.Lock() + defer m.mu.Unlock() + + // Ensure parent directories exist + if err := os.MkdirAll(filepath.Dir(m.configPath), 0755); err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + // Set file permissions + if err := os.Chmod(m.configPath, 0666); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to set file permissions: %w", err) + } + + // Read existing config to preserve other fields + var originalFile map[string]interface{} + originalFileContent, err := os.ReadFile(m.configPath) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to read original file: %w", err) + } else if err == nil { + if err := json.Unmarshal(originalFileContent, &originalFile); err != nil { + return fmt.Errorf("failed to parse original file: %w", err) + } + } else { + originalFile = make(map[string]interface{}) + } + + // Update fields + originalFile["telemetry.sqmId"] = config.TelemetrySqmId + originalFile["telemetry.macMachineId"] = config.TelemetryMacMachineId + originalFile["telemetry.machineId"] = config.TelemetryMachineId + originalFile["telemetry.devDeviceId"] = config.TelemetryDevDeviceId + originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339) + originalFile["version"] = "1.0.1" + + // Marshal with indentation + newFileContent, err := json.MarshalIndent(originalFile, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) + } + + // Write to temporary file + tmpPath := m.configPath + ".tmp" + if err := os.WriteFile(tmpPath, newFileContent, 0666); err != nil { + return fmt.Errorf("failed to write temporary file: %w", err) + } + + // Set final permissions + fileMode := os.FileMode(0666) + if readOnly { + fileMode = 0444 + } + + if err := os.Chmod(tmpPath, fileMode); err != nil { + os.Remove(tmpPath) + return fmt.Errorf("failed to set temporary file permissions: %w", err) + } + + // Atomic rename + if err := os.Rename(tmpPath, m.configPath); err != nil { + os.Remove(tmpPath) + return fmt.Errorf("failed to rename file: %w", err) + } + + // Sync directory + if dir, err := os.Open(filepath.Dir(m.configPath)); err == nil { + dir.Sync() + dir.Close() + } + + return nil +} + +// getConfigPath returns the path to the configuration file +func getConfigPath(username string) (string, error) { + var configDir string + switch runtime.GOOS { + case "windows": + configDir = filepath.Join(os.Getenv("APPDATA"), "Cursor", "User", "globalStorage") + case "darwin": + configDir = filepath.Join("/Users", username, "Library", "Application Support", "Cursor", "User", "globalStorage") + case "linux": + configDir = filepath.Join("/home", username, ".config", "Cursor", "User", "globalStorage") + default: + return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS) + } + return filepath.Join(configDir, "storage.json"), nil +} diff --git a/internal/lang/lang.go b/internal/lang/lang.go new file mode 100644 index 0000000..f5d8a7a --- /dev/null +++ b/internal/lang/lang.go @@ -0,0 +1,156 @@ +package lang + +import ( + "os" + "os/exec" + "strings" + "sync" +) + +// Language represents a supported language +type Language string + +const ( + // CN represents Chinese language + CN Language = "cn" + // EN represents English language + EN Language = "en" +) + +// TextResource contains all translatable text resources +type TextResource struct { + SuccessMessage string + RestartMessage string + ReadingConfig string + GeneratingIds string + PressEnterToExit string + ErrorPrefix string + PrivilegeError string + RunAsAdmin string + RunWithSudo string + SudoExample string + ConfigLocation string + CheckingProcesses string + ClosingProcesses string + ProcessesClosed string + PleaseWait string + SetReadOnlyMessage string +} + +var ( + currentLanguage Language + currentLanguageOnce sync.Once + languageMutex sync.RWMutex +) + +// GetCurrentLanguage returns the current language, detecting it if not already set +func GetCurrentLanguage() Language { + currentLanguageOnce.Do(func() { + currentLanguage = detectLanguage() + }) + + languageMutex.RLock() + defer languageMutex.RUnlock() + return currentLanguage +} + +// SetLanguage sets the current language +func SetLanguage(lang Language) { + languageMutex.Lock() + defer languageMutex.Unlock() + currentLanguage = lang +} + +// GetText returns the TextResource for the current language +func GetText() TextResource { + return texts[GetCurrentLanguage()] +} + +// detectLanguage detects the system language +func detectLanguage() Language { + // Check environment variables + for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} { + if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") { + return CN + } + } + + // Check Windows language settings + if isWindows() { + if isWindowsChineseLocale() { + return CN + } + } else { + // Check Unix locale + if isUnixChineseLocale() { + return CN + } + } + + return EN +} + +func isWindows() bool { + return os.Getenv("OS") == "Windows_NT" +} + +func isWindowsChineseLocale() bool { + // Check Windows UI culture + cmd := exec.Command("powershell", "-Command", + "[System.Globalization.CultureInfo]::CurrentUICulture.Name") + output, err := cmd.Output() + if err == nil && strings.HasPrefix(strings.ToLower(strings.TrimSpace(string(output))), "zh") { + return true + } + + // Check Windows locale + cmd = exec.Command("wmic", "os", "get", "locale") + output, err = cmd.Output() + return err == nil && strings.Contains(string(output), "2052") +} + +func isUnixChineseLocale() bool { + cmd := exec.Command("locale") + output, err := cmd.Output() + return err == nil && strings.Contains(strings.ToLower(string(output)), "zh_cn") +} + +// texts contains all translations +var texts = map[Language]TextResource{ + CN: { + SuccessMessage: "[√] 配置文件已成功更新!", + RestartMessage: "[!] 请手动重启 Cursor 以使更新生效", + ReadingConfig: "正在读取配置文件...", + GeneratingIds: "正在生成新的标识符...", + PressEnterToExit: "按回车键退出程序...", + ErrorPrefix: "程序发生严重错误: %v", + PrivilegeError: "\n[!] 错误:需要管理员权限", + RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」", + RunWithSudo: "请使用 sudo 命令运行此程序", + SudoExample: "示例: sudo %s", + ConfigLocation: "配置文件位置:", + CheckingProcesses: "正在检查运行中的 Cursor 实例...", + ClosingProcesses: "正在关闭 Cursor 实例...", + ProcessesClosed: "所有 Cursor 实例已关闭", + PleaseWait: "请稍候...", + SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题", + }, + EN: { + SuccessMessage: "[√] Configuration file updated successfully!", + RestartMessage: "[!] Please restart Cursor manually for changes to take effect", + ReadingConfig: "Reading configuration file...", + GeneratingIds: "Generating new identifiers...", + PressEnterToExit: "Press Enter to exit...", + ErrorPrefix: "Program encountered a serious error: %v", + PrivilegeError: "\n[!] Error: Administrator privileges required", + RunAsAdmin: "Please right-click and select 'Run as Administrator'", + RunWithSudo: "Please run this program with sudo", + SudoExample: "Example: sudo %s", + ConfigLocation: "Config file location:", + CheckingProcesses: "Checking for running Cursor instances...", + ClosingProcesses: "Closing Cursor instances...", + ProcessesClosed: "All Cursor instances have been closed", + PleaseWait: "Please wait...", + SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records", + }, +} diff --git a/internal/process/manager.go b/internal/process/manager.go new file mode 100644 index 0000000..3d29ee9 --- /dev/null +++ b/internal/process/manager.go @@ -0,0 +1,162 @@ +package process + +import ( + "context" + "fmt" + "os/exec" + "runtime" + "strings" + "sync" + "time" + + "github.com/sirupsen/logrus" +) + +// Config holds process manager configuration +type Config struct { + RetryAttempts int + RetryDelay time.Duration + Timeout time.Duration +} + +// DefaultConfig returns the default configuration +func DefaultConfig() *Config { + return &Config{ + RetryAttempts: 3, + RetryDelay: time.Second, + Timeout: 30 * time.Second, + } +} + +// Manager handles process-related operations +type Manager struct { + config *Config + log *logrus.Logger + mu sync.Mutex +} + +// NewManager creates a new process manager +func NewManager(config *Config, log *logrus.Logger) *Manager { + if config == nil { + config = DefaultConfig() + } + if log == nil { + log = logrus.New() + } + return &Manager{ + config: config, + log: log, + } +} + +// KillCursorProcesses attempts to kill all Cursor processes +func (m *Manager) KillCursorProcesses() error { + m.mu.Lock() + defer m.mu.Unlock() + + ctx, cancel := context.WithTimeout(context.Background(), m.config.Timeout) + defer cancel() + + for attempt := 0; attempt < m.config.RetryAttempts; attempt++ { + m.log.Debugf("Attempt %d/%d to kill Cursor processes", attempt+1, m.config.RetryAttempts) + + if err := m.killProcess(ctx); err != nil { + m.log.Warnf("Failed to kill processes on attempt %d: %v", attempt+1, err) + time.Sleep(m.config.RetryDelay) + continue + } + return nil + } + + return fmt.Errorf("failed to kill all Cursor processes after %d attempts", m.config.RetryAttempts) +} + +// IsCursorRunning checks if any Cursor process is running +func (m *Manager) IsCursorRunning() bool { + m.mu.Lock() + defer m.mu.Unlock() + + processes, err := m.listCursorProcesses() + if err != nil { + m.log.Warnf("Failed to list Cursor processes: %v", err) + return false + } + + return len(processes) > 0 +} + +func (m *Manager) killProcess(ctx context.Context) error { + if runtime.GOOS == "windows" { + return m.killWindowsProcess(ctx) + } + return m.killUnixProcess(ctx) +} + +func (m *Manager) killWindowsProcess(ctx context.Context) error { + // First try graceful termination + if err := exec.CommandContext(ctx, "taskkill", "/IM", "Cursor.exe").Run(); err != nil { + m.log.Debugf("Graceful termination failed: %v", err) + } + + time.Sleep(m.config.RetryDelay) + + // Force kill if still running + if err := exec.CommandContext(ctx, "taskkill", "/F", "/IM", "Cursor.exe").Run(); err != nil { + return fmt.Errorf("failed to force kill Cursor process: %w", err) + } + + return nil +} + +func (m *Manager) killUnixProcess(ctx context.Context) error { + processes, err := m.listCursorProcesses() + if err != nil { + return fmt.Errorf("failed to list processes: %w", err) + } + + for _, pid := range processes { + if err := m.forceKillProcess(ctx, pid); err != nil { + m.log.Warnf("Failed to kill process %s: %v", pid, err) + continue + } + } + + return nil +} + +func (m *Manager) forceKillProcess(ctx context.Context, pid string) error { + // Try graceful termination first + if err := exec.CommandContext(ctx, "kill", pid).Run(); err == nil { + m.log.Debugf("Process %s terminated gracefully", pid) + time.Sleep(2 * time.Second) + return nil + } + + // Force kill if still running + if err := exec.CommandContext(ctx, "kill", "-9", pid).Run(); err != nil { + return fmt.Errorf("failed to force kill process %s: %w", pid, err) + } + + m.log.Debugf("Process %s force killed", pid) + return nil +} + +func (m *Manager) listCursorProcesses() ([]string, error) { + cmd := exec.Command("ps", "aux") + output, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("failed to execute ps command: %w", err) + } + + var pids []string + for _, line := range strings.Split(string(output), "\n") { + if strings.Contains(strings.ToLower(line), "apprun") { + fields := strings.Fields(line) + if len(fields) > 1 { + pids = append(pids, fields[1]) + } + } + } + + return pids, nil +} diff --git a/internal/ui/display.go b/internal/ui/display.go new file mode 100644 index 0000000..0f94f69 --- /dev/null +++ b/internal/ui/display.go @@ -0,0 +1,101 @@ +package ui + +import ( + "fmt" + "os" + "os/exec" + "runtime" + "strings" + + "github.com/fatih/color" +) + +// Display handles UI display operations +type Display struct { + spinner *Spinner +} + +// NewDisplay creates a new display handler +func NewDisplay(spinner *Spinner) *Display { + if spinner == nil { + spinner = NewSpinner(nil) + } + return &Display{ + spinner: spinner, + } +} + +// ShowProgress shows a progress message with spinner +func (d *Display) ShowProgress(message string) { + d.spinner.SetMessage(message) + d.spinner.Start() +} + +// StopProgress stops the progress spinner +func (d *Display) StopProgress() { + d.spinner.Stop() +} + +// ClearScreen clears the terminal screen +func (d *Display) ClearScreen() error { + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.Command("cmd", "/c", "cls") + } else { + cmd = exec.Command("clear") + } + cmd.Stdout = os.Stdout + return cmd.Run() +} + +// ShowProcessStatus shows the current process status +func (d *Display) ShowProcessStatus(message string) { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) // Clear line + fmt.Printf("\r%s", color.CyanString("⚡ "+message)) +} + +// ShowPrivilegeError shows the privilege error message +func (d *Display) ShowPrivilegeError(errorMsg, adminMsg, sudoMsg, sudoExample string) { + red := color.New(color.FgRed, color.Bold) + yellow := color.New(color.FgYellow) + + red.Println(errorMsg) + if runtime.GOOS == "windows" { + yellow.Println(adminMsg) + } else { + yellow.Printf("%s\n%s\n", sudoMsg, fmt.Sprintf(sudoExample, os.Args[0])) + } +} + +// ShowSuccess shows a success message +func (d *Display) ShowSuccess(successMsg, restartMsg string) { + green := color.New(color.FgGreen, color.Bold) + yellow := color.New(color.FgYellow, color.Bold) + + green.Printf("\n%s\n", successMsg) + yellow.Printf("%s\n", restartMsg) +} + +// ShowError shows an error message +func (d *Display) ShowError(message string) { + red := color.New(color.FgRed, color.Bold) + red.Printf("\n%s\n", message) +} + +// ShowWarning shows a warning message +func (d *Display) ShowWarning(message string) { + yellow := color.New(color.FgYellow, color.Bold) + yellow.Printf("\n%s\n", message) +} + +// ShowInfo shows an info message +func (d *Display) ShowInfo(message string) { + cyan := color.New(color.FgCyan) + cyan.Printf("\n%s\n", message) +} + +// ShowPrompt shows a prompt message and waits for user input +func (d *Display) ShowPrompt(message string) { + fmt.Print(message) + os.Stdout.Sync() +} diff --git a/internal/ui/spinner.go b/internal/ui/spinner.go new file mode 100644 index 0000000..4f85fcb --- /dev/null +++ b/internal/ui/spinner.go @@ -0,0 +1,110 @@ +package ui + +import ( + "fmt" + "sync" + "time" + + "github.com/fatih/color" +) + +// SpinnerConfig defines spinner configuration +type SpinnerConfig struct { + Frames []string + Delay time.Duration +} + +// DefaultSpinnerConfig returns the default spinner configuration +func DefaultSpinnerConfig() *SpinnerConfig { + return &SpinnerConfig{ + Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, + Delay: 100 * time.Millisecond, + } +} + +// Spinner represents a progress spinner +type Spinner struct { + config *SpinnerConfig + message string + current int + active bool + stopCh chan struct{} + mu sync.RWMutex +} + +// NewSpinner creates a new spinner with the given configuration +func NewSpinner(config *SpinnerConfig) *Spinner { + if config == nil { + config = DefaultSpinnerConfig() + } + return &Spinner{ + config: config, + stopCh: make(chan struct{}), + } +} + +// SetMessage sets the spinner message +func (s *Spinner) SetMessage(message string) { + s.mu.Lock() + defer s.mu.Unlock() + s.message = message +} + +// Start starts the spinner animation +func (s *Spinner) Start() { + s.mu.Lock() + if s.active { + s.mu.Unlock() + return + } + s.active = true + s.mu.Unlock() + + go s.run() +} + +// Stop stops the spinner animation +func (s *Spinner) Stop() { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.active { + return + } + + s.active = false + close(s.stopCh) + s.stopCh = make(chan struct{}) + fmt.Println() +} + +// IsActive returns whether the spinner is currently active +func (s *Spinner) IsActive() bool { + s.mu.RLock() + defer s.mu.RUnlock() + return s.active +} + +func (s *Spinner) run() { + ticker := time.NewTicker(s.config.Delay) + defer ticker.Stop() + + for { + select { + case <-s.stopCh: + return + case <-ticker.C: + s.mu.RLock() + if !s.active { + s.mu.RUnlock() + return + } + frame := s.config.Frames[s.current%len(s.config.Frames)] + message := s.message + s.current++ + s.mu.RUnlock() + + fmt.Printf("\r%s %s", color.CyanString(frame), message) + } + } +} diff --git a/main.go b/main.go deleted file mode 100644 index 88520cc..0000000 --- a/main.go +++ /dev/null @@ -1,1050 +0,0 @@ -package main - -// Core imports / 核心导入 -import ( - "bufio" - "context" - cryptorand "crypto/rand" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "errors" - "flag" - "fmt" - "log" - "math/rand" - "os" - "os/exec" - "os/user" - "path/filepath" - "runtime" - "runtime/debug" - "strings" - "time" - - "github.com/fatih/color" -) - -// Version information -var version = "dev" // This will be overwritten by goreleaser - -// Types and Constants / 类型和常量 -type Language string - -const ( - // Language options / 语言选项 - CN Language = "cn" - EN Language = "en" - - // Error types / 错误类型 - ErrPermission = "permission_error" - ErrConfig = "config_error" - ErrProcess = "process_error" - ErrSystem = "system_error" -) - -// Configuration Structures / 配置结构 -type ( - // TextResource stores multilingual text / 存储多语言文本 - TextResource struct { - SuccessMessage string - RestartMessage string - ReadingConfig string - GeneratingIds string - PressEnterToExit string - ErrorPrefix string - PrivilegeError string - RunAsAdmin string - RunWithSudo string - SudoExample string - ConfigLocation string - CheckingProcesses string - ClosingProcesses string - ProcessesClosed string - PleaseWait string - SetReadOnlyMessage string - } - - // StorageConfig optimized storage configuration / 优化的存储配置 - StorageConfig struct { - TelemetryMacMachineId string `json:"telemetry.macMachineId"` - TelemetryMachineId string `json:"telemetry.machineId"` - TelemetryDevDeviceId string `json:"telemetry.devDeviceId"` - TelemetrySqmId string `json:"telemetry.sqmId"` - } - // AppError defines error types / 定义错误类型 - AppError struct { - Type string - Op string - Path string - Err error - Context map[string]interface{} - } - - // Config structures / 配置结构 - Config struct { - Storage StorageConfig - UI UIConfig - System SystemConfig - } - - UIConfig struct { - Language Language - Theme string - Spinner SpinnerConfig - } - - SystemConfig struct { - RetryAttempts int - RetryDelay time.Duration - Timeout time.Duration - } - - // SpinnerConfig defines spinner configuration / 定义进度条配置 - SpinnerConfig struct { - Frames []string - Delay time.Duration - } - - // ProgressSpinner for showing progress animation / 用于显示进度动画 - ProgressSpinner struct { - frames []string - current int - message string - } -) - -// Global Variables / 全局变量 -var ( - currentLanguage = CN // Default to Chinese / 默认为中文 - - texts = map[Language]TextResource{ - CN: { - SuccessMessage: "[√] 配置文件已成功更新!", - RestartMessage: "[!] 请手动重启 Cursor 以使更新生效", - ReadingConfig: "正在读取配置文件...", - GeneratingIds: "正在生成新的标识符...", - PressEnterToExit: "按回车键退出程序...", - ErrorPrefix: "程序发生严重错误: %v", - PrivilegeError: "\n[!] 错误:需要管理员权限", - RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」", - RunWithSudo: "请使用 sudo 命令运行此程序", - SudoExample: "示例: sudo %s", - ConfigLocation: "配置文件位置:", - CheckingProcesses: "正在检查运行中的 Cursor 实例...", - ClosingProcesses: "正在关闭 Cursor 实例...", - ProcessesClosed: "所有 Cursor 实例已关闭", - PleaseWait: "请稍候...", - SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题", - }, - EN: { - SuccessMessage: "[√] Configuration file updated successfully!", - RestartMessage: "[!] Please restart Cursor manually for changes to take effect", - ReadingConfig: "Reading configuration file...", - GeneratingIds: "Generating new identifiers...", - PressEnterToExit: "Press Enter to exit...", - ErrorPrefix: "Program encountered a serious error: %v", - PrivilegeError: "\n[!] Error: Administrator privileges required", - RunAsAdmin: "Please right-click and select 'Run as Administrator'", - RunWithSudo: "Please run this program with sudo", - SudoExample: "Example: sudo %s", - ConfigLocation: "Config file location:", - CheckingProcesses: "Checking for running Cursor instances...", - ClosingProcesses: "Closing Cursor instances...", - ProcessesClosed: "All Cursor instances have been closed", - PleaseWait: "Please wait...", - SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records", - }, - } -) - -var setReadOnly *bool = flag.Bool("r", false, "set storage.json to read-only mode") - -// Error Implementation / 错误实现 -func (e *AppError) Error() string { - if e.Context != nil { - return fmt.Sprintf("[%s] %s: %v (context: %v)", e.Type, e.Op, e.Err, e.Context) - } - return fmt.Sprintf("[%s] %s: %v", e.Type, e.Op, e.Err) -} - -// Configuration Functions / 配置函数 -func NewStorageConfig(oldConfig *StorageConfig) *StorageConfig { - // Use different ID generation functions for different fields - // 为不同字段用不同的ID生成函数 - // Reason: machineId needs new format while others keep old format - // 原因:machineId需要使用新格式,而其他ID保持旧格式 - newConfig := &StorageConfig{ - TelemetryMacMachineId: generateMacMachineId(), // Use old format / 使用旧格式 - TelemetryMachineId: generateMachineId(), // Use new format / 使用新格式 - TelemetryDevDeviceId: generateDevDeviceId(), - } - - // Keep sqmId from old config or generate new one using old format - // 保留旧配置的sqmId或使用旧格式生成新的 - if oldConfig != nil { - newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId - } else { - newConfig.TelemetrySqmId = generateMacMachineId() - } - - if newConfig.TelemetrySqmId == "" { - newConfig.TelemetrySqmId = generateMacMachineId() - } - - return newConfig -} - -func generateMachineId() string { - // 基础结构:auth0|user_XX[unique_id] - prefix := "auth0|user_" - - // 生成两位数字序列 (00-99) - sequence := fmt.Sprintf("%02d", rand.Intn(100)) - - // 生成唯一标识部分 (23字符) - uniqueId := generateUniqueId(23) - - // 组合完整ID - fullId := prefix + sequence + uniqueId - - // 转换为十六进制 - return hex.EncodeToString([]byte(fullId)) -} - -func generateUniqueId(length int) string { - // 字符集:使用类似 Crockford's Base32 的字符集 - const charset = "0123456789ABCDEFGHJKLMNPQRSTVWXYZ" - - // 生成随机字符串 - b := make([]byte, length) - for i := range b { - b[i] = charset[rand.Intn(len(charset))] - } - return string(b) -} - -func generateMacMachineId() string { - data := make([]byte, 32) - if _, err := cryptorand.Read(data); err != nil { - panic(fmt.Errorf("failed to generate random data: %v", err)) - } - hash := sha256.Sum256(data) - return hex.EncodeToString(hash[:]) -} - -func generateDevDeviceId() string { - uuid := make([]byte, 16) - if _, err := cryptorand.Read(uuid); err != nil { - panic(fmt.Errorf("failed to generate UUID: %v", err)) - } - uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 - uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant - return fmt.Sprintf("%x-%x-%x-%x-%x", - uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16]) -} - -// File Operations / 文件操作 -func getConfigPath(username string) (string, error) { - var configDir string - switch runtime.GOOS { - case "windows": - configDir = filepath.Join(os.Getenv("APPDATA"), "Cursor", "User", "globalStorage") - case "darwin": - configDir = filepath.Join("/Users", username, "Library", "Application Support", "Cursor", "User", "globalStorage") - case "linux": - configDir = filepath.Join("/home", username, ".config", "Cursor", "User", "globalStorage") - default: - return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS) - } - return filepath.Join(configDir, "storage.json"), nil -} - -func saveConfig(config *StorageConfig, username string) error { - configPath, err := getConfigPath(username) - if err != nil { - return err - } - - // Create parent directories with proper permissions - dir := filepath.Dir(configPath) - if err := os.MkdirAll(dir, 0755); err != nil { - return &AppError{ - Type: ErrSystem, - Op: "create directory", - Path: dir, - Err: err, - } - } - - // First ensure we can write to the file - if err := os.Chmod(configPath, 0666); err != nil && !os.IsNotExist(err) { - return &AppError{ - Type: ErrSystem, - Op: "modify file permissions", - Path: configPath, - Err: err, - } - } - - // Read the original file to preserve all fields - var originalFile map[string]interface{} - originalFileContent, err := os.ReadFile(configPath) - if err != nil { - if !os.IsNotExist(err) { - return &AppError{ - Type: ErrSystem, - Op: "read original file", - Path: configPath, - Err: err, - } - } - // If file doesn't exist, create a new map - originalFile = make(map[string]interface{}) - } else { - if err := json.Unmarshal(originalFileContent, &originalFile); err != nil { - return &AppError{ - Type: ErrSystem, - Op: "unmarshal original file", - Path: configPath, - Err: err, - } - } - } - - // Get original file mode - var originalFileMode os.FileMode = 0666 - if stat, err := os.Stat(configPath); err == nil { - originalFileMode = stat.Mode() - } - - // Update only the telemetry fields while preserving all other fields - originalFile["telemetry.sqmId"] = config.TelemetrySqmId - originalFile["telemetry.macMachineId"] = config.TelemetryMacMachineId - originalFile["telemetry.machineId"] = config.TelemetryMachineId - originalFile["telemetry.devDeviceId"] = config.TelemetryDevDeviceId - - // Add lastModified and version fields if they don't exist - if _, exists := originalFile["lastModified"]; !exists { - originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339) - } - if _, exists := originalFile["version"]; !exists { - originalFile["version"] = "1.0.1" - } - - // Marshal with indentation - newFileContent, err := json.MarshalIndent(originalFile, "", " ") - if err != nil { - return &AppError{ - Type: ErrSystem, - Op: "marshal new file", - Path: configPath, - Err: err, - } - } - - // Write to temporary file first - tmpPath := configPath + ".tmp" - if err := os.WriteFile(tmpPath, newFileContent, 0666); err != nil { - return &AppError{ - Type: ErrSystem, - Op: "write temporary file", - Path: tmpPath, - Err: err, - } - } - - if *setReadOnly { - originalFileMode = 0444 - } - - // Ensure proper permissions on temporary file - if err := os.Chmod(tmpPath, originalFileMode); err != nil { - os.Remove(tmpPath) - return &AppError{ - Type: ErrSystem, - Op: "set temporary file permissions", - Path: tmpPath, - Err: err, - } - } - - // Atomic rename - if err := os.Rename(tmpPath, configPath); err != nil { - os.Remove(tmpPath) - return &AppError{ - Type: ErrSystem, - Op: "rename file", - Path: configPath, - Err: err, - } - } - - // Sync the directory to ensure changes are written to disk - if dir, err := os.Open(filepath.Dir(configPath)); err == nil { - dir.Sync() - dir.Close() - } - - return nil -} - -func readExistingConfig(username string) (*StorageConfig, error) { // Modified to take username - configPath, err := getConfigPath(username) - if err != nil { - return nil, err - } - - data, err := os.ReadFile(configPath) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, err - } - - var config StorageConfig - if err := json.Unmarshal(data, &config); err != nil { - return nil, err - } - - return &config, nil -} - -// Process Management / 进程管理 -type ProcessManager struct { - config *SystemConfig -} - -func (pm *ProcessManager) killCursorProcesses() error { - ctx, cancel := context.WithTimeout(context.Background(), pm.config.Timeout) - defer cancel() - - for attempt := 0; attempt < pm.config.RetryAttempts; attempt++ { - if err := pm.killProcess(ctx); err != nil { - time.Sleep(pm.config.RetryDelay) - continue - } - 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 (pm *ProcessManager) killWindowsProcess(ctx context.Context) error { - 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 { - // Search for the process by it's executable name (AppRun) in ps output - cmd := exec.CommandContext(ctx, "ps", "aux") - output, err := cmd.Output() - if err != nil { - return fmt.Errorf("failed to execute ps command: %w", err) - } - - lines := strings.Split(string(output), "\n") - for _, line := range lines { - if strings.Contains(line, "AppRun") { - parts := strings.Fields(line) - if len(parts) > 1 { - pid := parts[1] - if err := pm.forceKillProcess(ctx, pid); err != nil { - return err - } - } - } - - // handle lowercase - if strings.Contains(line, "apprun") { - parts := strings.Fields(line) - if len(parts) > 1 { - pid := parts[1] - if err := pm.forceKillProcess(ctx, pid); err != nil { - return err - } - } - } - } - - return nil -} - -// helper function to kill process by pid -func (pm *ProcessManager) forceKillProcess(ctx context.Context, pid string) error { - // First try graceful termination - if err := exec.CommandContext(ctx, "kill", pid).Run(); err == nil { - // Wait for processes to terminate gracefully - time.Sleep(2 * time.Second) - } - - // Force kill if still running - if err := exec.CommandContext(ctx, "kill", "-9", pid).Run(); err != nil { - return fmt.Errorf("failed to force kill process %s: %w", pid, err) - } - - return nil -} - -func checkCursorRunning() bool { - cmd := exec.Command("ps", "aux") - output, err := cmd.Output() - if err != nil { - return false - } - - lines := strings.Split(string(output), "\n") - for _, line := range lines { - if strings.Contains(line, "AppRun") || strings.Contains(line, "apprun") { - return true - } - } - - return false -} - -// UI Components / UI组件 -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() - } -} - -// Display Functions / 显示函数 -func showSuccess() { - text := texts[currentLanguage] - successColor := color.New(color.FgGreen, color.Bold) - warningColor := color.New(color.FgYellow, color.Bold) - pathColor := color.New(color.FgCyan) - - // Clear any previous output - fmt.Println() - - if currentLanguage == EN { - // English messages with extra spacing - successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") - successColor.Printf("%s\n", text.SuccessMessage) - fmt.Println() - warningColor.Printf("%s\n", text.RestartMessage) - successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") - } else { - // Chinese messages with extra spacing - successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") - successColor.Printf("%s\n", text.SuccessMessage) - fmt.Println() - warningColor.Printf("%s\n", text.RestartMessage) - successColor.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") - } - - // Add spacing before config location - fmt.Println() - - username := os.Getenv("SUDO_USER") - if username == "" { - user, err := user.Current() - if err != nil { - panic(err) - } - username = user.Username - } - if configPath, err := getConfigPath(username); err == nil { - pathColor.Printf("%s\n%s\n", text.ConfigLocation, configPath) - } -} - -func showReadOnlyMessage() { - if *setReadOnly { - warningColor := color.New(color.FgYellow, color.Bold) - warningColor.Printf("%s\n", texts[currentLanguage].SetReadOnlyMessage) - fmt.Println("Press Enter to continue...") - bufio.NewReader(os.Stdin).ReadString('\n') - } -} - -func showPrivilegeError() { - text := texts[currentLanguage] - red := color.New(color.FgRed, color.Bold) - yellow := color.New(color.FgYellow) - - if currentLanguage == EN { - red.Println(text.PrivilegeError) - if runtime.GOOS == "windows" { - yellow.Println(text.RunAsAdmin) - } else { - yellow.Printf("%s\n%s\n", text.RunWithSudo, fmt.Sprintf(text.SudoExample, os.Args[0])) - } - } else { - red.Printf("\n%s\n", text.PrivilegeError) - if runtime.GOOS == "windows" { - yellow.Printf("%s\n", text.RunAsAdmin) - } else { - yellow.Printf("%s\n%s\n", text.RunWithSudo, fmt.Sprintf(text.SudoExample, os.Args[0])) - } - } -} - -// System Functions / 系统函数 -func checkAdminPrivileges() (bool, error) { - switch runtime.GOOS { - case "windows": - // 使用更可靠的方法检查Windows管理员权限 - cmd := exec.Command("net", "session") - err := cmd.Run() - if err == nil { - return true, nil - } - // 如果命令执行失败,说明没有管理员权限 - return false, 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 detectLanguage() Language { - // Check common environment variables - for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} { - if lang := os.Getenv(envVar); lang != "" { - if strings.Contains(strings.ToLower(lang), "zh") { - return CN - } - } - } - - // Windows-specific language check - if runtime.GOOS == "windows" { - cmd := exec.Command("powershell", "-Command", - "[System.Globalization.CultureInfo]::CurrentUICulture.Name") - output, err := cmd.Output() - if err == nil { - lang := strings.ToLower(strings.TrimSpace(string(output))) - if strings.HasPrefix(lang, "zh") { - return CN - } - } - - // Check Windows locale - cmd = exec.Command("wmic", "os", "get", "locale") - output, err = cmd.Output() - if err == nil && strings.Contains(string(output), "2052") { - return CN - } - } - - // Check Unix locale - if runtime.GOOS != "windows" { - cmd := exec.Command("locale") - output, err := cmd.Output() - if err == nil && strings.Contains(strings.ToLower(string(output)), "zh_cn") { - return CN - } - } - - return EN -} - -func selfElevate() error { - switch runtime.GOOS { - case "windows": - // Set automated mode for the elevated process - os.Setenv("AUTOMATED_MODE", "1") - - 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": - // Set automated mode for the elevated process - os.Setenv("AUTOMATED_MODE", "1") - - 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) - } -} - -// Utility Functions / 实用函数 -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) - } -} - -func waitExit() { - // Skip waiting in automated mode - if os.Getenv("AUTOMATED_MODE") == "1" { - return - } - - if currentLanguage == EN { - fmt.Println("\nPress Enter to exit...") - } else { - fmt.Println("\n按��车键退出程序...") - } - os.Stdout.Sync() - bufio.NewReader(os.Stdin).ReadString('\n') -} - -// Add this new function near the other process management functions -func ensureCursorClosed() error { - maxAttempts := 3 - text := texts[currentLanguage] - - showProcessStatus(text.CheckingProcesses) - - for attempt := 1; attempt <= maxAttempts; attempt++ { - if !checkCursorRunning() { - showProcessStatus(text.ProcessesClosed) - fmt.Println() // New line after status - return nil - } - - if currentLanguage == EN { - showProcessStatus(fmt.Sprintf("Please close Cursor before continuing. Attempt %d/%d\n%s", - attempt, maxAttempts, text.PleaseWait)) - } else { - showProcessStatus(fmt.Sprintf("请在继续之前关闭 Cursor。尝试 %d/%d\n%s", - attempt, maxAttempts, text.PleaseWait)) - } - - time.Sleep(5 * time.Second) - } - - return errors.New("cursor is still running") -} - -func main() { - // Initialize error recovery - defer func() { - if r := recover(); r != nil { - log.Printf("Panic recovered: %v\n", r) - debug.PrintStack() - waitExit() - } - }() - - flag.Parse() - showReadOnlyMessage() - - var username string - if username = os.Getenv("SUDO_USER"); username == "" { - user, err := user.Current() - if err != nil { - panic(err) - } - username = user.Username - } - log.Println("Current user: ", username) - - // Initialize configuration - ui := NewUI(&UIConfig{ - Language: detectLanguage(), - Theme: "default", - Spinner: SpinnerConfig{ - Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, - Delay: 100 * time.Millisecond, - }, - }) - - // Check privileges - os.Stdout.Sync() - currentLanguage = detectLanguage() - log.Println("Current language: ", currentLanguage) - isAdmin, err := checkAdminPrivileges() - if err != nil { - handleError(err) - waitExit() - return - } - - if !isAdmin && runtime.GOOS == "windows" { - if currentLanguage == EN { - fmt.Println("\nRequesting administrator privileges...") - } else { - fmt.Println("\n请求管理员权限...") - } - if err := selfElevate(); err != nil { - handleError(err) - showPrivilegeError() - waitExit() - return - } - return - } else if !isAdmin { - showPrivilegeError() - waitExit() - return - } - - // Ensure all Cursor instances are closed - if err := ensureCursorClosed(); err != nil { - if currentLanguage == EN { - fmt.Println("\nError: Please close Cursor manually before running this program.") - } else { - fmt.Println("\n错误:请在运���此程序之前手动关闭 Cursor。") - } - waitExit() - return - } - - // Process management - pm := &ProcessManager{ - config: &SystemConfig{ - RetryAttempts: 3, - RetryDelay: time.Second, - Timeout: 30 * time.Second, - }, - } - if checkCursorRunning() { - text := texts[currentLanguage] - showProcessStatus(text.ClosingProcesses) - - if err := pm.killCursorProcesses(); err != nil { - fmt.Println() // New line after status - if currentLanguage == EN { - fmt.Println("Warning: Could not close all Cursor instances. Please close them manually.") - } else { - fmt.Println("警告:无法关闭所有 Cursor 实例,请手动关闭。") - } - waitExit() - return - } - - time.Sleep(2 * time.Second) - if checkCursorRunning() { - fmt.Println() // New line after status - if currentLanguage == EN { - fmt.Println("\nWarning: Cursor is still running. Please close it manually.") - } else { - fmt.Println("\n警告:Cursor 仍在运行,请手动关闭。") - } - waitExit() - return - } - - showProcessStatus(text.ProcessesClosed) - fmt.Println() // New line after status - } - - // Clear screen and show banner - clearScreen() - printCyberpunkBanner() - - // Read and update configuration - oldConfig, err := readExistingConfig(username) // add username parameter - if err != nil { - oldConfig = nil - } - - storageConfig, err := loadAndUpdateConfig(ui, username) // add username parameter - if err != nil { - handleError(err) - waitExit() - return - } - - // Show changes and save - showIdComparison(oldConfig, storageConfig) - - if err := saveConfig(storageConfig, username); err != nil { // add username parameter - handleError(err) - waitExit() - return - } - - // Show success and exit - showSuccess() - if currentLanguage == EN { - fmt.Println("\nOperation completed!") - } else { - fmt.Println("\n操作完成!") - } - - // Check if running in automated mode - if os.Getenv("AUTOMATED_MODE") == "1" { - return - } - - waitExit() -} - -// Progress spinner functions / 进度条函数 -func NewProgressSpinner(message string) *ProgressSpinner { - return &ProgressSpinner{ - frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, - message: message, - } -} - -func (s *ProgressSpinner) Spin() { - frame := s.frames[s.current%len(s.frames)] - s.current++ - fmt.Printf("\r%s %s", color.CyanString(frame), s.message) -} - -func (s *ProgressSpinner) Stop() { - fmt.Println() -} - -func (s *ProgressSpinner) Start() { - s.current = 0 -} - -// Display utility functions / 显示工具函数 -func clearScreen() { - var cmd *exec.Cmd - if runtime.GOOS == "windows" { - cmd = exec.Command("cmd", "/c", "cls") - } else { - cmd = exec.Command("clear") - } - cmd.Stdout = os.Stdout - cmd.Run() -} - -func printCyberpunkBanner() { - cyan := color.New(color.FgCyan, color.Bold) - yellow := color.New(color.FgYellow, color.Bold) - magenta := color.New(color.FgMagenta, color.Bold) - green := color.New(color.FgGreen, color.Bold) - - banner := ` - ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ - ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ - ██║ ██║ ██║██████╔╝███████╗██║ ██║█████╔╝ - ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ - ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ - ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ - ` - cyan.Println(banner) - yellow.Printf("\t\t>> Cursor ID Modifier %s <<\n", version) - magenta.Println("\t\t [ By Pancake Fruit Rolled Shark Chili ]") - - langText := "当前语言/Language: " - if currentLanguage == CN { - langText += "简体中文" - } else { - langText += "English" - } - green.Printf("\n\t\t %s\n\n", langText) -} - -func showIdComparison(oldConfig *StorageConfig, newConfig *StorageConfig) { - cyan := color.New(color.FgCyan) - yellow := color.New(color.FgYellow) - - fmt.Println("\n=== ID Modification Comparison / ID 修改对比 ===") - - if oldConfig != nil { - cyan.Println("\n[Original IDs / 原始 ID]") - yellow.Printf("Machine ID: %s\n", oldConfig.TelemetryMachineId) - yellow.Printf("Mac Machine ID: %s\n", oldConfig.TelemetryMacMachineId) - yellow.Printf("Dev Device ID: %s\n", oldConfig.TelemetryDevDeviceId) - } - - cyan.Println("\n[Newly Generated IDs / 新生成 ID]") - yellow.Printf("Machine ID: %s\n", newConfig.TelemetryMachineId) - yellow.Printf("Mac Machine ID: %s\n", newConfig.TelemetryMacMachineId) - yellow.Printf("Dev Device ID: %s\n", newConfig.TelemetryDevDeviceId) - yellow.Printf("SQM ID: %s\n", newConfig.TelemetrySqmId) - fmt.Println() -} - -// Configuration functions / 配置函数 -func loadAndUpdateConfig(ui *UI, username string) (*StorageConfig, error) { // add username parameter - configPath, err := getConfigPath(username) // add username parameter - if err != nil { - return nil, err - } - - text := texts[currentLanguage] - ui.showProgress(text.ReadingConfig) - - oldConfig, err := readExistingConfig(username) // add username parameter - if err != nil && !os.IsNotExist(err) { - return nil, &AppError{ - Type: ErrSystem, - Op: "read config file", - Path: configPath, - Err: err, - } - } - - ui.showProgress(text.GeneratingIds) - return NewStorageConfig(oldConfig), nil -} - -// Add a new function to show process status -func showProcessStatus(message string) { - cyan := color.New(color.FgCyan) - fmt.Printf("\r%s", strings.Repeat(" ", 80)) // Clear line - fmt.Printf("\r%s", cyan.Sprint("⚡ "+message)) -} diff --git a/pkg/idgen/generator.go b/pkg/idgen/generator.go new file mode 100644 index 0000000..5a0058f --- /dev/null +++ b/pkg/idgen/generator.go @@ -0,0 +1,95 @@ +package idgen + +import ( + cryptorand "crypto/rand" + "crypto/sha256" + "encoding/hex" + "fmt" + "math/big" + "sync" +) + +// Generator handles the generation of various IDs +type Generator struct { + charsetMu sync.RWMutex + charset string +} + +// NewGenerator creates a new ID generator with default settings +func NewGenerator() *Generator { + return &Generator{ + charset: "0123456789ABCDEFGHJKLMNPQRSTVWXYZ", + } +} + +// SetCharset allows customizing the character set used for ID generation +func (g *Generator) SetCharset(charset string) { + g.charsetMu.Lock() + defer g.charsetMu.Unlock() + g.charset = charset +} + +// GenerateMachineID generates a new machine ID with the format auth0|user_XX[unique_id] +func (g *Generator) GenerateMachineID() (string, error) { + prefix := "auth0|user_" + + // Generate random sequence number between 0-99 + seqNum, err := cryptorand.Int(cryptorand.Reader, big.NewInt(100)) + if err != nil { + return "", fmt.Errorf("failed to generate sequence number: %w", err) + } + sequence := fmt.Sprintf("%02d", seqNum.Int64()) + + uniqueID, err := g.generateUniqueID(23) + if err != nil { + return "", fmt.Errorf("failed to generate unique ID: %w", err) + } + + fullID := prefix + sequence + uniqueID + return hex.EncodeToString([]byte(fullID)), nil +} + +// GenerateMacMachineID generates a new MAC machine ID using SHA-256 +func (g *Generator) GenerateMacMachineID() (string, error) { + data := make([]byte, 32) + if _, err := cryptorand.Read(data); err != nil { + return "", fmt.Errorf("failed to generate random data: %w", err) + } + + hash := sha256.Sum256(data) + return hex.EncodeToString(hash[:]), nil +} + +// GenerateDeviceID generates a new device ID in UUID v4 format +func (g *Generator) GenerateDeviceID() (string, error) { + uuid := make([]byte, 16) + if _, err := cryptorand.Read(uuid); err != nil { + return "", fmt.Errorf("failed to generate UUID: %w", err) + } + + // Set version (4) and variant (2) bits according to RFC 4122 + uuid[6] = (uuid[6] & 0x0f) | 0x40 + uuid[8] = (uuid[8] & 0x3f) | 0x80 + + return fmt.Sprintf("%x-%x-%x-%x-%x", + uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16]), nil +} + +// generateUniqueID generates a random string of specified length using the configured charset +func (g *Generator) generateUniqueID(length int) (string, error) { + g.charsetMu.RLock() + defer g.charsetMu.RUnlock() + + result := make([]byte, length) + max := big.NewInt(int64(len(g.charset))) + + for i := range result { + randNum, err := cryptorand.Int(cryptorand.Reader, max) + if err != nil { + return "", fmt.Errorf("failed to generate random number: %w", err) + } + result[i] = g.charset[randNum.Int64()] + } + + return string(result), nil +} diff --git a/pkg/idgen/generator_test.go b/pkg/idgen/generator_test.go new file mode 100644 index 0000000..81dcea9 --- /dev/null +++ b/pkg/idgen/generator_test.go @@ -0,0 +1,26 @@ +package idgen + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewGenerator(t *testing.T) { + gen := NewGenerator() + assert.NotNil(t, gen, "Generator should not be nil") +} + +func TestGenerateMachineID(t *testing.T) { + gen := NewGenerator() + id, err := gen.GenerateMachineID() + assert.NoError(t, err, "Should not return an error") + assert.NotEmpty(t, id, "Generated machine ID should not be empty") +} + +func TestGenerateDeviceID(t *testing.T) { + gen := NewGenerator() + id, err := gen.GenerateDeviceID() + assert.NoError(t, err, "Should not return an error") + assert.NotEmpty(t, id, "Generated device ID should not be empty") +} diff --git a/scripts/build_all.bat b/scripts/build_all.bat index 0ae443f..e07e387 100644 --- a/scripts/build_all.bat +++ b/scripts/build_all.bat @@ -1,128 +1,74 @@ @echo off setlocal EnableDelayedExpansion +:: Build optimization flags +set "OPTIMIZATION_FLAGS=-trimpath -ldflags=\"-s -w\"" +set "BUILD_JOBS=4" + :: Messages / 消息 set "EN_MESSAGES[0]=Starting build process for version" set "EN_MESSAGES[1]=Using optimization flags:" set "EN_MESSAGES[2]=Cleaning old builds..." set "EN_MESSAGES[3]=Cleanup completed" -set "EN_MESSAGES[4]=bin directory does not exist, no cleanup needed" -set "EN_MESSAGES[5]=Starting builds for all platforms..." -set "EN_MESSAGES[6]=Building for" -set "EN_MESSAGES[7]=Build successful:" -set "EN_MESSAGES[8]=Build failed for" -set "EN_MESSAGES[9]=All builds completed! Total time:" -set "EN_MESSAGES[10]=seconds" - -set "CN_MESSAGES[0]=开始构建版本" -set "CN_MESSAGES[1]=使用优化标志:" -set "CN_MESSAGES[2]=正在清理旧的构建文件..." -set "CN_MESSAGES[3]=清理完成" -set "CN_MESSAGES[4]=bin 目录不存在,无需清理" -set "CN_MESSAGES[5]=开始编译所有平台..." -set "CN_MESSAGES[6]=正在构建" -set "CN_MESSAGES[7]=构建成功:" -set "CN_MESSAGES[8]=构建失败:" -set "CN_MESSAGES[9]=所有构建完成!总耗时:" -set "CN_MESSAGES[10]=秒" - -:: 设置版本信息 / Set version -set VERSION=2.0.0 +set "EN_MESSAGES[4]=Starting builds for all platforms..." +set "EN_MESSAGES[5]=Building for" +set "EN_MESSAGES[6]=Build successful:" +set "EN_MESSAGES[7]=All builds completed!" -:: 设置颜色代码 / Set color codes +:: Colors set "GREEN=[32m" set "RED=[31m" -set "YELLOW=[33m" set "RESET=[0m" -:: 设置编译优化标志 / Set build optimization flags -set "LDFLAGS=-s -w" -set "BUILDMODE=pie" -set "GCFLAGS=-N -l" - -:: 设置 CGO / Set CGO -set CGO_ENABLED=0 - -:: 检测系统语言 / Detect system language -for /f "tokens=2 delims==" %%a in ('wmic os get OSLanguage /value') do set OSLanguage=%%a -if "%OSLanguage%"=="2052" (set LANG=cn) else (set LANG=en) - -:: 显示编译信息 / Display build info -echo %YELLOW%!%LANG%_MESSAGES[0]! %VERSION%%RESET% -echo %YELLOW%!%LANG%_MESSAGES[1]! LDFLAGS=%LDFLAGS%, BUILDMODE=%BUILDMODE%%RESET% -echo %YELLOW%CGO_ENABLED=%CGO_ENABLED%%RESET% - -:: 清理旧的构建文件 / Clean old builds -echo %YELLOW%!%LANG%_MESSAGES[2]!%RESET% +:: Cleanup function +:cleanup if exist "..\bin" ( rd /s /q "..\bin" - echo %GREEN%!%LANG%_MESSAGES[3]!%RESET% -) else ( - echo %YELLOW%!%LANG%_MESSAGES[4]!%RESET% + echo %GREEN%!EN_MESSAGES[3]!%RESET% ) - -:: 创建输出目录 / Create output directory mkdir "..\bin" 2>nul -:: 定义目标平台数组 / Define target platforms array -set platforms[0].os=windows -set platforms[0].arch=amd64 -set platforms[0].ext=.exe -set platforms[0].suffix= +:: Build function with optimizations +:build +set "os=%~1" +set "arch=%~2" +set "ext=" +if "%os%"=="windows" set "ext=.exe" -set platforms[1].os=darwin -set platforms[1].arch=amd64 -set platforms[1].ext= -set platforms[1].suffix=_intel +echo %GREEN%!EN_MESSAGES[5]! %os%/%arch%%RESET% -set platforms[2].os=darwin -set platforms[2].arch=arm64 -set platforms[2].ext= -set platforms[2].suffix=_m1 +set "CGO_ENABLED=0" +set "GOOS=%os%" +set "GOARCH=%arch%" -set platforms[3].os=linux -set platforms[3].arch=amd64 -set platforms[3].ext= -set platforms[3].suffix= +start /b cmd /c "go build -trimpath -ldflags=\"-s -w\" -o ..\bin\%os%\%arch%\cursor-id-modifier%ext% -a -installsuffix cgo -mod=readonly ..\cmd\cursor-id-modifier" +exit /b 0 -:: 设置开始时间 / Set start time -set start_time=%time% +:: Main execution +echo %GREEN%!EN_MESSAGES[0]!%RESET% +echo %GREEN%!EN_MESSAGES[1]! %OPTIMIZATION_FLAGS%%RESET% -:: 编译所有目标 / Build all targets -echo !%LANG%_MESSAGES[5]! +call :cleanup -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 !%LANG%_MESSAGES[6]! !os! !arch!... - - set GOOS=!os! - set GOARCH=!arch! - - :: 构建输出文件名 / Build output filename - set "outfile=..\bin\cursor_id_modifier_v%VERSION%_!os!_!arch!!suffix!!ext!" - - :: 执行构建 / Execute build - go build -trimpath -buildmode=%BUILDMODE% -ldflags="%LDFLAGS%" -gcflags="%GCFLAGS%" -o "!outfile!" ..\main.go - - if !errorlevel! equ 0 ( - echo %GREEN%!%LANG%_MESSAGES[7]! !outfile!%RESET% - ) else ( - echo %RED%!%LANG%_MESSAGES[8]! !os! !arch!%RESET% +echo %GREEN%!EN_MESSAGES[4]!%RESET% + +:: Start builds in parallel +set "pending=0" +for %%o in (windows linux darwin) do ( + for %%a in (amd64 386) do ( + call :build %%o %%a + set /a "pending+=1" + if !pending! geq %BUILD_JOBS% ( + timeout /t 1 /nobreak >nul + set "pending=0" + ) ) ) -:: 计算总耗时 / Calculate total time -set end_time=%time% -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%!%LANG%_MESSAGES[9]! %duration% !%LANG%_MESSAGES[10]!%RESET% -if exist "..\bin" dir /b "..\bin" +:: Wait for all builds to complete +:wait_builds +timeout /t 2 /nobreak >nul +tasklist /fi "IMAGENAME eq go.exe" 2>nul | find "go.exe" >nul +if not errorlevel 1 goto wait_builds -pause -endlocal \ No newline at end of file +echo %GREEN%!EN_MESSAGES[7]!%RESET% \ No newline at end of file diff --git a/scripts/build_all.sh b/scripts/build_all.sh index d7e38a9..7db5e7b 100644 --- a/scripts/build_all.sh +++ b/scripts/build_all.sh @@ -5,6 +5,10 @@ GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' # No Color / 无颜色 +# Build optimization flags +OPTIMIZATION_FLAGS="-trimpath -ldflags=\"-s -w\"" +PARALLEL_JOBS=$(nproc || echo "4") # Get number of CPU cores or default to 4 + # Messages / 消息 EN_MESSAGES=( "Starting build process for version" @@ -18,8 +22,6 @@ EN_MESSAGES=( "Successful builds:" "Failed builds:" "Generated files:" - "Build process interrupted" - "Error:" ) CN_MESSAGES=( @@ -70,82 +72,68 @@ handle_error() { # 清理函数 / Cleanup function cleanup() { - echo "$(get_message 1)" - rm -rf ../bin -} - -# 创建输出目录 / Create output directory -create_output_dir() { - echo "$(get_message 2)" - mkdir -p ../bin || handle_error "$(get_message 3)" + if [ -d "../bin" ]; then + rm -rf ../bin + echo -e "${GREEN}$(get_message 1)${NC}" + fi } -# 构建函数 / Build function +# Build function with optimizations build() { local os=$1 local arch=$2 - local suffix=$3 - - echo -e "\n$(get_message 4) $os ($arch)..." + local ext="" + [ "$os" = "windows" ] && ext=".exe" - output_name="../bin/cursor_id_modifier_v${VERSION}_${os}_${arch}${suffix}" + echo -e "${GREEN}$(get_message 4) $os/$arch${NC}" - GOOS=$os GOARCH=$arch go build -o "$output_name" ../main.go - - if [ $? -eq 0 ]; then - echo -e "${GREEN}✓ $(get_message 5) ${output_name}${NC}" - else - echo -e "${RED}✗ $(get_message 6) $os $arch${NC}" - return 1 - fi + GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build \ + -trimpath \ + -ldflags="-s -w" \ + -o "../bin/$os/$arch/cursor-id-modifier$ext" \ + -a -installsuffix cgo \ + -mod=readonly \ + ../cmd/cursor-id-modifier & } -# 主函数 / Main function -main() { - # 显示构建信息 / Display build info - echo "$(get_message 0) ${VERSION}" - - # 清理旧文件 / Clean old files - cleanup - - # 创建输出目录 / Create output directory - create_output_dir +# Parallel build execution +build_all() { + local builds=0 + local max_parallel=$PARALLEL_JOBS - # 定义构建目标 / Define build targets + # Define build targets declare -A targets=( - ["windows_amd64"]=".exe" - ["darwin_amd64"]="" - ["darwin_arm64"]="" - ["linux_amd64"]="" + ["linux/amd64"]=1 + ["linux/386"]=1 + ["linux/arm64"]=1 + ["windows/amd64"]=1 + ["windows/386"]=1 + ["darwin/amd64"]=1 + ["darwin/arm64"]=1 ) - # 构建计数器 / Build counters - local success_count=0 - local fail_count=0 - - # 遍历所有目标进行构建 / Build all targets for target in "${!targets[@]}"; do - os=${target%_*} - arch=${target#*_} - suffix=${targets[$target]} + IFS='/' read -r os arch <<< "$target" + build "$os" "$arch" - if build "$os" "$arch" "$suffix"; then - ((success_count++)) - else - ((fail_count++)) + ((builds++)) + + if ((builds >= max_parallel)); then + wait + builds=0 fi done - # 显示构建结果 / Display build results - echo -e "\n$(get_message 7)" - echo -e "${GREEN}$(get_message 8) $success_count${NC}" - if [ $fail_count -gt 0 ]; then - echo -e "${RED}$(get_message 9) $fail_count${NC}" - fi - - # 显示生成的文件列表 / Display generated files - echo -e "\n$(get_message 10)" - ls -1 ../bin + # Wait for remaining builds + wait +} + +# Main execution +main() { + cleanup + mkdir -p ../bin || { echo -e "${RED}$(get_message 3)${NC}"; exit 1; } + build_all + echo -e "${GREEN}Build completed successfully${NC}" } # 捕获错误信号 / Catch error signals diff --git a/scripts/install.ps1 b/scripts/install.ps1 index cd4d5dd..747b65b 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -6,303 +6,123 @@ if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdenti Exit } -# Set TLS to 1.2 / 设置 TLS 为 1.2 +# Set TLS to 1.2 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -# Colors for output / 输出颜色 +# Colors for output $Red = "`e[31m" $Green = "`e[32m" $Blue = "`e[36m" $Yellow = "`e[33m" $Reset = "`e[0m" -# Messages / 消息 -$EN_MESSAGES = @( - "Starting installation...", - "Detected architecture:", - "Only 64-bit Windows is supported", - "Latest version:", - "Creating installation directory...", - "Downloading latest release from:", - "Failed to download binary:", - "Downloaded file not found", - "Installing binary...", - "Failed to install binary:", - "Adding to PATH...", - "Cleaning up...", - "Installation completed successfully!", - "You can now use 'cursor-id-modifier' directly", - "Checking for running Cursor instances...", - "Found running Cursor processes. Attempting to close them...", - "Successfully closed all Cursor instances", - "Failed to close Cursor instances. Please close them manually", - "Backing up storage.json...", - "Backup created at:" -) +# Create temporary directory +$TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString()) +New-Item -ItemType Directory -Path $TmpDir | Out-Null -$CN_MESSAGES = @( - "开始安装...", - "检测到架构:", - "仅支持64位Windows系统", - "最新版本:", - "正在创建安装目录...", - "正在从以下地址下载最新版本:", - "下载二进制文件失败:", - "未找到下载的文件", - "正在安装程序...", - "安装二进制文件失败:", - "正在添加到PATH...", - "正在清理...", - "安装成功完成!", - "现在可以直接使用 'cursor-id-modifier' 了", - "正在检查运行中的Cursor进程...", - "发现正在运行的Cursor进程,尝试关闭...", - "成功关闭所有Cursor实例", - "无法关闭Cursor实例,请手动关闭", - "正在备份storage.json...", - "备份已创建于:" -) - -# Detect system language / 检测系统语言 -function Get-SystemLanguage { - if ((Get-Culture).Name -like "zh-CN") { - return "cn" +# Cleanup function +function Cleanup { + if (Test-Path $TmpDir) { + Remove-Item -Recurse -Force $TmpDir } - return "en" -} - -# Get message based on language / 根据语言获取消息 -function Get-Message($Index) { - $lang = Get-SystemLanguage - if ($lang -eq "cn") { - return $CN_MESSAGES[$Index] - } - return $EN_MESSAGES[$Index] -} - -# Functions for colored output / 彩色输出函数 -function Write-Status($Message) { - Write-Host "${Blue}[*]${Reset} $Message" -} - -function Write-Success($Message) { - Write-Host "${Green}[✓]${Reset} $Message" -} - -function Write-Warning($Message) { - Write-Host "${Yellow}[!]${Reset} $Message" } -function Write-Error($Message) { - Write-Host "${Red}[✗]${Reset} $Message" - Exit 1 +# Error handler +trap { + Write-Host "${Red}Error: $_${Reset}" + Cleanup + exit 1 } -# Close Cursor instances / 关闭Cursor实例 -function Close-CursorInstances { - Write-Status (Get-Message 14) - $cursorProcesses = Get-Process "Cursor" -ErrorAction SilentlyContinue - - if ($cursorProcesses) { - Write-Status (Get-Message 15) - try { - $cursorProcesses | ForEach-Object { $_.CloseMainWindow() | Out-Null } - Start-Sleep -Seconds 2 - $cursorProcesses | Where-Object { !$_.HasExited } | Stop-Process -Force - Write-Success (Get-Message 16) - } catch { - Write-Error (Get-Message 17) - } - } -} - -# Backup storage.json / 备份storage.json -function Backup-StorageJson { - Write-Status (Get-Message 18) - $storageJsonPath = "$env:APPDATA\Cursor\User\globalStorage\storage.json" - if (Test-Path $storageJsonPath) { - $backupPath = "$storageJsonPath.backup" - Copy-Item -Path $storageJsonPath -Destination $backupPath -Force - Write-Success "$(Get-Message 19) $backupPath" +# Detect system architecture +function Get-SystemArch { + if ([Environment]::Is64BitOperatingSystem) { + return "amd64" + } else { + return "386" } } -# Get latest release version from GitHub / 从GitHub获取最新版本 -function Get-LatestVersion { - $repo = "yuaotian/go-cursor-help" - $release = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/releases/latest" - return $release.tag_name -} - -# 在文件开头添加日志函数 -function Write-Log { - param( - [string]$Message, - [string]$Level = "INFO" +# Download with progress +function Download-WithProgress { + param ( + [string]$Url, + [string]$OutputFile ) - $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - $logMessage = "[$timestamp] [$Level] $Message" - $logFile = "$env:TEMP\cursor-id-modifier-install.log" - Add-Content -Path $logFile -Value $logMessage - # 同时输出到控制台 - switch ($Level) { - "ERROR" { Write-Error $Message } - "WARNING" { Write-Warning $Message } - "SUCCESS" { Write-Success $Message } - default { Write-Status $Message } + try { + $webClient = New-Object System.Net.WebClient + $webClient.Headers.Add("User-Agent", "PowerShell Script") + + $webClient.DownloadFile($Url, $OutputFile) + return $true + } + catch { + Write-Host "${Red}Failed to download: $_${Reset}" + return $false } } -# 添加安装前检查函数 -function Test-Prerequisites { - Write-Log "Checking prerequisites..." "INFO" +# Main installation function +function Install-CursorModifier { + Write-Host "${Blue}Starting installation...${Reset}" - # 检查PowerShell版本 - if ($PSVersionTable.PSVersion.Major -lt 5) { - Write-Log "PowerShell 5.0 or higher is required" "ERROR" - return $false + # Detect architecture + $arch = Get-SystemArch + Write-Host "${Green}Detected architecture: $arch${Reset}" + + # Set installation directory + $InstallDir = "$env:ProgramFiles\CursorModifier" + if (!(Test-Path $InstallDir)) { + New-Item -ItemType Directory -Path $InstallDir | Out-Null } - # 检查网络连接 + # Get latest release try { - $testConnection = Test-Connection -ComputerName "github.com" -Count 1 -Quiet - if (-not $testConnection) { - Write-Log "No internet connection available" "ERROR" - return $false + $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/dacrab/cursor-id-modifier/releases/latest" + $downloadUrl = $latestRelease.assets | Where-Object { $_.name -match "windows_$arch" } | Select-Object -ExpandProperty browser_download_url + + if (!$downloadUrl) { + throw "Could not find download URL for windows_$arch" } - } catch { - Write-Log "Failed to check internet connection: $_" "ERROR" - return $false + } + catch { + Write-Host "${Red}Failed to get latest release: $_${Reset}" + exit 1 } - return $true -} - -# 添加文件验证函数 -function Test-FileHash { - param( - [string]$FilePath, - [string]$ExpectedHash - ) + # Download binary + Write-Host "${Blue}Downloading latest release...${Reset}" + $binaryPath = Join-Path $TmpDir "cursor-id-modifier.exe" - $actualHash = Get-FileHash -Path $FilePath -Algorithm SHA256 - return $actualHash.Hash -eq $ExpectedHash -} - -# 修改下载函数,添加进度条 -function Download-File { - param( - [string]$Url, - [string]$OutFile - ) + if (!(Download-WithProgress -Url $downloadUrl -OutputFile $binaryPath)) { + exit 1 + } + # Install binary + Write-Host "${Blue}Installing...${Reset}" try { - $webClient = New-Object System.Net.WebClient - $webClient.Headers.Add("User-Agent", "PowerShell Script") + Copy-Item -Path $binaryPath -Destination "$InstallDir\cursor-id-modifier.exe" -Force - $webClient.DownloadFileAsync($Url, $OutFile) - - while ($webClient.IsBusy) { - Write-Progress -Activity "Downloading..." -Status "Progress:" -PercentComplete -1 - Start-Sleep -Milliseconds 100 + # Add to PATH if not already present + $currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine") + if ($currentPath -notlike "*$InstallDir*") { + [Environment]::SetEnvironmentVariable("Path", "$currentPath;$InstallDir", "Machine") } - - Write-Progress -Activity "Downloading..." -Completed - return $true } catch { - Write-Log "Download failed: $_" "ERROR" - return $false - } - finally { - if ($webClient) { - $webClient.Dispose() - } - } -} - -# Main installation process / 主安装过程 -Write-Status (Get-Message 0) - -# Close any running Cursor instances -Close-CursorInstances - -# Backup storage.json -Backup-StorageJson - -# Get system architecture / 获取系统架构 -$arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" } -Write-Status "$(Get-Message 1) $arch" - -if ($arch -ne "amd64") { - Write-Error (Get-Message 2) -} - -# Get latest version / 获取最新版本 -$version = Get-LatestVersion -Write-Status "$(Get-Message 3) $version" - -# Set up paths / 设置路径 -$installDir = "$env:ProgramFiles\cursor-id-modifier" -$versionWithoutV = $version.TrimStart('v') # 移除版本号前面的 'v' 字符 -$binaryName = "cursor_id_modifier_${versionWithoutV}_windows_amd64.exe" -$downloadUrl = "https://github.com/yuaotian/go-cursor-help/releases/download/$version/$binaryName" -$tempFile = "$env:TEMP\$binaryName" - -# Create installation directory / 创建安装目录 -Write-Status (Get-Message 4) -if (-not (Test-Path $installDir)) { - New-Item -ItemType Directory -Path $installDir -Force | Out-Null -} - -# Download binary / 下载二进制文件 -Write-Status "$(Get-Message 5) $downloadUrl" -try { - if (-not (Download-File -Url $downloadUrl -OutFile $tempFile)) { - Write-Error "$(Get-Message 6)" + Write-Host "${Red}Failed to install: $_${Reset}" + exit 1 } -} catch { - Write-Error "$(Get-Message 6) $_" -} - -# Verify download / 验证下载 -if (-not (Test-Path $tempFile)) { - Write-Error (Get-Message 7) + + Write-Host "${Green}Installation completed successfully!${Reset}" + Write-Host "${Blue}You can now run: cursor-id-modifier${Reset}" } -# Install binary / 安装二进制文件 -Write-Status (Get-Message 8) +# Run installation try { - Move-Item -Force $tempFile "$installDir\cursor-id-modifier.exe" -} catch { - Write-Error "$(Get-Message 9) $_" + Install-CursorModifier } - -# Add to PATH if not already present / 如果尚未添加则添加到PATH -$userPath = [Environment]::GetEnvironmentVariable("Path", "User") -if ($userPath -notlike "*$installDir*") { - Write-Status (Get-Message 10) - [Environment]::SetEnvironmentVariable( - "Path", - "$userPath;$installDir", - "User" - ) -} - -# Cleanup / 清理 -Write-Status (Get-Message 11) -if (Test-Path $tempFile) { - Remove-Item -Force $tempFile -} - -Write-Success (Get-Message 12) -Write-Success (Get-Message 13) -Write-Host "" - -# 直接运行程序 -try { - Start-Process "$installDir\cursor-id-modifier.exe" -NoNewWindow -} catch { - Write-Warning "Failed to start cursor-id-modifier: $_" +finally { + Cleanup } \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh index 0aed487..2112421 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,294 +2,98 @@ set -e -# Colors for output / 输出颜色 +# Colors for output RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;36m' YELLOW='\033[0;33m' -NC='\033[0m' # No Color / 无颜色 +NC='\033[0m' -# Messages / 消息 -EN_MESSAGES=( - "Starting installation..." - "Detected OS:" - "Downloading latest release..." - "URL:" - "Installing binary..." - "Cleaning up..." - "Installation completed successfully!" - "You can now use 'sudo %s' from your terminal" - "Failed to download binary from:" - "Failed to download the binary" - "curl is required but not installed. Please install curl first." - "sudo is required but not installed. Please install sudo first." - "Unsupported operating system" - "Unsupported architecture:" - "Checking for running Cursor instances..." - "Found running Cursor processes. Attempting to close them..." - "Successfully closed all Cursor instances" - "Failed to close Cursor instances. Please close them manually" - "Backing up storage.json..." - "Backup created at:" - "This script requires root privileges. Requesting sudo access..." -) +# Temporary directory for downloads +TMP_DIR=$(mktemp -d) +trap 'rm -rf "$TMP_DIR"' EXIT -CN_MESSAGES=( - "开始安装..." - "检测到操作系统:" - "正在下载最新版本..." - "下载地址:" - "正在安装程序..." - "正在清理..." - "安装成功完成!" - "现在可以在终端中使用 'sudo %s' 了" - "从以下地址下载二进制文件失败:" - "下载二进制文件失败" - "需要 curl 但未安装。请先安装 curl。" - "需要 sudo 但未安装。请先安装 sudo。" - "不支持的操作系统" - "不支持的架构:" - "正在检查运行中的Cursor进程..." - "发现正在运行的Cursor进程,尝试关闭..." - "成功关闭所有Cursor实例" - "无法关闭Cursor实例,请手动关闭" - "正在备份storage.json..." - "备份已创建于:" - "此脚本需要root权限。正在请求sudo访问..." -) +# Detect system information +detect_system() { + local os arch -# Detect system language / 检测系统语言 -detect_language() { - if [[ $(locale | grep "LANG=zh_CN") ]]; then - echo "cn" - else - echo "en" - fi -} - -# Get message based on language / 根据语言获取消息 -get_message() { - local index=$1 - local lang=$(detect_language) - - if [[ "$lang" == "cn" ]]; then - echo "${CN_MESSAGES[$index]}" - else - echo "${EN_MESSAGES[$index]}" - fi -} - -# Print with color / 带颜色打印 -print_status() { - echo -e "${BLUE}[*]${NC} $1" -} - -print_success() { - echo -e "${GREEN}[✓]${NC} $1" -} - -print_warning() { - echo -e "${YELLOW}[!]${NC} $1" -} - -print_error() { - echo -e "${RED}[✗]${NC} $1" - exit 1 -} + case "$(uname -s)" in + Linux*) os="linux";; + Darwin*) os="darwin";; + *) echo "Unsupported OS"; exit 1;; + esac -# Check and request root privileges / 检查并请求root权限 -check_root() { - if [ "$EUID" -ne 0 ]; then - print_status "$(get_message 20)" - if command -v sudo >/dev/null 2>&1; then - exec sudo bash "$0" "$@" - else - print_error "$(get_message 11)" - fi - fi -} + case "$(uname -m)" in + x86_64) arch="amd64";; + aarch64) arch="arm64";; + arm64) arch="arm64";; + *) echo "Unsupported architecture"; exit 1;; + esac -# Close Cursor instances / 关闭Cursor实例 -close_cursor_instances() { - print_status "$(get_message 14)" - - if pgrep -x "Cursor" >/dev/null; then - print_status "$(get_message 15)" - if pkill -x "Cursor" 2>/dev/null; then - sleep 2 - print_success "$(get_message 16)" - else - print_error "$(get_message 17)" - fi - fi + echo "$os $arch" } -# Backup storage.json / 备份storage.json -backup_storage_json() { - print_status "$(get_message 18)" - local storage_path - - if [ "$(uname)" == "Darwin" ]; then - storage_path="$HOME/Library/Application Support/Cursor/User/globalStorage/storage.json" - else - storage_path="$HOME/.config/Cursor/User/globalStorage/storage.json" - fi +# Download with progress using curl or wget +download() { + local url="$1" + local output="$2" - if [ -f "$storage_path" ]; then - cp "$storage_path" "${storage_path}.backup" - print_success "$(get_message 19) ${storage_path}.backup" - fi -} - -# Detect OS / 检测操作系统 -detect_os() { - if [[ "$OSTYPE" == "darwin"* ]]; then - echo "darwin" - elif [[ "$OSTYPE" == "linux-gnu"* ]]; then - echo "linux" + if command -v curl >/dev/null 2>&1; then + curl -#L "$url" -o "$output" + elif command -v wget >/dev/null 2>&1; then + wget --show-progress -q "$url" -O "$output" else - print_error "$(get_message 12)" + echo "Error: curl or wget is required" + exit 1 fi } -# Get latest release version from GitHub / 从GitHub获取最新版本 -get_latest_version() { - local repo="yuaotian/go-cursor-help" - curl -s "https://api.github.com/repos/${repo}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' -} - -# Get the binary name based on OS and architecture / 根据操作系统和架构获取二进制文件名 -get_binary_name() { - OS=$(detect_os) - ARCH=$(uname -m) - VERSION=$(get_latest_version) +# Check and create installation directory +setup_install_dir() { + local install_dir="$1" - case "$ARCH" in - x86_64) - echo "cursor_id_modifier_${VERSION}_${OS}_amd64" - ;; - aarch64|arm64) - echo "cursor_id_modifier_${VERSION}_${OS}_arm64" - ;; - *) - print_error "$(get_message 13) $ARCH" - ;; - esac -} - -# Add download progress display function -download_with_progress() { - local url="$1" - local output_file="$2" - - curl -L -f --progress-bar "$url" -o "$output_file" - return $? + if [ ! -d "$install_dir" ]; then + mkdir -p "$install_dir" || { + echo "Failed to create installation directory" + exit 1 + } + fi } -# Optimize installation function -install_binary() { - OS=$(detect_os) - VERSION=$(get_latest_version) - VERSION_WITHOUT_V=${VERSION#v} # Remove 'v' from version number - BINARY_NAME="cursor_id_modifier_${VERSION_WITHOUT_V}_${OS}_$(get_arch)" - REPO="yuaotian/go-cursor-help" - DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${BINARY_NAME}" - TMP_DIR=$(mktemp -d) - FINAL_BINARY_NAME="cursor-id-modifier" - - print_status "$(get_message 2)" - print_status "$(get_message 3) ${DOWNLOAD_URL}" - - if ! download_with_progress "$DOWNLOAD_URL" "$TMP_DIR/$BINARY_NAME"; then - rm -rf "$TMP_DIR" - print_error "$(get_message 8) $DOWNLOAD_URL" - fi +# Main installation function +main() { + echo -e "${BLUE}Starting installation...${NC}" - if [ ! -f "$TMP_DIR/$BINARY_NAME" ]; then - rm -rf "$TMP_DIR" - print_error "$(get_message 9)" - fi + # Detect system + read -r OS ARCH <<< "$(detect_system)" + echo -e "${GREEN}Detected: $OS $ARCH${NC}" - print_status "$(get_message 4)" + # Set installation directory INSTALL_DIR="/usr/local/bin" + [ "$OS" = "darwin" ] && INSTALL_DIR="/usr/local/bin" - # Create directory if it doesn't exist - mkdir -p "$INSTALL_DIR" - - # Move binary to installation directory - if ! mv "$TMP_DIR/$BINARY_NAME" "$INSTALL_DIR/$FINAL_BINARY_NAME"; then - rm -rf "$TMP_DIR" - print_error "Failed to move binary to installation directory" - fi - - if ! chmod +x "$INSTALL_DIR/$FINAL_BINARY_NAME"; then - rm -rf "$TMP_DIR" - print_error "Failed to set executable permissions" - fi - - # Cleanup - print_status "$(get_message 5)" - rm -rf "$TMP_DIR" + # Setup installation directory + setup_install_dir "$INSTALL_DIR" - print_success "$(get_message 6)" - printf "${GREEN}[✓]${NC} $(get_message 7)\n" "$FINAL_BINARY_NAME" + # Download latest release + LATEST_URL="https://api.github.com/repos/dacrab/cursor-id-modifier/releases/latest" + DOWNLOAD_URL=$(curl -s "$LATEST_URL" | grep "browser_download_url.*${OS}_${ARCH}" | cut -d '"' -f 4) - # Try to run the program directly - if [ -x "$INSTALL_DIR/$FINAL_BINARY_NAME" ]; then - "$INSTALL_DIR/$FINAL_BINARY_NAME" & - else - print_warning "Failed to start cursor-id-modifier" - fi -} - -# Optimize architecture detection function -get_arch() { - case "$(uname -m)" in - x86_64) - echo "amd64" - ;; - aarch64|arm64) - echo "arm64" - ;; - *) - print_error "$(get_message 13) $(uname -m)" - ;; - esac -} - -# Check for required tools / 检查必需工具 -check_requirements() { - if ! command -v curl >/dev/null 2>&1; then - print_error "$(get_message 10)" + if [ -z "$DOWNLOAD_URL" ]; then + echo -e "${RED}Error: Could not find download URL for $OS $ARCH${NC}" + exit 1 fi - if ! command -v sudo >/dev/null 2>&1; then - print_error "$(get_message 11)" - fi -} - -# Main installation process / 主安装过程 -main() { - print_status "$(get_message 0)" - - # Check root privileges / 检查root权限 - check_root "$@" - - # Check required tools / 检查必需工具 - check_requirements - - # Close Cursor instances / 关闭Cursor实例 - close_cursor_instances - - # Backup storage.json / 备份storage.json - backup_storage_json + echo -e "${BLUE}Downloading latest release...${NC}" + download "$DOWNLOAD_URL" "$TMP_DIR/cursor-id-modifier" - OS=$(detect_os) - print_status "$(get_message 1) $OS" + # Install binary + echo -e "${BLUE}Installing...${NC}" + chmod +x "$TMP_DIR/cursor-id-modifier" + sudo mv "$TMP_DIR/cursor-id-modifier" "$INSTALL_DIR/" - # Install the binary / 安装二进制文件 - install_binary + echo -e "${GREEN}Installation completed successfully!${NC}" + echo -e "${BLUE}You can now run: cursor-id-modifier${NC}" } -# Run main function / 运行主函数 -main "$@" +main From dae6dbcdaa71bc2e802d6dcac05da231121cfc69 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 12:33:52 +0200 Subject: [PATCH 02/68] Remove Makefile and update README for improved clarity and installation instructions - Deleted the Makefile as it was no longer needed for the build process. - Enhanced the README with clearer installation instructions, including a one-click solution for Linux/macOS and Windows. - Improved formatting and added details for system support and configuration files. - Updated the project stats section for better visibility. These changes streamline the documentation and improve user experience for installation and usage of the Cursor ID Modifier tool. --- Makefile | 21 ------ README.md | 151 +++++++++++++++++++++++++++++++++---------- scripts/build_all.sh | 0 3 files changed, 116 insertions(+), 56 deletions(-) delete mode 100644 Makefile mode change 100644 => 100755 scripts/build_all.sh diff --git a/Makefile b/Makefile deleted file mode 100644 index f60895c..0000000 --- a/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -.PHONY: build clean test vet - -# Build the application -build: - go build -v ./cmd/cursor-id-modifier - -# Clean build artifacts -clean: - rm -f cursor-id-modifier - go clean - -# Run tests -test: - go test -v ./... - -# Run go vet -vet: - go vet ./... - -# Run all checks -all: vet test build \ No newline at end of file diff --git a/README.md b/README.md index ec98ffc..20dba40 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,21 @@ [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/dacrab/cursor-id-modifier/blob/main/LICENSE) [![Stars](https://img.shields.io/github/stars/dacrab/cursor-id-modifier?style=flat-square&logo=github)](https://github.com/dacrab/cursor-id-modifier/stargazers) -[English](#-english) | [中文](#-chinese) +[🌟 English](#english) | [🌏 中文](#chinese) Cursor Logo
-# 🌟 English +--- + +## 🌟 English ### 📝 Description -Resets Cursor's free trial limitation when you see: +> Resets Cursor's free trial limitation when you see: -``` +```text Too many free trial accounts used on this machine. Please upgrade to pro. We have this limit in place to prevent abuse. Please let us know if you believe @@ -27,81 +29,120 @@ this is a mistake. ### 💻 System Support + + + + + + +
+ **Windows** ✅ - x64 (64-bit) - x86 (32-bit) + + **macOS** ✅ - Intel (x64) - Apple Silicon (M1/M2) + + **Linux** ✅ - x64 (64-bit) - x86 (32-bit) - ARM64 -### 📥 One-Click Solution +
+ +### 🚀 One-Click Solution + +
+Linux/macOS: Copy and paste in terminal -**Linux/macOS**: Copy and paste in terminal: ```bash curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier ``` +
+ +
+Windows: Copy and paste in PowerShell (Admin) -**Windows**: Copy and paste in PowerShell (Admin): ```powershell irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier ``` +
That's it! The script will: -1. Install the tool automatically -2. Reset your Cursor trial immediately +1. ✨ Install the tool automatically +2. 🔄 Reset your Cursor trial immediately + +### 📦 Manual Installation -### 🔧 Manual Installation +> Download the appropriate file for your system from [releases](https://github.com/dacrab/cursor-id-modifier/releases/latest) -Download the appropriate file for your system from [releases](https://github.com/dacrab/cursor-id-modifier/releases/latest): +
+Windows Packages -**Windows**: - 64-bit: `cursor-id-modifier_vX.X.X_Windows_x64.zip` - 32-bit: `cursor-id-modifier_vX.X.X_Windows_x86.zip` +
+ +
+macOS Packages -**macOS**: - Intel: `cursor-id-modifier_vX.X.X_macOS_x64_intel.tar.gz` - M1/M2: `cursor-id-modifier_vX.X.X_macOS_arm64_apple_silicon.tar.gz` +
+ +
+Linux Packages -**Linux**: - 64-bit: `cursor-id-modifier_vX.X.X_Linux_x64.tar.gz` - 32-bit: `cursor-id-modifier_vX.X.X_Linux_x86.tar.gz` - ARM64: `cursor-id-modifier_vX.X.X_Linux_arm64.tar.gz` +
### 🔧 Technical Details -#### Configuration Files +
+Configuration Files + The program modifies Cursor's `storage.json` config file located at: + - Windows: `%APPDATA%\Cursor\User\globalStorage\storage.json` - macOS: `~/Library/Application Support/Cursor/User/globalStorage/storage.json` - Linux: `~/.config/Cursor/User/globalStorage/storage.json` +
+ +
+Modified Fields -#### Modified Fields The tool generates new unique identifiers for: - `telemetry.machineId` - `telemetry.macMachineId` - `telemetry.devDeviceId` - `telemetry.sqmId` +
+ +
+Safety Features -#### Safety Features - ✅ Safe process termination - ✅ Atomic file operations - ✅ Error handling and recovery +
--- -# 🌏 Chinese +## 🌏 Chinese ### 📝 问题描述 -当看到以下提示时重置Cursor试用期: +> 当看到以下提示时重置Cursor试用期: -``` +```text Too many free trial accounts used on this machine. Please upgrade to pro. We have this limit in place to prevent abuse. Please let us know if you believe @@ -110,57 +151,97 @@ this is a mistake. ### 💻 系统支持 -**Windows** ✅ x64 & x86 -**macOS** ✅ Intel & M-series -**Linux** ✅ x64 & ARM64 + + + + + + +
-### 📥 一键解决 +**Windows** ✅ +- x64 & x86 + + + +**macOS** ✅ +- Intel & M-series + + + +**Linux** ✅ +- x64 & ARM64 + +
+ +### 🚀 一键解决 + +
+Linux/macOS: 在终端中复制粘贴 -**Linux/macOS**: 在终端中复制粘贴: ```bash curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier ``` +
+ +
+Windows: 在PowerShell(管理员)中复制粘贴 -**Windows**: 在PowerShell(管理员)中复制粘贴: ```powershell irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier ``` +
就这样!脚本会: -1. 自动安装工具 -2. 立即重置Cursor试用期 +1. ✨ 自动安装工具 +2. 🔄 立即重置Cursor试用期 ### 🔧 技术细节 -#### 配置文件 +
+配置文件 + 程序修改Cursor的`storage.json`配置文件,位于: + - Windows: `%APPDATA%\Cursor\User\globalStorage\` - macOS: `~/Library/Application Support/Cursor/User/globalStorage/` - Linux: `~/.config/Cursor/User/globalStorage/` +
+ +
+修改字段 -#### 修改字段 工具会生成新的唯一标识符: - `telemetry.machineId` - `telemetry.macMachineId` - `telemetry.devDeviceId` - `telemetry.sqmId` +
+ +
+安全特性 -#### 安全特性 - ✅ 安全的进程终止 - ✅ 原子文件操作 - ✅ 错误处理和恢复 +
-## ⭐ Star History or Repobeats +--- -[![Star History Chart](https://api.star-history.com/svg?repos=yuaotian/go-cursor-help&type=Date)](https://star-history.com/#yuaotian/go-cursor-help&Date) +## ⭐ Project Stats +
+ +[![Star History Chart](https://api.star-history.com/svg?repos=yuaotian/go-cursor-help&type=Date)](https://star-history.com/#yuaotian/go-cursor-help&Date) ![Repobeats analytics image](https://repobeats.axiom.co/api/embed/ddaa9df9a94b0029ec3fad399e1c1c4e75755477.svg "Repobeats analytics image") +
## 📄 License -MIT License +
+MIT License Copyright (c) 2024 @@ -173,4 +254,4 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - +
diff --git a/scripts/build_all.sh b/scripts/build_all.sh old mode 100644 new mode 100755 From 4dc00341652a13d3efaf1c68374037809b8c5d28 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 12:42:19 +0200 Subject: [PATCH 03/68] fix: update goreleaser config to latest format --- .goreleaser.yml | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 4a01b1f..b6f393c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -25,22 +25,6 @@ builds: - -trimpath mod_timestamp: '{{ .CommitTimestamp }}' - # Build matrix - matrix: - # Special builds for macOS - - goos: [darwin] - goarch: [amd64] - tags: ["intel"] - - goos: [darwin] - goarch: [arm64] - tags: ["apple_silicon"] - # Windows builds - - goos: [windows] - goarch: [amd64, "386"] - # Linux builds - - goos: [linux] - goarch: [amd64, arm64, "386"] - archives: - format: tar.gz format_overrides: @@ -49,20 +33,16 @@ archives: name_template: >- {{ .ProjectName }}_ {{- .Version }}_ - {{- .Os }}_ - {{- .Arch }} - {{- with .Tags }}_{{ . }}{{ end }} + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x64{{ end }} + {{- if eq .Arch "386" }}x86{{ end }} + {{- if eq .Arch "arm64" }}arm64{{ end }} + {{- if and (eq .Os "darwin") (eq .Arch "amd64") }}_intel{{ end }} + {{- if and (eq .Os "darwin") (eq .Arch "arm64") }}_apple_silicon{{ end }} files: - README.md - LICENSE - scripts/* # Include installation scripts - replacements: - darwin: macOS - linux: Linux - windows: Windows - 386: x86 - amd64: x64 - arm64: arm64 checksum: name_template: 'checksums.txt' From 992941fc68955f55a16379ec94f41543ba155023 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 12:50:02 +0200 Subject: [PATCH 04/68] feat: add version flag and fix gitignore --- .gitignore | 4 +- cmd/cursor-id-modifier/main.go | 308 +++++++++++++++++++++++++++++++++ 2 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 cmd/cursor-id-modifier/main.go diff --git a/.gitignore b/.gitignore index ef225bb..2d90d08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Compiled binary -cursor-id-modifier -cursor-id-modifier.exe +/cursor-id-modifier +/cursor-id-modifier.exe # Build output directories bin/ diff --git a/cmd/cursor-id-modifier/main.go b/cmd/cursor-id-modifier/main.go new file mode 100644 index 0000000..6314dd2 --- /dev/null +++ b/cmd/cursor-id-modifier/main.go @@ -0,0 +1,308 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "os" + "os/exec" + "os/user" + "runtime" + "runtime/debug" + "strings" + "time" + + "github.com/dacrab/go-cursor-help/internal/config" + "github.com/dacrab/go-cursor-help/internal/lang" + "github.com/dacrab/go-cursor-help/internal/process" + "github.com/dacrab/go-cursor-help/internal/ui" + "github.com/dacrab/go-cursor-help/pkg/idgen" + + "github.com/sirupsen/logrus" +) + +var ( + version = "dev" + setReadOnly = flag.Bool("r", false, "set storage.json to read-only mode") + showVersion = flag.Bool("v", false, "show version information") + log = logrus.New() +) + +func main() { + // Initialize error recovery + defer func() { + if r := recover(); r != nil { + log.Errorf("Panic recovered: %v\n", r) + debug.PrintStack() + waitExit() + } + }() + + // Parse flags + flag.Parse() + + // Show version if requested + if *showVersion { + fmt.Printf("Cursor ID Modifier v%s\n", version) + return + } + + // Initialize logger + log.SetFormatter(&logrus.TextFormatter{ + FullTimestamp: true, + }) + + // Get current user + username := os.Getenv("SUDO_USER") + if username == "" { + user, err := user.Current() + if err != nil { + log.Fatal(err) + } + username = user.Username + } + + // Initialize components + display := ui.NewDisplay(nil) + procManager := process.NewManager(process.DefaultConfig(), log) + configManager, err := config.NewManager(username) + if err != nil { + log.Fatal(err) + } + generator := idgen.NewGenerator() + + // Check privileges + isAdmin, err := checkAdminPrivileges() + if err != nil { + log.Error(err) + waitExit() + return + } + + if !isAdmin { + if runtime.GOOS == "windows" { + message := "\nRequesting administrator privileges..." + if lang.GetCurrentLanguage() == lang.CN { + message = "\n请求管理员权限..." + } + fmt.Println(message) + if err := selfElevate(); err != nil { + log.Error(err) + display.ShowPrivilegeError( + lang.GetText().PrivilegeError, + lang.GetText().RunAsAdmin, + lang.GetText().RunWithSudo, + lang.GetText().SudoExample, + ) + waitExit() + return + } + return + } + display.ShowPrivilegeError( + lang.GetText().PrivilegeError, + lang.GetText().RunAsAdmin, + lang.GetText().RunWithSudo, + lang.GetText().SudoExample, + ) + waitExit() + return + } + + // Ensure Cursor is closed + if err := ensureCursorClosed(display, procManager); err != nil { + message := "\nError: Please close Cursor manually before running this program." + if lang.GetCurrentLanguage() == lang.CN { + message = "\n错误:请在运行此程序之前手动关闭 Cursor。" + } + display.ShowError(message) + waitExit() + return + } + + // Kill any remaining Cursor processes + if procManager.IsCursorRunning() { + text := lang.GetText() + display.ShowProcessStatus(text.ClosingProcesses) + + if err := procManager.KillCursorProcesses(); err != nil { + fmt.Println() + message := "Warning: Could not close all Cursor instances. Please close them manually." + if lang.GetCurrentLanguage() == lang.CN { + message = "警告:无法关闭所有 Cursor 实例,请手动关闭。" + } + display.ShowWarning(message) + waitExit() + return + } + + if procManager.IsCursorRunning() { + fmt.Println() + message := "\nWarning: Cursor is still running. Please close it manually." + if lang.GetCurrentLanguage() == lang.CN { + message = "\n警告:Cursor 仍在运行,请手动关闭。" + } + display.ShowWarning(message) + waitExit() + return + } + + display.ShowProcessStatus(text.ProcessesClosed) + fmt.Println() + } + + // Clear screen + if err := display.ClearScreen(); err != nil { + log.Warn("Failed to clear screen:", err) + } + + // Read existing config + text := lang.GetText() + display.ShowProgress(text.ReadingConfig) + + oldConfig, err := configManager.ReadConfig() + if err != nil { + log.Warn("Failed to read existing config:", err) + oldConfig = nil + } + + // Generate new IDs + display.ShowProgress(text.GeneratingIds) + + machineID, err := generator.GenerateMachineID() + if err != nil { + log.Fatal("Failed to generate machine ID:", err) + } + + macMachineID, err := generator.GenerateMacMachineID() + if err != nil { + log.Fatal("Failed to generate MAC machine ID:", err) + } + + deviceID, err := generator.GenerateDeviceID() + if err != nil { + log.Fatal("Failed to generate device ID:", err) + } + + // Create new config + newConfig := &config.StorageConfig{ + TelemetryMachineId: machineID, + TelemetryMacMachineId: macMachineID, + TelemetryDevDeviceId: deviceID, + } + + if oldConfig != nil && oldConfig.TelemetrySqmId != "" { + newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId + } else { + sqmID, err := generator.GenerateMacMachineID() + if err != nil { + log.Fatal("Failed to generate SQM ID:", err) + } + newConfig.TelemetrySqmId = sqmID + } + + // Save config + if err := configManager.SaveConfig(newConfig, *setReadOnly); err != nil { + log.Error(err) + waitExit() + return + } + + // Show success + display.ShowSuccess(text.SuccessMessage, text.RestartMessage) + message := "\nOperation completed!" + if lang.GetCurrentLanguage() == lang.CN { + message = "\n操作完成!" + } + display.ShowInfo(message) + + if os.Getenv("AUTOMATED_MODE") != "1" { + waitExit() + } +} + +func waitExit() { + if os.Getenv("AUTOMATED_MODE") == "1" { + return + } + + fmt.Println(lang.GetText().PressEnterToExit) + os.Stdout.Sync() + bufio.NewReader(os.Stdin).ReadString('\n') +} + +func ensureCursorClosed(display *ui.Display, procManager *process.Manager) error { + maxAttempts := 3 + text := lang.GetText() + + display.ShowProcessStatus(text.CheckingProcesses) + + for attempt := 1; attempt <= maxAttempts; attempt++ { + if !procManager.IsCursorRunning() { + display.ShowProcessStatus(text.ProcessesClosed) + fmt.Println() + return nil + } + + message := fmt.Sprintf("Please close Cursor before continuing. Attempt %d/%d\n%s", + attempt, maxAttempts, text.PleaseWait) + if lang.GetCurrentLanguage() == lang.CN { + message = fmt.Sprintf("请在继续之前关闭 Cursor。尝试 %d/%d\n%s", + attempt, maxAttempts, text.PleaseWait) + } + display.ShowProcessStatus(message) + + time.Sleep(5 * time.Second) + } + + return fmt.Errorf("cursor is still running") +} + +func checkAdminPrivileges() (bool, error) { + switch runtime.GOOS { + case "windows": + cmd := exec.Command("net", "session") + return cmd.Run() == nil, nil + + case "darwin", "linux": + currentUser, err := user.Current() + if err != nil { + return false, fmt.Errorf("failed to get current user: %w", err) + } + return currentUser.Uid == "0", nil + + default: + return false, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) + } +} + +func selfElevate() error { + os.Setenv("AUTOMATED_MODE", "1") + + switch runtime.GOOS { + case "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": + 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) + } +} From 23295a049d4bc34436b44f856650f824589aaa56 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 12:51:16 +0200 Subject: [PATCH 05/68] ci: add automatic version tagging workflow --- .github/workflows/auto-tag.yml | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/auto-tag.yml diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml new file mode 100644 index 0000000..8056e18 --- /dev/null +++ b/.github/workflows/auto-tag.yml @@ -0,0 +1,42 @@ +name: Auto Tag + +on: + push: + branches: + - master + - main + paths-ignore: + - '**.md' + - '.gitignore' + - '.github/**' + +jobs: + auto-tag: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get latest tag + id: get_latest_tag + run: | + git fetch --tags + latest_tag=$(git tag -l 'v*' --sort=-v:refname | head -n 1) + if [ -z "$latest_tag" ]; then + echo "version=v0.1.0" >> $GITHUB_OUTPUT + else + major=$(echo $latest_tag | cut -d. -f1) + minor=$(echo $latest_tag | cut -d. -f2) + patch=$(echo $latest_tag | cut -d. -f3) + new_patch=$((patch + 1)) + echo "version=$major.$minor.$new_patch" >> $GITHUB_OUTPUT + fi + + - name: Create new tag + run: | + new_tag=${{ steps.get_latest_tag.outputs.version }} + git tag $new_tag + git push origin $new_tag \ No newline at end of file From 114ac32618aa2e3260b5e11bc203e6343c6015ba Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 12:54:04 +0200 Subject: [PATCH 06/68] fix: update goreleaser config to fix build errors --- .goreleaser.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index b6f393c..1425f67 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -3,7 +3,8 @@ before: - go mod tidy builds: - - main: ./cmd/cursor-id-modifier + - id: cursor-id-modifier + main: ./cmd/cursor-id-modifier/main.go binary: cursor-id-modifier env: - CGO_ENABLED=0 @@ -12,12 +13,12 @@ builds: - windows - darwin goarch: - - amd64 # Intel 64-bit - - arm64 # Apple Silicon/ARM64 - - "386" # Intel 32-bit + - amd64 + - arm64 + - "386" ignore: - goos: darwin - goarch: "386" # No 32-bit support for macOS + goarch: "386" ldflags: - -s -w - -X main.version={{.Version}} @@ -26,23 +27,27 @@ builds: mod_timestamp: '{{ .CommitTimestamp }}' archives: - - format: tar.gz + - id: default + format: tar.gz format_overrides: - goos: windows format: zip name_template: >- {{ .ProjectName }}_ {{- .Version }}_ - {{- title .Os }}_ + {{- .Os }}_ {{- if eq .Arch "amd64" }}x64{{ end }} {{- if eq .Arch "386" }}x86{{ end }} {{- if eq .Arch "arm64" }}arm64{{ end }} {{- if and (eq .Os "darwin") (eq .Arch "amd64") }}_intel{{ end }} {{- if and (eq .Os "darwin") (eq .Arch "arm64") }}_apple_silicon{{ end }} files: - - README.md - - LICENSE - - scripts/* # Include installation scripts + - src: README.md + dst: . + - src: LICENSE + dst: . + - src: scripts/* + dst: scripts checksum: name_template: 'checksums.txt' From 78ea5908db02ee565e1154b0da3b4333002125c0 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 12:56:03 +0200 Subject: [PATCH 07/68] fix: update repository paths and improve release config --- .goreleaser.yml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 1425f67..79037bd 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -55,6 +55,16 @@ checksum: changelog: sort: asc + use: github + groups: + - title: Features + regexp: "^.*feat[(\\w)]*:+.*$" + order: 0 + - title: 'Bug fixes' + regexp: "^.*fix[(\\w)]*:+.*$" + order: 1 + - title: Others + order: 999 filters: exclude: - '^docs:' @@ -64,6 +74,9 @@ changelog: - Merge branch release: + github: + owner: dacrab + name: go-cursor-help draft: false prerelease: auto mode: replace @@ -77,18 +90,18 @@ release: See [CHANGELOG](CHANGELOG.md) for more details. footer: | - **Full Changelog**: https://github.com/dacrab/cursor-id-modifier/compare/{{ .PreviousTag }}...{{ .Tag }} + **Full Changelog**: https://github.com/dacrab/go-cursor-help/compare/{{ .PreviousTag }}...{{ .Tag }} ## Quick Installation **Linux/macOS**: ```bash - curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier + curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/main/scripts/install.sh | sudo bash && cursor-id-modifier ``` **Windows** (PowerShell Admin): ```powershell - irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier + irm https://raw.githubusercontent.com/dacrab/go-cursor-help/main/scripts/install.ps1 | iex; cursor-id-modifier ``` snapshot: From 17c2cb9c2aaba21b58434a008352b3cea775b4b6 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:02:56 +0200 Subject: [PATCH 08/68] ci: simplify and fix workflows --- .github/workflows/auto-tag.yml | 8 ++- .github/workflows/release.yml | 91 +--------------------------------- 2 files changed, 8 insertions(+), 91 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 8056e18..ddf3803 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -8,7 +8,8 @@ on: paths-ignore: - '**.md' - '.gitignore' - - '.github/**' + - '.github/workflows/**' + - 'LICENSE' jobs: auto-tag: @@ -19,6 +20,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} - name: Get latest tag id: get_latest_tag @@ -38,5 +40,7 @@ jobs: - name: Create new tag run: | new_tag=${{ steps.get_latest_tag.outputs.version }} - git tag $new_tag + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git tag -a $new_tag -m "Release $new_tag" git push origin $new_tag \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 47d5628..f7576e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,35 +7,10 @@ on: permissions: contents: write + packages: write jobs: - test: - name: Test on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - arch: [amd64, arm64] - exclude: - - os: windows-latest - arch: arm64 - - os: ubuntu-latest - arch: arm64 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.21' - cache: true - - - name: Run tests - run: go test -v ./... - - release: - needs: test + goreleaser: runs-on: ubuntu-latest steps: - name: Checkout @@ -48,15 +23,6 @@ jobs: with: go-version: '1.21' cache: true - check-latest: true - - - name: Install dependencies - run: go mod tidy - - - name: Set Repository Variables - run: | - echo "GITHUB_REPOSITORY_OWNER=$(echo ${{ github.repository }} | cut -d '/' -f 1)" >> $GITHUB_ENV - echo "GITHUB_REPOSITORY_NAME=$(echo ${{ github.repository }} | cut -d '/' -f 2)" >> $GITHUB_ENV - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 @@ -66,56 +32,3 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CGO_ENABLED: 0 - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: binaries - path: dist/* - retention-days: 5 - - - name: Generate changelog - if: success() - run: | - echo "# 🚀 Cursor ID Modifier ${{ github.ref_name }}" > ${{ github.workspace }}-CHANGELOG.txt - echo "" >> ${{ github.workspace }}-CHANGELOG.txt - - echo "## ✨ Supported Platforms" >> ${{ github.workspace }}-CHANGELOG.txt - echo "" >> ${{ github.workspace }}-CHANGELOG.txt - echo "### 🪟 Windows" >> ${{ github.workspace }}-CHANGELOG.txt - echo "- Windows x64 (64-bit)" >> ${{ github.workspace }}-CHANGELOG.txt - echo "- Windows x86 (32-bit)" >> ${{ github.workspace }}-CHANGELOG.txt - echo "" >> ${{ github.workspace }}-CHANGELOG.txt - echo "### 🍎 macOS" >> ${{ github.workspace }}-CHANGELOG.txt - echo "- macOS Intel (x64)" >> ${{ github.workspace }}-CHANGELOG.txt - echo "- macOS Apple Silicon (M1/M2)" >> ${{ github.workspace }}-CHANGELOG.txt - echo "" >> ${{ github.workspace }}-CHANGELOG.txt - echo "### 🐧 Linux" >> ${{ github.workspace }}-CHANGELOG.txt - echo "- Linux x64 (64-bit)" >> ${{ github.workspace }}-CHANGELOG.txt - echo "- Linux x86 (32-bit)" >> ${{ github.workspace }}-CHANGELOG.txt - echo "- Linux ARM64" >> ${{ github.workspace }}-CHANGELOG.txt - echo "" >> ${{ github.workspace }}-CHANGELOG.txt - - echo "## 📦 Quick Start" >> ${{ github.workspace }}-CHANGELOG.txt - echo "" >> ${{ github.workspace }}-CHANGELOG.txt - echo "### Linux/macOS" >> ${{ github.workspace }}-CHANGELOG.txt - echo "\`\`\`bash" >> ${{ github.workspace }}-CHANGELOG.txt - echo "curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier" >> ${{ github.workspace }}-CHANGELOG.txt - echo "\`\`\`" >> ${{ github.workspace }}-CHANGELOG.txt - echo "" >> ${{ github.workspace }}-CHANGELOG.txt - echo "### Windows (PowerShell Admin)" >> ${{ github.workspace }}-CHANGELOG.txt - echo "\`\`\`powershell" >> ${{ github.workspace }}-CHANGELOG.txt - echo "irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier" >> ${{ github.workspace }}-CHANGELOG.txt - echo "\`\`\`" >> ${{ github.workspace }}-CHANGELOG.txt - echo "" >> ${{ github.workspace }}-CHANGELOG.txt - - echo "## 🔄 Changes" >> ${{ github.workspace }}-CHANGELOG.txt - echo "* 📦 Release version: ${{ github.ref_name }}" >> ${{ github.workspace }}-CHANGELOG.txt - echo "* 📝 Full changelog: https://github.com/${{ github.repository }}/commits/${{ github.ref_name }}" >> ${{ github.workspace }}-CHANGELOG.txt - - echo "" >> ${{ github.workspace }}-CHANGELOG.txt - echo "## 🔍 Download Files" >> ${{ github.workspace }}-CHANGELOG.txt - echo "- Windows: \`cursor-id-modifier_${{ github.ref_name }}_Windows_[x64/x86].zip\`" >> ${{ github.workspace }}-CHANGELOG.txt - echo "- macOS: \`cursor-id-modifier_${{ github.ref_name }}_macOS_[x64/arm64]_[intel/apple_silicon].tar.gz\`" >> ${{ github.workspace }}-CHANGELOG.txt - echo "- Linux: \`cursor-id-modifier_${{ github.ref_name }}_Linux_[x64/x86/arm64].tar.gz\`" >> ${{ github.workspace }}-CHANGELOG.txt From 98537b94f999f8c660e9b38e0c4a41619fd17176 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:05:04 +0200 Subject: [PATCH 09/68] ci: improve workflow reliability and error handling --- .github/workflows/auto-tag.yml | 8 +++++--- .github/workflows/release.yml | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index ddf3803..6411ac2 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -7,20 +7,20 @@ on: - main paths-ignore: - '**.md' - - '.gitignore' - - '.github/workflows/**' - 'LICENSE' + - '.gitignore' jobs: auto-tag: runs-on: ubuntu-latest permissions: contents: write + outputs: + new_tag: ${{ steps.get_latest_tag.outputs.version }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - name: Get latest tag id: get_latest_tag @@ -38,6 +38,8 @@ jobs: fi - name: Create new tag + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | new_tag=${{ steps.get_latest_tag.outputs.version }} git config --global user.name 'github-actions[bot]' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f7576e0..5f907e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,14 @@ jobs: go-version: '1.21' cache: true + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + if: ${{ env.GPG_PRIVATE_KEY != '' }} + - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 with: @@ -32,3 +40,4 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} From e54f1236ec06904489560f5187809f87ab91f6cc Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:07:03 +0200 Subject: [PATCH 10/68] ci: make release workflow reusable and call it from auto-tag --- .github/workflows/auto-tag.yml | 7 ++++++- .github/workflows/release.yml | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 6411ac2..adfe54b 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -45,4 +45,9 @@ jobs: git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' git tag -a $new_tag -m "Release $new_tag" - git push origin $new_tag \ No newline at end of file + git push origin $new_tag + + release: + needs: auto-tag + uses: ./.github/workflows/release.yml + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f907e0..210977a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,7 @@ name: Release on: + workflow_call: push: tags: - 'v*' From 2617a8ece86511c12db14e9b4eba0efe0e0393bb Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:09:20 +0200 Subject: [PATCH 11/68] fix: update go module dependencies --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 05e7116..8bf96e2 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module cursor-id-modifier +module github.com/dacrab/go-cursor-help go 1.21 From 98d60390f56ce62a0c4161e43a16abd44fffbff1 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:12:31 +0200 Subject: [PATCH 12/68] fix: update version injection in goreleaser config --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 79037bd..a9eccea 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -21,7 +21,7 @@ builds: goarch: "386" ldflags: - -s -w - - -X main.version={{.Version}} + - -X 'main.version={{.Version}}' flags: - -trimpath mod_timestamp: '{{ .CommitTimestamp }}' From ab4806bc2ec2aac3e5aa18fc020693e86ee4cdb3 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:13:31 +0200 Subject: [PATCH 13/68] chore: add MIT license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..11eeb01 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 dacrab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From fa364cb3ce2395d678eea5841430c25354a3e135 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:27:57 +0200 Subject: [PATCH 14/68] ci: improve file handling in release workflow --- .github/workflows/release.yml | 9 +++++++++ .goreleaser.yml | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 210977a..fccb6f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,15 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + lfs: true + submodules: recursive + + - name: Debug Files + run: | + pwd + ls -la + echo "Current directory contents:" + ls -R - name: Set up Go uses: actions/setup-go@v5 diff --git a/.goreleaser.yml b/.goreleaser.yml index a9eccea..a677341 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -42,11 +42,11 @@ archives: {{- if and (eq .Os "darwin") (eq .Arch "amd64") }}_intel{{ end }} {{- if and (eq .Os "darwin") (eq .Arch "arm64") }}_apple_silicon{{ end }} files: - - src: README.md + - src: ./README.md dst: . - - src: LICENSE + - src: ./LICENSE dst: . - - src: scripts/* + - src: ./scripts/* dst: scripts checksum: From f96a71f9097084689ee8af2b6c621479be843e28 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:32:53 +0200 Subject: [PATCH 15/68] docs: update installation links to use correct repository --- .goreleaser.yml | 4 ++-- README.md | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index a677341..696d740 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -96,12 +96,12 @@ release: **Linux/macOS**: ```bash - curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/main/scripts/install.sh | sudo bash && cursor-id-modifier + curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash ``` **Windows** (PowerShell Admin): ```powershell - irm https://raw.githubusercontent.com/dacrab/go-cursor-help/main/scripts/install.ps1 | iex; cursor-id-modifier + irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex ``` snapshot: diff --git a/README.md b/README.md index 20dba40..f009477 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@
-[![Release](https://img.shields.io/github/v/release/dacrab/cursor-id-modifier?style=flat-square&logo=github&color=blue)](https://github.com/dacrab/cursor-id-modifier/releases/latest) -[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/dacrab/cursor-id-modifier/blob/main/LICENSE) -[![Stars](https://img.shields.io/github/stars/dacrab/cursor-id-modifier?style=flat-square&logo=github)](https://github.com/dacrab/cursor-id-modifier/stargazers) +[![Release](https://img.shields.io/github/v/release/dacrab/go-cursor-help?style=flat-square&logo=github&color=blue)](https://github.com/dacrab/go-cursor-help/releases/latest) +[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/dacrab/go-cursor-help/blob/master/LICENSE) +[![Stars](https://img.shields.io/github/stars/dacrab/go-cursor-help?style=flat-square&logo=github)](https://github.com/dacrab/go-cursor-help/stargazers) [🌟 English](#english) | [🌏 中文](#chinese) @@ -62,7 +62,7 @@ this is a mistake. Linux/macOS: Copy and paste in terminal ```bash -curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier +curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash ``` @@ -70,7 +70,7 @@ curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scri Windows: Copy and paste in PowerShell (Admin) ```powershell -irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier +irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex ``` @@ -80,7 +80,7 @@ That's it! The script will: ### 📦 Manual Installation -> Download the appropriate file for your system from [releases](https://github.com/dacrab/cursor-id-modifier/releases/latest) +> Download the appropriate file for your system from [releases](https://github.com/dacrab/go-cursor-help/releases/latest)
Windows Packages @@ -180,7 +180,7 @@ this is a mistake. Linux/macOS: 在终端中复制粘贴 ```bash -curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.sh | sudo bash && cursor-id-modifier +curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash ```
@@ -188,7 +188,7 @@ curl -fsSL https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scri Windows: 在PowerShell(管理员)中复制粘贴 ```powershell -irm https://raw.githubusercontent.com/dacrab/cursor-id-modifier/main/scripts/install.ps1 | iex; cursor-id-modifier +irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex ``` From 62129aa239cbb74c132e022482e30fcc8786b07d Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:35:32 +0200 Subject: [PATCH 16/68] ci: simplify release artifacts to direct binaries --- .goreleaser.yml | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 696d740..e324890 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -27,27 +27,17 @@ builds: mod_timestamp: '{{ .CommitTimestamp }}' archives: - - id: default - format: tar.gz - format_overrides: - - goos: windows - format: zip + - id: binary + format: binary name_template: >- - {{ .ProjectName }}_ - {{- .Version }}_ + {{ .Binary }}_ {{- .Os }}_ {{- if eq .Arch "amd64" }}x64{{ end }} {{- if eq .Arch "386" }}x86{{ end }} {{- if eq .Arch "arm64" }}arm64{{ end }} {{- if and (eq .Os "darwin") (eq .Arch "amd64") }}_intel{{ end }} {{- if and (eq .Os "darwin") (eq .Arch "arm64") }}_apple_silicon{{ end }} - files: - - src: ./README.md - dst: . - - src: ./LICENSE - dst: . - - src: ./scripts/* - dst: scripts + {{- if eq .Os "windows" }}.exe{{ end }} checksum: name_template: 'checksums.txt' From 1f1f4144bbb55b4512648afa74bfe5e690535423 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:42:42 +0200 Subject: [PATCH 17/68] fix: update release file names and install scripts --- .goreleaser.yml | 6 +++--- scripts/install.ps1 | 11 ++++++----- scripts/install.sh | 22 ++++++++++++++-------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index e324890..eb448d2 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -30,14 +30,14 @@ archives: - id: binary format: binary name_template: >- - {{ .Binary }}_ + {{ .Binary }} + {{- if eq .Os "windows" }}.exe{{ else }}_ {{- .Os }}_ {{- if eq .Arch "amd64" }}x64{{ end }} {{- if eq .Arch "386" }}x86{{ end }} {{- if eq .Arch "arm64" }}arm64{{ end }} {{- if and (eq .Os "darwin") (eq .Arch "amd64") }}_intel{{ end }} - {{- if and (eq .Os "darwin") (eq .Arch "arm64") }}_apple_silicon{{ end }} - {{- if eq .Os "windows" }}.exe{{ end }} + {{- if and (eq .Os "darwin") (eq .Arch "arm64") }}_apple_silicon{{ end }}{{ end }} checksum: name_template: 'checksums.txt' diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 747b65b..a851bac 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -37,9 +37,9 @@ trap { # Detect system architecture function Get-SystemArch { if ([Environment]::Is64BitOperatingSystem) { - return "amd64" + return "x64" } else { - return "386" + return "x86" } } @@ -79,11 +79,12 @@ function Install-CursorModifier { # Get latest release try { - $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/dacrab/cursor-id-modifier/releases/latest" - $downloadUrl = $latestRelease.assets | Where-Object { $_.name -match "windows_$arch" } | Select-Object -ExpandProperty browser_download_url + $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/dacrab/go-cursor-help/releases/latest" + $binaryName = "cursor-id-modifier.exe" + $downloadUrl = $latestRelease.assets | Where-Object { $_.name -eq $binaryName } | Select-Object -ExpandProperty browser_download_url if (!$downloadUrl) { - throw "Could not find download URL for windows_$arch" + throw "Could not find download URL for $binaryName" } } catch { diff --git a/scripts/install.sh b/scripts/install.sh index 2112421..c528a5f 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -15,7 +15,7 @@ trap 'rm -rf "$TMP_DIR"' EXIT # Detect system information detect_system() { - local os arch + local os arch suffix case "$(uname -s)" in Linux*) os="linux";; @@ -24,13 +24,18 @@ detect_system() { esac case "$(uname -m)" in - x86_64) arch="amd64";; - aarch64) arch="arm64";; - arm64) arch="arm64";; + x86_64) + arch="x64" + [ "$os" = "darwin" ] && suffix="_intel" + ;; + aarch64|arm64) + arch="arm64" + [ "$os" = "darwin" ] && suffix="_apple_silicon" + ;; *) echo "Unsupported architecture"; exit 1;; esac - echo "$os $arch" + echo "$os $arch $suffix" } # Download with progress using curl or wget @@ -65,7 +70,7 @@ main() { echo -e "${BLUE}Starting installation...${NC}" # Detect system - read -r OS ARCH <<< "$(detect_system)" + read -r OS ARCH SUFFIX <<< "$(detect_system)" echo -e "${GREEN}Detected: $OS $ARCH${NC}" # Set installation directory @@ -76,8 +81,9 @@ main() { setup_install_dir "$INSTALL_DIR" # Download latest release - LATEST_URL="https://api.github.com/repos/dacrab/cursor-id-modifier/releases/latest" - DOWNLOAD_URL=$(curl -s "$LATEST_URL" | grep "browser_download_url.*${OS}_${ARCH}" | cut -d '"' -f 4) + LATEST_URL="https://api.github.com/repos/dacrab/go-cursor-help/releases/latest" + BINARY_NAME="cursor-id-modifier_${OS}_${ARCH}${SUFFIX}" + DOWNLOAD_URL=$(curl -s "$LATEST_URL" | grep "browser_download_url.*${BINARY_NAME}" | cut -d '"' -f 4) if [ -z "$DOWNLOAD_URL" ]; then echo -e "${RED}Error: Could not find download URL for $OS $ARCH${NC}" From b3e748554fbc17b135cb9d30260865e7b8fc2d9b Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:44:43 +0200 Subject: [PATCH 18/68] fix: ensure unique binary names for all platforms --- .goreleaser.yml | 6 +++--- scripts/install.ps1 | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index eb448d2..e324890 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -30,14 +30,14 @@ archives: - id: binary format: binary name_template: >- - {{ .Binary }} - {{- if eq .Os "windows" }}.exe{{ else }}_ + {{ .Binary }}_ {{- .Os }}_ {{- if eq .Arch "amd64" }}x64{{ end }} {{- if eq .Arch "386" }}x86{{ end }} {{- if eq .Arch "arm64" }}arm64{{ end }} {{- if and (eq .Os "darwin") (eq .Arch "amd64") }}_intel{{ end }} - {{- if and (eq .Os "darwin") (eq .Arch "arm64") }}_apple_silicon{{ end }}{{ end }} + {{- if and (eq .Os "darwin") (eq .Arch "arm64") }}_apple_silicon{{ end }} + {{- if eq .Os "windows" }}.exe{{ end }} checksum: name_template: 'checksums.txt' diff --git a/scripts/install.ps1 b/scripts/install.ps1 index a851bac..211be45 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -80,7 +80,7 @@ function Install-CursorModifier { # Get latest release try { $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/dacrab/go-cursor-help/releases/latest" - $binaryName = "cursor-id-modifier.exe" + $binaryName = "cursor-id-modifier_windows_$arch.exe" $downloadUrl = $latestRelease.assets | Where-Object { $_.name -eq $binaryName } | Select-Object -ExpandProperty browser_download_url if (!$downloadUrl) { From 9d41a9a2c7db5e1cf499d8a43ee879c84dd95189 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:50:01 +0200 Subject: [PATCH 19/68] feat: add cyberpunk logo and auto-execute after install --- cmd/cursor-id-modifier/main.go | 3 +++ internal/ui/logo.go | 20 ++++++++++++++++++++ scripts/install.ps1 | 16 +++++++++++++++- scripts/install.sh | 9 ++++++++- 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 internal/ui/logo.go diff --git a/cmd/cursor-id-modifier/main.go b/cmd/cursor-id-modifier/main.go index 6314dd2..ea846ea 100644 --- a/cmd/cursor-id-modifier/main.go +++ b/cmd/cursor-id-modifier/main.go @@ -156,6 +156,9 @@ func main() { log.Warn("Failed to clear screen:", err) } + // Show logo + display.ShowLogo() + // Read existing config text := lang.GetText() display.ShowProgress(text.ReadingConfig) diff --git a/internal/ui/logo.go b/internal/ui/logo.go new file mode 100644 index 0000000..439af52 --- /dev/null +++ b/internal/ui/logo.go @@ -0,0 +1,20 @@ +package ui + +import ( + "github.com/fatih/color" +) + +const cyberpunkLogo = ` + ______ ______ ______ + / ____/_ __________ ___ _____/ __/ // / / / + / / / / / / ___/ _ \/ __ \/ ___/ /_/ // /_/ / +/ /___/ /_/ / / / __/ /_/ (__ ) __/__ __/ / +\____/\__,_/_/ \___/\____/____/_/ /_/ /_/ + +` + +// ShowLogo displays the cyberpunk-style logo +func (d *Display) ShowLogo() { + cyan := color.New(color.FgCyan, color.Bold) + cyan.Println(cyberpunkLogo) +} diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 211be45..fb14a5b 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -117,7 +117,21 @@ function Install-CursorModifier { } Write-Host "${Green}Installation completed successfully!${Reset}" - Write-Host "${Blue}You can now run: cursor-id-modifier${Reset}" + Write-Host "${Blue}Running cursor-id-modifier...${Reset}" + + # Run the program + try { + $env:AUTOMATED_MODE = "1" + & "$InstallDir\cursor-id-modifier.exe" + if ($LASTEXITCODE -ne 0) { + Write-Host "${Red}Failed to run cursor-id-modifier${Reset}" + exit 1 + } + } + catch { + Write-Host "${Red}Failed to run cursor-id-modifier: $_${Reset}" + exit 1 + } } # Run installation diff --git a/scripts/install.sh b/scripts/install.sh index c528a5f..75ee94a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -99,7 +99,14 @@ main() { sudo mv "$TMP_DIR/cursor-id-modifier" "$INSTALL_DIR/" echo -e "${GREEN}Installation completed successfully!${NC}" - echo -e "${BLUE}You can now run: cursor-id-modifier${NC}" + echo -e "${BLUE}Running cursor-id-modifier...${NC}" + + # Run the program + export AUTOMATED_MODE=1 + if ! cursor-id-modifier; then + echo -e "${RED}Failed to run cursor-id-modifier${NC}" + exit 1 + fi } main From 56f09677ca2dbc9348c4214fefe6bc4d1dc19d34 Mon Sep 17 00:00:00 2001 From: dacrab Date: Fri, 27 Dec 2024 13:53:36 +0200 Subject: [PATCH 20/68] feat: improve process management for auto-closing Cursor instances --- internal/process/manager.go | 176 ++++++++++++++++++------------------ 1 file changed, 87 insertions(+), 89 deletions(-) diff --git a/internal/process/manager.go b/internal/process/manager.go index 3d29ee9..00d568a 100644 --- a/internal/process/manager.go +++ b/internal/process/manager.go @@ -1,12 +1,10 @@ package process import ( - "context" "fmt" "os/exec" "runtime" "strings" - "sync" "time" "github.com/sirupsen/logrus" @@ -14,17 +12,23 @@ import ( // Config holds process manager configuration type Config struct { - RetryAttempts int - RetryDelay time.Duration - Timeout time.Duration + MaxAttempts int + RetryDelay time.Duration + ProcessPatterns []string } // DefaultConfig returns the default configuration func DefaultConfig() *Config { return &Config{ - RetryAttempts: 3, - RetryDelay: time.Second, - Timeout: 30 * time.Second, + MaxAttempts: 3, + RetryDelay: time.Second, + ProcessPatterns: []string{ + "Cursor.exe", // Windows + "Cursor", // Linux/macOS binary + "cursor", // Linux/macOS process + "cursor-helper", // Helper process + "cursor-id-modifier", // Our tool + }, } } @@ -32,7 +36,6 @@ func DefaultConfig() *Config { type Manager struct { config *Config log *logrus.Logger - mu sync.Mutex } // NewManager creates a new process manager @@ -49,114 +52,109 @@ func NewManager(config *Config, log *logrus.Logger) *Manager { } } -// KillCursorProcesses attempts to kill all Cursor processes -func (m *Manager) KillCursorProcesses() error { - m.mu.Lock() - defer m.mu.Unlock() - - ctx, cancel := context.WithTimeout(context.Background(), m.config.Timeout) - defer cancel() - - for attempt := 0; attempt < m.config.RetryAttempts; attempt++ { - m.log.Debugf("Attempt %d/%d to kill Cursor processes", attempt+1, m.config.RetryAttempts) - - if err := m.killProcess(ctx); err != nil { - m.log.Warnf("Failed to kill processes on attempt %d: %v", attempt+1, err) - time.Sleep(m.config.RetryDelay) - continue - } - return nil - } - - return fmt.Errorf("failed to kill all Cursor processes after %d attempts", m.config.RetryAttempts) -} - // IsCursorRunning checks if any Cursor process is running func (m *Manager) IsCursorRunning() bool { - m.mu.Lock() - defer m.mu.Unlock() - - processes, err := m.listCursorProcesses() + processes, err := m.getCursorProcesses() if err != nil { - m.log.Warnf("Failed to list Cursor processes: %v", err) + m.log.Warn("Failed to get Cursor processes:", err) return false } - return len(processes) > 0 } -func (m *Manager) killProcess(ctx context.Context) error { - if runtime.GOOS == "windows" { - return m.killWindowsProcess(ctx) - } - return m.killUnixProcess(ctx) -} +// KillCursorProcesses attempts to kill all Cursor processes +func (m *Manager) KillCursorProcesses() error { + for attempt := 1; attempt <= m.config.MaxAttempts; attempt++ { + processes, err := m.getCursorProcesses() + if err != nil { + return fmt.Errorf("failed to get processes: %w", err) + } -func (m *Manager) killWindowsProcess(ctx context.Context) error { - // First try graceful termination - if err := exec.CommandContext(ctx, "taskkill", "/IM", "Cursor.exe").Run(); err != nil { - m.log.Debugf("Graceful termination failed: %v", err) - } + if len(processes) == 0 { + return nil + } - time.Sleep(m.config.RetryDelay) + for _, proc := range processes { + if err := m.killProcess(proc); err != nil { + m.log.Warnf("Failed to kill process %s: %v", proc, err) + } + } + + time.Sleep(m.config.RetryDelay) + } - // Force kill if still running - if err := exec.CommandContext(ctx, "taskkill", "/F", "/IM", "Cursor.exe").Run(); err != nil { - return fmt.Errorf("failed to force kill Cursor process: %w", err) + if m.IsCursorRunning() { + return fmt.Errorf("failed to kill all Cursor processes after %d attempts", m.config.MaxAttempts) } return nil } -func (m *Manager) killUnixProcess(ctx context.Context) error { - processes, err := m.listCursorProcesses() +func (m *Manager) getCursorProcesses() ([]string, error) { + var cmd *exec.Cmd + var processes []string + + switch runtime.GOOS { + case "windows": + cmd = exec.Command("tasklist", "/FO", "CSV", "/NH") + case "darwin": + cmd = exec.Command("ps", "-ax") + case "linux": + cmd = exec.Command("ps", "-A") + default: + return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) + } + + output, err := cmd.Output() if err != nil { - return fmt.Errorf("failed to list processes: %w", err) + return nil, fmt.Errorf("failed to execute command: %w", err) } - for _, pid := range processes { - if err := m.forceKillProcess(ctx, pid); err != nil { - m.log.Warnf("Failed to kill process %s: %v", pid, err) - continue + lines := strings.Split(string(output), "\n") + for _, line := range lines { + for _, pattern := range m.config.ProcessPatterns { + if strings.Contains(strings.ToLower(line), strings.ToLower(pattern)) { + // Extract PID based on OS + pid := m.extractPID(line) + if pid != "" { + processes = append(processes, pid) + } + } } } - return nil + return processes, nil } -func (m *Manager) forceKillProcess(ctx context.Context, pid string) error { - // Try graceful termination first - if err := exec.CommandContext(ctx, "kill", pid).Run(); err == nil { - m.log.Debugf("Process %s terminated gracefully", pid) - time.Sleep(2 * time.Second) - return nil - } - - // Force kill if still running - if err := exec.CommandContext(ctx, "kill", "-9", pid).Run(); err != nil { - return fmt.Errorf("failed to force kill process %s: %w", pid, err) +func (m *Manager) extractPID(line string) string { + switch runtime.GOOS { + case "windows": + // Windows CSV format: "ImageName","PID",... + parts := strings.Split(line, ",") + if len(parts) >= 2 { + return strings.Trim(parts[1], "\"") + } + case "darwin", "linux": + // Unix format: PID TTY TIME CMD + parts := strings.Fields(line) + if len(parts) >= 1 { + return parts[0] + } } - - m.log.Debugf("Process %s force killed", pid) - return nil + return "" } -func (m *Manager) listCursorProcesses() ([]string, error) { - cmd := exec.Command("ps", "aux") - output, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("failed to execute ps command: %w", err) - } +func (m *Manager) killProcess(pid string) error { + var cmd *exec.Cmd - var pids []string - for _, line := range strings.Split(string(output), "\n") { - if strings.Contains(strings.ToLower(line), "apprun") { - fields := strings.Fields(line) - if len(fields) > 1 { - pids = append(pids, fields[1]) - } - } + switch runtime.GOOS { + case "windows": + cmd = exec.Command("taskkill", "/F", "/PID", pid) + case "darwin", "linux": + cmd = exec.Command("kill", "-9", pid) + default: + return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) } - return pids, nil + return cmd.Run() } From 947d11fbc6feee882927687f37fd7bae95cd844f Mon Sep 17 00:00:00 2001 From: Vaggelis kavouras Date: Sat, 28 Dec 2024 23:52:24 +0200 Subject: [PATCH 21/68] refactor: streamline configuration management and enhance UI interactions - Updated go.mod and go.sum to include necessary dependencies. - Refactored README.md for clearer installation instructions and improved formatting. - Enhanced main.go with better error handling and user feedback during execution. - Improved configuration management in config.go, ensuring atomic writes and better error handling. - Updated language support in lang.go for clearer user messages. - Enhanced process management in manager.go to ensure more reliable process termination. - Improved UI display methods for better user experience. - Removed outdated test file generator_test.go to clean up the codebase. - Updated install.ps1 script for better output formatting and error handling. --- README.md | 34 ++-- cmd/cursor-id-modifier/main.go | 308 ++++++++++++++++++--------------- go.mod | 5 +- go.sum | 1 - internal/config/config.go | 45 ++--- internal/lang/lang.go | 129 ++++++++------ internal/process/manager.go | 148 +++++++++++----- internal/ui/display.go | 105 ++++++----- internal/ui/logo.go | 14 +- internal/ui/spinner.go | 38 ++-- pkg/idgen/generator.go | 106 ++++-------- pkg/idgen/generator_test.go | 26 --- scripts/install.ps1 | 48 ++--- 13 files changed, 523 insertions(+), 484 deletions(-) delete mode 100644 pkg/idgen/generator_test.go diff --git a/README.md b/README.md index f009477..2d4eea2 100644 --- a/README.md +++ b/README.md @@ -58,21 +58,15 @@ this is a mistake. ### 🚀 One-Click Solution -
-Linux/macOS: Copy and paste in terminal - +**Linux/macOS**: Copy and paste in terminal ```bash curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash ``` -
- -
-Windows: Copy and paste in PowerShell (Admin) +**Windows**: Copy and paste in PowerShell (Admin) ```powershell irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex ``` -
That's it! The script will: 1. ✨ Install the tool automatically @@ -85,23 +79,23 @@ That's it! The script will:
Windows Packages -- 64-bit: `cursor-id-modifier_vX.X.X_Windows_x64.zip` -- 32-bit: `cursor-id-modifier_vX.X.X_Windows_x86.zip` +- 64-bit: `cursor-id-modifier_windows_x64.exe` +- 32-bit: `cursor-id-modifier_windows_x86.exe`
macOS Packages -- Intel: `cursor-id-modifier_vX.X.X_macOS_x64_intel.tar.gz` -- M1/M2: `cursor-id-modifier_vX.X.X_macOS_arm64_apple_silicon.tar.gz` +- Intel: `cursor-id-modifier_darwin_x64_intel` +- M1/M2: `cursor-id-modifier_darwin_arm64_apple_silicon`
Linux Packages -- 64-bit: `cursor-id-modifier_vX.X.X_Linux_x64.tar.gz` -- 32-bit: `cursor-id-modifier_vX.X.X_Linux_x86.tar.gz` -- ARM64: `cursor-id-modifier_vX.X.X_Linux_arm64.tar.gz` +- 64-bit: `cursor-id-modifier_linux_x64` +- 32-bit: `cursor-id-modifier_linux_x86` +- ARM64: `cursor-id-modifier_linux_arm64`
### 🔧 Technical Details @@ -176,21 +170,15 @@ this is a mistake. ### 🚀 一键解决 -
-Linux/macOS: 在终端中复制粘贴 - +**Linux/macOS**: 在终端中复制粘贴 ```bash curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash ``` -
- -
-Windows: 在PowerShell(管理员)中复制粘贴 +**Windows**: 在PowerShell(管理员)中复制粘贴 ```powershell irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex ``` -
就这样!脚本会: 1. ✨ 自动安装工具 diff --git a/cmd/cursor-id-modifier/main.go b/cmd/cursor-id-modifier/main.go index ea846ea..856a816 100644 --- a/cmd/cursor-id-modifier/main.go +++ b/cmd/cursor-id-modifier/main.go @@ -10,7 +10,6 @@ import ( "runtime" "runtime/debug" "strings" - "time" "github.com/dacrab/go-cursor-help/internal/config" "github.com/dacrab/go-cursor-help/internal/lang" @@ -21,6 +20,7 @@ import ( "github.com/sirupsen/logrus" ) +// Global variables var ( version = "dev" setReadOnly = flag.Bool("r", false, "set storage.json to read-only mode") @@ -29,7 +29,51 @@ var ( ) func main() { - // Initialize error recovery + setupErrorRecovery() + handleFlags() + setupLogger() + + username := getCurrentUser() + log.Debug("Running as user:", username) + + // Initialize components + display := ui.NewDisplay(nil) + configManager := initConfigManager(username) + generator := idgen.NewGenerator() + processManager := process.NewManager(nil, log) + + // Check and handle privileges + if err := handlePrivileges(display); err != nil { + return + } + + // Setup display + setupDisplay(display) + + text := lang.GetText() + + // Handle Cursor processes + if err := handleCursorProcesses(display, processManager); err != nil { + return + } + + // Handle configuration + oldConfig := readExistingConfig(display, configManager, text) + newConfig := generateNewConfig(display, generator, oldConfig, text) + + if err := saveConfiguration(display, configManager, newConfig); err != nil { + return + } + + // Show completion messages + showCompletionMessages(display) + + if os.Getenv("AUTOMATED_MODE") != "1" { + waitExit() + } +} + +func setupErrorRecovery() { defer func() { if r := recover(); r != nil { log.Errorf("Panic recovered: %v\n", r) @@ -37,230 +81,206 @@ func main() { waitExit() } }() +} - // Parse flags +func handleFlags() { flag.Parse() - - // Show version if requested if *showVersion { fmt.Printf("Cursor ID Modifier v%s\n", version) - return + os.Exit(0) } +} - // Initialize logger +func setupLogger() { log.SetFormatter(&logrus.TextFormatter{ - FullTimestamp: true, + FullTimestamp: true, + DisableLevelTruncation: true, + PadLevelText: true, }) + log.SetLevel(logrus.InfoLevel) +} - // Get current user - username := os.Getenv("SUDO_USER") - if username == "" { - user, err := user.Current() - if err != nil { - log.Fatal(err) - } - username = user.Username +func getCurrentUser() string { + if username := os.Getenv("SUDO_USER"); username != "" { + return username } - // Initialize components - display := ui.NewDisplay(nil) - procManager := process.NewManager(process.DefaultConfig(), log) + user, err := user.Current() + if err != nil { + log.Fatal(err) + } + return user.Username +} + +func initConfigManager(username string) *config.Manager { configManager, err := config.NewManager(username) if err != nil { log.Fatal(err) } - generator := idgen.NewGenerator() + return configManager +} - // Check privileges +func handlePrivileges(display *ui.Display) error { isAdmin, err := checkAdminPrivileges() if err != nil { log.Error(err) waitExit() - return + return err } if !isAdmin { if runtime.GOOS == "windows" { - message := "\nRequesting administrator privileges..." - if lang.GetCurrentLanguage() == lang.CN { - message = "\n请求管理员权限..." - } - fmt.Println(message) - if err := selfElevate(); err != nil { - log.Error(err) - display.ShowPrivilegeError( - lang.GetText().PrivilegeError, - lang.GetText().RunAsAdmin, - lang.GetText().RunWithSudo, - lang.GetText().SudoExample, - ) - waitExit() - return - } - return + return handleWindowsPrivileges(display) } display.ShowPrivilegeError( lang.GetText().PrivilegeError, - lang.GetText().RunAsAdmin, lang.GetText().RunWithSudo, lang.GetText().SudoExample, ) waitExit() - return + return fmt.Errorf("insufficient privileges") } + return nil +} - // Ensure Cursor is closed - if err := ensureCursorClosed(display, procManager); err != nil { - message := "\nError: Please close Cursor manually before running this program." - if lang.GetCurrentLanguage() == lang.CN { - message = "\n错误:请在运行此程序之前手动关闭 Cursor。" - } - display.ShowError(message) +func handleWindowsPrivileges(display *ui.Display) error { + message := "\nRequesting administrator privileges..." + if lang.GetCurrentLanguage() == lang.CN { + message = "\n请求管理员权限..." + } + fmt.Println(message) + + if err := selfElevate(); err != nil { + log.Error(err) + display.ShowPrivilegeError( + lang.GetText().PrivilegeError, + lang.GetText().RunAsAdmin, + lang.GetText().RunWithSudo, + lang.GetText().SudoExample, + ) waitExit() - return + return err } + return nil +} - // Kill any remaining Cursor processes - if procManager.IsCursorRunning() { - text := lang.GetText() - display.ShowProcessStatus(text.ClosingProcesses) - - if err := procManager.KillCursorProcesses(); err != nil { - fmt.Println() - message := "Warning: Could not close all Cursor instances. Please close them manually." - if lang.GetCurrentLanguage() == lang.CN { - message = "警告:无法关闭所有 Cursor 实例,请手动关闭。" - } - display.ShowWarning(message) - waitExit() - return - } +func setupDisplay(display *ui.Display) { + if err := display.ClearScreen(); err != nil { + log.Warn("Failed to clear screen:", err) + } + display.ShowLogo() + fmt.Println() +} - if procManager.IsCursorRunning() { - fmt.Println() - message := "\nWarning: Cursor is still running. Please close it manually." - if lang.GetCurrentLanguage() == lang.CN { - message = "\n警告:Cursor 仍在运行,请手动关闭。" - } - display.ShowWarning(message) - waitExit() - return - } +func handleCursorProcesses(display *ui.Display, processManager *process.Manager) error { + if os.Getenv("AUTOMATED_MODE") == "1" { + log.Debug("Running in automated mode, skipping Cursor process closing") + return nil + } + + display.ShowProgress("Closing Cursor...") + log.Debug("Attempting to close Cursor processes") - display.ShowProcessStatus(text.ProcessesClosed) - fmt.Println() + if err := processManager.KillCursorProcesses(); err != nil { + log.Error("Failed to close Cursor:", err) + display.StopProgress() + display.ShowError("Failed to close Cursor. Please close it manually and try again.") + waitExit() + return err } - // Clear screen - if err := display.ClearScreen(); err != nil { - log.Warn("Failed to clear screen:", err) + if processManager.IsCursorRunning() { + log.Error("Cursor processes still detected after closing") + display.StopProgress() + display.ShowError("Failed to close Cursor completely. Please close it manually and try again.") + waitExit() + return fmt.Errorf("cursor still running") } - // Show logo - display.ShowLogo() + log.Debug("Successfully closed all Cursor processes") + display.StopProgress() + fmt.Println() + return nil +} - // Read existing config - text := lang.GetText() +func readExistingConfig(display *ui.Display, configManager *config.Manager, text lang.TextResource) *config.StorageConfig { + fmt.Println() display.ShowProgress(text.ReadingConfig) - oldConfig, err := configManager.ReadConfig() if err != nil { log.Warn("Failed to read existing config:", err) oldConfig = nil } + display.StopProgress() + fmt.Println() + return oldConfig +} - // Generate new IDs +func generateNewConfig(display *ui.Display, generator *idgen.Generator, oldConfig *config.StorageConfig, text lang.TextResource) *config.StorageConfig { display.ShowProgress(text.GeneratingIds) + newConfig := &config.StorageConfig{} - machineID, err := generator.GenerateMachineID() - if err != nil { + if machineID, err := generator.GenerateMachineID(); err != nil { log.Fatal("Failed to generate machine ID:", err) + } else { + newConfig.TelemetryMachineId = machineID } - macMachineID, err := generator.GenerateMacMachineID() - if err != nil { + if macMachineID, err := generator.GenerateMacMachineID(); err != nil { log.Fatal("Failed to generate MAC machine ID:", err) + } else { + newConfig.TelemetryMacMachineId = macMachineID } - deviceID, err := generator.GenerateDeviceID() - if err != nil { + if deviceID, err := generator.GenerateDeviceID(); err != nil { log.Fatal("Failed to generate device ID:", err) - } - - // Create new config - newConfig := &config.StorageConfig{ - TelemetryMachineId: machineID, - TelemetryMacMachineId: macMachineID, - TelemetryDevDeviceId: deviceID, + } else { + newConfig.TelemetryDevDeviceId = deviceID } if oldConfig != nil && oldConfig.TelemetrySqmId != "" { newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId + } else if sqmID, err := generator.GenerateMacMachineID(); err != nil { + log.Fatal("Failed to generate SQM ID:", err) } else { - sqmID, err := generator.GenerateMacMachineID() - if err != nil { - log.Fatal("Failed to generate SQM ID:", err) - } newConfig.TelemetrySqmId = sqmID } - // Save config + display.StopProgress() + fmt.Println() + return newConfig +} + +func saveConfiguration(display *ui.Display, configManager *config.Manager, newConfig *config.StorageConfig) error { + display.ShowProgress("Saving configuration...") if err := configManager.SaveConfig(newConfig, *setReadOnly); err != nil { log.Error(err) waitExit() - return + return err } + display.StopProgress() + fmt.Println() + return nil +} + +func showCompletionMessages(display *ui.Display) { + display.ShowSuccess(lang.GetText().SuccessMessage, lang.GetText().RestartMessage) + fmt.Println() - // Show success - display.ShowSuccess(text.SuccessMessage, text.RestartMessage) - message := "\nOperation completed!" + message := "Operation completed!" if lang.GetCurrentLanguage() == lang.CN { - message = "\n操作完成!" + message = "操作完成!" } display.ShowInfo(message) - - if os.Getenv("AUTOMATED_MODE") != "1" { - waitExit() - } + fmt.Println() } func waitExit() { - if os.Getenv("AUTOMATED_MODE") == "1" { - return - } - - fmt.Println(lang.GetText().PressEnterToExit) + fmt.Print(lang.GetText().PressEnterToExit) os.Stdout.Sync() bufio.NewReader(os.Stdin).ReadString('\n') } -func ensureCursorClosed(display *ui.Display, procManager *process.Manager) error { - maxAttempts := 3 - text := lang.GetText() - - display.ShowProcessStatus(text.CheckingProcesses) - - for attempt := 1; attempt <= maxAttempts; attempt++ { - if !procManager.IsCursorRunning() { - display.ShowProcessStatus(text.ProcessesClosed) - fmt.Println() - return nil - } - - message := fmt.Sprintf("Please close Cursor before continuing. Attempt %d/%d\n%s", - attempt, maxAttempts, text.PleaseWait) - if lang.GetCurrentLanguage() == lang.CN { - message = fmt.Sprintf("请在继续之前关闭 Cursor。尝试 %d/%d\n%s", - attempt, maxAttempts, text.PleaseWait) - } - display.ShowProcessStatus(message) - - time.Sleep(5 * time.Second) - } - - return fmt.Errorf("cursor is still running") -} - func checkAdminPrivileges() (bool, error) { switch runtime.GOOS { case "windows": diff --git a/go.mod b/go.mod index 8bf96e2..d3192c7 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,11 @@ go 1.21 require ( github.com/fatih/color v1.15.0 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.10.0 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect golang.org/x/sys v0.13.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 75c1611..d930a66 100644 --- a/go.sum +++ b/go.sum @@ -20,7 +20,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/config/config.go b/internal/config/config.go index e3f3f53..b3d2c81 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -32,10 +32,7 @@ func NewManager(username string) (*Manager, error) { if err != nil { return nil, fmt.Errorf("failed to get config path: %w", err) } - - return &Manager{ - configPath: configPath, - }, nil + return &Manager{configPath: configPath}, nil } // ReadConfig reads the existing configuration @@ -69,22 +66,23 @@ func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error { return fmt.Errorf("failed to create config directory: %w", err) } - // Set file permissions - if err := os.Chmod(m.configPath, 0666); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to set file permissions: %w", err) + // Prepare updated configuration + updatedConfig := m.prepareUpdatedConfig(config) + + // Write configuration + if err := m.writeConfigFile(updatedConfig, readOnly); err != nil { + return err } - // Read existing config to preserve other fields - var originalFile map[string]interface{} - originalFileContent, err := os.ReadFile(m.configPath) - if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to read original file: %w", err) - } else if err == nil { - if err := json.Unmarshal(originalFileContent, &originalFile); err != nil { - return fmt.Errorf("failed to parse original file: %w", err) - } - } else { - originalFile = make(map[string]interface{}) + return nil +} + +// prepareUpdatedConfig merges existing config with updates +func (m *Manager) prepareUpdatedConfig(config *StorageConfig) map[string]interface{} { + // Read existing config + originalFile := make(map[string]interface{}) + if data, err := os.ReadFile(m.configPath); err == nil { + json.Unmarshal(data, &originalFile) } // Update fields @@ -95,15 +93,20 @@ func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error { originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339) originalFile["version"] = "1.0.1" + return originalFile +} + +// writeConfigFile handles the atomic write of the config file +func (m *Manager) writeConfigFile(config map[string]interface{}, readOnly bool) error { // Marshal with indentation - newFileContent, err := json.MarshalIndent(originalFile, "", " ") + content, err := json.MarshalIndent(config, "", " ") if err != nil { return fmt.Errorf("failed to marshal config: %w", err) } // Write to temporary file tmpPath := m.configPath + ".tmp" - if err := os.WriteFile(tmpPath, newFileContent, 0666); err != nil { + if err := os.WriteFile(tmpPath, content, 0666); err != nil { return fmt.Errorf("failed to write temporary file: %w", err) } @@ -126,8 +129,8 @@ func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error { // Sync directory if dir, err := os.Open(filepath.Dir(m.configPath)); err == nil { + defer dir.Close() dir.Sync() - dir.Close() } return nil diff --git a/internal/lang/lang.go b/internal/lang/lang.go index f5d8a7a..5b6ddde 100644 --- a/internal/lang/lang.go +++ b/internal/lang/lang.go @@ -7,34 +7,43 @@ import ( "sync" ) -// Language represents a supported language +// Language represents a supported language code type Language string const ( // CN represents Chinese language CN Language = "cn" - // EN represents English language + // EN represents English language EN Language = "en" ) // TextResource contains all translatable text resources type TextResource struct { - SuccessMessage string - RestartMessage string - ReadingConfig string - GeneratingIds string - PressEnterToExit string - ErrorPrefix string - PrivilegeError string + // Success messages + SuccessMessage string + RestartMessage string + + // Progress messages + ReadingConfig string + GeneratingIds string + CheckingProcesses string + ClosingProcesses string + ProcessesClosed string + PleaseWait string + + // Error messages + ErrorPrefix string + PrivilegeError string + + // Instructions RunAsAdmin string RunWithSudo string SudoExample string - ConfigLocation string - CheckingProcesses string - ClosingProcesses string - ProcessesClosed string - PleaseWait string + PressEnterToExit string SetReadOnlyMessage string + + // Info messages + ConfigLocation string } var ( @@ -68,28 +77,32 @@ func GetText() TextResource { // detectLanguage detects the system language func detectLanguage() Language { - // Check environment variables - for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} { - if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") { - return CN - } + // Check environment variables first + if isChineseEnvVar() { + return CN } - // Check Windows language settings + // Then check OS-specific locale if isWindows() { if isWindowsChineseLocale() { return CN } - } else { - // Check Unix locale - if isUnixChineseLocale() { - return CN - } + } else if isUnixChineseLocale() { + return CN } return EN } +func isChineseEnvVar() bool { + for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} { + if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") { + return true + } + } + return false +} + func isWindows() bool { return os.Getenv("OS") == "Windows_NT" } @@ -118,39 +131,57 @@ func isUnixChineseLocale() bool { // texts contains all translations var texts = map[Language]TextResource{ CN: { - SuccessMessage: "[√] 配置文件已成功更新!", - RestartMessage: "[!] 请手动重启 Cursor 以使更新生效", - ReadingConfig: "正在读取配置文件...", - GeneratingIds: "正在生成新的标识符...", - PressEnterToExit: "按回车键退出程序...", - ErrorPrefix: "程序发生严重错误: %v", - PrivilegeError: "\n[!] 错误:需要管理员权限", + // Success messages + SuccessMessage: "[√] 配置文件已成功更新!", + RestartMessage: "[!] 请手动重启 Cursor 以使更新生效", + + // Progress messages + ReadingConfig: "正在读取配置文件...", + GeneratingIds: "正在生成新的标识符...", + CheckingProcesses: "正在检查运行中的 Cursor 实例...", + ClosingProcesses: "正在关闭 Cursor 实例...", + ProcessesClosed: "所有 Cursor 实例已关闭", + PleaseWait: "请稍候...", + + // Error messages + ErrorPrefix: "程序发生严重错误: %v", + PrivilegeError: "\n[!] 错误:需要管理员权限", + + // Instructions RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」", RunWithSudo: "请使用 sudo 命令运行此程序", SudoExample: "示例: sudo %s", - ConfigLocation: "配置文件位置:", - CheckingProcesses: "正在检查运行中的 Cursor 实例...", - ClosingProcesses: "正在关闭 Cursor 实例...", - ProcessesClosed: "所有 Cursor 实例已关闭", - PleaseWait: "请稍候...", + PressEnterToExit: "按回车键退出程序...", SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题", + + // Info messages + ConfigLocation: "配置文件位置:", }, EN: { - SuccessMessage: "[√] Configuration file updated successfully!", - RestartMessage: "[!] Please restart Cursor manually for changes to take effect", - ReadingConfig: "Reading configuration file...", - GeneratingIds: "Generating new identifiers...", - PressEnterToExit: "Press Enter to exit...", - ErrorPrefix: "Program encountered a serious error: %v", - PrivilegeError: "\n[!] Error: Administrator privileges required", + // Success messages + SuccessMessage: "[√] Configuration file updated successfully!", + RestartMessage: "[!] Please restart Cursor manually for changes to take effect", + + // Progress messages + ReadingConfig: "Reading configuration file...", + GeneratingIds: "Generating new identifiers...", + CheckingProcesses: "Checking for running Cursor instances...", + ClosingProcesses: "Closing Cursor instances...", + ProcessesClosed: "All Cursor instances have been closed", + PleaseWait: "Please wait...", + + // Error messages + ErrorPrefix: "Program encountered a serious error: %v", + PrivilegeError: "\n[!] Error: Administrator privileges required", + + // Instructions RunAsAdmin: "Please right-click and select 'Run as Administrator'", RunWithSudo: "Please run this program with sudo", SudoExample: "Example: sudo %s", - ConfigLocation: "Config file location:", - CheckingProcesses: "Checking for running Cursor instances...", - ClosingProcesses: "Closing Cursor instances...", - ProcessesClosed: "All Cursor instances have been closed", - PleaseWait: "Please wait...", + PressEnterToExit: "Press Enter to exit...", SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records", + + // Info messages + ConfigLocation: "Config file location:", }, } diff --git a/internal/process/manager.go b/internal/process/manager.go index 00d568a..f48ac20 100644 --- a/internal/process/manager.go +++ b/internal/process/manager.go @@ -12,22 +12,24 @@ import ( // Config holds process manager configuration type Config struct { - MaxAttempts int - RetryDelay time.Duration - ProcessPatterns []string + MaxAttempts int // Maximum number of attempts to kill processes + RetryDelay time.Duration // Delay between retry attempts + ProcessPatterns []string // Process names to look for } // DefaultConfig returns the default configuration func DefaultConfig() *Config { return &Config{ MaxAttempts: 3, - RetryDelay: time.Second, + RetryDelay: 2 * time.Second, ProcessPatterns: []string{ - "Cursor.exe", // Windows - "Cursor", // Linux/macOS binary - "cursor", // Linux/macOS process - "cursor-helper", // Helper process - "cursor-id-modifier", // Our tool + "Cursor.exe", // Windows executable + "Cursor ", // Linux/macOS executable with space + "cursor ", // Linux/macOS executable lowercase with space + "cursor", // Linux/macOS executable lowercase + "Cursor", // Linux/macOS executable + "*cursor*", // Any process containing cursor + "*Cursor*", // Any process containing Cursor }, } } @@ -38,7 +40,7 @@ type Manager struct { log *logrus.Logger } -// NewManager creates a new process manager +// NewManager creates a new process manager with optional config and logger func NewManager(config *Config, log *logrus.Logger) *Manager { if config == nil { config = DefaultConfig() @@ -52,7 +54,7 @@ func NewManager(config *Config, log *logrus.Logger) *Manager { } } -// IsCursorRunning checks if any Cursor process is running +// IsCursorRunning checks if any Cursor process is currently running func (m *Manager) IsCursorRunning() bool { processes, err := m.getCursorProcesses() if err != nil { @@ -62,7 +64,7 @@ func (m *Manager) IsCursorRunning() bool { return len(processes) > 0 } -// KillCursorProcesses attempts to kill all Cursor processes +// KillCursorProcesses attempts to kill all running Cursor processes func (m *Manager) KillCursorProcesses() error { for attempt := 1; attempt <= m.config.MaxAttempts; attempt++ { processes, err := m.getCursorProcesses() @@ -74,68 +76,116 @@ func (m *Manager) KillCursorProcesses() error { return nil } - for _, proc := range processes { - if err := m.killProcess(proc); err != nil { - m.log.Warnf("Failed to kill process %s: %v", proc, err) + // Try graceful shutdown first on Windows + if runtime.GOOS == "windows" { + for _, pid := range processes { + exec.Command("taskkill", "/PID", pid).Run() + time.Sleep(500 * time.Millisecond) } } + // Force kill remaining processes + remainingProcesses, _ := m.getCursorProcesses() + for _, pid := range remainingProcesses { + m.killProcess(pid) + } + time.Sleep(m.config.RetryDelay) - } - if m.IsCursorRunning() { - return fmt.Errorf("failed to kill all Cursor processes after %d attempts", m.config.MaxAttempts) + if processes, _ := m.getCursorProcesses(); len(processes) == 0 { + return nil + } } return nil } +// getCursorProcesses returns PIDs of running Cursor processes func (m *Manager) getCursorProcesses() ([]string, error) { - var cmd *exec.Cmd - var processes []string + cmd := m.getProcessListCommand() + if cmd == nil { + return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) + } + + output, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("failed to execute command: %w", err) + } + + return m.parseProcessList(string(output)), nil +} +// getProcessListCommand returns the appropriate command to list processes based on OS +func (m *Manager) getProcessListCommand() *exec.Cmd { switch runtime.GOOS { case "windows": - cmd = exec.Command("tasklist", "/FO", "CSV", "/NH") + return exec.Command("tasklist", "/FO", "CSV", "/NH") case "darwin": - cmd = exec.Command("ps", "-ax") + return exec.Command("ps", "-ax") case "linux": - cmd = exec.Command("ps", "-A") + return exec.Command("ps", "-A") default: - return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) + return nil } +} - output, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("failed to execute command: %w", err) +// parseProcessList extracts Cursor process PIDs from process list output +func (m *Manager) parseProcessList(output string) []string { + var processes []string + for _, line := range strings.Split(output, "\n") { + lowerLine := strings.ToLower(line) + + if m.isOwnProcess(lowerLine) { + continue + } + + if pid := m.findCursorProcess(line, lowerLine); pid != "" { + processes = append(processes, pid) + } } + return processes +} - lines := strings.Split(string(output), "\n") - for _, line := range lines { - for _, pattern := range m.config.ProcessPatterns { - if strings.Contains(strings.ToLower(line), strings.ToLower(pattern)) { - // Extract PID based on OS - pid := m.extractPID(line) - if pid != "" { - processes = append(processes, pid) - } - } +// isOwnProcess checks if the process belongs to this application +func (m *Manager) isOwnProcess(line string) bool { + return strings.Contains(line, "cursor-id-modifier") || + strings.Contains(line, "cursor-helper") +} + +// findCursorProcess checks if a process line matches Cursor patterns and returns its PID +func (m *Manager) findCursorProcess(line, lowerLine string) string { + for _, pattern := range m.config.ProcessPatterns { + if m.matchPattern(lowerLine, strings.ToLower(pattern)) { + return m.extractPID(line) } } + return "" +} - return processes, nil +// matchPattern checks if a line matches a pattern, supporting wildcards +func (m *Manager) matchPattern(line, pattern string) bool { + switch { + case strings.HasPrefix(pattern, "*") && strings.HasSuffix(pattern, "*"): + search := pattern[1 : len(pattern)-1] + return strings.Contains(line, search) + case strings.HasPrefix(pattern, "*"): + return strings.HasSuffix(line, pattern[1:]) + case strings.HasSuffix(pattern, "*"): + return strings.HasPrefix(line, pattern[:len(pattern)-1]) + default: + return line == pattern + } } +// extractPID extracts process ID from a process list line based on OS format func (m *Manager) extractPID(line string) string { switch runtime.GOOS { case "windows": - // Windows CSV format: "ImageName","PID",... parts := strings.Split(line, ",") if len(parts) >= 2 { return strings.Trim(parts[1], "\"") } case "darwin", "linux": - // Unix format: PID TTY TIME CMD parts := strings.Fields(line) if len(parts) >= 1 { return parts[0] @@ -144,17 +194,23 @@ func (m *Manager) extractPID(line string) string { return "" } +// killProcess forcefully terminates a process by PID func (m *Manager) killProcess(pid string) error { - var cmd *exec.Cmd + cmd := m.getKillCommand(pid) + if cmd == nil { + return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) + } + return cmd.Run() +} +// getKillCommand returns the appropriate command to kill a process based on OS +func (m *Manager) getKillCommand(pid string) *exec.Cmd { switch runtime.GOOS { case "windows": - cmd = exec.Command("taskkill", "/F", "/PID", pid) + return exec.Command("taskkill", "/F", "/PID", pid) case "darwin", "linux": - cmd = exec.Command("kill", "-9", pid) + return exec.Command("kill", "-9", pid) default: - return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) + return nil } - - return cmd.Run() } diff --git a/internal/ui/display.go b/internal/ui/display.go index 0f94f69..32ed566 100644 --- a/internal/ui/display.go +++ b/internal/ui/display.go @@ -10,92 +10,85 @@ import ( "github.com/fatih/color" ) -// Display handles UI display operations +// Display handles UI operations for terminal output type Display struct { spinner *Spinner } -// NewDisplay creates a new display handler +// NewDisplay creates a new display instance with an optional spinner func NewDisplay(spinner *Spinner) *Display { if spinner == nil { spinner = NewSpinner(nil) } - return &Display{ - spinner: spinner, - } + return &Display{spinner: spinner} } -// ShowProgress shows a progress message with spinner -func (d *Display) ShowProgress(message string) { - d.spinner.SetMessage(message) - d.spinner.Start() -} +// Terminal Operations -// StopProgress stops the progress spinner -func (d *Display) StopProgress() { - d.spinner.Stop() -} - -// ClearScreen clears the terminal screen +// ClearScreen clears the terminal screen based on OS func (d *Display) ClearScreen() error { var cmd *exec.Cmd - if runtime.GOOS == "windows" { + switch runtime.GOOS { + case "windows": cmd = exec.Command("cmd", "/c", "cls") - } else { + default: cmd = exec.Command("clear") } cmd.Stdout = os.Stdout return cmd.Run() } -// ShowProcessStatus shows the current process status -func (d *Display) ShowProcessStatus(message string) { - fmt.Printf("\r%s", strings.Repeat(" ", 80)) // Clear line - fmt.Printf("\r%s", color.CyanString("⚡ "+message)) -} - -// ShowPrivilegeError shows the privilege error message -func (d *Display) ShowPrivilegeError(errorMsg, adminMsg, sudoMsg, sudoExample string) { - red := color.New(color.FgRed, color.Bold) - yellow := color.New(color.FgYellow) +// Progress Indicator - red.Println(errorMsg) - if runtime.GOOS == "windows" { - yellow.Println(adminMsg) - } else { - yellow.Printf("%s\n%s\n", sudoMsg, fmt.Sprintf(sudoExample, os.Args[0])) - } +// ShowProgress displays a progress message with a spinner +func (d *Display) ShowProgress(message string) { + d.spinner.SetMessage(message) + d.spinner.Start() } -// ShowSuccess shows a success message -func (d *Display) ShowSuccess(successMsg, restartMsg string) { - green := color.New(color.FgGreen, color.Bold) - yellow := color.New(color.FgYellow, color.Bold) - - green.Printf("\n%s\n", successMsg) - yellow.Printf("%s\n", restartMsg) +// StopProgress stops the progress spinner +func (d *Display) StopProgress() { + d.spinner.Stop() } -// ShowError shows an error message -func (d *Display) ShowError(message string) { - red := color.New(color.FgRed, color.Bold) - red.Printf("\n%s\n", message) -} +// Message Display -// ShowWarning shows a warning message -func (d *Display) ShowWarning(message string) { - yellow := color.New(color.FgYellow, color.Bold) - yellow.Printf("\n%s\n", message) +// ShowSuccess displays success messages in green +func (d *Display) ShowSuccess(messages ...string) { + green := color.New(color.FgGreen) + for _, msg := range messages { + green.Println(msg) + } } -// ShowInfo shows an info message +// ShowInfo displays an info message in cyan func (d *Display) ShowInfo(message string) { cyan := color.New(color.FgCyan) - cyan.Printf("\n%s\n", message) + cyan.Println(message) +} + +// ShowError displays an error message in red +func (d *Display) ShowError(message string) { + red := color.New(color.FgRed) + red.Println(message) } -// ShowPrompt shows a prompt message and waits for user input -func (d *Display) ShowPrompt(message string) { - fmt.Print(message) - os.Stdout.Sync() +// ShowPrivilegeError displays privilege error messages with instructions +func (d *Display) ShowPrivilegeError(messages ...string) { + red := color.New(color.FgRed, color.Bold) + yellow := color.New(color.FgYellow) + + // Main error message + red.Println(messages[0]) + fmt.Println() + + // Additional instructions + for _, msg := range messages[1:] { + if strings.Contains(msg, "%s") { + exe, _ := os.Executable() + yellow.Printf(msg+"\n", exe) + } else { + yellow.Println(msg) + } + } } diff --git a/internal/ui/logo.go b/internal/ui/logo.go index 439af52..564525a 100644 --- a/internal/ui/logo.go +++ b/internal/ui/logo.go @@ -5,15 +5,15 @@ import ( ) const cyberpunkLogo = ` - ______ ______ ______ - / ____/_ __________ ___ _____/ __/ // / / / - / / / / / / ___/ _ \/ __ \/ ___/ /_/ // /_/ / -/ /___/ /_/ / / / __/ /_/ (__ ) __/__ __/ / -\____/\__,_/_/ \___/\____/____/_/ /_/ /_/ - + ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ + ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ + ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ + ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ + ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ + ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ` -// ShowLogo displays the cyberpunk-style logo +// ShowLogo displays the application logo func (d *Display) ShowLogo() { cyan := color.New(color.FgCyan, color.Bold) cyan.Println(cyberpunkLogo) diff --git a/internal/ui/spinner.go b/internal/ui/spinner.go index 4f85fcb..145f730 100644 --- a/internal/ui/spinner.go +++ b/internal/ui/spinner.go @@ -10,8 +10,8 @@ import ( // SpinnerConfig defines spinner configuration type SpinnerConfig struct { - Frames []string - Delay time.Duration + Frames []string // Animation frames for the spinner + Delay time.Duration // Delay between frame updates } // DefaultSpinnerConfig returns the default spinner configuration @@ -43,6 +43,8 @@ func NewSpinner(config *SpinnerConfig) *Spinner { } } +// State management + // SetMessage sets the spinner message func (s *Spinner) SetMessage(message string) { s.mu.Lock() @@ -50,7 +52,16 @@ func (s *Spinner) SetMessage(message string) { s.message = message } -// Start starts the spinner animation +// IsActive returns whether the spinner is currently active +func (s *Spinner) IsActive() bool { + s.mu.RLock() + defer s.mu.RUnlock() + return s.active +} + +// Control methods + +// Start begins the spinner animation func (s *Spinner) Start() { s.mu.Lock() if s.active { @@ -63,7 +74,7 @@ func (s *Spinner) Start() { go s.run() } -// Stop stops the spinner animation +// Stop halts the spinner animation func (s *Spinner) Stop() { s.mu.Lock() defer s.mu.Unlock() @@ -75,20 +86,21 @@ func (s *Spinner) Stop() { s.active = false close(s.stopCh) s.stopCh = make(chan struct{}) - fmt.Println() + fmt.Print("\r") // Clear the spinner line } -// IsActive returns whether the spinner is currently active -func (s *Spinner) IsActive() bool { - s.mu.RLock() - defer s.mu.RUnlock() - return s.active -} +// Internal methods func (s *Spinner) run() { ticker := time.NewTicker(s.config.Delay) defer ticker.Stop() + cyan := color.New(color.FgCyan, color.Bold) + message := s.message + + // Print initial state + fmt.Printf("\r %s %s", cyan.Sprint(s.config.Frames[0]), message) + for { select { case <-s.stopCh: @@ -100,11 +112,11 @@ func (s *Spinner) run() { return } frame := s.config.Frames[s.current%len(s.config.Frames)] - message := s.message s.current++ s.mu.RUnlock() - fmt.Printf("\r%s %s", color.CyanString(frame), message) + fmt.Printf("\r %s", cyan.Sprint(frame)) + fmt.Printf("\033[%dG%s", 4, message) // Move cursor and print message } } } diff --git a/pkg/idgen/generator.go b/pkg/idgen/generator.go index 5a0058f..4a69341 100644 --- a/pkg/idgen/generator.go +++ b/pkg/idgen/generator.go @@ -1,95 +1,59 @@ package idgen import ( - cryptorand "crypto/rand" - "crypto/sha256" + "crypto/rand" "encoding/hex" "fmt" - "math/big" - "sync" + "time" ) -// Generator handles the generation of various IDs -type Generator struct { - charsetMu sync.RWMutex - charset string -} +// Generator handles secure ID generation for machines and devices +type Generator struct{} -// NewGenerator creates a new ID generator with default settings +// NewGenerator creates a new ID generator func NewGenerator() *Generator { - return &Generator{ - charset: "0123456789ABCDEFGHJKLMNPQRSTVWXYZ", - } + return &Generator{} } -// SetCharset allows customizing the character set used for ID generation -func (g *Generator) SetCharset(charset string) { - g.charsetMu.Lock() - defer g.charsetMu.Unlock() - g.charset = charset -} +// Helper methods +// ------------- -// GenerateMachineID generates a new machine ID with the format auth0|user_XX[unique_id] -func (g *Generator) GenerateMachineID() (string, error) { - prefix := "auth0|user_" +// simulateWork adds a small delay to make progress visible +func (g *Generator) simulateWork() { + time.Sleep(800 * time.Millisecond) +} - // Generate random sequence number between 0-99 - seqNum, err := cryptorand.Int(cryptorand.Reader, big.NewInt(100)) - if err != nil { - return "", fmt.Errorf("failed to generate sequence number: %w", err) +// generateRandomHex generates a random hex string of specified length +func (g *Generator) generateRandomHex(length int) (string, error) { + bytes := make([]byte, length) + if _, err := rand.Read(bytes); err != nil { + return "", fmt.Errorf("failed to generate random bytes: %w", err) } - sequence := fmt.Sprintf("%02d", seqNum.Int64()) + return hex.EncodeToString(bytes), nil +} - uniqueID, err := g.generateUniqueID(23) - if err != nil { - return "", fmt.Errorf("failed to generate unique ID: %w", err) - } +// Public methods +// ------------- - fullID := prefix + sequence + uniqueID - return hex.EncodeToString([]byte(fullID)), nil +// GenerateMachineID generates a new 32-byte machine ID +func (g *Generator) GenerateMachineID() (string, error) { + g.simulateWork() + return g.generateRandomHex(32) } -// GenerateMacMachineID generates a new MAC machine ID using SHA-256 +// GenerateMacMachineID generates a new 64-byte MAC machine ID func (g *Generator) GenerateMacMachineID() (string, error) { - data := make([]byte, 32) - if _, err := cryptorand.Read(data); err != nil { - return "", fmt.Errorf("failed to generate random data: %w", err) - } - - hash := sha256.Sum256(data) - return hex.EncodeToString(hash[:]), nil + g.simulateWork() + return g.generateRandomHex(64) } -// GenerateDeviceID generates a new device ID in UUID v4 format +// GenerateDeviceID generates a new device ID in UUID format func (g *Generator) GenerateDeviceID() (string, error) { - uuid := make([]byte, 16) - if _, err := cryptorand.Read(uuid); err != nil { - return "", fmt.Errorf("failed to generate UUID: %w", err) - } - - // Set version (4) and variant (2) bits according to RFC 4122 - uuid[6] = (uuid[6] & 0x0f) | 0x40 - uuid[8] = (uuid[8] & 0x3f) | 0x80 - - return fmt.Sprintf("%x-%x-%x-%x-%x", - uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16]), nil -} - -// generateUniqueID generates a random string of specified length using the configured charset -func (g *Generator) generateUniqueID(length int) (string, error) { - g.charsetMu.RLock() - defer g.charsetMu.RUnlock() - - result := make([]byte, length) - max := big.NewInt(int64(len(g.charset))) - - for i := range result { - randNum, err := cryptorand.Int(cryptorand.Reader, max) - if err != nil { - return "", fmt.Errorf("failed to generate random number: %w", err) - } - result[i] = g.charset[randNum.Int64()] + g.simulateWork() + id, err := g.generateRandomHex(16) + if err != nil { + return "", err } - - return string(result), nil + return fmt.Sprintf("%s-%s-%s-%s-%s", + id[0:8], id[8:12], id[12:16], id[16:20], id[20:32]), nil } diff --git a/pkg/idgen/generator_test.go b/pkg/idgen/generator_test.go deleted file mode 100644 index 81dcea9..0000000 --- a/pkg/idgen/generator_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package idgen - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNewGenerator(t *testing.T) { - gen := NewGenerator() - assert.NotNil(t, gen, "Generator should not be nil") -} - -func TestGenerateMachineID(t *testing.T) { - gen := NewGenerator() - id, err := gen.GenerateMachineID() - assert.NoError(t, err, "Should not return an error") - assert.NotEmpty(t, id, "Generated machine ID should not be empty") -} - -func TestGenerateDeviceID(t *testing.T) { - gen := NewGenerator() - id, err := gen.GenerateDeviceID() - assert.NoError(t, err, "Should not return an error") - assert.NotEmpty(t, id, "Generated device ID should not be empty") -} diff --git a/scripts/install.ps1 b/scripts/install.ps1 index fb14a5b..18e6cb9 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -9,13 +9,6 @@ if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdenti # Set TLS to 1.2 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -# Colors for output -$Red = "`e[31m" -$Green = "`e[32m" -$Blue = "`e[36m" -$Yellow = "`e[33m" -$Reset = "`e[0m" - # Create temporary directory $TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString()) New-Item -ItemType Directory -Path $TmpDir | Out-Null @@ -29,7 +22,7 @@ function Cleanup { # Error handler trap { - Write-Host "${Red}Error: $_${Reset}" + Write-Host "Error: $_" -ForegroundColor Red Cleanup exit 1 } @@ -44,7 +37,7 @@ function Get-SystemArch { } # Download with progress -function Download-WithProgress { +function Get-FileWithProgress { param ( [string]$Url, [string]$OutputFile @@ -58,18 +51,18 @@ function Download-WithProgress { return $true } catch { - Write-Host "${Red}Failed to download: $_${Reset}" + Write-Host "Failed to download: $_" -ForegroundColor Red return $false } } # Main installation function function Install-CursorModifier { - Write-Host "${Blue}Starting installation...${Reset}" + Write-Host "Starting installation..." -ForegroundColor Cyan # Detect architecture $arch = Get-SystemArch - Write-Host "${Green}Detected architecture: $arch${Reset}" + Write-Host "Detected architecture: $arch" -ForegroundColor Green # Set installation directory $InstallDir = "$env:ProgramFiles\CursorModifier" @@ -80,28 +73,36 @@ function Install-CursorModifier { # Get latest release try { $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/dacrab/go-cursor-help/releases/latest" + Write-Host "Found latest release: $($latestRelease.tag_name)" -ForegroundColor Cyan + + # Updated binary name format to match actual assets $binaryName = "cursor-id-modifier_windows_$arch.exe" - $downloadUrl = $latestRelease.assets | Where-Object { $_.name -eq $binaryName } | Select-Object -ExpandProperty browser_download_url + Write-Host "Looking for asset: $binaryName" -ForegroundColor Cyan + + $asset = $latestRelease.assets | Where-Object { $_.name -eq $binaryName } + $downloadUrl = $asset.browser_download_url if (!$downloadUrl) { + Write-Host "Available assets:" -ForegroundColor Yellow + $latestRelease.assets | ForEach-Object { Write-Host $_.name } throw "Could not find download URL for $binaryName" } } catch { - Write-Host "${Red}Failed to get latest release: $_${Reset}" + Write-Host "Failed to get latest release: $_" -ForegroundColor Red exit 1 } # Download binary - Write-Host "${Blue}Downloading latest release...${Reset}" + Write-Host "Downloading latest release from $downloadUrl..." -ForegroundColor Cyan $binaryPath = Join-Path $TmpDir "cursor-id-modifier.exe" - if (!(Download-WithProgress -Url $downloadUrl -OutputFile $binaryPath)) { + if (!(Get-FileWithProgress -Url $downloadUrl -OutputFile $binaryPath)) { exit 1 } # Install binary - Write-Host "${Blue}Installing...${Reset}" + Write-Host "Installing..." -ForegroundColor Cyan try { Copy-Item -Path $binaryPath -Destination "$InstallDir\cursor-id-modifier.exe" -Force @@ -112,24 +113,23 @@ function Install-CursorModifier { } } catch { - Write-Host "${Red}Failed to install: $_${Reset}" + Write-Host "Failed to install: $_" -ForegroundColor Red exit 1 } - Write-Host "${Green}Installation completed successfully!${Reset}" - Write-Host "${Blue}Running cursor-id-modifier...${Reset}" + Write-Host "Installation completed successfully!" -ForegroundColor Green + Write-Host "Running cursor-id-modifier..." -ForegroundColor Cyan # Run the program try { - $env:AUTOMATED_MODE = "1" & "$InstallDir\cursor-id-modifier.exe" if ($LASTEXITCODE -ne 0) { - Write-Host "${Red}Failed to run cursor-id-modifier${Reset}" + Write-Host "Failed to run cursor-id-modifier" -ForegroundColor Red exit 1 } } catch { - Write-Host "${Red}Failed to run cursor-id-modifier: $_${Reset}" + Write-Host "Failed to run cursor-id-modifier: $_" -ForegroundColor Red exit 1 } } @@ -140,4 +140,6 @@ try { } finally { Cleanup + Write-Host "Press any key to continue..." + $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') } \ No newline at end of file From 96af6471e40f18ae5fe328f40b14337f5422ca7c Mon Sep 17 00:00:00 2001 From: Vaggelis kavouras Date: Sun, 29 Dec 2024 00:04:28 +0200 Subject: [PATCH 22/68] fix: improve user feedback and error handling in installation scripts and language support - Removed unnecessary print statement in main.go to streamline output. - Updated language strings in lang.go to include newline characters for better formatting. - Enhanced PowerShell installation script (install.ps1) with improved error handling and user prompts for admin rights. - Modified shell installation script (install.sh) to provide clearer error messages and find matching assets for different architectures. --- cmd/cursor-id-modifier/main.go | 1 - internal/lang/lang.go | 6 ++-- scripts/install.ps1 | 63 +++++++++++++++++++++++++--------- scripts/install.sh | 53 +++++++++++++++++++++++----- 4 files changed, 95 insertions(+), 28 deletions(-) diff --git a/cmd/cursor-id-modifier/main.go b/cmd/cursor-id-modifier/main.go index 856a816..860b444 100644 --- a/cmd/cursor-id-modifier/main.go +++ b/cmd/cursor-id-modifier/main.go @@ -272,7 +272,6 @@ func showCompletionMessages(display *ui.Display) { message = "操作完成!" } display.ShowInfo(message) - fmt.Println() } func waitExit() { diff --git a/internal/lang/lang.go b/internal/lang/lang.go index 5b6ddde..973fc24 100644 --- a/internal/lang/lang.go +++ b/internal/lang/lang.go @@ -13,7 +13,7 @@ type Language string const ( // CN represents Chinese language CN Language = "cn" - // EN represents English language + // EN represents English language EN Language = "en" ) @@ -151,7 +151,7 @@ var texts = map[Language]TextResource{ RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」", RunWithSudo: "请使用 sudo 命令运行此程序", SudoExample: "示例: sudo %s", - PressEnterToExit: "按回车键退出程序...", + PressEnterToExit: "\n按回车键退出程序...", SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题", // Info messages @@ -178,7 +178,7 @@ var texts = map[Language]TextResource{ RunAsAdmin: "Please right-click and select 'Run as Administrator'", RunWithSudo: "Please run this program with sudo", SudoExample: "Example: sudo %s", - PressEnterToExit: "Press Enter to exit...", + PressEnterToExit: "\nPress Enter to exit...", SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records", // Info messages diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 18e6cb9..b411308 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -1,9 +1,18 @@ # Auto-elevate to admin rights if not already running as admin -if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { - Write-Host "Requesting administrator privileges..." - $arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -ExecutionFromElevated" - Start-Process powershell.exe -ArgumentList $arguments -Verb RunAs - Exit +$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") +if (-NOT $isAdmin) { + try { + Write-Host "Requesting administrator privileges..." -ForegroundColor Cyan + $argList = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" + Start-Process powershell.exe -Verb RunAs -ArgumentList $argList -Wait + exit + } + catch { + Write-Host "Failed to get admin rights. Please run as Administrator." -ForegroundColor Red + Write-Host "Press any key to exit..." + $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') + exit 1 + } } # Set TLS to 1.2 @@ -24,6 +33,8 @@ function Cleanup { trap { Write-Host "Error: $_" -ForegroundColor Red Cleanup + Write-Host "Press any key to exit..." + $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') exit 1 } @@ -75,18 +86,31 @@ function Install-CursorModifier { $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/dacrab/go-cursor-help/releases/latest" Write-Host "Found latest release: $($latestRelease.tag_name)" -ForegroundColor Cyan - # Updated binary name format to match actual assets - $binaryName = "cursor-id-modifier_windows_$arch.exe" - Write-Host "Looking for asset: $binaryName" -ForegroundColor Cyan + # Look for Windows binary with our architecture + $possibleNames = @( + "cursor-id-modifier_windows_$($arch).exe", + "cursor-id-modifier_windows_$($arch).exe.exe", + "cursor-id-modifier_Windows_$($arch).exe", + "cursor-id-modifier_Windows_$($arch).exe.exe" + ) - $asset = $latestRelease.assets | Where-Object { $_.name -eq $binaryName } - $downloadUrl = $asset.browser_download_url + $asset = $null + foreach ($name in $possibleNames) { + Write-Host "Checking for asset: $name" -ForegroundColor Cyan + $asset = $latestRelease.assets | Where-Object { $_.name -eq $name } + if ($asset) { + Write-Host "Found matching asset: $($asset.name)" -ForegroundColor Green + break + } + } - if (!$downloadUrl) { - Write-Host "Available assets:" -ForegroundColor Yellow - $latestRelease.assets | ForEach-Object { Write-Host $_.name } - throw "Could not find download URL for $binaryName" + if (!$asset) { + Write-Host "`nAvailable assets:" -ForegroundColor Yellow + $latestRelease.assets | ForEach-Object { Write-Host "- $($_.name)" } + throw "Could not find appropriate Windows binary for $arch architecture" } + + $downloadUrl = $asset.browser_download_url } catch { Write-Host "Failed to get latest release: $_" -ForegroundColor Red @@ -94,7 +118,7 @@ function Install-CursorModifier { } # Download binary - Write-Host "Downloading latest release from $downloadUrl..." -ForegroundColor Cyan + Write-Host "`nDownloading latest release..." -ForegroundColor Cyan $binaryPath = Join-Path $TmpDir "cursor-id-modifier.exe" if (!(Get-FileWithProgress -Url $downloadUrl -OutputFile $binaryPath)) { @@ -138,8 +162,15 @@ function Install-CursorModifier { try { Install-CursorModifier } +catch { + Write-Host "Installation failed: $_" -ForegroundColor Red + Cleanup + Write-Host "Press any key to exit..." + $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') + exit 1 +} finally { Cleanup - Write-Host "Press any key to continue..." + Write-Host "Press any key to exit..." -ForegroundColor Green $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') } \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh index 75ee94a..3b49765 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -20,7 +20,7 @@ detect_system() { case "$(uname -s)" in Linux*) os="linux";; Darwin*) os="darwin";; - *) echo "Unsupported OS"; exit 1;; + *) echo -e "${RED}Unsupported OS${NC}"; exit 1;; esac case "$(uname -m)" in @@ -32,7 +32,11 @@ detect_system() { arch="arm64" [ "$os" = "darwin" ] && suffix="_apple_silicon" ;; - *) echo "Unsupported architecture"; exit 1;; + i386|i686) + arch="x86" + [ "$os" = "darwin" ] && { echo -e "${RED}32-bit not supported on macOS${NC}"; exit 1; } + ;; + *) echo -e "${RED}Unsupported architecture${NC}"; exit 1;; esac echo "$os $arch $suffix" @@ -48,7 +52,7 @@ download() { elif command -v wget >/dev/null 2>&1; then wget --show-progress -q "$url" -O "$output" else - echo "Error: curl or wget is required" + echo -e "${RED}Error: curl or wget is required${NC}" exit 1 fi } @@ -59,12 +63,42 @@ setup_install_dir() { if [ ! -d "$install_dir" ]; then mkdir -p "$install_dir" || { - echo "Failed to create installation directory" + echo -e "${RED}Failed to create installation directory${NC}" exit 1 } fi } +# Find matching asset from release +find_asset() { + local json="$1" + local os="$2" + local arch="$3" + local suffix="$4" + + # Try possible binary names + local binary_names=( + "cursor-id-modifier_${os}_${arch}${suffix}" # lowercase os + "cursor-id-modifier_$(tr '[:lower:]' '[:upper:]' <<< ${os:0:1})${os:1}_${arch}${suffix}" # capitalized os + ) + + local url="" + for name in "${binary_names[@]}"; do + echo -e "${BLUE}Looking for asset: $name${NC}" + url=$(echo "$json" | grep -o "\"browser_download_url\": \"[^\"]*${name}\"" | cut -d'"' -f4) + if [ -n "$url" ]; then + echo -e "${GREEN}Found matching asset: $name${NC}" + echo "$url" + return 0 + fi + done + + # If no match found, show available assets + echo -e "${YELLOW}Available assets:${NC}" + echo "$json" | grep "\"name\":" | cut -d'"' -f4 + return 1 +} + # Main installation function main() { echo -e "${BLUE}Starting installation...${NC}" @@ -80,13 +114,16 @@ main() { # Setup installation directory setup_install_dir "$INSTALL_DIR" - # Download latest release + # Get latest release info + echo -e "${BLUE}Fetching latest release information...${NC}" LATEST_URL="https://api.github.com/repos/dacrab/go-cursor-help/releases/latest" - BINARY_NAME="cursor-id-modifier_${OS}_${ARCH}${SUFFIX}" - DOWNLOAD_URL=$(curl -s "$LATEST_URL" | grep "browser_download_url.*${BINARY_NAME}" | cut -d '"' -f 4) + RELEASE_JSON=$(curl -s "$LATEST_URL") + + # Find matching asset + DOWNLOAD_URL=$(find_asset "$RELEASE_JSON" "$OS" "$ARCH" "$SUFFIX") if [ -z "$DOWNLOAD_URL" ]; then - echo -e "${RED}Error: Could not find download URL for $OS $ARCH${NC}" + echo -e "${RED}Error: Could not find appropriate binary for $OS $ARCH${NC}" exit 1 fi From fc9ebdec989ec7f3526b411d5720efc35a26efa1 Mon Sep 17 00:00:00 2001 From: Vaggelis kavouras Date: Sun, 29 Dec 2024 00:08:04 +0200 Subject: [PATCH 23/68] fix: correct binary name duplication in PowerShell installation script - Removed redundant ".exe" suffix from binary names in install.ps1 to ensure unique and accurate naming for Windows binaries based on architecture. --- scripts/install.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index b411308..e25a82b 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -89,9 +89,9 @@ function Install-CursorModifier { # Look for Windows binary with our architecture $possibleNames = @( "cursor-id-modifier_windows_$($arch).exe", - "cursor-id-modifier_windows_$($arch).exe.exe", + "cursor-id-modifier_windows_$($arch).exe", "cursor-id-modifier_Windows_$($arch).exe", - "cursor-id-modifier_Windows_$($arch).exe.exe" + "cursor-id-modifier_Windows_$($arch).exe" ) $asset = $null From 0f0d1c7d6fbcfc35e5874bb919381cf39c3bf4ec Mon Sep 17 00:00:00 2001 From: Vaggelis kavouras Date: Sun, 29 Dec 2024 00:11:40 +0200 Subject: [PATCH 24/68] fix: remove redundant extension assignment in build script - Eliminated the unnecessary assignment of the ".exe" extension for Windows in build_all.bat to streamline the script and avoid confusion regarding binary naming conventions. --- scripts/build_all.bat | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/build_all.bat b/scripts/build_all.bat index e07e387..a5c94a6 100644 --- a/scripts/build_all.bat +++ b/scripts/build_all.bat @@ -33,7 +33,6 @@ mkdir "..\bin" 2>nul set "os=%~1" set "arch=%~2" set "ext=" -if "%os%"=="windows" set "ext=.exe" echo %GREEN%!EN_MESSAGES[5]! %os%/%arch%%RESET% From ee2532175a34a7f2612d9da3d2cbd75947a575ea Mon Sep 17 00:00:00 2001 From: Vaggelis kavouras Date: Sun, 29 Dec 2024 00:14:35 +0200 Subject: [PATCH 25/68] fix: streamline Windows binary extension handling in configuration - Removed the redundant assignment of the ".exe" extension in .goreleaser.yml. - Added conditional assignment of the ".exe" extension in build_all.bat to ensure clarity and consistency in binary naming for Windows builds. --- .goreleaser.yml | 1 - scripts/build_all.bat | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index e324890..791cd44 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -37,7 +37,6 @@ archives: {{- if eq .Arch "arm64" }}arm64{{ end }} {{- if and (eq .Os "darwin") (eq .Arch "amd64") }}_intel{{ end }} {{- if and (eq .Os "darwin") (eq .Arch "arm64") }}_apple_silicon{{ end }} - {{- if eq .Os "windows" }}.exe{{ end }} checksum: name_template: 'checksums.txt' diff --git a/scripts/build_all.bat b/scripts/build_all.bat index a5c94a6..e07e387 100644 --- a/scripts/build_all.bat +++ b/scripts/build_all.bat @@ -33,6 +33,7 @@ mkdir "..\bin" 2>nul set "os=%~1" set "arch=%~2" set "ext=" +if "%os%"=="windows" set "ext=.exe" echo %GREEN%!EN_MESSAGES[5]! %os%/%arch%%RESET% From a9094d34ddc31849195130692dd9c014386add03 Mon Sep 17 00:00:00 2001 From: Vaggelis kavouras Date: Sun, 29 Dec 2024 00:39:01 +0200 Subject: [PATCH 26/68] docs: enhance Windows installation instructions and features in README.md; improve PowerShell script for admin rights detection and user feedback - Updated README.md to include new Windows installation features and manual installation instructions. - Enhanced PowerShell installation script (install.ps1) to detect PowerShell version, handle elevation more effectively, and provide clearer user prompts for admin rights. --- README.md | 44 +++++++++++++++++++++++++++++++++++++++++--- scripts/install.ps1 | 37 +++++++++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2d4eea2..ce7dcbd 100644 --- a/README.md +++ b/README.md @@ -63,11 +63,17 @@ this is a mistake. curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash ``` -**Windows**: Copy and paste in PowerShell (Admin) +**Windows**: Copy and paste in PowerShell ```powershell irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex ``` +#### Windows Installation Features: +- 🔍 Automatically detects and uses PowerShell 7 if available +- 🛡️ Requests administrator privileges via UAC prompt +- 📝 Falls back to Windows PowerShell if PS7 isn't found +- 💡 Provides manual instructions if elevation fails + That's it! The script will: 1. ✨ Install the tool automatically 2. 🔄 Reset your Cursor trial immediately @@ -175,15 +181,47 @@ this is a mistake. curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash ``` -**Windows**: 在PowerShell(管理员)中复制粘贴 +**Windows**: 在PowerShell中复制粘贴 ```powershell irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex ``` -就这样!脚本会: +#### Windows 安装特性: +- 🔍 自动检测并使用 PowerShell 7(如果可用) +- 🛡️ 通过 UAC 提示请求管理员权限 +- 📝 如果没有 PS7 则使用 Windows PowerShell +- 💡 如果提权失败会提供手动说明 + +That's it! The script will: 1. ✨ 自动安装工具 2. 🔄 立即重置Cursor试用期 +### 📦 Manual Installation + +> Download the appropriate file for your system from [releases](https://github.com/dacrab/go-cursor-help/releases/latest) + +
+Windows Packages + +- 64-bit: `cursor-id-modifier_windows_x64.exe` +- 32-bit: `cursor-id-modifier_windows_x86.exe` +
+ +
+macOS Packages + +- Intel: `cursor-id-modifier_darwin_x64_intel` +- M1/M2: `cursor-id-modifier_darwin_arm64_apple_silicon` +
+ +
+Linux Packages + +- 64-bit: `cursor-id-modifier_linux_x64` +- 32-bit: `cursor-id-modifier_linux_x86` +- ARM64: `cursor-id-modifier_linux_arm64` +
+ ### 🔧 技术细节
diff --git a/scripts/install.ps1 b/scripts/install.ps1 index e25a82b..a92fc1f 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -1,15 +1,30 @@ -# Auto-elevate to admin rights if not already running as admin +# Check for admin rights and handle elevation $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") if (-NOT $isAdmin) { + # Detect PowerShell version and path + $pwshPath = if (Get-Command "pwsh" -ErrorAction SilentlyContinue) { + (Get-Command "pwsh").Source # PowerShell 7+ + } elseif (Test-Path "$env:ProgramFiles\PowerShell\7\pwsh.exe") { + "$env:ProgramFiles\PowerShell\7\pwsh.exe" + } else { + "powershell.exe" # Windows PowerShell + } + try { - Write-Host "Requesting administrator privileges..." -ForegroundColor Cyan - $argList = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" - Start-Process powershell.exe -Verb RunAs -ArgumentList $argList -Wait + Write-Host "`nRequesting administrator privileges..." -ForegroundColor Cyan + $scriptPath = $MyInvocation.MyCommand.Path + $argList = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" + Start-Process -FilePath $pwshPath -Verb RunAs -ArgumentList $argList -Wait exit } catch { - Write-Host "Failed to get admin rights. Please run as Administrator." -ForegroundColor Red - Write-Host "Press any key to exit..." + Write-Host "`nError: Administrator privileges required" -ForegroundColor Red + Write-Host "Please run this script from an Administrator PowerShell window" -ForegroundColor Yellow + Write-Host "`nTo do this:" -ForegroundColor Cyan + Write-Host "1. Press Win + X" -ForegroundColor White + Write-Host "2. Click 'Windows Terminal (Admin)' or 'PowerShell (Admin)'" -ForegroundColor White + Write-Host "3. Run the installation command again" -ForegroundColor White + Write-Host "`nPress enter to exit..." $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') exit 1 } @@ -33,7 +48,7 @@ function Cleanup { trap { Write-Host "Error: $_" -ForegroundColor Red Cleanup - Write-Host "Press any key to exit..." + Write-Host "Press enter to exit..." $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') exit 1 } @@ -165,12 +180,14 @@ try { catch { Write-Host "Installation failed: $_" -ForegroundColor Red Cleanup - Write-Host "Press any key to exit..." + Write-Host "Press enter to exit..." $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') exit 1 } finally { Cleanup - Write-Host "Press any key to exit..." -ForegroundColor Green - $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') + if ($LASTEXITCODE -ne 0) { + Write-Host "Press enter to exit..." -ForegroundColor Green + $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') + } } \ No newline at end of file From ad7ae6fa582161a56f2afa684e913e43171a14a7 Mon Sep 17 00:00:00 2001 From: dacrab Date: Sun, 29 Dec 2024 16:51:01 +0200 Subject: [PATCH 27/68] feat: enhance installation script with requirement checks and streamlined download process - Added a function to check for required commands (curl). - Simplified the download function to exclusively use curl for downloading assets. - Improved error handling and user feedback when fetching the latest release information. - Updated the main installation function to construct the binary name directly and provide clearer output during the installation process. --- CHANGELOG.md | 53 +++++++++++++++++++++++++++++++++++ scripts/install.sh | 70 +++++++++++++++------------------------------- 2 files changed, 76 insertions(+), 47 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..261d36b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,53 @@ +# 📝 Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.23] - 2024-12-29 🚀 + +### ✨ Features +- **Initial Release**: First public release of Cursor ID Modifier +- **Multi-Platform Support**: + - 🪟 Windows (x64, x86) + - 🍎 macOS (Intel & Apple Silicon) + - 🐧 Linux (x64, x86, ARM64) +- **Installation**: + - Automated installation scripts for all platforms + - One-line installation commands + - Secure download and verification +- **Core Functionality**: + - Telemetry ID modification for Cursor IDE + - Automatic process management + - Secure configuration handling + +### 🐛 Bug Fixes +- **Installation**: + - Fixed environment variable preservation in sudo operations + - Enhanced error handling during installation + - Improved binary download reliability +- **Process Management**: + - Improved Cursor process detection accuracy + - Enhanced process termination reliability + - Better handling of edge cases +- **User Experience**: + - Enhanced error messages and user feedback + - Improved progress indicators + - Better handling of system permissions + +### 🔧 Technical Improvements +- Optimized binary size with proper build flags +- Enhanced cross-platform compatibility +- Improved error handling and logging +- Better system resource management + +### 📚 Documentation +- Added comprehensive installation instructions +- Included platform-specific guidelines +- Enhanced error troubleshooting guide + +--- +*For more details about the changes, please refer to the [commit history](https://github.com/dacrab/go-cursor-help/commits/main).* + +[0.1.22]: https://github.com/dacrab/go-cursor-help/releases/tag/v0.1.23 \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh index 3b49765..7ebe96d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -13,6 +13,14 @@ NC='\033[0m' TMP_DIR=$(mktemp -d) trap 'rm -rf "$TMP_DIR"' EXIT +# Check for required commands +check_requirements() { + if ! command -v curl >/dev/null 2>&1; then + echo -e "${RED}Error: curl is required${NC}" + exit 1 + fi +} + # Detect system information detect_system() { local os arch suffix @@ -42,19 +50,11 @@ detect_system() { echo "$os $arch $suffix" } -# Download with progress using curl or wget +# Download with progress download() { local url="$1" local output="$2" - - if command -v curl >/dev/null 2>&1; then - curl -#L "$url" -o "$output" - elif command -v wget >/dev/null 2>&1; then - wget --show-progress -q "$url" -O "$output" - else - echo -e "${RED}Error: curl or wget is required${NC}" - exit 1 - fi + curl -#L "$url" -o "$output" } # Check and create installation directory @@ -69,38 +69,10 @@ setup_install_dir() { fi } -# Find matching asset from release -find_asset() { - local json="$1" - local os="$2" - local arch="$3" - local suffix="$4" - - # Try possible binary names - local binary_names=( - "cursor-id-modifier_${os}_${arch}${suffix}" # lowercase os - "cursor-id-modifier_$(tr '[:lower:]' '[:upper:]' <<< ${os:0:1})${os:1}_${arch}${suffix}" # capitalized os - ) - - local url="" - for name in "${binary_names[@]}"; do - echo -e "${BLUE}Looking for asset: $name${NC}" - url=$(echo "$json" | grep -o "\"browser_download_url\": \"[^\"]*${name}\"" | cut -d'"' -f4) - if [ -n "$url" ]; then - echo -e "${GREEN}Found matching asset: $name${NC}" - echo "$url" - return 0 - fi - done - - # If no match found, show available assets - echo -e "${YELLOW}Available assets:${NC}" - echo "$json" | grep "\"name\":" | cut -d'"' -f4 - return 1 -} - # Main installation function main() { + check_requirements + echo -e "${BLUE}Starting installation...${NC}" # Detect system @@ -109,7 +81,6 @@ main() { # Set installation directory INSTALL_DIR="/usr/local/bin" - [ "$OS" = "darwin" ] && INSTALL_DIR="/usr/local/bin" # Setup installation directory setup_install_dir "$INSTALL_DIR" @@ -117,17 +88,22 @@ main() { # Get latest release info echo -e "${BLUE}Fetching latest release information...${NC}" LATEST_URL="https://api.github.com/repos/dacrab/go-cursor-help/releases/latest" - RELEASE_JSON=$(curl -s "$LATEST_URL") - # Find matching asset - DOWNLOAD_URL=$(find_asset "$RELEASE_JSON" "$OS" "$ARCH" "$SUFFIX") + # Construct binary name + BINARY_NAME="cursor-id-modifier_${OS}_${ARCH}${SUFFIX}" + echo -e "${BLUE}Looking for asset: $BINARY_NAME${NC}" + + # Get download URL directly + DOWNLOAD_URL=$(curl -s "$LATEST_URL" | tr -d '\n' | grep -o "\"browser_download_url\": \"[^\"]*${BINARY_NAME}[^\"]*\"" | cut -d'"' -f4) if [ -z "$DOWNLOAD_URL" ]; then echo -e "${RED}Error: Could not find appropriate binary for $OS $ARCH${NC}" exit 1 fi - echo -e "${BLUE}Downloading latest release...${NC}" + echo -e "${GREEN}Found matching asset: $BINARY_NAME${NC}" + echo -e "${BLUE}Downloading from: $DOWNLOAD_URL${NC}" + download "$DOWNLOAD_URL" "$TMP_DIR/cursor-id-modifier" # Install binary @@ -138,9 +114,9 @@ main() { echo -e "${GREEN}Installation completed successfully!${NC}" echo -e "${BLUE}Running cursor-id-modifier...${NC}" - # Run the program + # Run the program with sudo, preserving environment variables export AUTOMATED_MODE=1 - if ! cursor-id-modifier; then + if ! sudo -E cursor-id-modifier; then echo -e "${RED}Failed to run cursor-id-modifier${NC}" exit 1 fi From 1b8aeb4b81cf8facedeedae6d1f07a7f1f45155f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 01:01:50 +0800 Subject: [PATCH 28/68] docs: Enhance README.md with new section for public WeChat account and additional resources - Added a new section to promote the WeChat public account for updates and programming tips. - Included a QR code image for easy access to the WeChat account. - This update aims to improve user engagement and provide more resources for developers. --- README.md | 9 +++++++++ img/wx_public.jpg | Bin 0 -> 28062 bytes img/wx_public_2.png | Bin 0 -> 31028 bytes 3 files changed, 9 insertions(+) create mode 100644 img/wx_public.jpg create mode 100644 img/wx_public_2.png diff --git a/README.md b/README.md index 0f3b3f6..80e1c01 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,15 @@ irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/ins - 原子文件操作 - 错误处理和回滚 +## 🔔 关注公众号 +#### 获取更多精彩内容 +- 第一时间获取最新版本更新 +- CursorAI使用技巧和最佳实践 +- 利用AI提升编程效率 +- 更多AI工具和开发资源 + +![微信公众号二维码](img/wx_public_2.png) + ## ⭐ Star History or Repobeats [![Star History Chart](https://api.star-history.com/svg?repos=yuaotian/go-cursor-help&type=Date)](https://star-history.com/#yuaotian/go-cursor-help&Date) diff --git a/img/wx_public.jpg b/img/wx_public.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2455fc53fcfdf30411b64622b0b10c7854e02d93 GIT binary patch literal 28062 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 Q$(tmA|3CRFLs#(s05F#%mjD0& literal 0 HcmV?d00001 diff --git a/img/wx_public_2.png b/img/wx_public_2.png new file mode 100644 index 0000000000000000000000000000000000000000..5974ac8d5a66eeb17e09e5464928b45346d5c637 GIT binary patch literal 31028 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 literal 0 HcmV?d00001 From fa5372328307fc655464de36279dbdb50d6b1ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:03:05 +0800 Subject: [PATCH 29/68] chore: update GitHub Actions workflows for auto-tagging and release processes - Updated permissions in both auto-tag.yml and release.yml to include write access for issues and pull requests. - Changed the checkout action version from v4 to v3 in both workflows for consistency. - Updated Go setup action version from v5 to v4 and specified the Go version as "1.21". - Adjusted GPG import action version from v6 to v5. - Updated GoReleaser action version from v5 to v4 and specified the release version as v1.21.2. - Standardized string quotes in YAML files for consistency. --- .github/workflows/auto-tag.yml | 19 +++++++++++++------ .github/workflows/release.yml | 16 +++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index adfe54b..1ed02b5 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -6,22 +6,24 @@ on: - master - main paths-ignore: - - '**.md' - - 'LICENSE' - - '.gitignore' + - "**.md" + - "LICENSE" + - ".gitignore" jobs: auto-tag: runs-on: ubuntu-latest permissions: contents: write + issues: write + pull-requests: write outputs: new_tag: ${{ steps.get_latest_tag.outputs.version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 with: fetch-depth: 0 - + - name: Get latest tag id: get_latest_tag run: | @@ -50,4 +52,9 @@ jobs: release: needs: auto-tag uses: ./.github/workflows/release.yml - secrets: inherit \ No newline at end of file + permissions: + contents: write + packages: write + issues: write + pull-requests: write + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fccb6f0..a7048d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,18 +4,20 @@ on: workflow_call: push: tags: - - 'v*' + - "v*" permissions: contents: write packages: write + issues: write + pull-requests: write jobs: goreleaser: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: fetch-depth: 0 lfs: true @@ -29,24 +31,24 @@ jobs: ls -R - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v4 with: - go-version: '1.21' + go-version: "1.21" cache: true - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} if: ${{ env.GPG_PRIVATE_KEY != '' }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v4 with: distribution: goreleaser - version: latest + version: v1.21.2 args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From dff58fb4fbe6eebd6b1062ada9d02e4768c2f45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:07:39 +0800 Subject: [PATCH 30/68] chore: update GitHub Actions workflows for permissions and token usage - Changed permissions in auto-tag.yml and release.yml to 'write-all' for broader access. - Updated GITHUB_TOKEN to use RELEASE_TOKEN in both workflows for enhanced security. - Streamlined the workflows by removing redundant permission specifications. --- .github/workflows/auto-tag.yml | 14 ++++---------- .github/workflows/release.yml | 8 ++------ 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 1ed02b5..2a2d2da 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -10,13 +10,11 @@ on: - "LICENSE" - ".gitignore" +permissions: write-all + jobs: auto-tag: runs-on: ubuntu-latest - permissions: - contents: write - issues: write - pull-requests: write outputs: new_tag: ${{ steps.get_latest_tag.outputs.version }} steps: @@ -41,7 +39,7 @@ jobs: - name: Create new tag env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | new_tag=${{ steps.get_latest_tag.outputs.version }} git config --global user.name 'github-actions[bot]' @@ -52,9 +50,5 @@ jobs: release: needs: auto-tag uses: ./.github/workflows/release.yml - permissions: - contents: write - packages: write - issues: write - pull-requests: write + permissions: write-all secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7048d8..4192cb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,11 +6,7 @@ on: tags: - "v*" -permissions: - contents: write - packages: write - issues: write - pull-requests: write +permissions: write-all jobs: goreleaser: @@ -51,5 +47,5 @@ jobs: version: v1.21.2 args: release --clean env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} From e0db041e936e82b69d9d09c7812461dcede82ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:10:00 +0800 Subject: [PATCH 31/68] chore: enhance GoReleaser configuration and GitHub Actions workflows for GPG signing - Added GPG signing configuration to .goreleaser.yml to support artifact signing. - Updated auto-tag.yml and release.yml to include necessary secrets for GPG signing, enhancing security and functionality. - Ensured that GPG keys and passphrase are conditionally utilized in the release process. --- .github/workflows/auto-tag.yml | 5 ++++- .github/workflows/release.yml | 9 ++++++++- .goreleaser.yml | 9 +++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 2a2d2da..409c8ca 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -51,4 +51,7 @@ jobs: needs: auto-tag uses: ./.github/workflows/release.yml permissions: write-all - secrets: inherit + secrets: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + PASSPHRASE: ${{ secrets.PASSPHRASE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4192cb1..a897c05 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,13 @@ name: Release on: workflow_call: + secrets: + RELEASE_TOKEN: + required: true + GPG_PRIVATE_KEY: + required: false + PASSPHRASE: + required: false push: tags: - "v*" @@ -38,7 +45,7 @@ jobs: with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} - if: ${{ env.GPG_PRIVATE_KEY != '' }} + if: ${{ secrets.GPG_PRIVATE_KEY != '' }} - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 diff --git a/.goreleaser.yml b/.goreleaser.yml index 791cd44..690aaf4 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -95,3 +95,12 @@ release: snapshot: name_template: "{{ incpatch .Version }}-next" + +signs: + - cmd: gpg + args: + - "--output" + - "${signature}" + - "--detach-sign" + - "${artifact}" + artifacts: checksum From c681b2473ca3c4634228f84e547c58454b152b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:13:28 +0800 Subject: [PATCH 32/68] chore: clean up GitHub Actions workflow by removing debug steps and standardizing GPG key import condition - Removed the debug files step from release.yml to streamline the workflow. - Standardized the conditional check for GPG key import to improve clarity and maintainability. --- .github/workflows/release.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a897c05..763e3f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,13 +26,6 @@ jobs: lfs: true submodules: recursive - - name: Debug Files - run: | - pwd - ls -la - echo "Current directory contents:" - ls -R - - name: Set up Go uses: actions/setup-go@v4 with: @@ -42,10 +35,10 @@ jobs: - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@v5 + if: "${{ secrets.GPG_PRIVATE_KEY != '' }}" with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} - if: ${{ secrets.GPG_PRIVATE_KEY != '' }} - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 From e65c5f5fa8fb74bf41d97c38c5da216922d114a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:14:28 +0800 Subject: [PATCH 33/68] chore: update GitHub Actions workflow to streamline release process - Removed explicit permissions and secrets from auto-tag.yml, inheriting them instead for improved security and simplicity. - Added a conditional check for the release job to only run on successful completion of the auto-tag job, enhancing workflow reliability. --- .github/workflows/auto-tag.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 409c8ca..1b65afc 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -49,9 +49,6 @@ jobs: release: needs: auto-tag + if: success() uses: ./.github/workflows/release.yml - permissions: write-all - secrets: - RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - PASSPHRASE: ${{ secrets.PASSPHRASE }} + secrets: inherit From a8765f85a767988f688b0bd54d7863f1dc3470d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:16:05 +0800 Subject: [PATCH 34/68] chore: update GitHub Actions workflows to latest action versions and enhance release configuration - Upgraded checkout action from v3 to v4 in both auto-tag.yml and release.yml for improved performance. - Added optional input for GPG private key in release.yml to support signing releases. - Updated setup-go action from v4 to v5 and goreleaser action from v4 to v5, ensuring compatibility with the latest features. - Changed GoReleaser version specification to 'latest' for automatic updates. --- .github/workflows/auto-tag.yml | 2 +- .github/workflows/release.yml | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 1b65afc..a449eb3 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -18,7 +18,7 @@ jobs: outputs: new_tag: ${{ steps.get_latest_tag.outputs.version }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 763e3f5..95e8b34 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,11 @@ name: Release on: workflow_call: + inputs: + gpg_private_key: + description: "GPG private key to sign releases" + required: false + type: string secrets: RELEASE_TOKEN: required: true @@ -20,31 +25,31 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 lfs: true submodules: recursive - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.21" cache: true - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v5 + uses: crazy-max/ghaction-import-gpg@v6 if: "${{ secrets.GPG_PRIVATE_KEY != '' }}" with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: distribution: goreleaser - version: v1.21.2 + version: latest args: release --clean env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} From 01a932f3e54000e3898ad447ab86efe5cc8b8e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:18:04 +0800 Subject: [PATCH 35/68] chore: enhance GitHub Actions workflows with GPG key input for signing releases - Added an optional input for GPG private key in release.yml to support signing releases. - Updated the conditional check for GPG key import to utilize the new input, improving flexibility and maintainability. - Ensured that the auto-tag workflow inherits secrets for better security practices. --- .github/workflows/auto-tag.yml | 2 ++ .github/workflows/release.yml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index a449eb3..d048f05 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -51,4 +51,6 @@ jobs: needs: auto-tag if: success() uses: ./.github/workflows/release.yml + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 95e8b34..b31febb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,9 +40,9 @@ jobs: - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@v6 - if: "${{ secrets.GPG_PRIVATE_KEY != '' }}" + if: inputs.gpg_private_key != '' with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg_private_key: ${{ inputs.gpg_private_key }} passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser From 5ab9a96c32e65ecd5a03c7916f5feaab3e7e20ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:28:46 +0800 Subject: [PATCH 36/68] chore: update auto-tag workflow to explicitly define secrets for enhanced security - Added explicit definitions for RELEASE_TOKEN, GPG_PRIVATE_KEY, and PASSPHRASE in the auto-tag.yml workflow. - This change improves security by ensuring that necessary secrets are clearly specified for the release process. --- .github/workflows/auto-tag.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index d048f05..80d9bb5 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -53,4 +53,7 @@ jobs: uses: ./.github/workflows/release.yml with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - secrets: inherit + secrets: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + PASSPHRASE: ${{ secrets.PASSPHRASE }} From 9690adf5a42ac812e9cea3d5f41f26e0f8496f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:29:50 +0800 Subject: [PATCH 37/68] chore: downgrade action versions in GitHub workflows for compatibility - Changed checkout action from v4 to v3 in both auto-tag.yml and release.yml for consistency. - Downgraded setup-go action from v5 to v4 and goreleaser action from v5 to v4 to ensure compatibility with existing configurations. - Removed explicit secrets definitions in auto-tag.yml, allowing the workflow to inherit secrets for improved security. --- .github/workflows/auto-tag.yml | 7 ++----- .github/workflows/release.yml | 8 ++++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 80d9bb5..07a1d01 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -18,7 +18,7 @@ jobs: outputs: new_tag: ${{ steps.get_latest_tag.outputs.version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 with: fetch-depth: 0 @@ -51,9 +51,6 @@ jobs: needs: auto-tag if: success() uses: ./.github/workflows/release.yml + secrets: inherit with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - secrets: - RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - PASSPHRASE: ${{ secrets.PASSPHRASE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b31febb..1d916b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,28 +25,28 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: fetch-depth: 0 lfs: true submodules: recursive - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v4 with: go-version: "1.21" cache: true - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v5 if: inputs.gpg_private_key != '' with: gpg_private_key: ${{ inputs.gpg_private_key }} passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v4 with: distribution: goreleaser version: latest From 622f681377a4774a040e34bffed890478f5833a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:31:37 +0800 Subject: [PATCH 38/68] chore: update GitHub Actions workflows to enforce required secrets for release process - Explicitly defined RELEASE_TOKEN, GPG_PRIVATE_KEY, and PASSPHRASE as required secrets in both auto-tag.yml and release.yml to enhance security. - Updated the release.yml to require a tag input for the release process, ensuring clarity in workflow execution. - Adjusted the GPG key import step to utilize the defined secrets, improving the reliability of the signing process. --- .github/workflows/auto-tag.yml | 7 +++++-- .github/workflows/release.yml | 13 ++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 07a1d01..ba33cf2 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -51,6 +51,9 @@ jobs: needs: auto-tag if: success() uses: ./.github/workflows/release.yml - secrets: inherit with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + tag: ${{ needs.auto-tag.outputs.new_tag }} + secrets: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + PASSPHRASE: ${{ secrets.PASSPHRASE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1d916b7..af5d89c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,17 +3,17 @@ name: Release on: workflow_call: inputs: - gpg_private_key: - description: "GPG private key to sign releases" - required: false + tag: + description: "The tag to release" + required: true type: string secrets: RELEASE_TOKEN: required: true GPG_PRIVATE_KEY: - required: false + required: true PASSPHRASE: - required: false + required: true push: tags: - "v*" @@ -40,9 +40,8 @@ jobs: - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@v5 - if: inputs.gpg_private_key != '' with: - gpg_private_key: ${{ inputs.gpg_private_key }} + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser From d7fa7a14428bd791e02137d6bf88c47e33fc46f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:33:33 +0800 Subject: [PATCH 39/68] chore: update GitHub Actions workflows to use Ubuntu 22.04 and rename tag input - Changed the runner environment from 'ubuntu-latest' to 'ubuntu-22.04' in both auto-tag.yml and release.yml for consistency and to ensure compatibility with the latest features. - Renamed the input parameter from 'tag' to 'version' in release.yml to improve clarity regarding its purpose in the release process. --- .github/workflows/auto-tag.yml | 4 ++-- .github/workflows/release.yml | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index ba33cf2..ffa12e3 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -14,7 +14,7 @@ permissions: write-all jobs: auto-tag: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: new_tag: ${{ steps.get_latest_tag.outputs.version }} steps: @@ -52,7 +52,7 @@ jobs: if: success() uses: ./.github/workflows/release.yml with: - tag: ${{ needs.auto-tag.outputs.new_tag }} + version: ${{ needs.auto-tag.outputs.new_tag }} secrets: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af5d89c..1a7bf1c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,8 +3,8 @@ name: Release on: workflow_call: inputs: - tag: - description: "The tag to release" + version: + description: "Version to release" required: true type: string secrets: @@ -22,7 +22,7 @@ permissions: write-all jobs: goreleaser: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v3 @@ -53,3 +53,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + VERSION: ${{ inputs.version }} From 32a30afaebd148f12fa481acd89912634fb8f389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:35:17 +0800 Subject: [PATCH 40/68] chore: enhance GoReleaser and GitHub Actions workflows for GPG signing - Added GPG signing configuration in .goreleaser.yml to support artifact signing for all artifacts. - Updated release.yml to enforce GPG signing by setting global Git configuration options for signing commits and tags. - Improved the signing process by utilizing the GPG fingerprint from environment variables, enhancing security and flexibility. --- .github/workflows/release.yml | 4 ++++ .goreleaser.yml | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a7bf1c..aee0535 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,6 +43,10 @@ jobs: with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} + git_config_global: true + git_user_signingkey: true + git_commit_gpgsign: true + git_tag_gpgsign: true - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 diff --git a/.goreleaser.yml b/.goreleaser.yml index 690aaf4..90bee67 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -42,6 +42,18 @@ checksum: name_template: 'checksums.txt' algorithm: sha256 +signs: + - cmd: gpg + args: + - "--batch" + - "--local-user" + - "{{ .Env.GPG_FINGERPRINT }}" + - "--output" + - "${signature}" + - "--detach-sign" + - "${artifact}" + artifacts: all + changelog: sort: asc use: github @@ -95,12 +107,3 @@ release: snapshot: name_template: "{{ incpatch .Version }}-next" - -signs: - - cmd: gpg - args: - - "--output" - - "${signature}" - - "--detach-sign" - - "${artifact}" - artifacts: checksum From f440f47e578d4a80c86511da1cc9450b116531d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:38:14 +0800 Subject: [PATCH 41/68] chore: update GitHub Actions workflows to use latest action versions and streamline release process - Upgraded checkout action from v3 to v4 in both auto-tag.yml and release.yml for improved performance. - Updated setup-go action from v4 to v5 and goreleaser action from v4 to v5 to leverage the latest features. - Changed the release job to inherit secrets instead of explicitly defining them, enhancing security. - Added environment specification for the goreleaser job and refined the conditional execution for the release process. --- .github/workflows/auto-tag.yml | 9 +++------ .github/workflows/release.yml | 14 ++++++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index ffa12e3..386e155 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -18,7 +18,7 @@ jobs: outputs: new_tag: ${{ steps.get_latest_tag.outputs.version }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -50,10 +50,7 @@ jobs: release: needs: auto-tag if: success() - uses: ./.github/workflows/release.yml + uses: ./.github/workflows/release.yml@${{ github.sha }} with: version: ${{ needs.auto-tag.outputs.new_tag }} - secrets: - RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - PASSPHRASE: ${{ secrets.PASSPHRASE }} + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aee0535..7a5cbd9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,13 @@ on: secrets: RELEASE_TOKEN: required: true + description: "GitHub token for release" GPG_PRIVATE_KEY: required: true + description: "GPG private key for signing" PASSPHRASE: required: true + description: "Passphrase for GPG key" push: tags: - "v*" @@ -22,24 +25,25 @@ permissions: write-all jobs: goreleaser: + environment: production runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 lfs: true submodules: recursive - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.21" cache: true - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v5 + uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} @@ -49,7 +53,7 @@ jobs: git_tag_gpgsign: true - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: distribution: goreleaser version: latest @@ -58,3 +62,5 @@ jobs: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} VERSION: ${{ inputs.version }} + + if: github.event_name == 'workflow_call' || startsWith(github.ref, 'refs/tags/v') From 6fb415bfcb3fc2280b97ce41ae7cba9be43ce49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:39:38 +0800 Subject: [PATCH 42/68] chore: enhance auto-tag workflow with version validation and error handling - Added error handling for git fetch command to ensure workflow fails on tag fetch issues. - Introduced a validation step to check the format of the new version tag, ensuring it adheres to semantic versioning. - Updated the conditional execution for the release job to prevent running if no new tag is generated, improving workflow reliability. --- .github/workflows/auto-tag.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 386e155..f2794fd 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -25,7 +25,8 @@ jobs: - name: Get latest tag id: get_latest_tag run: | - git fetch --tags + set -e + git fetch --tags || exit 1 latest_tag=$(git tag -l 'v*' --sort=-v:refname | head -n 1) if [ -z "$latest_tag" ]; then echo "version=v0.1.0" >> $GITHUB_OUTPUT @@ -37,6 +38,14 @@ jobs: echo "version=$major.$minor.$new_patch" >> $GITHUB_OUTPUT fi + - name: Validate version + run: | + new_tag=${{ steps.get_latest_tag.outputs.version }} + if [[ ! $new_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Invalid version format: $new_tag" + exit 1 + fi + - name: Create new tag env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} @@ -49,8 +58,10 @@ jobs: release: needs: auto-tag - if: success() - uses: ./.github/workflows/release.yml@${{ github.sha }} + if: | + success() && + needs.auto-tag.outputs.new_tag != '' + uses: ./.github/workflows/release.yml with: version: ${{ needs.auto-tag.outputs.new_tag }} secrets: inherit From c4ecb7cede90cc8cffd56978fecec27d06fac12a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:42:18 +0800 Subject: [PATCH 43/68] chore: refine auto-tag workflow with version output and enhanced debugging - Changed output variable from 'new_tag' to 'version' for clarity in the auto-tag job. - Introduced a new version variable to store the generated version, improving readability. - Added debug steps to output version information and job outputs, aiding in troubleshooting. - Updated validation step to provide clearer error messages and confirmation of version validation success. --- .github/workflows/auto-tag.yml | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index f2794fd..ab5df6d 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -16,7 +16,7 @@ jobs: auto-tag: runs-on: ubuntu-22.04 outputs: - new_tag: ${{ steps.get_latest_tag.outputs.version }} + version: ${{ steps.get_latest_tag.outputs.version }} steps: - uses: actions/checkout@v4 with: @@ -29,22 +29,26 @@ jobs: git fetch --tags || exit 1 latest_tag=$(git tag -l 'v*' --sort=-v:refname | head -n 1) if [ -z "$latest_tag" ]; then - echo "version=v0.1.0" >> $GITHUB_OUTPUT + new_version="v0.1.0" else major=$(echo $latest_tag | cut -d. -f1) minor=$(echo $latest_tag | cut -d. -f2) patch=$(echo $latest_tag | cut -d. -f3) new_patch=$((patch + 1)) - echo "version=$major.$minor.$new_patch" >> $GITHUB_OUTPUT + new_version="$major.$minor.$new_patch" fi + echo "version=$new_version" >> $GITHUB_OUTPUT + echo "Generated version: $new_version" - name: Validate version run: | - new_tag=${{ steps.get_latest_tag.outputs.version }} + new_tag="${{ steps.get_latest_tag.outputs.version }}" + echo "Validating version: $new_tag" if [[ ! $new_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Invalid version format: $new_tag" + echo "::error::Invalid version format: $new_tag" exit 1 fi + echo "Version validation passed" - name: Create new tag env: @@ -56,12 +60,16 @@ jobs: git tag -a $new_tag -m "Release $new_tag" git push origin $new_tag + - name: Debug output + run: | + echo "Version output: ${{ steps.get_latest_tag.outputs.version }}" + echo "Job outputs: ${{ toJSON(needs.auto-tag.outputs) }}" + echo "::debug::Version value: ${{ steps.get_latest_tag.outputs.version }}" + release: needs: auto-tag - if: | - success() && - needs.auto-tag.outputs.new_tag != '' + if: success() && needs.auto-tag.outputs.version != '' uses: ./.github/workflows/release.yml with: - version: ${{ needs.auto-tag.outputs.new_tag }} + version: ${{ needs.auto-tag.outputs.version }} secrets: inherit From deb1aafcb5ee36c82b66a3d65a4bf721f282e1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:44:19 +0800 Subject: [PATCH 44/68] chore: improve auto-tag workflow with enhanced output verification and debugging - Updated the output redirection for the generated version to use quotes for better compatibility. - Enhanced debug output to provide clearer visibility into step and job outputs, aiding in troubleshooting. - Added a verification step to display the contents of GITHUB_OUTPUT, ensuring the version value is correctly set before proceeding to the release job. - Refined the conditional execution for the release job to ensure it runs only when the auto-tag job succeeds and a version is generated. --- .github/workflows/auto-tag.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index ab5df6d..01aba01 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -37,7 +37,7 @@ jobs: new_patch=$((patch + 1)) new_version="$major.$minor.$new_patch" fi - echo "version=$new_version" >> $GITHUB_OUTPUT + echo "version=$new_version" >> "$GITHUB_OUTPUT" echo "Generated version: $new_version" - name: Validate version @@ -62,13 +62,28 @@ jobs: - name: Debug output run: | - echo "Version output: ${{ steps.get_latest_tag.outputs.version }}" - echo "Job outputs: ${{ toJSON(needs.auto-tag.outputs) }}" - echo "::debug::Version value: ${{ steps.get_latest_tag.outputs.version }}" + echo "Step outputs:" + echo " version: ${{ steps.get_latest_tag.outputs.version }}" + echo "Job outputs:" + echo " all: ${{ toJSON(job.outputs) }}" + echo " direct: ${{ job.outputs.version }}" + echo "Needs outputs:" + echo " all: ${{ toJSON(needs) }}" + echo "::debug::Raw version value: ${{ steps.get_latest_tag.outputs.version }}" + + - name: Verify outputs + run: | + echo "GITHUB_OUTPUT contents:" + cat $GITHUB_OUTPUT + echo "---" + echo "Direct version value: ${{ steps.get_latest_tag.outputs.version }}" release: needs: auto-tag - if: success() && needs.auto-tag.outputs.version != '' + if: | + always() && + needs.auto-tag.result == 'success' && + needs.auto-tag.outputs.version != '' uses: ./.github/workflows/release.yml with: version: ${{ needs.auto-tag.outputs.version }} From 1e4f2457c21f565b524a8883616d31e7e630bbe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:47:06 +0800 Subject: [PATCH 45/68] chore: update GitHub Actions workflows for improved error handling and version validation - Downgraded actions in both auto-tag.yml and release.yml to v3 and v4 respectively for compatibility. - Enhanced error handling in the auto-tag workflow by adding checks for git fetch failures and validating version format. - Introduced additional validation for version numbers to ensure they remain within acceptable ranges. - Added a verification step in the release workflow to confirm Go installation, improving reliability. --- .github/workflows/auto-tag.yml | 18 +++++++++++++++--- .github/workflows/release.yml | 17 +++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 01aba01..3857d71 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -15,18 +15,22 @@ permissions: write-all jobs: auto-tag: runs-on: ubuntu-22.04 + timeout-minutes: 10 outputs: version: ${{ steps.get_latest_tag.outputs.version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Get latest tag id: get_latest_tag run: | - set -e - git fetch --tags || exit 1 + set -euo pipefail + git fetch --tags --force || { + echo "::error::Failed to fetch tags" + exit 1 + } latest_tag=$(git tag -l 'v*' --sort=-v:refname | head -n 1) if [ -z "$latest_tag" ]; then new_version="v0.1.0" @@ -42,12 +46,20 @@ jobs: - name: Validate version run: | + set -euo pipefail new_tag="${{ steps.get_latest_tag.outputs.version }}" echo "Validating version: $new_tag" if [[ ! $new_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "::error::Invalid version format: $new_tag" exit 1 fi + major=$(echo $new_tag | cut -d. -f1 | tr -d 'v') + minor=$(echo $new_tag | cut -d. -f2) + patch=$(echo $new_tag | cut -d. -f3) + if [[ $major -gt 99 || $minor -gt 99 || $patch -gt 999 ]]; then + echo "::error::Version numbers out of valid range" + exit 1 + fi echo "Version validation passed" - name: Create new tag diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a5cbd9..4ba69df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,23 +27,24 @@ jobs: goreleaser: environment: production runs-on: ubuntu-22.04 + timeout-minutes: 15 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: fetch-depth: 0 lfs: true submodules: recursive - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v4 with: go-version: "1.21" cache: true - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} @@ -52,8 +53,15 @@ jobs: git_commit_gpgsign: true git_tag_gpgsign: true + - name: Verify Go installation + run: | + go version || { + echo "::error::Go installation failed" + exit 1 + } + - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v4 with: distribution: goreleaser version: latest @@ -62,5 +70,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} VERSION: ${{ inputs.version }} + continue-on-error: false if: github.event_name == 'workflow_call' || startsWith(github.ref, 'refs/tags/v') From 84cd8c15c7976ffaff8c9c7517fa6f3a9ad582a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:48:49 +0800 Subject: [PATCH 46/68] chore: update GitHub Actions workflows for improved tagging and release process - Changed GITHUB_TOKEN reference in auto-tag.yml to use the standard token for better security. - Added debug steps in both workflows to enhance visibility into the release process and check permissions. - Updated permissions in release.yml to explicitly define required access levels for contents, packages, and actions. - Refined the conditional execution for the release job to ensure it only runs when a valid version is generated. --- .github/workflows/auto-tag.yml | 20 +++++++++++++++++--- .github/workflows/release.yml | 22 +++++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index 3857d71..d9a214c 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -64,7 +64,7 @@ jobs: - name: Create new tag env: - GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | new_tag=${{ steps.get_latest_tag.outputs.version }} git config --global user.name 'github-actions[bot]' @@ -90,13 +90,27 @@ jobs: echo "---" echo "Direct version value: ${{ steps.get_latest_tag.outputs.version }}" + - name: Debug Release Trigger + run: | + echo "Auto-tag completed successfully" + echo "Version to be released: ${{ steps.get_latest_tag.outputs.version }}" + echo "Checking release conditions:" + echo " - Version not empty: ${{ steps.get_latest_tag.outputs.version != '' }}" + echo " - Current job status: ${{ job.status }}" + release: needs: auto-tag + permissions: + contents: write + packages: write + actions: write if: | - always() && needs.auto-tag.result == 'success' && needs.auto-tag.outputs.version != '' uses: ./.github/workflows/release.yml with: version: ${{ needs.auto-tag.outputs.version }} - secrets: inherit + secrets: + RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + PASSPHRASE: ${{ secrets.PASSPHRASE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4ba69df..5a346ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,10 @@ on: tags: - "v*" -permissions: write-all +permissions: + contents: write + packages: write + actions: write jobs: goreleaser: @@ -29,6 +32,23 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 15 steps: + - name: Check Permissions + run: | + echo "Checking required permissions..." + TOKEN="${{ secrets.RELEASE_TOKEN }}" + if [ -z "$TOKEN" ]; then + echo "::error::RELEASE_TOKEN is not set" + exit 1 + fi + echo "Token permissions check passed" + + - name: Debug Workflow Trigger + run: | + echo "Event name: ${{ github.event_name }}" + echo "Ref: ${{ github.ref }}" + echo "Version input: ${{ inputs.version }}" + echo "Token exists: ${{ secrets.RELEASE_TOKEN != '' }}" + - name: Checkout uses: actions/checkout@v3 with: From f90d6d7e5f197538750980e41eff42e591949378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:52:02 +0800 Subject: [PATCH 47/68] chore: enhance GitHub Actions workflows with improved job structure and error handling - Introduced a pre-job in auto-tag.yml to skip duplicate actions based on content, optimizing workflow execution. - Updated the auto-tag job to run on ubuntu-latest and added concurrency controls for better resource management. - Enhanced error handling in both workflows by adding checks for workflow status and notifying on failures. - Upgraded actions to their latest versions for improved performance and reliability. - Added caching steps in both workflows to speed up builds and reduce redundant operations. --- .github/workflows/auto-tag.yml | 36 +++++++++++++++++++-- .github/workflows/release.yml | 58 ++++++++++++++++++---------------- 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index d9a214c..1973f4f 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -13,16 +13,40 @@ on: permissions: write-all jobs: + pre_job: + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@v5.3.1 + with: + cancel_others: "true" + concurrent_skipping: "same_content" + auto-tag: - runs-on: ubuntu-22.04 + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' + runs-on: ubuntu-latest timeout-minutes: 10 outputs: version: ${{ steps.get_latest_tag.outputs.version }} + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: actions/cache@v3 + with: + path: | + ~/.cache/git + key: ${{ runner.os }}-git-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-git- + - name: Get latest tag id: get_latest_tag run: | @@ -98,6 +122,14 @@ jobs: echo " - Version not empty: ${{ steps.get_latest_tag.outputs.version != '' }}" echo " - Current job status: ${{ job.status }}" + - name: Check workflow status + if: always() + run: | + if [[ "${{ job.status }}" == "failure" ]]; then + echo "::error::Auto-tag workflow failed" + exit 1 + fi + release: needs: auto-tag permissions: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5a346ee..098fe8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,28 +29,14 @@ permissions: jobs: goreleaser: environment: production - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest timeout-minutes: 15 + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true steps: - - name: Check Permissions - run: | - echo "Checking required permissions..." - TOKEN="${{ secrets.RELEASE_TOKEN }}" - if [ -z "$TOKEN" ]; then - echo "::error::RELEASE_TOKEN is not set" - exit 1 - fi - echo "Token permissions check passed" - - - name: Debug Workflow Trigger - run: | - echo "Event name: ${{ github.event_name }}" - echo "Ref: ${{ github.ref }}" - echo "Version input: ${{ inputs.version }}" - echo "Token exists: ${{ secrets.RELEASE_TOKEN != '' }}" - - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 lfs: true @@ -60,8 +46,18 @@ jobs: uses: actions/setup-go@v4 with: go-version: "1.21" + check-latest: true cache: true + - uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@v5 @@ -73,23 +69,29 @@ jobs: git_commit_gpgsign: true git_tag_gpgsign: true - - name: Verify Go installation - run: | - go version || { - echo "::error::Go installation failed" - exit 1 - } - - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 with: distribution: goreleaser version: latest - args: release --clean + args: release --clean --timeout 60m env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} VERSION: ${{ inputs.version }} - continue-on-error: false + + - name: Notify on failure + if: failure() + run: | + echo "::error::Release process failed" + + - name: Verify Release + if: success() + run: | + echo "Verifying release artifacts..." + if [ ! -d "dist" ]; then + echo "::error::Release artifacts not found" + exit 1 + fi if: github.event_name == 'workflow_call' || startsWith(github.ref, 'refs/tags/v') From ff67a07ca705b1f85adfdf553930f2b2d61942f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:54:42 +0800 Subject: [PATCH 48/68] chore: remove deprecated GitHub Actions workflows for auto-tagging and release - Deleted the auto-tag.yml and release.yml workflows as they are no longer needed. - This cleanup helps streamline the repository by removing unused configurations and reducing maintenance overhead. --- .../{auto-tag.yml => auto-tag-release.yml} | 127 +++++++++++------- .github/workflows/release.yml | 97 ------------- 2 files changed, 76 insertions(+), 148 deletions(-) rename .github/workflows/{auto-tag.yml => auto-tag-release.yml} (51%) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag-release.yml similarity index 51% rename from .github/workflows/auto-tag.yml rename to .github/workflows/auto-tag-release.yml index 1973f4f..f2454c2 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag-release.yml @@ -1,16 +1,21 @@ -name: Auto Tag +name: Auto Tag & Release on: push: branches: - master - main + tags: + - "v*" paths-ignore: - "**.md" - "LICENSE" - ".gitignore" -permissions: write-all +permissions: + contents: write + packages: write + actions: write jobs: pre_job: @@ -24,30 +29,49 @@ jobs: cancel_others: "true" concurrent_skipping: "same_content" - auto-tag: + auto-tag-release: needs: pre_job - if: needs.pre_job.outputs.should_skip != 'true' + if: | + needs.pre_job.outputs.should_skip != 'true' || + startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 outputs: version: ${{ steps.get_latest_tag.outputs.version }} concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: fetch-depth: 0 + lfs: true + submodules: recursive + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: "1.21" + check-latest: true + cache: true - - uses: actions/cache@v3 + - name: Cache + uses: actions/cache@v3 with: path: | + ~/.cache/go-build + ~/go/pkg/mod ~/.cache/git - key: ${{ runner.os }}-git-${{ github.sha }} + key: ${{ runner.os }}-build-${{ hashFiles('**/go.sum') }} restore-keys: | - ${{ runner.os }}-git- + ${{ runner.os }}-build- + ${{ runner.os }}- + # 只在非tag推送时执行自动打tag - name: Get latest tag + if: "!startsWith(github.ref, 'refs/tags/v')" id: get_latest_tag run: | set -euo pipefail @@ -69,6 +93,7 @@ jobs: echo "Generated version: $new_version" - name: Validate version + if: "!startsWith(github.ref, 'refs/tags/v')" run: | set -euo pipefail new_tag="${{ steps.get_latest_tag.outputs.version }}" @@ -87,6 +112,7 @@ jobs: echo "Version validation passed" - name: Create new tag + if: "!startsWith(github.ref, 'refs/tags/v')" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -96,53 +122,52 @@ jobs: git tag -a $new_tag -m "Release $new_tag" git push origin $new_tag - - name: Debug output - run: | - echo "Step outputs:" - echo " version: ${{ steps.get_latest_tag.outputs.version }}" - echo "Job outputs:" - echo " all: ${{ toJSON(job.outputs) }}" - echo " direct: ${{ job.outputs.version }}" - echo "Needs outputs:" - echo " all: ${{ toJSON(needs) }}" - echo "::debug::Raw version value: ${{ steps.get_latest_tag.outputs.version }}" + # Release 相关步骤 + - name: Import GPG key + id: import_gpg + if: | + startsWith(github.ref, 'refs/tags/v') || + (success() && steps.get_latest_tag.outputs.version != '') + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + git_config_global: true + git_user_signingkey: true + git_commit_gpgsign: true + git_tag_gpgsign: true - - name: Verify outputs - run: | - echo "GITHUB_OUTPUT contents:" - cat $GITHUB_OUTPUT - echo "---" - echo "Direct version value: ${{ steps.get_latest_tag.outputs.version }}" + - name: Run GoReleaser + if: | + startsWith(github.ref, 'refs/tags/v') || + (success() && steps.get_latest_tag.outputs.version != '') + uses: goreleaser/goreleaser-action@v4 + with: + distribution: goreleaser + version: latest + args: release --clean --timeout 60m + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + VERSION: ${{ steps.get_latest_tag.outputs.version }} - - name: Debug Release Trigger + - name: Set Release Version + if: startsWith(github.ref, 'refs/tags/v') run: | - echo "Auto-tag completed successfully" - echo "Version to be released: ${{ steps.get_latest_tag.outputs.version }}" - echo "Checking release conditions:" - echo " - Version not empty: ${{ steps.get_latest_tag.outputs.version != '' }}" - echo " - Current job status: ${{ job.status }}" + echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - - name: Check workflow status - if: always() + - name: Verify Release + if: | + startsWith(github.ref, 'refs/tags/v') || + (success() && steps.get_latest_tag.outputs.version != '') run: | - if [[ "${{ job.status }}" == "failure" ]]; then - echo "::error::Auto-tag workflow failed" + echo "Verifying release artifacts..." + if [ ! -d "dist" ]; then + echo "::error::Release artifacts not found" exit 1 fi - release: - needs: auto-tag - permissions: - contents: write - packages: write - actions: write - if: | - needs.auto-tag.result == 'success' && - needs.auto-tag.outputs.version != '' - uses: ./.github/workflows/release.yml - with: - version: ${{ needs.auto-tag.outputs.version }} - secrets: - RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - PASSPHRASE: ${{ secrets.PASSPHRASE }} + - name: Notify on failure + if: failure() + run: | + echo "::error::Release process failed" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 098fe8e..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: Release - -on: - workflow_call: - inputs: - version: - description: "Version to release" - required: true - type: string - secrets: - RELEASE_TOKEN: - required: true - description: "GitHub token for release" - GPG_PRIVATE_KEY: - required: true - description: "GPG private key for signing" - PASSPHRASE: - required: true - description: "Passphrase for GPG key" - push: - tags: - - "v*" - -permissions: - contents: write - packages: write - actions: write - -jobs: - goreleaser: - environment: production - runs-on: ubuntu-latest - timeout-minutes: 15 - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - lfs: true - submodules: recursive - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.21" - check-latest: true - cache: true - - - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Import GPG key - id: import_gpg - uses: crazy-max/ghaction-import-gpg@v5 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.PASSPHRASE }} - git_config_global: true - git_user_signingkey: true - git_commit_gpgsign: true - git_tag_gpgsign: true - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 - with: - distribution: goreleaser - version: latest - args: release --clean --timeout 60m - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} - GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} - VERSION: ${{ inputs.version }} - - - name: Notify on failure - if: failure() - run: | - echo "::error::Release process failed" - - - name: Verify Release - if: success() - run: | - echo "Verifying release artifacts..." - if [ ! -d "dist" ]; then - echo "::error::Release artifacts not found" - exit 1 - fi - - if: github.event_name == 'workflow_call' || startsWith(github.ref, 'refs/tags/v') From f192037eb532d8bd6e9a58909d8175535bebb13c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 18:59:48 +0800 Subject: [PATCH 49/68] chore: update auto-tag workflow to require Ubuntu 22.04 - Modified the workflow to specify Ubuntu 22.04 as the runner environment, ensuring compatibility with the latest features. - Added a note indicating the requirement for Ubuntu 22.04 or 24.04, enhancing clarity for future contributors. --- .github/workflows/auto-tag-release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index f2454c2..536b0b3 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -1,3 +1,5 @@ +# This workflow requires Ubuntu 22.04 or 24.04 + name: Auto Tag & Release on: @@ -19,7 +21,7 @@ permissions: jobs: pre_job: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: should_skip: ${{ steps.skip_check.outputs.should_skip }} steps: @@ -34,7 +36,7 @@ jobs: if: | needs.pre_job.outputs.should_skip != 'true' || startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 15 outputs: version: ${{ steps.get_latest_tag.outputs.version }} From b9bd9948039496852968b5e13dc0165dcc2a2bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:03:03 +0800 Subject: [PATCH 50/68] chore: enhance auto-tag workflow with additional checks and Go environment setup - Added a configuration check step for GoReleaser to ensure the presence of the configuration file before execution. - Introduced a step to set up the Go workspace environment, improving the build process. - Implemented dependency checks to verify and download Go modules, enhancing reliability. - Modified the GoReleaser steps to handle cases with and without GPG signing, improving flexibility in the release process. - Enhanced the verification step to include checks for the executability of generated binaries, ensuring successful builds. --- .github/workflows/auto-tag-release.yml | 82 ++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index 536b0b3..4c9c7b1 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -128,8 +128,9 @@ jobs: - name: Import GPG key id: import_gpg if: | - startsWith(github.ref, 'refs/tags/v') || - (success() && steps.get_latest_tag.outputs.version != '') + (startsWith(github.ref, 'refs/tags/v') || + (success() && steps.get_latest_tag.outputs.version != '')) && + secrets.GPG_PRIVATE_KEY != '' uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} @@ -139,10 +140,36 @@ jobs: git_commit_gpgsign: true git_tag_gpgsign: true + # 在 Run GoReleaser 之前添加配置检查步骤 + - name: Check GoReleaser config + run: | + if [ ! -f ".goreleaser.yml" ] && [ ! -f ".goreleaser.yaml" ]; then + echo "::error::GoReleaser configuration file not found" + exit 1 + fi + + # 添加 Go 工作目录设置 + - name: Set Go Work Directory + run: | + echo "GOPATH=${{ github.workspace }}/go" >> $GITHUB_ENV + echo "${{ github.workspace }}/go/bin" >> $GITHUB_PATH + + # 添加依赖检查步骤 + - name: Check Dependencies + run: | + go mod verify + go mod download + # 如果使用 vendor 模式,则执行以下命令 + if [ -d "vendor" ]; then + go mod vendor + fi + + # 修改 GoReleaser 步骤的环境变量 - name: Run GoReleaser if: | - startsWith(github.ref, 'refs/tags/v') || - (success() && steps.get_latest_tag.outputs.version != '') + (startsWith(github.ref, 'refs/tags/v') || + (success() && steps.get_latest_tag.outputs.version != '')) && + (steps.import_gpg.outcome == 'success' || steps.import_gpg.outcome == 'skipped') uses: goreleaser/goreleaser-action@v4 with: distribution: goreleaser @@ -152,12 +179,40 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} VERSION: ${{ steps.get_latest_tag.outputs.version }} + CGO_ENABLED: 0 + GOFLAGS: -mod=vendor + GOPATH: ${{ github.workspace }}/go + GOROOT: ${{ env.GOROOT }} + GOCACHE: ${{ github.workspace }}/.cache/go-build + GOMODCACHE: ${{ github.workspace }}/go/pkg/mod + + # 修改无 GPG 的 GoReleaser 步骤 + - name: Run GoReleaser without GPG + if: | + (startsWith(github.ref, 'refs/tags/v') || + (success() && steps.get_latest_tag.outputs.version != '')) && + secrets.GPG_PRIVATE_KEY == '' + uses: goreleaser/goreleaser-action@v4 + with: + distribution: goreleaser + version: latest + args: release --clean --timeout 60m --skip-sign + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.get_latest_tag.outputs.version }} + CGO_ENABLED: 0 + GOFLAGS: -mod=vendor + GOPATH: ${{ github.workspace }}/go + GOROOT: ${{ env.GOROOT }} + GOCACHE: ${{ github.workspace }}/.cache/go-build + GOMODCACHE: ${{ github.workspace }}/go/pkg/mod - name: Set Release Version if: startsWith(github.ref, 'refs/tags/v') run: | echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + # 改进验证步骤 - name: Verify Release if: | startsWith(github.ref, 'refs/tags/v') || @@ -168,6 +223,25 @@ jobs: echo "::error::Release artifacts not found" exit 1 fi + # 验证生成的二进制文件 + for file in dist/cursor-id-modifier_*; do + if [ -f "$file" ]; then + echo "Verifying: $file" + if [[ "$file" == *.exe ]]; then + # Windows 二进制文件检查 + if ! [ -x "$file" ]; then + echo "::error::$file is not executable" + exit 1 + fi + else + # Unix 二进制文件检查 + if ! [ -x "$file" ]; then + echo "::error::$file is not executable" + exit 1 + fi + fi + fi + done - name: Notify on failure if: failure() From 97e6e5165e3b37eda773aff410e4eacde43dc67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:06:48 +0800 Subject: [PATCH 51/68] chore: enhance auto-tag workflow with GPG signing options and build summary - Added inputs for optional GPG private key and skip signing functionality to the workflow. - Updated conditions for GPG signing to allow skipping based on input parameters. - Introduced a new step to generate a build summary, detailing Go version, release version, GPG signing status, and build status, improving visibility into the release process. --- .github/workflows/auto-tag-release.yml | 30 ++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index 4c9c7b1..e6a0803 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -13,6 +13,17 @@ on: - "**.md" - "LICENSE" - ".gitignore" + workflow_call: + inputs: + gpg_private_key: + required: false + type: string + description: "GPG private key for signing releases" + skip_signing: + required: false + type: boolean + default: false + description: "Skip GPG signing of releases" permissions: contents: write @@ -130,7 +141,7 @@ jobs: if: | (startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '')) && - secrets.GPG_PRIVATE_KEY != '' + !inputs.skip_signing uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} @@ -191,7 +202,7 @@ jobs: if: | (startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '')) && - secrets.GPG_PRIVATE_KEY == '' + (inputs.skip_signing || steps.import_gpg.outcome == 'skipped') uses: goreleaser/goreleaser-action@v4 with: distribution: goreleaser @@ -247,3 +258,18 @@ jobs: if: failure() run: | echo "::error::Release process failed" + + # 添加构建摘要步骤 + - name: Build Summary + if: always() + run: | + echo "## Build Summary" >> $GITHUB_STEP_SUMMARY + echo "- Go Version: $(go version)" >> $GITHUB_STEP_SUMMARY + echo "- Release Version: ${VERSION:-N/A}" >> $GITHUB_STEP_SUMMARY + echo "- GPG Signing: ${{ steps.import_gpg.outcome == 'success' && 'Enabled' || 'Disabled' }}" >> $GITHUB_STEP_SUMMARY + echo "- Build Status: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY + + if [ -d "dist" ]; then + echo "### Generated Artifacts" >> $GITHUB_STEP_SUMMARY + ls -lh dist/ | awk '{print "- "$9" ("$5")"}' >> $GITHUB_STEP_SUMMARY + fi From c1d5ba84b3916cd363e6b8c585b2048ab0e6a423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:12:50 +0800 Subject: [PATCH 52/68] chore: update GoReleaser configuration and auto-tag workflow - Commented out GPG signing configuration in .goreleaser.yml for clarity. - Modified the default value for the skip_signing input in auto-tag-release.yml to true, allowing for easier skipping of GPG signing during releases. --- .github/workflows/auto-tag-release.yml | 6 +----- .goreleaser.yml | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index e6a0803..250f1c1 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -15,14 +15,10 @@ on: - ".gitignore" workflow_call: inputs: - gpg_private_key: - required: false - type: string - description: "GPG private key for signing releases" skip_signing: required: false type: boolean - default: false + default: true description: "Skip GPG signing of releases" permissions: diff --git a/.goreleaser.yml b/.goreleaser.yml index 90bee67..c247e1c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -42,17 +42,17 @@ checksum: name_template: 'checksums.txt' algorithm: sha256 -signs: - - cmd: gpg - args: - - "--batch" - - "--local-user" - - "{{ .Env.GPG_FINGERPRINT }}" - - "--output" - - "${signature}" - - "--detach-sign" - - "${artifact}" - artifacts: all +# signs: +# - cmd: gpg +# args: +# - "--batch" +# - "--local-user" +# - "{{ .Env.GPG_FINGERPRINT }}" +# - "--output" +# - "${signature}" +# - "--detach-sign" +# - "${artifact}" +# artifacts: all changelog: sort: asc From f0f77352ae05b6708ad2a39fdb7fc7897bf5a38b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:15:49 +0800 Subject: [PATCH 53/68] chore: simplify auto-tag workflow by removing GPG signing steps - Removed GPG key import and signing conditions from the auto-tag-release.yml workflow to streamline the release process. - Updated GoReleaser execution conditions to focus solely on version tagging, enhancing clarity and reducing complexity. --- .github/workflows/auto-tag-release.yml | 45 ++------------------------ 1 file changed, 2 insertions(+), 43 deletions(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index 250f1c1..322e583 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -131,22 +131,6 @@ jobs: git tag -a $new_tag -m "Release $new_tag" git push origin $new_tag - # Release 相关步骤 - - name: Import GPG key - id: import_gpg - if: | - (startsWith(github.ref, 'refs/tags/v') || - (success() && steps.get_latest_tag.outputs.version != '')) && - !inputs.skip_signing - uses: crazy-max/ghaction-import-gpg@v5 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.PASSPHRASE }} - git_config_global: true - git_user_signingkey: true - git_commit_gpgsign: true - git_tag_gpgsign: true - # 在 Run GoReleaser 之前添加配置检查步骤 - name: Check GoReleaser config run: | @@ -171,34 +155,9 @@ jobs: go mod vendor fi - # 修改 GoReleaser 步骤的环境变量 + # 修改 GoReleaser 步骤 - name: Run GoReleaser - if: | - (startsWith(github.ref, 'refs/tags/v') || - (success() && steps.get_latest_tag.outputs.version != '')) && - (steps.import_gpg.outcome == 'success' || steps.import_gpg.outcome == 'skipped') - uses: goreleaser/goreleaser-action@v4 - with: - distribution: goreleaser - version: latest - args: release --clean --timeout 60m - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} - VERSION: ${{ steps.get_latest_tag.outputs.version }} - CGO_ENABLED: 0 - GOFLAGS: -mod=vendor - GOPATH: ${{ github.workspace }}/go - GOROOT: ${{ env.GOROOT }} - GOCACHE: ${{ github.workspace }}/.cache/go-build - GOMODCACHE: ${{ github.workspace }}/go/pkg/mod - - # 修改无 GPG 的 GoReleaser 步骤 - - name: Run GoReleaser without GPG - if: | - (startsWith(github.ref, 'refs/tags/v') || - (success() && steps.get_latest_tag.outputs.version != '')) && - (inputs.skip_signing || steps.import_gpg.outcome == 'skipped') + if: startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') uses: goreleaser/goreleaser-action@v4 with: distribution: goreleaser From bf6adcbac2925777c99108d08280a8ef37d83de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:17:46 +0800 Subject: [PATCH 54/68] chore: update GoReleaser configuration and enhance auto-tag workflow - Added 'go mod download' step to ensure dependencies are available before builds. - Introduced debug flag in GoReleaser execution for improved troubleshooting. - Added error checking step to capture GoReleaser output and configuration details on failure. - Modified build summary step to reflect GPG signing status as disabled, streamlining the release process. --- .github/workflows/auto-tag-release.yml | 23 +++++++-- .goreleaser.yml | 65 +------------------------- 2 files changed, 21 insertions(+), 67 deletions(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index 322e583..2a677d9 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -162,7 +162,7 @@ jobs: with: distribution: goreleaser version: latest - args: release --clean --timeout 60m --skip-sign + args: release --clean --timeout 60m --skip-sign --debug env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ steps.get_latest_tag.outputs.version }} @@ -173,6 +173,23 @@ jobs: GOCACHE: ${{ github.workspace }}/.cache/go-build GOMODCACHE: ${{ github.workspace }}/go/pkg/mod + # 添加错误检查步骤 + - name: Check GoReleaser Output + if: failure() + run: | + echo "::group::GoReleaser Debug Info" + cat dist/artifacts.json || true + echo "::endgroup::" + + echo "::group::GoReleaser Config" + cat .goreleaser.yml + echo "::endgroup::" + + echo "::group::Environment Info" + go version + go env + echo "::endgroup::" + - name: Set Release Version if: startsWith(github.ref, 'refs/tags/v') run: | @@ -214,14 +231,14 @@ jobs: run: | echo "::error::Release process failed" - # 添加构建摘要步骤 + # 修改构建摘要步骤 - name: Build Summary if: always() run: | echo "## Build Summary" >> $GITHUB_STEP_SUMMARY echo "- Go Version: $(go version)" >> $GITHUB_STEP_SUMMARY echo "- Release Version: ${VERSION:-N/A}" >> $GITHUB_STEP_SUMMARY - echo "- GPG Signing: ${{ steps.import_gpg.outcome == 'success' && 'Enabled' || 'Disabled' }}" >> $GITHUB_STEP_SUMMARY + echo "- GPG Signing: Disabled" >> $GITHUB_STEP_SUMMARY echo "- Build Status: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY if [ -d "dist" ]; then diff --git a/.goreleaser.yml b/.goreleaser.yml index c247e1c..b170c38 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,6 +1,7 @@ before: hooks: - go mod tidy + - go mod download builds: - id: cursor-id-modifier @@ -35,75 +36,11 @@ archives: {{- if eq .Arch "amd64" }}x64{{ end }} {{- if eq .Arch "386" }}x86{{ end }} {{- if eq .Arch "arm64" }}arm64{{ end }} - {{- if and (eq .Os "darwin") (eq .Arch "amd64") }}_intel{{ end }} - {{- if and (eq .Os "darwin") (eq .Arch "arm64") }}_apple_silicon{{ end }} checksum: name_template: 'checksums.txt' algorithm: sha256 -# signs: -# - cmd: gpg -# args: -# - "--batch" -# - "--local-user" -# - "{{ .Env.GPG_FINGERPRINT }}" -# - "--output" -# - "${signature}" -# - "--detach-sign" -# - "${artifact}" -# artifacts: all - changelog: sort: asc use: github - groups: - - title: Features - regexp: "^.*feat[(\\w)]*:+.*$" - order: 0 - - title: 'Bug fixes' - regexp: "^.*fix[(\\w)]*:+.*$" - order: 1 - - title: Others - order: 999 - filters: - exclude: - - '^docs:' - - '^test:' - - '^ci:' - - Merge pull request - - Merge branch - -release: - github: - owner: dacrab - name: go-cursor-help - draft: false - prerelease: auto - mode: replace - header: | - ## Cursor ID Modifier {{ .Version }} - - ### Supported Platforms - - Windows: x64, x86 - - macOS: Intel (x64), Apple Silicon (M1/M2) - - Linux: x64, x86, ARM64 - - See [CHANGELOG](CHANGELOG.md) for more details. - footer: | - **Full Changelog**: https://github.com/dacrab/go-cursor-help/compare/{{ .PreviousTag }}...{{ .Tag }} - - ## Quick Installation - - **Linux/macOS**: - ```bash - curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash - ``` - - **Windows** (PowerShell Admin): - ```powershell - irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex - ``` - -snapshot: - name_template: "{{ incpatch .Version }}-next" From 2d008db25e6a2031d9046cbfd8ceb24d0bb9e7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:19:51 +0800 Subject: [PATCH 55/68] chore: simplify auto-tag workflow by removing GPG signing input - Removed the 'skip_signing' input from the auto-tag-release.yml workflow to streamline the release process. - Updated GoReleaser execution arguments to eliminate the '--skip-sign' option, enhancing clarity and focus on the release process. --- .github/workflows/auto-tag-release.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index 2a677d9..d8057a1 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -15,11 +15,6 @@ on: - ".gitignore" workflow_call: inputs: - skip_signing: - required: false - type: boolean - default: true - description: "Skip GPG signing of releases" permissions: contents: write @@ -162,7 +157,7 @@ jobs: with: distribution: goreleaser version: latest - args: release --clean --timeout 60m --skip-sign --debug + args: release --clean --timeout 60m --debug env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ steps.get_latest_tag.outputs.version }} From c41ade9b88801b782b19673898a74d668ddc12b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:21:30 +0800 Subject: [PATCH 56/68] chore: update auto-tag workflow and GitHub Actions versions - Simplified the workflow_call input in auto-tag-release.yml by removing unnecessary inputs. - Updated actions/checkout and actions/setup-go to version 3 for improved performance and compatibility. - Refined conditional checks for version tagging to use the new syntax for better clarity. - Adjusted GoReleaser action to version 3, ensuring alignment with the latest features and fixes. --- .github/workflows/auto-tag-release.yml | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index d8057a1..eb55560 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -13,8 +13,7 @@ on: - "**.md" - "LICENSE" - ".gitignore" - workflow_call: - inputs: + workflow_call: {} permissions: contents: write @@ -28,7 +27,7 @@ jobs: should_skip: ${{ steps.skip_check.outputs.should_skip }} steps: - id: skip_check - uses: fkirc/skip-duplicate-actions@v5.3.1 + uses: fkirc/skip-duplicate-actions@v5.3.0 with: cancel_others: "true" concurrent_skipping: "same_content" @@ -48,14 +47,14 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: fetch-depth: 0 lfs: true submodules: recursive - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v3 with: go-version: "1.21" check-latest: true @@ -75,7 +74,7 @@ jobs: # 只在非tag推送时执行自动打tag - name: Get latest tag - if: "!startsWith(github.ref, 'refs/tags/v')" + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} id: get_latest_tag run: | set -euo pipefail @@ -97,7 +96,7 @@ jobs: echo "Generated version: $new_version" - name: Validate version - if: "!startsWith(github.ref, 'refs/tags/v')" + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} run: | set -euo pipefail new_tag="${{ steps.get_latest_tag.outputs.version }}" @@ -116,7 +115,7 @@ jobs: echo "Version validation passed" - name: Create new tag - if: "!startsWith(github.ref, 'refs/tags/v')" + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -152,8 +151,8 @@ jobs: # 修改 GoReleaser 步骤 - name: Run GoReleaser - if: startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') - uses: goreleaser/goreleaser-action@v4 + if: ${{ startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') }} + uses: goreleaser/goreleaser-action@v3 with: distribution: goreleaser version: latest @@ -192,9 +191,7 @@ jobs: # 改进验证步骤 - name: Verify Release - if: | - startsWith(github.ref, 'refs/tags/v') || - (success() && steps.get_latest_tag.outputs.version != '') + if: ${{ startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') }} run: | echo "Verifying release artifacts..." if [ ! -d "dist" ]; then From 3050a6c0adca32ec6b9c6edd43c4b557da0f4001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:23:34 +0800 Subject: [PATCH 57/68] chore: update auto-tag workflow to use snapshot mode and enhance debugging - Changed GoReleaser execution argument from '--debug' to '--snapshot' for improved release management. - Added GORELEASER_DEBUG environment variable to enable detailed logging during the release process. - Introduced GORELEASER_CURRENT_TAG environment variable to capture the current version tag, enhancing visibility in the workflow. --- .github/workflows/auto-tag-release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index eb55560..38945e8 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -156,7 +156,7 @@ jobs: with: distribution: goreleaser version: latest - args: release --clean --timeout 60m --debug + args: release --clean --timeout 60m --snapshot env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ steps.get_latest_tag.outputs.version }} @@ -166,6 +166,8 @@ jobs: GOROOT: ${{ env.GOROOT }} GOCACHE: ${{ github.workspace }}/.cache/go-build GOMODCACHE: ${{ github.workspace }}/go/pkg/mod + GORELEASER_DEBUG: 1 + GORELEASER_CURRENT_TAG: ${{ steps.get_latest_tag.outputs.version }} # 添加错误检查步骤 - name: Check GoReleaser Output From 8f40c70504948959d78a5879bc591be5651af12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:25:59 +0800 Subject: [PATCH 58/68] chore: update GoReleaser configuration and enhance auto-tag workflow - Upgraded GoReleaser configuration to version 2, adding build verification and improved release settings. - Replaced 'go mod download' with 'go mod vendor' and 'go mod verify' for better dependency management. - Introduced additional build information such as version, commit, and date in the build flags. - Enhanced the auto-tag workflow by adding a step to prepare the build environment and sync the vendor directory. - Improved release notes formatting with a header and footer for better clarity in changelogs. --- .github/workflows/auto-tag-release.yml | 32 ++++++++++++++++++++++++-- .goreleaser.yml | 27 +++++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index 38945e8..73e2f54 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -149,6 +149,14 @@ jobs: go mod vendor fi + # 添加构建环境准备步骤 + - name: Prepare Build Environment + run: | + echo "Building version: ${VERSION:-development}" + echo "GOOS=${GOOS:-$(go env GOOS)}" >> $GITHUB_ENV + echo "GOARCH=${GOARCH:-$(go env GOARCH)}" >> $GITHUB_ENV + echo "GO111MODULE=on" >> $GITHUB_ENV + # 修改 GoReleaser 步骤 - name: Run GoReleaser if: ${{ startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') }} @@ -156,18 +164,38 @@ jobs: with: distribution: goreleaser version: latest - args: release --clean --timeout 60m --snapshot + args: release --clean --timeout 60m env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ steps.get_latest_tag.outputs.version }} CGO_ENABLED: 0 - GOFLAGS: -mod=vendor GOPATH: ${{ github.workspace }}/go GOROOT: ${{ env.GOROOT }} GOCACHE: ${{ github.workspace }}/.cache/go-build GOMODCACHE: ${{ github.workspace }}/go/pkg/mod GORELEASER_DEBUG: 1 GORELEASER_CURRENT_TAG: ${{ steps.get_latest_tag.outputs.version }} + # 添加额外的构建信息 + BUILD_TIME: ${{ steps.get_latest_tag.outputs.version }} + BUILD_COMMIT: ${{ github.sha }} + + # 优化 vendor 同步步骤 + - name: Sync vendor directory + run: | + echo "Syncing vendor directory..." + go mod tidy + go mod vendor + go mod verify + # 验证 vendor 目录 + if [ -d "vendor" ]; then + echo "Verifying vendor directory..." + go mod verify + # 检查是否有未跟踪的文件 + if [ -n "$(git status --porcelain vendor/)" ]; then + echo "Warning: Vendor directory has uncommitted changes" + git status vendor/ + fi + fi # 添加错误检查步骤 - name: Check GoReleaser Output diff --git a/.goreleaser.yml b/.goreleaser.yml index b170c38..1934821 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,7 +1,10 @@ +version: 2 + before: hooks: - go mod tidy - - go mod download + - go mod vendor + - go mod verify builds: - id: cursor-id-modifier @@ -23,10 +26,32 @@ builds: ldflags: - -s -w - -X 'main.version={{.Version}}' + - -X 'main.commit={{.Commit}}' + - -X 'main.date={{.Date}}' flags: - -trimpath mod_timestamp: '{{ .CommitTimestamp }}' +# 添加构建时的项目设置 +project_name: cursor-id-modifier + +# 添加构建验证 +builds_verify: + - skip: false + cmd: "{{.Binary}} --version" + +# 改进发布配置 +release: + draft: true + prerelease: auto + mode: replace + header: | + ## Release {{.Tag}} ({{.Date}}) + + See [CHANGELOG.md](CHANGELOG.md) for details. + footer: | + **Full Changelog**: https://github.com/owner/repo/compare/{{ .PreviousTag }}...{{ .Tag }} + archives: - id: binary format: binary From d36f868f4edb388f5dc0a4fb46228b4ade8a1401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:29:04 +0800 Subject: [PATCH 59/68] chore: update GoReleaser configuration for improved build and release process - Added GO111MODULE environment variable to ensure module support during builds. - Updated build flags to include short commit and commit date for better traceability. - Enhanced release configuration with additional files and improved changelog filtering. - Reorganized project settings and removed obsolete build verification steps for clarity. --- .goreleaser.yml | 65 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 1934821..561e6b5 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -12,6 +12,7 @@ builds: binary: cursor-id-modifier env: - CGO_ENABLED=0 + - GO111MODULE=on goos: - linux - windows @@ -26,32 +27,13 @@ builds: ldflags: - -s -w - -X 'main.version={{.Version}}' - - -X 'main.commit={{.Commit}}' - - -X 'main.date={{.Date}}' + - -X 'main.commit={{.ShortCommit}}' + - -X 'main.date={{.CommitDate}}' + - -X 'main.builtBy=goreleaser' flags: - -trimpath mod_timestamp: '{{ .CommitTimestamp }}' -# 添加构建时的项目设置 -project_name: cursor-id-modifier - -# 添加构建验证 -builds_verify: - - skip: false - cmd: "{{.Binary}} --version" - -# 改进发布配置 -release: - draft: true - prerelease: auto - mode: replace - header: | - ## Release {{.Tag}} ({{.Date}}) - - See [CHANGELOG.md](CHANGELOG.md) for details. - footer: | - **Full Changelog**: https://github.com/owner/repo/compare/{{ .PreviousTag }}...{{ .Tag }} - archives: - id: binary format: binary @@ -61,11 +43,50 @@ archives: {{- if eq .Arch "amd64" }}x64{{ end }} {{- if eq .Arch "386" }}x86{{ end }} {{- if eq .Arch "arm64" }}arm64{{ end }} + replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 checksum: name_template: 'checksums.txt' algorithm: sha256 +release: + draft: true + prerelease: auto + mode: replace + header: | + ## Release {{.Tag}} ({{.Date}}) + + See [CHANGELOG.md](CHANGELOG.md) for details. + footer: | + **Full Changelog**: https://github.com/owner/repo/compare/{{ .PreviousTag }}...{{ .Tag }} + extra_files: + - glob: 'LICENSE*' + - glob: 'README*' + - glob: 'CHANGELOG*' + changelog: sort: asc use: github + filters: + exclude: + - '^docs:' + - '^test:' + - '^ci:' + - Merge pull request + - Merge branch + groups: + - title: Features + regexp: "^.*feat[(\\w)]*:+.*$" + order: 0 + - title: 'Bug fixes' + regexp: "^.*fix[(\\w)]*:+.*$" + order: 1 + - title: Others + order: 999 + +project_name: cursor-id-modifier From dc7761593da39b085e805c559be6854cae0cbd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:32:49 +0800 Subject: [PATCH 60/68] chore: update GoReleaser configuration for improved binary naming and build settings - Modified binary naming template to include project name and version for better identification. - Enhanced architecture handling in the naming template to support additional architectures. - Added 'cursor-id-modifier' to the build settings for more specific build targeting. - Allowed different binary counts in the release process, improving flexibility in output. --- .goreleaser.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 561e6b5..13c0b21 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -38,17 +38,17 @@ archives: - id: binary format: binary name_template: >- - {{ .Binary }}_ + {{- .ProjectName }}_ + {{- .Version }}_ {{- .Os }}_ - {{- if eq .Arch "amd64" }}x64{{ end }} - {{- if eq .Arch "386" }}x86{{ end }} - {{- if eq .Arch "arm64" }}arm64{{ end }} - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + builds: + - cursor-id-modifier + allow_different_binary_count: true + files: + - none* checksum: name_template: 'checksums.txt' From 397e9d950ec30878d8d39e7b0d73d7250825fe16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:35:18 +0800 Subject: [PATCH 61/68] chore: update .gitignore and enhance auto-tag workflow with cleanup steps - Added Go workspace and cache directories to .gitignore to prevent unnecessary files from being tracked. - Introduced a cleanup step in the auto-tag workflow to remove temporary Go directories and cached files, improving workspace management. - Updated GOPATH, GOCACHE, and GOMODCACHE paths in the workflow to use temporary directories for better isolation during builds. --- .github/workflows/auto-tag-release.yml | 21 ++++++++++++--------- .gitignore | 2 ++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/auto-tag-release.yml b/.github/workflows/auto-tag-release.yml index 73e2f54..ab9e5e5 100644 --- a/.github/workflows/auto-tag-release.yml +++ b/.github/workflows/auto-tag-release.yml @@ -133,12 +133,6 @@ jobs: exit 1 fi - # 添加 Go 工作目录设置 - - name: Set Go Work Directory - run: | - echo "GOPATH=${{ github.workspace }}/go" >> $GITHUB_ENV - echo "${{ github.workspace }}/go/bin" >> $GITHUB_PATH - # 添加依赖检查步骤 - name: Check Dependencies run: | @@ -157,6 +151,15 @@ jobs: echo "GOARCH=${GOARCH:-$(go env GOARCH)}" >> $GITHUB_ENV echo "GO111MODULE=on" >> $GITHUB_ENV + # 添加清理步骤 + - name: Cleanup workspace + run: | + rm -rf /tmp/go/ + rm -rf .cache/ + rm -rf dist/ + git clean -fdx + git status + # 修改 GoReleaser 步骤 - name: Run GoReleaser if: ${{ startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') }} @@ -169,10 +172,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ steps.get_latest_tag.outputs.version }} CGO_ENABLED: 0 - GOPATH: ${{ github.workspace }}/go + GOPATH: /tmp/go GOROOT: ${{ env.GOROOT }} - GOCACHE: ${{ github.workspace }}/.cache/go-build - GOMODCACHE: ${{ github.workspace }}/go/pkg/mod + GOCACHE: /tmp/.cache/go-build + GOMODCACHE: /tmp/go/pkg/mod GORELEASER_DEBUG: 1 GORELEASER_CURRENT_TAG: ${{ steps.get_latest_tag.outputs.version }} # 添加额外的构建信息 diff --git a/.gitignore b/.gitignore index 2d90d08..27161b5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ dist/ # Go specific go.sum +go/ +.cache/ # IDE and editor files .vscode/ From df71df76e27b10511a6006e71be6df025b0d838e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:43:59 +0800 Subject: [PATCH 62/68] refactor: update project references from 'dacrab' to 'yuaotian' - Changed module path in go.mod and updated all relevant URLs in README, CHANGELOG, and installation scripts to reflect the new GitHub repository. - Updated import paths in the main.go file to align with the new module name. - Adjusted API calls in installation scripts to fetch the latest release from the new repository location. --- CHANGELOG.md | 4 ++-- README.md | 18 +++++++++--------- cmd/cursor-id-modifier/main.go | 10 +++++----- go.mod | 2 +- scripts/install.ps1 | 2 +- scripts/install.sh | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 261d36b..2af6908 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Enhanced error troubleshooting guide --- -*For more details about the changes, please refer to the [commit history](https://github.com/dacrab/go-cursor-help/commits/main).* +*For more details about the changes, please refer to the [commit history](https://github.com/yuaotian/go-cursor-help/commits/main).* -[0.1.22]: https://github.com/dacrab/go-cursor-help/releases/tag/v0.1.23 \ No newline at end of file +[0.1.22]: https://github.com/yuaotian/go-cursor-help/releases/tag/v0.1.23 \ No newline at end of file diff --git a/README.md b/README.md index faece6c..edc9eb6 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@
-[![Release](https://img.shields.io/github/v/release/dacrab/go-cursor-help?style=flat-square&logo=github&color=blue)](https://github.com/dacrab/go-cursor-help/releases/latest) -[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/dacrab/go-cursor-help/blob/master/LICENSE) -[![Stars](https://img.shields.io/github/stars/dacrab/go-cursor-help?style=flat-square&logo=github)](https://github.com/dacrab/go-cursor-help/stargazers) +[![Release](https://img.shields.io/github/v/release/yuaotian/go-cursor-help?style=flat-square&logo=github&color=blue)](https://github.com/yuaotian/go-cursor-help/releases/latest) +[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&logo=bookstack)](https://github.com/yuaotian/go-cursor-help/blob/master/LICENSE) +[![Stars](https://img.shields.io/github/stars/yuaotian/go-cursor-help?style=flat-square&logo=github)](https://github.com/yuaotian/go-cursor-help/stargazers) [🌟 English](#english) | [🌏 中文](#chinese) @@ -60,12 +60,12 @@ this is a mistake. **Linux/macOS**: Copy and paste in terminal ```bash -curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash +curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.sh | sudo bash ``` **Windows**: Copy and paste in PowerShell ```powershell -irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex +irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.ps1 | iex ``` #### Windows Installation Features: @@ -80,7 +80,7 @@ That's it! The script will: ### 📦 Manual Installation -> Download the appropriate file for your system from [releases](https://github.com/dacrab/go-cursor-help/releases/latest) +> Download the appropriate file for your system from [releases](https://github.com/yuaotian/go-cursor-help/releases/latest)
Windows Packages @@ -178,12 +178,12 @@ this is a mistake. **Linux/macOS**: 在终端中复制粘贴 ```bash -curl -fsSL https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.sh | sudo bash +curl -fsSL https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.sh | sudo bash ``` **Windows**: 在PowerShell中复制粘贴 ```powershell -irm https://raw.githubusercontent.com/dacrab/go-cursor-help/master/scripts/install.ps1 | iex +irm https://raw.githubusercontent.com/yuaotian/go-cursor-help/master/scripts/install.ps1 | iex ``` #### Windows 安装特性: @@ -198,7 +198,7 @@ That's it! The script will: ### 📦 Manual Installation -> Download the appropriate file for your system from [releases](https://github.com/dacrab/go-cursor-help/releases/latest) +> Download the appropriate file for your system from [releases](https://github.com/yuaotian/go-cursor-help/releases/latest)
Windows Packages diff --git a/cmd/cursor-id-modifier/main.go b/cmd/cursor-id-modifier/main.go index 860b444..7560e78 100644 --- a/cmd/cursor-id-modifier/main.go +++ b/cmd/cursor-id-modifier/main.go @@ -11,11 +11,11 @@ import ( "runtime/debug" "strings" - "github.com/dacrab/go-cursor-help/internal/config" - "github.com/dacrab/go-cursor-help/internal/lang" - "github.com/dacrab/go-cursor-help/internal/process" - "github.com/dacrab/go-cursor-help/internal/ui" - "github.com/dacrab/go-cursor-help/pkg/idgen" + "github.com/yuaotian/go-cursor-help/internal/config" + "github.com/yuaotian/go-cursor-help/internal/lang" + "github.com/yuaotian/go-cursor-help/internal/process" + "github.com/yuaotian/go-cursor-help/internal/ui" + "github.com/yuaotian/go-cursor-help/pkg/idgen" "github.com/sirupsen/logrus" ) diff --git a/go.mod b/go.mod index d3192c7..6bcebe3 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/dacrab/go-cursor-help +module github.com/yuaotian/go-cursor-help go 1.21 diff --git a/scripts/install.ps1 b/scripts/install.ps1 index a92fc1f..4d4deaa 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -98,7 +98,7 @@ function Install-CursorModifier { # Get latest release try { - $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/dacrab/go-cursor-help/releases/latest" + $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest" Write-Host "Found latest release: $($latestRelease.tag_name)" -ForegroundColor Cyan # Look for Windows binary with our architecture diff --git a/scripts/install.sh b/scripts/install.sh index 7ebe96d..13dc62d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -87,7 +87,7 @@ main() { # Get latest release info echo -e "${BLUE}Fetching latest release information...${NC}" - LATEST_URL="https://api.github.com/repos/dacrab/go-cursor-help/releases/latest" + LATEST_URL="https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest" # Construct binary name BINARY_NAME="cursor-id-modifier_${OS}_${ARCH}${SUFFIX}" From a0d84c8290cb2a5cb83086485a8a884aa93e2826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:52:39 +0800 Subject: [PATCH 63/68] fix: update architecture naming in installation scripts - Changed architecture identifiers from 'x64' to 'x86_64' and 'x86' to 'i386' in both PowerShell and shell scripts for consistency with standard naming conventions. - Updated binary naming in the installation scripts to include the latest release tag for better version tracking. - Enhanced error handling in the shell script to display available assets when a binary is not found. --- scripts/install.ps1 | 10 ++++------ scripts/install.sh | 15 +++++++-------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 4d4deaa..c5bd05e 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -56,9 +56,9 @@ trap { # Detect system architecture function Get-SystemArch { if ([Environment]::Is64BitOperatingSystem) { - return "x64" + return "x86_64" } else { - return "x86" + return "i386" } } @@ -103,10 +103,8 @@ function Install-CursorModifier { # Look for Windows binary with our architecture $possibleNames = @( - "cursor-id-modifier_windows_$($arch).exe", - "cursor-id-modifier_windows_$($arch).exe", - "cursor-id-modifier_Windows_$($arch).exe", - "cursor-id-modifier_Windows_$($arch).exe" + "cursor-id-modifier_$($latestRelease.tag_name)_windows_$($arch).exe", + "cursor-id-modifier_windows_$($arch).exe" ) $asset = $null diff --git a/scripts/install.sh b/scripts/install.sh index 13dc62d..a437d6d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -33,21 +33,18 @@ detect_system() { case "$(uname -m)" in x86_64) - arch="x64" - [ "$os" = "darwin" ] && suffix="_intel" + arch="x86_64" ;; aarch64|arm64) arch="arm64" - [ "$os" = "darwin" ] && suffix="_apple_silicon" ;; i386|i686) - arch="x86" - [ "$os" = "darwin" ] && { echo -e "${RED}32-bit not supported on macOS${NC}"; exit 1; } + arch="i386" ;; *) echo -e "${RED}Unsupported architecture${NC}"; exit 1;; esac - echo "$os $arch $suffix" + echo "$os $arch" } # Download with progress @@ -90,14 +87,16 @@ main() { LATEST_URL="https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest" # Construct binary name - BINARY_NAME="cursor-id-modifier_${OS}_${ARCH}${SUFFIX}" + BINARY_NAME="cursor-id-modifier_${latestRelease.tag_name}_${OS}_${ARCH}" echo -e "${BLUE}Looking for asset: $BINARY_NAME${NC}" # Get download URL directly - DOWNLOAD_URL=$(curl -s "$LATEST_URL" | tr -d '\n' | grep -o "\"browser_download_url\": \"[^\"]*${BINARY_NAME}[^\"]*\"" | cut -d'"' -f4) + DOWNLOAD_URL=$(curl -s "$LATEST_URL" | grep -o "\"browser_download_url\": \"[^\"]*${BINARY_NAME}[^\"]*\"" | cut -d'"' -f4) if [ -z "$DOWNLOAD_URL" ]; then echo -e "${RED}Error: Could not find appropriate binary for $OS $ARCH${NC}" + echo -e "${YELLOW}Available assets:${NC}" + curl -s "$LATEST_URL" | grep "browser_download_url" | cut -d'"' -f4 exit 1 fi From d32476d8275765a05d049a43902a0cedcb096cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:54:23 +0800 Subject: [PATCH 64/68] feat: add architecture-specific binary naming in installation script - Introduced a new binary naming format for the cursor-id-modifier to include the latest release tag and support the x86_64 architecture. - This change enhances version tracking and aligns with standard naming conventions for better clarity in the installation process. --- scripts/install.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index c5bd05e..bf50c85 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -103,6 +103,7 @@ function Install-CursorModifier { # Look for Windows binary with our architecture $possibleNames = @( + "cursor-id-modifier_$($latestRelease.tag_name)_windows_x86_64.exe", "cursor-id-modifier_$($latestRelease.tag_name)_windows_$($arch).exe", "cursor-id-modifier_windows_$($arch).exe" ) From 8f52929e59c8a41ad1b6a8b1c6fef075e10c70e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 19:57:00 +0800 Subject: [PATCH 65/68] fix: update binary naming in installation scripts to remove 'v' prefix - Modified the installation scripts for both PowerShell and shell to remove the 'v' prefix from the latest release tag when constructing binary names. - This change ensures consistency in binary naming and improves compatibility with the expected format for asset retrieval. --- scripts/install.ps1 | 4 ++-- scripts/install.sh | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index bf50c85..8792208 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -103,8 +103,8 @@ function Install-CursorModifier { # Look for Windows binary with our architecture $possibleNames = @( - "cursor-id-modifier_$($latestRelease.tag_name)_windows_x86_64.exe", - "cursor-id-modifier_$($latestRelease.tag_name)_windows_$($arch).exe", + "cursor-id-modifier_$($latestRelease.tag_name.TrimStart('v'))_windows_x86_64.exe", + "cursor-id-modifier_$($latestRelease.tag_name.TrimStart('v'))_windows_$($arch).exe", "cursor-id-modifier_windows_$($arch).exe" ) diff --git a/scripts/install.sh b/scripts/install.sh index a437d6d..adc7881 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -86,8 +86,11 @@ main() { echo -e "${BLUE}Fetching latest release information...${NC}" LATEST_URL="https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest" + # Get latest version and remove 'v' prefix + VERSION=$(curl -s "$LATEST_URL" | grep "tag_name" | cut -d'"' -f4 | sed 's/^v//') + # Construct binary name - BINARY_NAME="cursor-id-modifier_${latestRelease.tag_name}_${OS}_${ARCH}" + BINARY_NAME="cursor-id-modifier_${VERSION}_${OS}_${ARCH}" echo -e "${BLUE}Looking for asset: $BINARY_NAME${NC}" # Get download URL directly From 12468bc3635ed8926369aab2bb75f6038b18f4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 30 Dec 2024 20:06:03 +0800 Subject: [PATCH 66/68] fix: comment out version assignment in config preparation - Commented out the version assignment in the prepareUpdatedConfig function within the config.go file to prevent hardcoding of the version number. - This change allows for more dynamic version handling in the configuration process, aligning with recent updates in the installation scripts that utilize the latest release tag for versioning. --- internal/config/config.go | 2 +- scripts/install.ps1 | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index b3d2c81..75b64df 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -91,7 +91,7 @@ func (m *Manager) prepareUpdatedConfig(config *StorageConfig) map[string]interfa originalFile["telemetry.machineId"] = config.TelemetryMachineId originalFile["telemetry.devDeviceId"] = config.TelemetryDevDeviceId originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339) - originalFile["version"] = "1.0.1" + // originalFile["version"] = "1.0.1" return originalFile } diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 8792208..b30350b 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -102,10 +102,11 @@ function Install-CursorModifier { Write-Host "Found latest release: $($latestRelease.tag_name)" -ForegroundColor Cyan # Look for Windows binary with our architecture + $version = $latestRelease.tag_name.TrimStart('v') + Write-Host "Version: $version" -ForegroundColor Cyan $possibleNames = @( - "cursor-id-modifier_$($latestRelease.tag_name.TrimStart('v'))_windows_x86_64.exe", - "cursor-id-modifier_$($latestRelease.tag_name.TrimStart('v'))_windows_$($arch).exe", - "cursor-id-modifier_windows_$($arch).exe" + "cursor-id-modifier_${version}_windows_x86_64.exe", + "cursor-id-modifier_${version}_windows_$($arch).exe" ) $asset = $null From 15d8210bd436651ba33eb02e4ddc7043844a93df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Wed, 1 Jan 2025 18:07:40 +0800 Subject: [PATCH 67/68] feat: enhance GenerateMachineID to include auth0|user_ prefix - Updated the GenerateMachineID function to generate a machine ID with the prefix "auth0|user_". - The random part of the ID is now 25 bytes, producing a 50-character hexadecimal string. - This change improves the uniqueness and format of generated machine IDs, aligning with authentication standards. --- pkg/idgen/generator.go | 19 +++++++++++++-- pkg/idgen/generator_test.go | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 pkg/idgen/generator_test.go diff --git a/pkg/idgen/generator.go b/pkg/idgen/generator.go index 4a69341..1b68d83 100644 --- a/pkg/idgen/generator.go +++ b/pkg/idgen/generator.go @@ -35,10 +35,25 @@ func (g *Generator) generateRandomHex(length int) (string, error) { // Public methods // ------------- -// GenerateMachineID generates a new 32-byte machine ID +// GenerateMachineID generates a new machine ID with auth0|user_ prefix func (g *Generator) GenerateMachineID() (string, error) { g.simulateWork() - return g.generateRandomHex(32) + + // 生成随机部分 (25字节,将产生50个十六进制字符) + randomPart, err := g.generateRandomHex(25) + if err != nil { + return "", err + } + + // 构建完整的ID: "auth0|user_" + random + prefix := "auth0|user_" + fullID := fmt.Sprintf("%x%x%s", + []byte(prefix), // 转换前缀为十六进制 + []byte("0"), // 添加一个字符 + randomPart, // 随机部分 + ) + + return fullID, nil } // GenerateMacMachineID generates a new 64-byte MAC machine ID diff --git a/pkg/idgen/generator_test.go b/pkg/idgen/generator_test.go new file mode 100644 index 0000000..6fbb373 --- /dev/null +++ b/pkg/idgen/generator_test.go @@ -0,0 +1,47 @@ +package idgen + +import ( + "fmt" + "strings" + "testing" +) + +func TestGenerateMachineID(t *testing.T) { + g := NewGenerator() + + fmt.Println("\n=== 开始测试 MachineID 生成 ===") + + // 运行多次测试以确保一致性 + for i := 0; i < 10; i++ { + id, err := g.GenerateMachineID() + if err != nil { + t.Fatalf("生成ID时发生错误: %v", err) + } + + fmt.Printf("\n第 %d 次测试:\n", i+1) + fmt.Printf("生成的 ID: %s\n", id) + fmt.Printf("ID 长度: %d\n", len(id)) + fmt.Printf("前缀部分: %s\n", id[:20]) + fmt.Printf("随机部分: %s\n", id[20:]) + + // 测试1: 验证总长度 + if len(id) != 74 { + t.Errorf("ID长度不正确. 期望: 74, 实际: %d", len(id)) + } + + // 测试2: 验证前缀 + expectedPrefix := "61757468307c757365725f" // "auth0|user_" 的十六进制 + if !strings.HasPrefix(id, expectedPrefix) { + t.Errorf("ID前缀不正确.\n期望前缀: %s\n实际ID: %s", expectedPrefix, id) + } + + // 测试3: 验证十六进制格式 + for _, c := range id { + if !strings.ContainsRune("0123456789abcdef", c) { + t.Errorf("ID包含非十六进制字符: %c", c) + } + } + } + + fmt.Println("\n=== 测试完成 ===") +} From 4683460ed591bcd06040758ab4ef9de0c33be76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=85=8E=E9=A5=BC=E6=9E=9C=E5=AD=90=E5=8D=B7=E9=B2=A8?= =?UTF-8?q?=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Wed, 1 Jan 2025 18:59:12 +0800 Subject: [PATCH 68/68] refactor: update ID generation methods and remove obsolete test file - Renamed the GenerateMacMachineID method to GenerateSQMID and updated its implementation to generate a new SQM ID in UUID format. - Enhanced the ID generation logic by introducing a buffer pool for efficient memory management. - Removed the generator_test.go file as it was no longer needed, streamlining the codebase. - Updated the generateNewConfig function to utilize the new SQMID generation method, ensuring consistency across ID generation processes. --- cmd/cursor-id-modifier/main.go | 2 +- pkg/idgen/generator.go | 108 +++++++++++++++++++++++---------- pkg/idgen/generator_test.go | 47 -------------- 3 files changed, 76 insertions(+), 81 deletions(-) delete mode 100644 pkg/idgen/generator_test.go diff --git a/cmd/cursor-id-modifier/main.go b/cmd/cursor-id-modifier/main.go index 7560e78..89de5f3 100644 --- a/cmd/cursor-id-modifier/main.go +++ b/cmd/cursor-id-modifier/main.go @@ -240,7 +240,7 @@ func generateNewConfig(display *ui.Display, generator *idgen.Generator, oldConfi if oldConfig != nil && oldConfig.TelemetrySqmId != "" { newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId - } else if sqmID, err := generator.GenerateMacMachineID(); err != nil { + } else if sqmID, err := generator.GenerateSQMID(); err != nil { log.Fatal("Failed to generate SQM ID:", err) } else { newConfig.TelemetrySqmId = sqmID diff --git a/pkg/idgen/generator.go b/pkg/idgen/generator.go index 1b68d83..3061f74 100644 --- a/pkg/idgen/generator.go +++ b/pkg/idgen/generator.go @@ -4,71 +4,113 @@ import ( "crypto/rand" "encoding/hex" "fmt" - "time" + "sync" ) // Generator handles secure ID generation for machines and devices -type Generator struct{} +type Generator struct { + bufferPool sync.Pool +} // NewGenerator creates a new ID generator func NewGenerator() *Generator { - return &Generator{} + return &Generator{ + bufferPool: sync.Pool{ + New: func() interface{} { + return make([]byte, 64) + }, + }, + } } -// Helper methods -// ------------- - -// simulateWork adds a small delay to make progress visible -func (g *Generator) simulateWork() { - time.Sleep(800 * time.Millisecond) -} +// Constants for ID generation +const ( + machineIDPrefix = "auth0|user_" + uuidFormat = "%s-%s-%s-%s-%s" +) // generateRandomHex generates a random hex string of specified length func (g *Generator) generateRandomHex(length int) (string, error) { - bytes := make([]byte, length) - if _, err := rand.Read(bytes); err != nil { + buffer := g.bufferPool.Get().([]byte) + defer g.bufferPool.Put(buffer) + + if _, err := rand.Read(buffer[:length]); err != nil { return "", fmt.Errorf("failed to generate random bytes: %w", err) } - return hex.EncodeToString(bytes), nil + return hex.EncodeToString(buffer[:length]), nil } -// Public methods -// ------------- - // GenerateMachineID generates a new machine ID with auth0|user_ prefix func (g *Generator) GenerateMachineID() (string, error) { - g.simulateWork() - - // 生成随机部分 (25字节,将产生50个十六进制字符) - randomPart, err := g.generateRandomHex(25) + randomPart, err := g.generateRandomHex(32) // 生成64字符的十六进制 if err != nil { return "", err } - // 构建完整的ID: "auth0|user_" + random - prefix := "auth0|user_" - fullID := fmt.Sprintf("%x%x%s", - []byte(prefix), // 转换前缀为十六进制 - []byte("0"), // 添加一个字符 - randomPart, // 随机部分 - ) - - return fullID, nil + return fmt.Sprintf("%x%s", []byte(machineIDPrefix), randomPart), nil } // GenerateMacMachineID generates a new 64-byte MAC machine ID func (g *Generator) GenerateMacMachineID() (string, error) { - g.simulateWork() - return g.generateRandomHex(64) + return g.generateRandomHex(32) // 生成64字符的十六进制 } // GenerateDeviceID generates a new device ID in UUID format func (g *Generator) GenerateDeviceID() (string, error) { - g.simulateWork() id, err := g.generateRandomHex(16) if err != nil { return "", err } - return fmt.Sprintf("%s-%s-%s-%s-%s", + return fmt.Sprintf(uuidFormat, id[0:8], id[8:12], id[12:16], id[16:20], id[20:32]), nil } + +// GenerateSQMID generates a new SQM ID in UUID format (with braces) +func (g *Generator) GenerateSQMID() (string, error) { + id, err := g.GenerateDeviceID() + if err != nil { + return "", err + } + return fmt.Sprintf("{%s}", id), nil +} + +// ValidateID validates the format of various ID types +func (g *Generator) ValidateID(id string, idType string) bool { + switch idType { + case "machineID", "macMachineID": + return len(id) == 64 && isHexString(id) + case "deviceID": + return isValidUUID(id) + case "sqmID": + if len(id) < 2 || id[0] != '{' || id[len(id)-1] != '}' { + return false + } + return isValidUUID(id[1 : len(id)-1]) + default: + return false + } +} + +// Helper functions +func isHexString(s string) bool { + _, err := hex.DecodeString(s) + return err == nil +} + +func isValidUUID(uuid string) bool { + if len(uuid) != 36 { + return false + } + for i, r := range uuid { + if i == 8 || i == 13 || i == 18 || i == 23 { + if r != '-' { + return false + } + continue + } + if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) { + return false + } + } + return true +} diff --git a/pkg/idgen/generator_test.go b/pkg/idgen/generator_test.go deleted file mode 100644 index 6fbb373..0000000 --- a/pkg/idgen/generator_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package idgen - -import ( - "fmt" - "strings" - "testing" -) - -func TestGenerateMachineID(t *testing.T) { - g := NewGenerator() - - fmt.Println("\n=== 开始测试 MachineID 生成 ===") - - // 运行多次测试以确保一致性 - for i := 0; i < 10; i++ { - id, err := g.GenerateMachineID() - if err != nil { - t.Fatalf("生成ID时发生错误: %v", err) - } - - fmt.Printf("\n第 %d 次测试:\n", i+1) - fmt.Printf("生成的 ID: %s\n", id) - fmt.Printf("ID 长度: %d\n", len(id)) - fmt.Printf("前缀部分: %s\n", id[:20]) - fmt.Printf("随机部分: %s\n", id[20:]) - - // 测试1: 验证总长度 - if len(id) != 74 { - t.Errorf("ID长度不正确. 期望: 74, 实际: %d", len(id)) - } - - // 测试2: 验证前缀 - expectedPrefix := "61757468307c757365725f" // "auth0|user_" 的十六进制 - if !strings.HasPrefix(id, expectedPrefix) { - t.Errorf("ID前缀不正确.\n期望前缀: %s\n实际ID: %s", expectedPrefix, id) - } - - // 测试3: 验证十六进制格式 - for _, c := range id { - if !strings.ContainsRune("0123456789abcdef", c) { - t.Errorf("ID包含非十六进制字符: %c", c) - } - } - } - - fmt.Println("\n=== 测试完成 ===") -}