Appearance
CLI 发布与分发
开发完 CLI 工具后,下一步就是让用户能够方便地安装和使用它。Go 的编译特性使得 CLI 分发相对简单——一个二进制文件即可运行。但要实现多平台支持、版本管理、自动更新、包管理器分发等,还需要一系列工具和流程。本篇将系统讲解 Go CLI 工具的发布与分发全流程。
一、跨平台编译
Go 原生支持交叉编译,通过 GOOS(目标操作系统)和 GOARCH(目标架构)两个环境变量控制编译目标。
1. 基本交叉编译
go
package main
import (
"fmt"
"runtime"
)
var (
version = "dev"
commit = "none"
buildTime = "unknown"
)
func main() {
fmt.Printf("myapp %s\n", version)
fmt.Printf("commit: %s\n", commit)
fmt.Printf("built: %s\n", buildTime)
fmt.Printf("platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
}编译命令示例:
bash
# 编译 Linux amd64
GOOS=linux GOARCH=amd64 go build -o bin/myapp-linux-amd64 main.go
# 编译 macOS amd64 (Intel)
GOOS=darwin GOARCH=amd64 go build -o bin/myapp-darwin-amd64 main.go
# 编译 macOS arm64 (Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o bin/myapp-darwin-arm64 main.go
# 编译 Windows amd64
GOOS=windows GOARCH=amd64 go build -o bin/myapp-windows-amd64.exe main.go
# 编译 Linux ARM64 (树莓派等)
GOOS=linux GOARCH=arm64 go build -o bin/myapp-linux-arm64 main.go2. 常用平台组合
| GOOS | GOARCH | 说明 |
|---|---|---|
| linux | amd64 | 64 位 Linux (最常见) |
| linux | arm64 | ARM64 Linux |
| linux | 386 | 32 位 Linux |
| darwin | amd64 | Intel Mac |
| darwin | arm64 | Apple Silicon Mac |
| windows | amd64 | 64 位 Windows |
| windows | 386 | 32 位 Windows |
| freebsd | amd64 | FreeBSD |
3. CGO 的影响
默认情况下交叉编译会禁用 CGO(CGO_ENABLED=0),这意味着不能使用 C 语言库。如果你的项目依赖 CGO(如使用 SQLite),则需要安装对应平台的 C 工具链,交叉编译会复杂很多。建议 CLI 工具尽量使用纯 Go 实现,避免 CGO 依赖。
bash
# 纯 Go 编译(推荐,无 CGO 依赖)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o myapp main.go二、构建脚本:Makefile
当需要支持多个平台时,手动执行编译命令太繁琐。Makefile 可以自动化构建流程。
makefile
# 变量定义
BINARY_NAME := myapp
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_TIME := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildTime=$(BUILD_TIME)
# 目标平台列表
PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64
# 默认目标
.PHONY: build
build:
go build -ldflags "$(LDFLAGS)" -o bin/$(BINARY_NAME) main.go
# 编译所有平台
.PHONY: build-all
build-all:
@for platform in $(PLATFORMS); do \
GOOS=$${platform%/*}; \
GOARCH=$${platform#*/}; \
OUTPUT=bin/$(BINARY_NAME)-$$GOOS-$$GOARCH; \
if [ $$GOOS = windows ]; then \
OUTPUT=$$OUTPUT.exe; \
fi; \
echo "Building $$OUTPUT..."; \
CGO_ENABLED=0 GOOS=$$GOOS GOARCH=$$GOARCH go build -ldflags "$(LDFLAGS)" -o $$OUTPUT main.go; \
done
# 清理
.PHONY: clean
clean:
rm -rf bin/
# 运行测试
.PHONY: test
test:
go test ./... -v -cover
# 安装
.PHONY: install
install:
go install -ldflags "$(LDFLAGS)" main.go
# 生成自动补全脚本
.PHONY: completion
completion:
@echo "生成 shell 补全脚本..."
go run main.go completion bash > completion/myapp.bash
go run main.go completion zsh > completion/_myapp
go run main.go completion fish > completion/myapp.fish使用方式:
bash
make build # 编译当前平台
make build-all # 编译所有平台
make test # 运行测试
make clean # 清理构建产物
make install # 安装到 $GOPATH/bin在 Windows 上可以使用
make(通过 Chocolatey 或 WSL 安装),也可以用 PowerShell 脚本替代。GoReleaser(后面会讲)可以完全替代 Makefile 的跨平台编译功能。
三、版本信息注入:-ldflags
-ldflags 是 Go 链接器参数,可以在编译时将变量值注入到二进制文件中。最典型的用途是注入版本号、提交哈希和构建时间。
1. 原理
在 Go 代码中定义包级变量(默认值),编译时通过 -ldflags 覆盖:
go
package main
import (
"fmt"
"runtime"
)
// 这些变量在编译时通过 -ldflags 注入
var (
version = "dev" // 版本号
commit = "none" // 提交哈希
buildTime = "unknown" // 构建时间
goVersion = "unknown" // Go 版本
)
func main() {
fmt.Printf("myapp\n")
fmt.Printf(" version: %s\n", version)
fmt.Printf(" commit: %s\n", commit)
fmt.Printf(" build time: %s\n", buildTime)
fmt.Printf(" go version: %s\n", goVersion)
fmt.Printf(" platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
}2. 编译时注入
bash
# 获取版本信息
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")
COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
GO_VERSION=$(go version | awk '{print $3}')
# 编译并注入变量
go build -ldflags "\
-X main.version=$VERSION \
-X main.commit=$COMMIT \
-X main.buildTime=$BUILD_TIME \
-X main.goVersion=$GO_VERSION" \
-o myapp main.go3. 注入到子包
如果变量定义在子包中,需要使用完整导入路径:
go
// pkg/version/version.go
package version
var (
Version = "dev"
Commit = "none"
BuildTime = "unknown"
)bash
# 注意路径格式:-X <import-path>.<variable>=<value>
go build -ldflags "\
-X myapp/pkg/version.Version=$VERSION \
-X myapp/pkg/version.Commit=$COMMIT \
-X myapp/pkg/version.BuildTime=$BUILD_TIME" \
-o myapp main.go4. 在 Cobra 中使用
go
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var (
version = "dev"
commit = "none"
date = "unknown"
)
func main() {
rootCmd := &cobra.Command{
Use: "myapp",
Short: "一个 CLI 工具",
}
versionCmd := &cobra.Command{
Use: "version",
Short: "显示版本信息",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("myapp %s\n", version)
fmt.Printf(" commit: %s\n", commit)
fmt.Printf(" date: %s\n", date)
},
}
// 也可以设置 rootCmd.Version,使 --version 自动可用
rootCmd.Version = version
rootCmd.SetVersionTemplate("myapp {{.Version}}\n")
rootCmd.AddCommand(versionCmd)
rootCmd.Execute()
}四、GoReleaser:自动化发布工具
GoReleaser 是 Go 生态中最流行的发布自动化工具。它可以从 git tag 出发,自动完成:多平台编译、打包、生成 changelog、创建 GitHub Release、发布到 Homebrew/Docker 等。
1. 安装 GoReleaser
bash
# 方式一:go install
go install github.com/goreleaser/goreleaser@latest
# 方式二:Homebrew (macOS)
brew install goreleaser
# 方式三:Scoop (Windows)
scoop install goreleaser2. 初始化配置
在项目根目录执行:
bash
goreleaser init这会生成 .goreleaser.yaml 配置文件。
3. .goreleaser.yaml 配置详解
下面是一个完整的配置示例:
yaml
# 项目信息
project_name: myapp
# 编译前检查
before:
hooks:
- go mod tidy
- go test ./...
# 编译配置
builds:
- id: myapp
main: ./main.go
binary: myapp
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
ldflags:
- -s -w # 去除调试信息,减小体积
- -X main.version={{.Version}}
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
# 归档配置
archives:
- id: default
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- if eq .Os "darwin" }}macos
{{- else }}{{ .Os }}{{ end }}_
{{- .Arch }}
format_overrides:
- goos: windows
formats: [zip]
files:
- README.md
- LICENSE
- completions/*
# 生成 changelog
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- '^chore:'
- Merge pull request
- Merge branch
# GitHub Release
release:
github:
owner: yourusername
name: myapp
draft: false
prerelease: auto
name_template: "{{.TagName}} ({{.Date}})"
# Homebrew Tap
brews:
- name: myapp
repository:
owner: yourusername
name: homebrew-tap
homepage: https://github.com/yourusername/myapp
description: "A CLI tool built with Go"
test: |
system "#{bin}/myapp", "--version"
install: |
bin.install "myapp"
bash_completion.install "completions/myapp.bash" => "myapp"
zsh_completion.install "completions/_myapp" => "_myapp"
# Docker 镜像
dockers:
- image_templates:
- "yourusername/myapp:latest"
- "yourusername/myapp:{{ .Tag }}"
- "yourusername/myapp:v{{ .Major }}.{{ .Minor }}"
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--pull"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
# 生成 SBOM (软件物料清单)
sboms:
- artifacts: archive
# 快照模式(不发布,仅测试)
snapshot:
name_template: "{{ incpatch .Version }}-next"
# 校验和
checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"4. 本地测试
在正式发布前,可以用 --snapshot 模式本地测试构建流程:
bash
# 快照构建(不需要 git tag,不会发布)
goreleaser release --snapshot --clean
# 仅构建,跳过发布
goreleaser build --clean
# 检查配置
goreleaser check5. 正式发布
GoReleaser 依赖 git tag 触发发布:
bash
# 1. 打标签
git tag v1.0.0
git push origin v1.0.0
# 2. 设置 GitHub Token
export GITHUB_TOKEN=ghp_xxxxxxxxxxxx
# 3. 发布
goreleaser release --cleanGoReleaser 会自动执行:编译多平台二进制 → 打包归档 → 生成 changelog → 创建 GitHub Release → 上传产物 → 发布 Homebrew/Docker。
6. CI/CD 集成
在 GitHub Actions 中集成 GoReleaser(.github/workflows/release.yml):
yaml
name: release
on:
push:
tags:
- "v*"
permissions:
contents: write
packages: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.23
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 如果要推送到 Homebrew Tap 仓库,需要额外 token
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}五、语义化版本管理
语义化版本(Semantic Versioning,SemVer)是软件版本号的规范。格式为 MAJOR.MINOR.PATCH:
- MAJOR:不兼容的 API 变更(破坏性更新)
- MINOR:向后兼容的功能新增
- PATCH:向后兼容的 Bug 修复
示例:v1.2.3 表示主版本 1、次版本 2、补丁版本 3。
1. Git Tag 规范
bash
# 发布 v1.0.0
git tag -a v1.0.0 -m "首个正式版本"
git push origin v1.0.0
# Bug 修复
git tag -a v1.0.1 -m "修复登录问题"
git push origin v1.0.1
# 新功能
git tag -a v1.1.0 -m "新增导出功能"
git push origin v1.1.0
# 破坏性变更
git tag -a v2.0.0 -m "重构 API"
git push origin v2.0.02. 预发布版本
bash
# Alpha 版本
git tag v1.0.0-alpha.1
# Beta 版本
git tag v1.0.0-beta.1
# RC (Release Candidate)
git tag v1.0.0-rc.1六、更新检查机制
CLI 工具应该让用户知道是否有新版本可用。常见的实现方式是在运行时(或定期)检查 GitHub Release 的最新版本。
go
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"time"
"github.com/spf13/cobra"
)
var version = "1.0.0"
// GitHubRelease 表示 GitHub Release 的响应
type GitHubRelease struct {
TagName string `json:"tag_name"`
Name string `json:"name"`
HTMLURL string `json:"html_url"`
}
// checkUpdate 检查是否有新版本
func checkUpdate(currentVersion string) (*GitHubRelease, bool, error) {
url := "https://api.github.com/repos/yourusername/myapp/releases/latest"
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(url)
if err != nil {
return nil, false, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, false, fmt.Errorf("GitHub API 返回 %d", resp.StatusCode)
}
var release GitHubRelease
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return nil, false, err
}
// 比较版本号(简化版,实际应使用 semver 库)
latest := release.TagName
if latest == "" {
return nil, false, nil
}
// 去掉 v 前缀
if latest[0] == 'v' {
latest = latest[1:]
}
hasUpdate := latest != currentVersion && latest != ""
return &release, hasUpdate, nil
}
func main() {
rootCmd := &cobra.Command{
Use: "myapp",
Short: "一个支持更新检查的 CLI 工具",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// 异步检查更新(不阻塞主流程)
go func() {
release, hasUpdate, err := checkUpdate(version)
if err != nil || !hasUpdate {
return
}
fmt.Fprintf(os.Stderr, "\n⚠ 有新版本可用: %s (当前: v%s)\n", release.TagName, version)
fmt.Fprintf(os.Stderr, " 下载: %s\n\n", release.HTMLURL)
}()
},
}
versionCmd := &cobra.Command{
Use: "version",
Short: "显示版本信息并检查更新",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("当前版本: v%s\n", version)
fmt.Println("正在检查更新...")
release, hasUpdate, err := checkUpdate(version)
if err != nil {
fmt.Fprintf(os.Stderr, "检查更新失败: %v\n", err)
return
}
if hasUpdate {
fmt.Printf("发现新版本: %s\n", release.TagName)
fmt.Printf("下载地址: %s\n", release.HTMLURL)
} else {
fmt.Println("已是最新版本")
}
},
}
rootCmd.AddCommand(versionCmd)
rootCmd.Execute()
}七、安装脚本
提供一个安装脚本,让用户一行命令安装:
go
package main
import (
"fmt"
"os"
)
// 这个程序演示安装脚本的生成逻辑
// 实际的安装脚本是一个 shell 脚本,由用户通过 curl | bash 执行
func main() {
// 生成 install.sh 脚本内容
installScript := generateInstallScript()
fmt.Println("生成的 install.sh 脚本:")
fmt.Println("========================================")
fmt.Println(installScript)
fmt.Println("========================================")
fmt.Println()
fmt.Println("用户安装方式:")
fmt.Println(" curl -fsSL https://raw.githubusercontent.com/yourusername/myapp/main/install.sh | bash")
}
func generateInstallScript() string {
return `#!/bin/sh
# myapp 安装脚本
# 用法: curl -fsSL https://raw.githubusercontent.com/yourusername/myapp/main/install.sh | bash
set -e
# 配置
REPO="yourusername/myapp"
INSTALL_DIR="/usr/local/bin"
BINARY_NAME="myapp"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
info() { printf "${GREEN}[INFO]${NC} %s\n" "$1"; }
warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$1"; }
error() { printf "${RED}[ERROR]${NC} %s\n" "$1" >&2; exit 1; }
# 检测操作系统
detect_os() {
case "$(uname -s)" in
Linux*) OS="linux";;
Darwin*) OS="darwin";;
*) error "不支持的操作系统: $(uname -s)";;
esac
}
# 检测架构
detect_arch() {
case "$(uname -m)" in
x86_64|amd64) ARCH="amd64";;
arm64|aarch64) ARCH="arm64";;
*) error "不支持的架构: $(uname -m)";;
esac
}
# 获取最新版本
get_latest_version() {
VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
| grep '"tag_name"' \
| sed -E 's/.*"([^"]+)".*/\1/')
if [ -z "$VERSION" ]; then
error "无法获取最新版本号"
fi
info "最新版本: ${VERSION}"
}
# 下载二进制
download_binary() {
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${BINARY_NAME}-${OS}-${ARCH}"
if [ "$OS" = "windows" ]; then
DOWNLOAD_URL="${DOWNLOAD_URL}.exe"
BINARY_NAME="${BINARY_NAME}.exe"
fi
TMP_FILE="/tmp/${BINARY_NAME}"
info "下载 ${DOWNLOAD_URL}"
curl -fsSL "$DOWNLOAD_URL" -o "$TMP_FILE"
chmod +x "$TMP_FILE"
}
# 安装
install_binary() {
if [ -w "$INSTALL_DIR" ]; then
mv "$TMP_FILE" "${INSTALL_DIR}/${BINARY_NAME}"
else
info "需要 sudo 权限安装到 ${INSTALL_DIR}"
sudo mv "$TMP_FILE" "${INSTALL_DIR}/${BINARY_NAME}"
fi
info "已安装到 ${INSTALL_DIR}/${BINARY_NAME}"
}
# 验证
verify_install() {
if command -v "$BINARY_NAME" >/dev/null 2>&1; then
info "安装成功!"
$BINARY_NAME --version
else
warn "${BINARY_NAME} 不在 PATH 中,请将 ${INSTALL_DIR} 加入 PATH"
fi
}
# 主流程
main() {
info "开始安装 ${BINARY_NAME}..."
detect_os
detect_arch
info "系统: ${OS}/${ARCH}"
get_latest_version
download_binary
install_binary
verify_install
info "完成!"
}
main
`
}将 install.sh 放在仓库根目录,用户通过以下命令安装:
bash
curl -fsSL https://raw.githubusercontent.com/yourusername/myapp/main/install.sh | bash八、完整示例:CLI 工具的完整发布流程
下面演示一个完整的项目结构和发布流程。项目结构如下:
myapp/
├── main.go # 入口文件
├── go.mod
├── Makefile # 构建脚本
├── .goreleaser.yaml # GoReleaser 配置
├── install.sh # 安装脚本
├── Dockerfile # Docker 镜像配置
└── .github/
└── workflows/
└── release.yml # CI/CD 发布流程main.go:
go
package main
import (
"fmt"
"os"
"runtime"
"github.com/spf13/cobra"
)
// 编译时注入的版本信息
var (
version = "dev"
commit = "none"
buildTime = "unknown"
)
func main() {
rootCmd := &cobra.Command{
Use: "myapp",
Short: "一个完整的 CLI 工具示例",
Long: "MyApp 是一个演示完整发布流程的 Go CLI 工具。",
Version: version,
}
rootCmd.SetVersionTemplate(fmt.Sprintf(
"myapp %s\n commit: %s\n built: %s\n go: %s\n os: %s/%s\n",
version, commit, buildTime, runtime.Version(), runtime.GOOS, runtime.GOARCH,
))
// hello 子命令
helloCmd := &cobra.Command{
Use: "hello [name]",
Short: "打招呼",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := "World"
if len(args) > 0 {
name = args[0]
}
verbose, _ := cmd.Flags().GetBool("verbose")
if verbose {
fmt.Printf("详细信息: version=%s, commit=%s\n", version, commit)
}
fmt.Printf("Hello, %s!\n", name)
},
}
helloCmd.Flags().BoolP("verbose", "v", false, "显示详细信息")
// info 子命令
infoCmd := &cobra.Command{
Use: "info",
Short: "显示系统信息",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("操作系统: %s\n", runtime.GOOS)
fmt.Printf("架构: %s\n", runtime.GOARCH)
fmt.Printf("CPU 核数: %d\n", runtime.NumCPU())
fmt.Printf("Go 版本: %s\n", runtime.Version())
},
}
rootCmd.AddCommand(helloCmd, infoCmd)
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}Dockerfile:
dockerfile
# 构建阶段
FROM golang:1.23-alpine AS builder
WORKDIR /app
# 复制源码
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# 编译
ARG VERSION=dev
ARG COMMIT=none
ARG BUILD_TIME=unknown
RUN CGO_ENABLED=0 go build \
-ldflags "-s -w \
-X main.version=${VERSION} \
-X main.commit=${COMMIT} \
-X main.buildTime=${BUILD_TIME}" \
-o /myapp main.go
# 运行阶段
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
COPY --from=builder /myapp /usr/local/bin/myapp
ENTRYPOINT ["myapp"]
CMD ["--help"]本地构建 Docker 镜像:
bash
docker build \
--build-arg VERSION=$(git describe --tags --always) \
--build-arg COMMIT=$(git rev-parse --short HEAD) \
--build-arg BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
-t myapp:latest .发布流程汇总:
bash
# 1. 确保代码通过测试
go test ./...
# 2. 更新版本(语义化版本)
# 2.1 修复 Bug → 补丁版本
git tag v1.0.1
# 2.2 新增功能 → 次版本
git tag v1.1.0
# 2.3 破坏性变更 → 主版本
git tag v2.0.0
# 3. 推送 tag 触发 GoReleaser
git push origin v1.0.0
# 4. GoReleaser 自动完成:
# - 多平台编译(linux/darwin/windows × amd64/arm64)
# - 注入版本信息
# - 打包归档(tar.gz / zip)
# - 生成 changelog
# - 创建 GitHub Release
# - 发布 Homebrew Tap
# - 推送 Docker 镜像
# - 生成 checksums.txt
# 5. 用户安装
# Homebrew
brew install yourusername/tap/myapp
# Docker
docker run --rm yourusername/myapp --version
# 安装脚本
curl -fsSL https://raw.githubusercontent.com/yourusername/myapp/main/install.sh | bash
# go install
go install github.com/yourusername/myapp@latest九、小结
本篇系统讲解了 Go CLI 工具的发布与分发全流程:
- 跨平台编译:通过
GOOS/GOARCH实现交叉编译,CGO_ENABLED=0避免依赖问题。 - Makefile:自动化构建流程,支持多平台批量编译。
- 版本信息注入:通过
-ldflags -X在编译时注入版本号、提交哈希、构建时间。 - GoReleaser:自动化发布工具,一键完成编译、打包、发布。
.goreleaser.yaml配置编译目标、归档格式、changelog。- GitHub Release 自动创建。
- Homebrew Tap 自动发布。
- Docker 镜像自动推送。
- CI/CD 集成通过 GitHub Actions。
- 语义化版本:
MAJOR.MINOR.PATCH规范,支持 alpha/beta/rc 预发布。 - 更新检查:通过 GitHub API 检查最新版本,提示用户更新。
- 安装脚本:一行命令安装,自动检测平台架构。
- 完整流程:从代码到用户安装的完整链路,涵盖 Docker 镜像构建和多种分发渠道。
至此,Go CLI 开发系列教程完结。从标准库基础到 Cobra 框架、Viper 配置、交互式 CLI、Bubbletea TUI,再到发布分发,你已经掌握了构建生产级 Go CLI 工具的全部技能。