2026-11-10 01:24:28 +08:00
|
|
|
|
version: "3"
|
|
|
|
|
|
|
|
|
|
|
|
includes:
|
|
|
|
|
|
common: ./build/Taskfile.yml
|
|
|
|
|
|
windows: ./build/windows/Taskfile.yml
|
|
|
|
|
|
darwin: ./build/darwin/Taskfile.yml
|
|
|
|
|
|
linux: ./build/linux/Taskfile.yml
|
|
|
|
|
|
|
|
|
|
|
|
vars:
|
|
|
|
|
|
APP_NAME: "Local-Cursor"
|
|
|
|
|
|
BIN_DIR: "bin"
|
|
|
|
|
|
APP_VERSION:
|
|
|
|
|
|
sh: 'go run ./scripts/release version -config ./build/config.yml'
|
|
|
|
|
|
RELEASE_REPO: ""
|
|
|
|
|
|
RELEASE_BASE_NAME: "Local-Cursor"
|
|
|
|
|
|
RELEASE_SOURCE_PATH: "release-notes.md"
|
|
|
|
|
|
RELEASE_DIR: '{{.BIN_DIR}}/release/{{.APP_VERSION}}'
|
|
|
|
|
|
WINDOWS_RELEASE_DIR: '{{.BIN_DIR}}/release/v{{.APP_VERSION}}'
|
|
|
|
|
|
RELEASE_NOTES_PATH: '{{.RELEASE_DIR}}/.release-notes.md'
|
|
|
|
|
|
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
|
|
|
|
|
|
SCAN: '{{.SCAN | default "false"}}'
|
|
|
|
|
|
CURRENT_WINDOWS_ARCH: '{{if eq ARCH "386"}}386{{else}}amd64{{end}}'
|
|
|
|
|
|
CURRENT_WINDOWS_NAME: '{{if eq .CURRENT_WINDOWS_ARCH "386"}}windows-32{{else}}windows-64{{end}}'
|
|
|
|
|
|
CURRENT_DARWIN_NAME: '{{if eq ARCH "arm64"}}macos-arm64{{else}}macos-intel{{end}}'
|
|
|
|
|
|
CURRENT_LINUX_ARCH: "amd64"
|
|
|
|
|
|
CURRENT_LINUX_NAME: "linux-amd64"
|
|
|
|
|
|
|
|
|
|
|
|
tasks:
|
|
|
|
|
|
build:
|
|
|
|
|
|
summary: 构建当前系统分发包
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: '[ "{{OS}}" = "darwin" ] || [ "{{OS}}" = "windows" ] || [ "{{OS}}" = "linux" ]'
|
|
|
|
|
|
msg: "仅支持在 macOS、Windows 或 Linux 上执行 task build"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: clean:dist
|
|
|
|
|
|
- task: build:current
|
|
|
|
|
|
|
|
|
|
|
|
run:
|
|
|
|
|
|
summary: 运行当前系统版本
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: "{{OS}}:run"
|
|
|
|
|
|
|
|
|
|
|
|
build:all:
|
|
|
|
|
|
summary: 构建全部 macOS/Windows 分发包(Linux 需在原生 Linux 主机单独构建)
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: '[ "{{OS}}" = "darwin" ]'
|
|
|
|
|
|
msg: "task build:all 仅支持在 macOS 上执行;Linux 需在原生 Linux 主机构建。"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: clean:dist
|
|
|
|
|
|
- task: build:darwin:arm64
|
|
|
|
|
|
- task: build:darwin:amd64
|
|
|
|
|
|
- task: build:windows:386
|
|
|
|
|
|
- task: build:windows:amd64
|
|
|
|
|
|
|
|
|
|
|
|
build:current:
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: '{{if eq OS "darwin"}}build:darwin:current{{else if eq OS "linux"}}build:linux:current{{else}}build:windows:current{{end}}'
|
|
|
|
|
|
|
|
|
|
|
|
build:darwin:current:
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: darwin:package:dmg
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: '{{ARCH}}'
|
|
|
|
|
|
BINARY_NAME: '{{.CURRENT_DARWIN_NAME}}'
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/{{.CURRENT_DARWIN_NAME}}'
|
|
|
|
|
|
APP_BUNDLE: '{{.CURRENT_DARWIN_NAME}}.app'
|
|
|
|
|
|
DMG_NAME: '{{.CURRENT_DARWIN_NAME}}.dmg'
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
build:darwin:arm64:
|
|
|
|
|
|
internal: false
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: darwin:package:dmg
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: arm64
|
|
|
|
|
|
BINARY_NAME: macos-arm64
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/macos-arm64'
|
|
|
|
|
|
APP_BUNDLE: macos-arm64.app
|
|
|
|
|
|
DMG_NAME: macos-arm64.dmg
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
build:darwin:amd64:
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: darwin:package:dmg
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: amd64
|
|
|
|
|
|
BINARY_NAME: macos-intel
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/macos-intel'
|
|
|
|
|
|
APP_BUNDLE: macos-intel.app
|
|
|
|
|
|
DMG_NAME: macos-intel.dmg
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
build:windows:current:
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: windows:create:zip
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: '{{.CURRENT_WINDOWS_ARCH}}'
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/{{.CURRENT_WINDOWS_NAME}}.exe'
|
|
|
|
|
|
ZIP_NAME: '{{.CURRENT_WINDOWS_NAME}}.zip'
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
build:windows:386:
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: windows:create:zip
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: 386
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/windows-32.exe'
|
|
|
|
|
|
ZIP_NAME: windows-32.zip
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
build:windows:amd64:
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: windows:create:zip
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: amd64
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/windows-64.exe'
|
|
|
|
|
|
ZIP_NAME: windows-64.zip
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
build:windows:installer:
|
|
|
|
|
|
summary: 构建 Windows NSIS 安装包(含卸载程序)
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: clean:dist
|
|
|
|
|
|
- task: windows:create:nsis:installer
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: amd64
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
build:linux:current:
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: linux:package:archive
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: '{{.CURRENT_LINUX_ARCH}}'
|
|
|
|
|
|
BINARY_NAME: '{{.CURRENT_LINUX_NAME}}'
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/{{.CURRENT_LINUX_NAME}}'
|
|
|
|
|
|
ARCHIVE_BINARY_NAME: '{{.APP_NAME}}'
|
|
|
|
|
|
ARCHIVE_PATH: '{{.BIN_DIR}}/{{.CURRENT_LINUX_NAME}}.tar.gz'
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
build:linux:amd64:
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: linux:package:archive
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: amd64
|
|
|
|
|
|
BINARY_NAME: linux-amd64
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/linux-amd64'
|
|
|
|
|
|
ARCHIVE_BINARY_NAME: '{{.APP_NAME}}'
|
|
|
|
|
|
ARCHIVE_PATH: '{{.BIN_DIR}}/linux-amd64.tar.gz'
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
cursor-tab-server:docker:amd64:
|
|
|
|
|
|
summary: 构建 cursor-tab-server 的 linux/amd64 Docker 镜像并保存到 cursor-tab-server 目录
|
|
|
|
|
|
vars:
|
|
|
|
|
|
IMAGE_NAME: '{{.IMAGE_NAME | default "cursor-tab-server:linux-amd64"}}'
|
|
|
|
|
|
IMAGE_TAR: '{{.IMAGE_TAR | default "cursor-tab-server/cursor-tab-server-linux-amd64.tar"}}'
|
|
|
|
|
|
CONTEXT_DIR: cursor-tab-server
|
|
|
|
|
|
DOCKERFILE: cursor-tab-server/Dockerfile
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: docker info >/dev/null 2>&1
|
|
|
|
|
|
msg: "未检测到可用的 Docker daemon,请先启动 Docker。"
|
|
|
|
|
|
- sh: test -f "{{.DOCKERFILE}}"
|
|
|
|
|
|
msg: "未找到 cursor-tab-server/Dockerfile。"
|
|
|
|
|
|
- sh: test -f "{{.CONTEXT_DIR}}/go.mod"
|
|
|
|
|
|
msg: "未找到 cursor-tab-server/go.mod。"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- mkdir -p "{{.CONTEXT_DIR}}"
|
|
|
|
|
|
- docker build --platform linux/amd64 -t "{{.IMAGE_NAME}}" -f "{{.DOCKERFILE}}" "{{.CONTEXT_DIR}}"
|
|
|
|
|
|
- docker save "{{.IMAGE_NAME}}" -o "{{.IMAGE_TAR}}"
|
|
|
|
|
|
- ls -lh "{{.IMAGE_TAR}}"
|
|
|
|
|
|
|
|
|
|
|
|
clean:dist:
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- mkdir -p "{{.BIN_DIR}}"
|
|
|
|
|
|
- rm -f "{{.BIN_DIR}}/macos-arm64.dmg" "{{.BIN_DIR}}/macos-intel.dmg" "{{.BIN_DIR}}/windows-32.zip" "{{.BIN_DIR}}/windows-64.zip" "{{.BIN_DIR}}/linux-amd64.tar.gz"
|
|
|
|
|
|
- rm -f "{{.BIN_DIR}}/macos-arm64" "{{.BIN_DIR}}/macos-intel" "{{.BIN_DIR}}/windows-32.exe" "{{.BIN_DIR}}/windows-64.exe" "{{.BIN_DIR}}/linux-amd64"
|
|
|
|
|
|
- rm -f "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.exe"
|
|
|
|
|
|
- rm -rf "{{.BIN_DIR}}/macos-arm64.app" "{{.BIN_DIR}}/macos-intel.app" "{{.BIN_DIR}}/{{.APP_NAME}}.app" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
|
|
|
|
|
|
|
|
|
|
|
|
dev:
|
|
|
|
|
|
summary: 启动开发模式(前台,终端会保持占用)
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
|
|
|
|
|
|
|
|
|
|
|
|
setup:docker:
|
|
|
|
|
|
summary: 构建 Linux 交叉编译 Docker 镜像
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: common:setup:docker
|
|
|
|
|
|
|
|
|
|
|
|
release:clean:
|
|
|
|
|
|
summary: 清理当前版本的发布目录
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- rm -rf "{{.RELEASE_DIR}}"
|
|
|
|
|
|
- mkdir -p "{{.RELEASE_DIR}}"
|
|
|
|
|
|
|
|
|
|
|
|
release:ensure:dir:
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- mkdir -p "{{.RELEASE_DIR}}"
|
|
|
|
|
|
|
|
|
|
|
|
release:notes:
|
|
|
|
|
|
summary: 从 release-notes.md 生成当前版本的发布说明文件(README.md 仅用于公开仓库同步)
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: test -f "{{.RELEASE_SOURCE_PATH}}"
|
|
|
|
|
|
msg: "未找到 release-notes.md,请先创建发布日志文件。"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- go run ./scripts/release notes -config ./build/config.yml -out "{{.RELEASE_NOTES_PATH}}" -source "{{.RELEASE_SOURCE_PATH}}"
|
|
|
|
|
|
|
|
|
|
|
|
release:build:macos:arm64:
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: darwin:package:archive
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: arm64
|
|
|
|
|
|
BINARY_NAME: cursor-release-macos-arm64
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/cursor-release-macos-arm64'
|
|
|
|
|
|
APP_BUNDLE: '{{.APP_NAME}}.app'
|
|
|
|
|
|
ARCHIVE_PATH: '{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-macos-arm64.tar.gz'
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
release:build:macos:amd64:
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: darwin:package:archive
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: amd64
|
|
|
|
|
|
BINARY_NAME: cursor-release-macos-amd64
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/cursor-release-macos-amd64'
|
|
|
|
|
|
APP_BUNDLE: '{{.APP_NAME}}.app'
|
|
|
|
|
|
ARCHIVE_PATH: '{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-macos-amd64.tar.gz'
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
release:build:windows:amd64:
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: windows:create:zip
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: amd64
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/{{.RELEASE_BASE_NAME}}-windows-amd64.exe'
|
|
|
|
|
|
ZIP_NAME: 'release/{{.APP_VERSION}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-windows-amd64.zip'
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
release:build:linux:amd64:
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: linux:package:archive
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: amd64
|
|
|
|
|
|
BINARY_NAME: cursor-release-linux-amd64
|
|
|
|
|
|
OUTPUT: '{{.BIN_DIR}}/cursor-release-linux-amd64'
|
|
|
|
|
|
ARCHIVE_BINARY_NAME: '{{.APP_NAME}}'
|
|
|
|
|
|
ARCHIVE_PATH: '{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-linux-amd64.tar.gz'
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
|
|
|
|
|
|
release:manifest:
|
|
|
|
|
|
summary: 生成 update.json
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- go run ./scripts/release manifest -config ./build/config.yml -assets-dir "{{.RELEASE_DIR}}" -out "{{.RELEASE_DIR}}/update.json" -repo "{{.RELEASE_REPO}}" -base-name "{{.RELEASE_BASE_NAME}}" -notes "{{.RELEASE_NOTES_PATH}}"
|
|
|
|
|
|
|
|
|
|
|
|
release:prepare:
|
|
|
|
|
|
summary: 在当前主机上生成对应的发布资产
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: '[ "{{OS}}" = "darwin" ] || [ "{{OS}}" = "linux" ] || [ "{{OS}}" = "windows" ]'
|
|
|
|
|
|
msg: "release:prepare 仅支持在 macOS、Linux 或 Windows 上执行。"
|
|
|
|
|
|
- sh: wails3 version >/dev/null 2>&1
|
|
|
|
|
|
msg: "未检测到 wails3,请先安装 Wails v3 CLI。"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: '{{if eq OS "darwin"}}release:prepare:darwin{{else if eq OS "linux"}}release:prepare:linux{{else}}release:prepare:windows{{end}}'
|
|
|
|
|
|
|
|
|
|
|
|
release:prepare:windows:
|
|
|
|
|
|
summary: 在 Windows 上生成约定结构的发布资产
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: '[ "{{OS}}" = "windows" ]'
|
|
|
|
|
|
msg: "release:prepare:windows 仅支持在 Windows 上执行。"
|
|
|
|
|
|
- sh: wails3 version >/dev/null 2>&1
|
|
|
|
|
|
msg: "未检测到 wails3,请先安装 Wails v3 CLI。"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- mkdir -p "{{.WINDOWS_RELEASE_DIR}}"
|
2026-11-10 09:59:34 +08:00
|
|
|
|
- cmd: go run ./scripts/release sync-metadata
|
2026-11-10 01:24:28 +08:00
|
|
|
|
- task: windows:create:zip
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: amd64
|
|
|
|
|
|
OUTPUT: '{{.WINDOWS_RELEASE_DIR}}/Local-Cursor.exe'
|
|
|
|
|
|
ZIP_NAME: 'release/v{{.APP_VERSION}}/local-cursor-v{{.APP_VERSION}}-windows-x64-portable.zip'
|
|
|
|
|
|
ARCHIVE_BINARY_NAME: windows-64.exe
|
|
|
|
|
|
KEEP_OUTPUT: "true"
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
- task: windows:create:nsis:installer
|
|
|
|
|
|
vars:
|
|
|
|
|
|
ARCH: amd64
|
|
|
|
|
|
OUTPUT: '{{.WINDOWS_RELEASE_DIR}}/Local-Cursor.exe'
|
|
|
|
|
|
INSTALLER_OUTPUT: '{{.WINDOWS_RELEASE_DIR}}/Local-Cursor-amd64-installer.exe'
|
|
|
|
|
|
SCAN: '{{.SCAN}}'
|
|
|
|
|
|
- powershell -NoProfile -Command "Compress-Archive -LiteralPath '{{.WINDOWS_RELEASE_DIR}}/Local-Cursor-amd64-installer.exe' -DestinationPath '{{.WINDOWS_RELEASE_DIR}}/local-cursor-v{{.APP_VERSION}}-installer.zip' -Force -ErrorAction Stop"
|
|
|
|
|
|
|
|
|
|
|
|
release:prepare:darwin:
|
|
|
|
|
|
summary: 在 macOS 上生成 macOS/Windows/Linux 发布资产
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: '[ "{{OS}}" = "darwin" ]'
|
|
|
|
|
|
msg: "release:prepare:darwin 仅支持在 macOS 上执行。"
|
|
|
|
|
|
- sh: wails3 version >/dev/null 2>&1
|
|
|
|
|
|
msg: "未检测到 wails3,请先安装 Wails v3 CLI。"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: release:clean
|
2026-11-10 09:59:34 +08:00
|
|
|
|
- cmd: go run ./scripts/release sync-metadata
|
2026-11-10 01:24:28 +08:00
|
|
|
|
- task: release:notes
|
|
|
|
|
|
- task: release:build:macos:arm64
|
|
|
|
|
|
- task: release:build:macos:amd64
|
|
|
|
|
|
- task: release:build:windows:amd64
|
|
|
|
|
|
- task: release:build:linux:amd64
|
|
|
|
|
|
|
|
|
|
|
|
release:prepare:linux:
|
|
|
|
|
|
summary: 在当前主机上生成 Linux 发布资产
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: '[ "{{OS}}" = "linux" ] || [ "{{OS}}" = "darwin" ]'
|
|
|
|
|
|
msg: "release:prepare:linux 当前仅支持在 Linux 或 macOS 上执行。"
|
|
|
|
|
|
- sh: wails3 version >/dev/null 2>&1
|
|
|
|
|
|
msg: "未检测到 wails3,请先安装 Wails v3 CLI。"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: release:ensure:dir
|
2026-11-10 09:59:34 +08:00
|
|
|
|
- cmd: go run ./scripts/release sync-metadata
|
2026-11-10 01:24:28 +08:00
|
|
|
|
- task: release:notes
|
|
|
|
|
|
- task: release:build:linux:amd64
|
|
|
|
|
|
|
|
|
|
|
|
release:verify:assets:
|
|
|
|
|
|
summary: 验证当前版本发布资产是否齐全
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: test -f "{{.RELEASE_NOTES_PATH}}"
|
|
|
|
|
|
msg: "未找到发布说明,请先执行对应的 release:prepare 任务。"
|
|
|
|
|
|
- sh: test -f "{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-macos-arm64.tar.gz"
|
|
|
|
|
|
msg: "缺少 macOS arm64 发布资产。"
|
|
|
|
|
|
- sh: test -f "{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-macos-amd64.tar.gz"
|
|
|
|
|
|
msg: "缺少 macOS amd64 发布资产。"
|
|
|
|
|
|
- sh: test -f "{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-windows-amd64.zip"
|
|
|
|
|
|
msg: "缺少 Windows amd64 发布资产。"
|
|
|
|
|
|
- sh: test -f "{{.RELEASE_DIR}}/{{.RELEASE_BASE_NAME}}-{{.APP_VERSION}}-linux-amd64.tar.gz"
|
|
|
|
|
|
msg: "缺少 Linux amd64 发布资产。"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- echo "release assets verified"
|
|
|
|
|
|
|
|
|
|
|
|
release:sync:readme:
|
|
|
|
|
|
summary: 同步 README 到公开发布仓库(发布日志来源固定为 release-notes.md)
|
|
|
|
|
|
internal: true
|
|
|
|
|
|
env:
|
|
|
|
|
|
GH_PAGER: cat
|
|
|
|
|
|
GIT_PAGER: cat
|
|
|
|
|
|
PAGER: cat
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: gh --version >/dev/null 2>&1
|
|
|
|
|
|
msg: "未检测到 gh,请先安装 GitHub CLI。"
|
|
|
|
|
|
- sh: gh auth token >/dev/null 2>&1
|
|
|
|
|
|
msg: "gh 未登录,请先执行 gh auth login。"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- |
|
|
|
|
|
|
python3 - <<'PY'
|
|
|
|
|
|
import base64
|
|
|
|
|
|
import json
|
|
|
|
|
|
import pathlib
|
|
|
|
|
|
import subprocess
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
RETRYABLE_TOKENS = ("EOF", "timeout", "TLS", "temporarily unavailable", "connection reset", "connection refused")
|
|
|
|
|
|
|
|
|
|
|
|
def run_gh(cmd, *, input_text=None, allow_404=False, retries=4):
|
|
|
|
|
|
last = None
|
|
|
|
|
|
for attempt in range(1, retries + 1):
|
|
|
|
|
|
completed = subprocess.run(
|
|
|
|
|
|
cmd,
|
|
|
|
|
|
input=input_text,
|
|
|
|
|
|
text=True,
|
|
|
|
|
|
capture_output=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
if completed.returncode == 0:
|
|
|
|
|
|
return completed
|
|
|
|
|
|
stderr = (completed.stderr or "").strip()
|
|
|
|
|
|
stdout = (completed.stdout or "").strip()
|
|
|
|
|
|
combined = f"{stderr}\n{stdout}".strip()
|
|
|
|
|
|
if allow_404 and "404" in combined:
|
|
|
|
|
|
return completed
|
|
|
|
|
|
last = completed
|
|
|
|
|
|
if not any(token.lower() in combined.lower() for token in RETRYABLE_TOKENS):
|
|
|
|
|
|
return completed
|
|
|
|
|
|
if attempt < retries:
|
|
|
|
|
|
time.sleep(min(2 ** (attempt - 1), 5))
|
|
|
|
|
|
return last
|
|
|
|
|
|
|
|
|
|
|
|
repo = "{{.RELEASE_REPO}}"
|
|
|
|
|
|
readme_path = pathlib.Path("README.md")
|
|
|
|
|
|
content = readme_path.read_bytes()
|
|
|
|
|
|
encoded = base64.b64encode(content).decode("ascii")
|
|
|
|
|
|
|
|
|
|
|
|
get_cmd = [
|
|
|
|
|
|
"gh", "api",
|
|
|
|
|
|
f"repos/{repo}/contents/README.md",
|
|
|
|
|
|
]
|
|
|
|
|
|
result = run_gh(get_cmd, allow_404=True)
|
|
|
|
|
|
|
|
|
|
|
|
payload = {
|
|
|
|
|
|
"message": "docs: sync README from source repo",
|
|
|
|
|
|
"content": encoded,
|
|
|
|
|
|
"branch": "main",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
|
|
|
existing = json.loads(result.stdout)
|
|
|
|
|
|
existing_content = base64.b64decode(existing["content"])
|
|
|
|
|
|
if existing_content == content:
|
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
payload["sha"] = existing["sha"]
|
|
|
|
|
|
|
|
|
|
|
|
put_cmd = [
|
|
|
|
|
|
"gh", "api",
|
|
|
|
|
|
f"repos/{repo}/contents/README.md",
|
|
|
|
|
|
"--method", "PUT",
|
|
|
|
|
|
"--input", "-",
|
|
|
|
|
|
]
|
|
|
|
|
|
completed = run_gh(put_cmd, input_text=json.dumps(payload))
|
|
|
|
|
|
if completed.returncode != 0 and completed.stderr:
|
|
|
|
|
|
sys.stderr.write(completed.stderr)
|
|
|
|
|
|
sys.exit(completed.returncode)
|
|
|
|
|
|
PY
|
|
|
|
|
|
|
|
|
|
|
|
release:github:
|
|
|
|
|
|
summary: 发布当前版本到 GitHub Releases
|
|
|
|
|
|
env:
|
|
|
|
|
|
GH_PAGER: cat
|
|
|
|
|
|
GIT_PAGER: cat
|
|
|
|
|
|
PAGER: cat
|
|
|
|
|
|
preconditions:
|
|
|
|
|
|
- sh: gh --version >/dev/null 2>&1
|
|
|
|
|
|
msg: "未检测到 gh,请先安装 GitHub CLI。"
|
|
|
|
|
|
- sh: gh auth token >/dev/null 2>&1
|
|
|
|
|
|
msg: "gh 未登录,请先执行 gh auth login。"
|
|
|
|
|
|
cmds:
|
|
|
|
|
|
- task: release:verify:assets
|
|
|
|
|
|
- task: release:manifest
|
|
|
|
|
|
- task: release:sync:readme
|
|
|
|
|
|
- |
|
|
|
|
|
|
python3 - <<'PY'
|
|
|
|
|
|
import mimetypes
|
|
|
|
|
|
import json
|
|
|
|
|
|
import pathlib
|
|
|
|
|
|
import subprocess
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
RETRYABLE_TOKENS = ("EOF", "timeout", "TLS", "temporarily unavailable", "connection reset", "connection refused")
|
|
|
|
|
|
|
|
|
|
|
|
def run_gh(cmd, *, input_text=None, allow_404=False, retries=4):
|
|
|
|
|
|
last = None
|
|
|
|
|
|
for attempt in range(1, retries + 1):
|
|
|
|
|
|
completed = subprocess.run(
|
|
|
|
|
|
cmd,
|
|
|
|
|
|
input=input_text,
|
|
|
|
|
|
text=True,
|
|
|
|
|
|
capture_output=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
if completed.returncode == 0:
|
|
|
|
|
|
return completed
|
|
|
|
|
|
stderr = (completed.stderr or "").strip()
|
|
|
|
|
|
stdout = (completed.stdout or "").strip()
|
|
|
|
|
|
combined = f"{stderr}\n{stdout}".strip()
|
|
|
|
|
|
if allow_404 and "404" in combined:
|
|
|
|
|
|
return completed
|
|
|
|
|
|
last = completed
|
|
|
|
|
|
if not any(token.lower() in combined.lower() for token in RETRYABLE_TOKENS):
|
|
|
|
|
|
return completed
|
|
|
|
|
|
if attempt < retries:
|
|
|
|
|
|
time.sleep(min(2 ** (attempt - 1), 5))
|
|
|
|
|
|
return last
|
|
|
|
|
|
|
|
|
|
|
|
repo = "{{.RELEASE_REPO}}"
|
|
|
|
|
|
version = "{{.APP_VERSION}}"
|
|
|
|
|
|
tag = f"v{version}"
|
|
|
|
|
|
notes = pathlib.Path("{{.RELEASE_NOTES_PATH}}").read_text(encoding="utf-8")
|
|
|
|
|
|
assets = sorted([
|
|
|
|
|
|
pathlib.Path("{{.RELEASE_DIR}}/update.json"),
|
|
|
|
|
|
*pathlib.Path("{{.RELEASE_DIR}}").glob("*.tar.gz"),
|
|
|
|
|
|
*pathlib.Path("{{.RELEASE_DIR}}").glob("*.zip"),
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
get_cmd = [
|
|
|
|
|
|
"gh", "api",
|
|
|
|
|
|
f"repos/{repo}/releases/tags/{tag}",
|
|
|
|
|
|
]
|
|
|
|
|
|
result = run_gh(get_cmd, allow_404=True)
|
|
|
|
|
|
|
|
|
|
|
|
payload = {
|
|
|
|
|
|
"tag_name": tag,
|
|
|
|
|
|
"target_commitish": "main",
|
|
|
|
|
|
"name": tag,
|
|
|
|
|
|
"body": notes,
|
|
|
|
|
|
"draft": False,
|
|
|
|
|
|
"prerelease": False,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
|
|
|
release = json.loads(result.stdout)
|
|
|
|
|
|
release_id = release["id"]
|
|
|
|
|
|
edit_cmd = [
|
|
|
|
|
|
"gh", "api",
|
|
|
|
|
|
f"repos/{repo}/releases/{release_id}",
|
|
|
|
|
|
"--method", "PATCH",
|
|
|
|
|
|
"--input", "-",
|
|
|
|
|
|
]
|
|
|
|
|
|
completed = run_gh(edit_cmd, input_text=json.dumps(payload))
|
|
|
|
|
|
if completed.returncode != 0:
|
|
|
|
|
|
if completed.stderr:
|
|
|
|
|
|
sys.stderr.write(completed.stderr)
|
|
|
|
|
|
sys.exit(completed.returncode)
|
|
|
|
|
|
refreshed = run_gh(get_cmd)
|
|
|
|
|
|
if refreshed.returncode != 0:
|
|
|
|
|
|
if refreshed.stderr:
|
|
|
|
|
|
sys.stderr.write(refreshed.stderr)
|
|
|
|
|
|
sys.exit(refreshed.returncode)
|
|
|
|
|
|
release = json.loads(refreshed.stdout)
|
|
|
|
|
|
else:
|
|
|
|
|
|
create_cmd = [
|
|
|
|
|
|
"gh", "api",
|
|
|
|
|
|
f"repos/{repo}/releases",
|
|
|
|
|
|
"--method", "POST",
|
|
|
|
|
|
"--input", "-",
|
|
|
|
|
|
]
|
|
|
|
|
|
completed = run_gh(create_cmd, input_text=json.dumps(payload))
|
|
|
|
|
|
if completed.returncode != 0:
|
|
|
|
|
|
sys.stderr.write(completed.stderr)
|
|
|
|
|
|
sys.exit(completed.returncode)
|
|
|
|
|
|
release = json.loads(completed.stdout)
|
|
|
|
|
|
|
|
|
|
|
|
upload_url = release["upload_url"].split("{", 1)[0]
|
|
|
|
|
|
existing = {asset["name"]: asset["id"] for asset in release.get("assets", [])}
|
|
|
|
|
|
|
|
|
|
|
|
for asset in assets:
|
|
|
|
|
|
name = asset.name
|
|
|
|
|
|
if name in existing:
|
|
|
|
|
|
delete_cmd = [
|
|
|
|
|
|
"gh", "api",
|
|
|
|
|
|
f"repos/{repo}/releases/assets/{existing[name]}",
|
|
|
|
|
|
"--method", "DELETE",
|
|
|
|
|
|
]
|
|
|
|
|
|
deleted = run_gh(delete_cmd)
|
|
|
|
|
|
if deleted.returncode != 0:
|
|
|
|
|
|
if deleted.stderr:
|
|
|
|
|
|
sys.stderr.write(deleted.stderr)
|
|
|
|
|
|
sys.exit(deleted.returncode)
|
|
|
|
|
|
|
|
|
|
|
|
content_type = mimetypes.guess_type(name)[0] or "application/octet-stream"
|
|
|
|
|
|
upload_cmd = [
|
|
|
|
|
|
"gh", "api",
|
|
|
|
|
|
f"{upload_url}?name={name}",
|
|
|
|
|
|
"--method", "POST",
|
|
|
|
|
|
"--header", f"Content-Type: {content_type}",
|
|
|
|
|
|
"--input", str(asset),
|
|
|
|
|
|
]
|
|
|
|
|
|
uploaded = run_gh(upload_cmd)
|
|
|
|
|
|
if uploaded.returncode != 0:
|
|
|
|
|
|
if uploaded.stderr:
|
|
|
|
|
|
sys.stderr.write(uploaded.stderr)
|
|
|
|
|
|
sys.exit(uploaded.returncode)
|
|
|
|
|
|
PY
|