feat: 迁移 versionctl 为独立模块并新增 make version-bump 命令
- 将 backend/cmd/versionctl 和 backend/pkg/projectversion 迁移至独立 versionctl/ Go 模块 - 新增 bump 子命令支持 major/minor/patch 和指定版本号,含版本倒退防护 - 新增 make version-bump 编排完整升迁流程(bump + sync + check + commit + tag) - 更新所有引用路径:根 Makefile、backend/Makefile、release.yml、.golangci.yml - 新增 versionctl/.golangci.yml(精简配置)和 Makefile(lint/test/coverage) - 根 Makefile lint/test 集成 versionctl 模块 - 同步 openspec specs:新增 version-bump spec,更新 release-pipeline spec
This commit is contained in:
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@@ -24,13 +24,15 @@ jobs:
|
|||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.work
|
go-version-file: go.work
|
||||||
cache-dependency-path: backend/go.sum
|
cache-dependency-path: |
|
||||||
|
backend/go.sum
|
||||||
|
versionctl/go.sum
|
||||||
|
|
||||||
- name: Verify tag and VERSION
|
- name: Verify tag and VERSION
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
version=$(go run ./backend/cmd/versionctl print)
|
version=$(go run ./versionctl print)
|
||||||
go run ./backend/cmd/versionctl verify-tag "${GITHUB_REF_NAME}"
|
go run ./versionctl verify-tag "${GITHUB_REF_NAME}"
|
||||||
printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT"
|
printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
@@ -47,7 +49,9 @@ jobs:
|
|||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.work
|
go-version-file: go.work
|
||||||
cache-dependency-path: backend/go.sum
|
cache-dependency-path: |
|
||||||
|
backend/go.sum
|
||||||
|
versionctl/go.sum
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
@@ -93,7 +97,9 @@ jobs:
|
|||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.work
|
go-version-file: go.work
|
||||||
cache-dependency-path: backend/go.sum
|
cache-dependency-path: |
|
||||||
|
backend/go.sum
|
||||||
|
versionctl/go.sum
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
@@ -153,7 +159,9 @@ jobs:
|
|||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.work
|
go-version-file: go.work
|
||||||
cache-dependency-path: backend/go.sum
|
cache-dependency-path: |
|
||||||
|
backend/go.sum
|
||||||
|
versionctl/go.sum
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
|
|||||||
45
Makefile
45
Makefile
@@ -1,11 +1,12 @@
|
|||||||
.PHONY: \
|
.PHONY: \
|
||||||
lint test clean \
|
lint test clean \
|
||||||
version-sync version-check \
|
version-sync version-check version-bump \
|
||||||
server-run server-build server-lint server-test server-clean \
|
server-run server-build server-lint server-test server-clean \
|
||||||
desktop-build-mac desktop-build-win desktop-build-linux \
|
desktop-build-mac desktop-build-win desktop-build-linux \
|
||||||
desktop-lint desktop-test desktop-clean \
|
desktop-lint desktop-test desktop-clean \
|
||||||
release-assets-linux release-assets-windows release-assets-macos \
|
release-assets-linux release-assets-windows release-assets-macos \
|
||||||
_backend-lint _backend-test _backend-clean _backend-build \
|
_backend-lint _backend-test _backend-clean _backend-build \
|
||||||
|
_versionctl-lint _versionctl-test \
|
||||||
_frontend-install _frontend-build _frontend-check _frontend-test _frontend-dev _frontend-clean \
|
_frontend-install _frontend-build _frontend-check _frontend-test _frontend-dev _frontend-clean \
|
||||||
_desktop-test _desktop-clean _desktop-prepare-frontend _desktop-prepare-embedfs _desktop-prepare-windows-resource \
|
_desktop-test _desktop-clean _desktop-prepare-frontend _desktop-prepare-embedfs _desktop-prepare-windows-resource \
|
||||||
_server-run-backend _server-run-frontend
|
_server-run-backend _server-run-frontend
|
||||||
@@ -13,28 +14,28 @@
|
|||||||
# Delay shell lookups until a target needs them, then cache the result for this make run.
|
# Delay shell lookups until a target needs them, then cache the result for this make run.
|
||||||
lazy_shell = $(or $($(1)),$(eval $(1) := $(shell $(2)))$($(1)))
|
lazy_shell = $(or $($(1)),$(eval $(1) := $(shell $(2)))$($(1)))
|
||||||
|
|
||||||
VERSION = $(call lazy_shell,_VERSION,go run ./backend/cmd/versionctl print)
|
VERSION = $(call lazy_shell,_VERSION,go run ./versionctl print)
|
||||||
GIT_COMMIT ?= $(call lazy_shell,_GIT_COMMIT,git rev-parse --short HEAD 2>/dev/null || printf 'unknown')
|
GIT_COMMIT ?= $(call lazy_shell,_GIT_COMMIT,git rev-parse --short HEAD 2>/dev/null || printf 'unknown')
|
||||||
BUILD_TIME ?= $(call lazy_shell,_BUILD_TIME,date -u +"%Y-%m-%dT%H:%M:%SZ")
|
BUILD_TIME ?= $(call lazy_shell,_BUILD_TIME,date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
GO_LDFLAGS = -X nex/backend/pkg/buildinfo.version=$(VERSION) -X nex/backend/pkg/buildinfo.commit=$(GIT_COMMIT) -X nex/backend/pkg/buildinfo.buildTime=$(BUILD_TIME)
|
GO_LDFLAGS = -X nex/backend/pkg/buildinfo.version=$(VERSION) -X nex/backend/pkg/buildinfo.commit=$(GIT_COMMIT) -X nex/backend/pkg/buildinfo.buildTime=$(BUILD_TIME)
|
||||||
GO_LDFLAGS_WIN = $(GO_LDFLAGS) -H=windowsgui
|
GO_LDFLAGS_WIN = $(GO_LDFLAGS) -H=windowsgui
|
||||||
RELEASE_DIR := build/release
|
RELEASE_DIR := build/release
|
||||||
SERVER_LINUX_ASSET = $(call lazy_shell,_SERVER_LINUX_ASSET,go run ./backend/cmd/versionctl asset-name server linux amd64)
|
SERVER_LINUX_ASSET = $(call lazy_shell,_SERVER_LINUX_ASSET,go run ./versionctl asset-name server linux amd64)
|
||||||
SERVER_WINDOWS_ASSET = $(call lazy_shell,_SERVER_WINDOWS_ASSET,go run ./backend/cmd/versionctl asset-name server windows amd64)
|
SERVER_WINDOWS_ASSET = $(call lazy_shell,_SERVER_WINDOWS_ASSET,go run ./versionctl asset-name server windows amd64)
|
||||||
SERVER_DARWIN_AMD64_ASSET = $(call lazy_shell,_SERVER_DARWIN_AMD64_ASSET,go run ./backend/cmd/versionctl asset-name server darwin amd64)
|
SERVER_DARWIN_AMD64_ASSET = $(call lazy_shell,_SERVER_DARWIN_AMD64_ASSET,go run ./versionctl asset-name server darwin amd64)
|
||||||
SERVER_DARWIN_ARM64_ASSET = $(call lazy_shell,_SERVER_DARWIN_ARM64_ASSET,go run ./backend/cmd/versionctl asset-name server darwin arm64)
|
SERVER_DARWIN_ARM64_ASSET = $(call lazy_shell,_SERVER_DARWIN_ARM64_ASSET,go run ./versionctl asset-name server darwin arm64)
|
||||||
DESKTOP_LINUX_ASSET = $(call lazy_shell,_DESKTOP_LINUX_ASSET,go run ./backend/cmd/versionctl asset-name desktop linux)
|
DESKTOP_LINUX_ASSET = $(call lazy_shell,_DESKTOP_LINUX_ASSET,go run ./versionctl asset-name desktop linux)
|
||||||
DESKTOP_WINDOWS_ASSET = $(call lazy_shell,_DESKTOP_WINDOWS_ASSET,go run ./backend/cmd/versionctl asset-name desktop windows)
|
DESKTOP_WINDOWS_ASSET = $(call lazy_shell,_DESKTOP_WINDOWS_ASSET,go run ./versionctl asset-name desktop windows)
|
||||||
DESKTOP_MACOS_ASSET = $(call lazy_shell,_DESKTOP_MACOS_ASSET,go run ./backend/cmd/versionctl asset-name desktop macos)
|
DESKTOP_MACOS_ASSET = $(call lazy_shell,_DESKTOP_MACOS_ASSET,go run ./versionctl asset-name desktop macos)
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# 全局命令
|
# 全局命令
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
lint: _backend-lint _frontend-check
|
lint: _backend-lint _frontend-check _versionctl-lint
|
||||||
@printf 'Lint complete\n'
|
@printf 'Lint complete\n'
|
||||||
|
|
||||||
test: _backend-test _frontend-test _desktop-test
|
test: _backend-test _frontend-test _desktop-test _versionctl-test
|
||||||
@printf 'All tests passed\n'
|
@printf 'All tests passed\n'
|
||||||
|
|
||||||
clean: _backend-clean _frontend-clean _desktop-clean
|
clean: _backend-clean _frontend-clean _desktop-clean
|
||||||
@@ -45,10 +46,20 @@ clean: _backend-clean _frontend-clean _desktop-clean
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
version-sync:
|
version-sync:
|
||||||
go run ./backend/cmd/versionctl sync
|
go run ./versionctl sync
|
||||||
|
|
||||||
version-check:
|
version-check:
|
||||||
go run ./backend/cmd/versionctl check
|
go run ./versionctl check
|
||||||
|
|
||||||
|
version-bump:
|
||||||
|
@test -n "$(BUMP)$(SET_VERSION)" || (printf '用法: make version-bump BUMP=major|minor|patch 或 make version-bump SET_VERSION=x.y.z\n' && exit 1)
|
||||||
|
@git diff --quiet HEAD || (printf '工作区不干净,请先提交或暂存改动\n' && exit 1)
|
||||||
|
$(eval _BUMP_ARG := $(if $(SET_VERSION),$(SET_VERSION),$(BUMP)))
|
||||||
|
$(eval _NEW_VERSION := $(shell go run ./versionctl bump $(_BUMP_ARG)))
|
||||||
|
git add VERSION frontend/
|
||||||
|
git commit -m "chore: 版本升迁 v$(_NEW_VERSION)"
|
||||||
|
git tag "v$(_NEW_VERSION)"
|
||||||
|
@printf '版本升迁完成: v%s\n' "$(_NEW_VERSION)"
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Server 模式
|
# Server 模式
|
||||||
@@ -97,7 +108,7 @@ desktop-build-mac: version-check _desktop-prepare-frontend _desktop-prepare-embe
|
|||||||
printf 'Unable to read macOS minimum version\n'; \
|
printf 'Unable to read macOS minimum version\n'; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi; \
|
fi; \
|
||||||
go run ./backend/cmd/versionctl macos-plist "$$MIN_MACOS_VERSION" > build/Nex.app/Contents/Info.plist
|
go run ./versionctl macos-plist "$$MIN_MACOS_VERSION" > build/Nex.app/Contents/Info.plist
|
||||||
chmod +x build/Nex.app/Contents/MacOS/nex
|
chmod +x build/Nex.app/Contents/MacOS/nex
|
||||||
@printf 'macOS desktop build complete\n'
|
@printf 'macOS desktop build complete\n'
|
||||||
|
|
||||||
@@ -216,6 +227,12 @@ _backend-test:
|
|||||||
_backend-clean:
|
_backend-clean:
|
||||||
@$(MAKE) -C backend clean
|
@$(MAKE) -C backend clean
|
||||||
|
|
||||||
|
_versionctl-lint:
|
||||||
|
@$(MAKE) -C versionctl lint
|
||||||
|
|
||||||
|
_versionctl-test:
|
||||||
|
@$(MAKE) -C versionctl test
|
||||||
|
|
||||||
_frontend-install:
|
_frontend-install:
|
||||||
cd frontend && bun install
|
cd frontend && bun install
|
||||||
|
|
||||||
|
|||||||
27
README.md
27
README.md
@@ -321,27 +321,24 @@ make desktop-clean # 清理 desktop 产物
|
|||||||
|
|
||||||
### 本地版本演进
|
### 本地版本演进
|
||||||
|
|
||||||
1. 手工修改根目录 `VERSION` 为新的 `x.y.z`
|
```bash
|
||||||
2. 同步镜像文件:
|
# 递增版本(自动 sync + check + commit + tag)
|
||||||
|
make version-bump BUMP=minor
|
||||||
|
|
||||||
|
# 或指定具体版本号
|
||||||
|
make version-bump SET_VERSION=1.0.0
|
||||||
|
|
||||||
|
# 推送到远程
|
||||||
|
git push --follow-tags
|
||||||
|
```
|
||||||
|
|
||||||
|
手动同步和校验:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make version-sync
|
make version-sync
|
||||||
```
|
|
||||||
|
|
||||||
3. 校验版本一致性:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make version-check
|
make version-check
|
||||||
```
|
```
|
||||||
|
|
||||||
4. 提交版本变更后,创建发布 tag:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git tag -a vX.Y.Z -m "Release vX.Y.Z"
|
|
||||||
git push origin main
|
|
||||||
git push origin vX.Y.Z
|
|
||||||
```
|
|
||||||
|
|
||||||
### 本地生成发布资产
|
### 本地生成发布资产
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -86,9 +86,6 @@ issues:
|
|||||||
linters:
|
linters:
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- gocritic
|
- gocritic
|
||||||
- path: '(internal/provider/client\.go|internal/service/model_service_impl\.go|internal/service/stats_buffer\.go|internal/handler/proxy_handler\.go|cmd/(desktop|server|versionctl)/main\.go)'
|
- path: '(internal/provider/client\.go|internal/service/model_service_impl\.go|internal/service/stats_buffer\.go|internal/handler/proxy_handler\.go|cmd/(desktop|server)/main\.go)'
|
||||||
linters:
|
linters:
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- path: 'cmd/versionctl/'
|
|
||||||
linters:
|
|
||||||
- forbidigo
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
migrate-up migrate-down migrate-status migrate-create \
|
migrate-up migrate-down migrate-status migrate-create \
|
||||||
mysql-up mysql-down mysql-test mysql-test-quick
|
mysql-up mysql-down mysql-test mysql-test-quick
|
||||||
|
|
||||||
VERSION := $(shell go run ./cmd/versionctl print)
|
VERSION := $(shell go run ../versionctl print)
|
||||||
GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || printf 'unknown')
|
GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || printf 'unknown')
|
||||||
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
GO_LDFLAGS := -X nex/backend/pkg/buildinfo.version=$(VERSION) -X nex/backend/pkg/buildinfo.commit=$(GIT_COMMIT) -X nex/backend/pkg/buildinfo.buildTime=$(BUILD_TIME)
|
GO_LDFLAGS := -X nex/backend/pkg/buildinfo.version=$(VERSION) -X nex/backend/pkg/buildinfo.commit=$(GIT_COMMIT) -X nex/backend/pkg/buildinfo.buildTime=$(BUILD_TIME)
|
||||||
|
|||||||
1
go.work
1
go.work
@@ -3,4 +3,5 @@ go 1.26.2
|
|||||||
use (
|
use (
|
||||||
backend
|
backend
|
||||||
embedfs
|
embedfs
|
||||||
|
versionctl
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQ
|
|||||||
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
|
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
|
||||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
|
||||||
github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw=
|
github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw=
|
||||||
github.com/ClickHouse/clickhouse-go/v2 v2.43.0/go.mod h1:o6jf7JM/zveWC/PP277BLxjHy5KjnGX/jfljhM4s34g=
|
github.com/ClickHouse/clickhouse-go/v2 v2.43.0/go.mod h1:o6jf7JM/zveWC/PP277BLxjHy5KjnGX/jfljhM4s34g=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
@@ -14,6 +12,7 @@ github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmO
|
|||||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/cristalhq/acmd v0.12.0/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ=
|
github.com/cristalhq/acmd v0.12.0/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||||
@@ -26,8 +25,6 @@ github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6v
|
|||||||
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
|
||||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||||
@@ -48,6 +45,7 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
|
|||||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
github.com/jordanlewis/gcassert v0.0.0-20250430164644-389ef753e22e/go.mod h1:ZybsQk6DWyN5t7An1MuPm1gtSZ1xDaTXS9ZjIOxvQrk=
|
github.com/jordanlewis/gcassert v0.0.0-20250430164644-389ef753e22e/go.mod h1:ZybsQk6DWyN5t7An1MuPm1gtSZ1xDaTXS9ZjIOxvQrk=
|
||||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
@@ -66,8 +64,10 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
|
|||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
|
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
|
||||||
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50=
|
github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA=
|
github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
|||||||
@@ -8,18 +8,28 @@
|
|||||||
|
|
||||||
### Requirement: Tag 驱动发布流水线
|
### Requirement: Tag 驱动发布流水线
|
||||||
|
|
||||||
系统 SHALL 仅在符合 `vX.Y.Z` 格式的 Git tag 上触发发布流水线,普通分支 push SHALL NOT 创建发布。
|
系统 SHALL 仅在符合 `vX.Y.Z` 格式的 Git tag 上触发发布流水线,普通分支 push SHALL NOT 创建发布。发布流水线 SHALL 使用 `./versionctl` 而非 `./backend/cmd/versionctl` 调用版本管理工具。
|
||||||
|
|
||||||
#### Scenario: 有效发布 tag
|
#### Scenario: 有效发布 tag
|
||||||
|
|
||||||
- **WHEN** 仓库收到 `v1.2.3` tag push
|
- **WHEN** 仓库收到 `v1.2.3` tag push
|
||||||
- **THEN** 发布流水线 SHALL 启动版本校验、构建和 Release 组装步骤
|
- **THEN** 发布流水线 SHALL 启动版本校验、构建和 Release 组装步骤
|
||||||
|
- **AND** 版本校验步骤 SHALL 使用 `go run ./versionctl print` 和 `go run ./versionctl verify-tag` 获取并验证版本
|
||||||
|
|
||||||
#### Scenario: 普通分支推送
|
#### Scenario: 普通分支推送
|
||||||
|
|
||||||
- **WHEN** 仓库收到非 tag 的分支 push
|
- **WHEN** 仓库收到非 tag 的分支 push
|
||||||
- **THEN** 系统 SHALL NOT 创建 GitHub Release
|
- **THEN** 系统 SHALL NOT 创建 GitHub Release
|
||||||
|
|
||||||
|
### Requirement: 发布流水线 Go 模块缓存覆盖
|
||||||
|
|
||||||
|
发布流水线 SHALL 在所有 Go module 的 go.sum 文件存在时正确设置 Go 模块缓存路径,确保新增的 `versionctl` module 依赖也被缓存。
|
||||||
|
|
||||||
|
#### Scenario: CI 缓存覆盖所有 module
|
||||||
|
|
||||||
|
- **WHEN** 发布流水线设置 Go 模块缓存
|
||||||
|
- **THEN** `cache-dependency-path` SHALL 覆盖 `backend/go.sum` 和 `versionctl/go.sum`
|
||||||
|
|
||||||
### Requirement: 三平台发布构建
|
### Requirement: 三平台发布构建
|
||||||
|
|
||||||
系统 SHALL 在发布流水线中构建 server 与 desktop 的 Linux、Windows、macOS 三个平台产物。
|
系统 SHALL 在发布流水线中构建 server 与 desktop 的 Linux、Windows、macOS 三个平台产物。
|
||||||
|
|||||||
113
openspec/specs/version-bump/spec.md
Normal file
113
openspec/specs/version-bump/spec.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# 版本升迁
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
定义 `version bump` 子命令的版本号递增、下游文件同步、倒退防护及 Makefile 编排规则,确保版本升迁流程安全可自动化。
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement: 版本号递增
|
||||||
|
|
||||||
|
`version bump` 子命令 SHALL 支持三种递增模式:`major`(major+1, minor=0, patch=0)、`minor`(minor+1, patch=0)、`patch`(patch+1),以及直接指定具体版本号。
|
||||||
|
|
||||||
|
#### Scenario: minor 递增
|
||||||
|
|
||||||
|
- **WHEN** 当前 VERSION 为 `0.1.0`,执行 `version bump minor`
|
||||||
|
- **THEN** VERSION 文件 SHALL 被更新为 `0.2.0`
|
||||||
|
|
||||||
|
#### Scenario: major 递增
|
||||||
|
|
||||||
|
- **WHEN** 当前 VERSION 为 `0.1.0`,执行 `version bump major`
|
||||||
|
- **THEN** VERSION 文件 SHALL 被更新为 `1.0.0`
|
||||||
|
|
||||||
|
#### Scenario: patch 递增
|
||||||
|
|
||||||
|
- **WHEN** 当前 VERSION 为 `0.1.0`,执行 `version bump patch`
|
||||||
|
- **THEN** VERSION 文件 SHALL 被更新为 `0.1.1`
|
||||||
|
|
||||||
|
#### Scenario: 指定具体版本号
|
||||||
|
|
||||||
|
- **WHEN** 当前 VERSION 为 `0.1.0`,执行 `version bump 1.0.0`
|
||||||
|
- **THEN** VERSION 文件 SHALL 被更新为 `1.0.0`
|
||||||
|
|
||||||
|
#### Scenario: 指定版本号等于当前 VERSION
|
||||||
|
|
||||||
|
- **WHEN** 当前 VERSION 为 `0.1.0`,执行 `version bump 0.1.0`
|
||||||
|
- **THEN** 命令 SHALL 正常执行,完成 sync 和 check,输出 `0.1.0`
|
||||||
|
|
||||||
|
#### Scenario: 非法 bump 参数
|
||||||
|
|
||||||
|
- **WHEN** 执行 `version bump` 传入既非 `major|minor|patch` 也非合法 semver 的参数
|
||||||
|
- **THEN** 命令 SHALL 以非零退出码失败并输出错误信息
|
||||||
|
|
||||||
|
### Requirement: bump 自动同步下游文件
|
||||||
|
|
||||||
|
`version bump` 子命令 SHALL 在写回 VERSION 文件后自动执行 sync 和 check,确保 `frontend/package.json` 和所有 `frontend/.env.*` 文件与新版本号一致。
|
||||||
|
|
||||||
|
#### Scenario: bump 自动 sync 和 check
|
||||||
|
|
||||||
|
- **WHEN** 执行 `version bump minor` 且当前 VERSION 为 `0.1.0`
|
||||||
|
- **THEN** 命令 SHALL 自动将新版本号 `0.2.0` 同步到 `frontend/package.json` 的 `version` 字段和所有 `frontend/.env.*` 的 `VITE_APP_VERSION` 变量
|
||||||
|
- **AND** 命令 SHALL 自动验证所有下游文件版本号一致性
|
||||||
|
|
||||||
|
#### Scenario: sync 失败时 bump 中止
|
||||||
|
|
||||||
|
- **WHEN** 执行 `version bump minor` 但下游文件同步失败(如文件缺失)
|
||||||
|
- **THEN** 命令 SHALL 以非零退出码失败
|
||||||
|
|
||||||
|
### Requirement: 版本号倒退防护
|
||||||
|
|
||||||
|
`version bump` 子命令 SHALL 检查新版本号严格大于所有已有 git tag 中的最大版本号,防止版本号倒退。
|
||||||
|
|
||||||
|
#### Scenario: 新版本大于已有 tag
|
||||||
|
|
||||||
|
- **WHEN** 已有 tag `v0.1.0`,执行 `version bump minor`
|
||||||
|
- **THEN** 命令 SHALL 成功将版本更新为 `0.2.0`
|
||||||
|
|
||||||
|
#### Scenario: 新版本等于已有 tag
|
||||||
|
|
||||||
|
- **WHEN** 已有 tag `v0.1.0`,执行 `version bump 0.1.0`
|
||||||
|
- **THEN** 命令 SHALL 以非零退出码失败并提示版本号已存在
|
||||||
|
|
||||||
|
#### Scenario: 新版本小于已有 tag
|
||||||
|
|
||||||
|
- **WHEN** 已有 tag `v0.2.0`,执行 `version bump 0.1.5`
|
||||||
|
- **THEN** 命令 SHALL 以非零退出码失败并提示版本号倒退
|
||||||
|
|
||||||
|
#### Scenario: 无已有 tag
|
||||||
|
|
||||||
|
- **WHEN** 不存在任何 `v*.*.*` 格式的 git tag,执行 `version bump 0.1.0`
|
||||||
|
- **THEN** 命令 SHALL 成功
|
||||||
|
|
||||||
|
### Requirement: bump 输出新版本号
|
||||||
|
|
||||||
|
`version bump` 子命令成功时 SHALL 仅将新版本号(不含 `v` 前缀)输出到 stdout,供 Makefile 等外部工具使用。
|
||||||
|
|
||||||
|
#### Scenario: 输出格式
|
||||||
|
|
||||||
|
- **WHEN** 执行 `version bump minor`,当前版本为 `0.1.0`
|
||||||
|
- **THEN** stdout SHALL 输出 `0.2.0`(换行结尾,无额外内容)
|
||||||
|
|
||||||
|
### Requirement: 版本升迁 Makefile 编排
|
||||||
|
|
||||||
|
`make version-bump` SHALL 编排完整的版本升迁流程:工作区干净检查 → `version bump`(含 sync/check/倒退检查)→ git add → git commit → git tag。
|
||||||
|
|
||||||
|
#### Scenario: 完整升迁流程
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make version-bump BUMP=minor`,工作区干净,当前版本 `0.1.0`
|
||||||
|
- **THEN** Makefile SHALL 依次执行:工作区检查 → `version bump minor` → `git add VERSION frontend/` → `git commit -m "chore: 版本升迁 v0.2.0"` → `git tag v0.2.0`
|
||||||
|
|
||||||
|
#### Scenario: 工作区不干净
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make version-bump BUMP=minor`,但工作区有未提交的改动
|
||||||
|
- **THEN** Makefile SHALL 以非零退出码失败并提示先提交或暂存改动
|
||||||
|
|
||||||
|
#### Scenario: 支持指定版本号
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make version-bump SET_VERSION=1.0.0`
|
||||||
|
- **THEN** Makefile SHALL 将 `1.0.0` 传递给 `version bump` 子命令
|
||||||
|
|
||||||
|
#### Scenario: 不自动推送
|
||||||
|
|
||||||
|
- **WHEN** `make version-bump` 成功完成
|
||||||
|
- **THEN** commit 和 tag SHALL 仅存在于本地,SHALL NOT 自动 push 到远程
|
||||||
52
versionctl/.golangci.yml
Normal file
52
versionctl/.golangci.yml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
tests: true
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- errorlint
|
||||||
|
- errcheck
|
||||||
|
- staticcheck
|
||||||
|
- revive
|
||||||
|
- gocritic
|
||||||
|
- gosec
|
||||||
|
- nilerr
|
||||||
|
- goimports
|
||||||
|
- gocyclo
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
errcheck:
|
||||||
|
check-blank: true
|
||||||
|
check-type-assertions: true
|
||||||
|
revive:
|
||||||
|
rules:
|
||||||
|
- name: exported
|
||||||
|
- name: var-naming
|
||||||
|
- name: indent-error-flow
|
||||||
|
- name: error-strings
|
||||||
|
- name: error-return
|
||||||
|
- name: blank-imports
|
||||||
|
goimports:
|
||||||
|
local-prefixes: nex/versionctl
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 10
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-generated: true
|
||||||
|
exclude-rules:
|
||||||
|
- path: '_test\.go'
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
|
source: '(^\s*_\s*=|,\s*_)'
|
||||||
|
- path: '_test\.go'
|
||||||
|
linters:
|
||||||
|
- revive
|
||||||
|
text: '^exported:'
|
||||||
|
- path: '_test\.go'
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
text: 'G(101|401|501)'
|
||||||
|
- path: 'main\.go'
|
||||||
|
linters:
|
||||||
|
- gocyclo
|
||||||
16
versionctl/Makefile
Normal file
16
versionctl/Makefile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.PHONY: \
|
||||||
|
lint test test-coverage clean
|
||||||
|
|
||||||
|
lint:
|
||||||
|
go tool golangci-lint run ./...
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test ./... -v
|
||||||
|
|
||||||
|
test-coverage:
|
||||||
|
go test ./... -coverprofile=coverage.out
|
||||||
|
go tool cover -html=coverage.out -o coverage.html
|
||||||
|
@printf 'Coverage report generated: versionctl/coverage.html\n'
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf coverage.out coverage.html
|
||||||
14
versionctl/go.mod
Normal file
14
versionctl/go.mod
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module nex/versionctl
|
||||||
|
|
||||||
|
go 1.26.2
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.11.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
8
versionctl/go.sum
Normal file
8
versionctl/go.sum
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"nex/backend/pkg/projectversion"
|
"nex/versionctl/projectversion"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -41,6 +41,11 @@ func run(args []string) error {
|
|||||||
return fmt.Errorf("verify-tag 需要一个 tag 参数")
|
return fmt.Errorf("verify-tag 需要一个 tag 参数")
|
||||||
}
|
}
|
||||||
return projectversion.VerifyTag(root, args[1])
|
return projectversion.VerifyTag(root, args[1])
|
||||||
|
case "bump":
|
||||||
|
if len(args) != 2 {
|
||||||
|
return fmt.Errorf("bump 需要一个参数: major|minor|patch 或具体版本号")
|
||||||
|
}
|
||||||
|
return runBump(root, args[1])
|
||||||
case "macos-plist":
|
case "macos-plist":
|
||||||
if len(args) != 2 {
|
if len(args) != 2 {
|
||||||
return fmt.Errorf("macos-plist 需要一个最低系统版本参数")
|
return fmt.Errorf("macos-plist 需要一个最低系统版本参数")
|
||||||
@@ -53,6 +58,33 @@ func run(args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runBump(root, arg string) error {
|
||||||
|
newVersion, err := projectversion.Bump(root, arg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err := projectversion.ListGitTags(root)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := projectversion.CheckNoRegression(newVersion, tags); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := projectversion.Sync(root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := projectversion.Check(root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(newVersion.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func printMacOSPlist(root, minMacOSVersion string) error {
|
func printMacOSPlist(root, minMacOSVersion string) error {
|
||||||
version, err := projectversion.ReadString(root)
|
version, err := projectversion.ReadString(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -115,5 +147,5 @@ func mustGetwd() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func usageError() error {
|
func usageError() error {
|
||||||
return fmt.Errorf("用法: versionctl <print|sync|check|verify-tag|macos-plist|asset-name>")
|
return fmt.Errorf("用法: version <print|sync|check|verify-tag|bump|macos-plist|asset-name>")
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -57,6 +58,18 @@ func (v Version) String() string {
|
|||||||
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v Version) Less(other Version) bool {
|
||||||
|
if v.Major != other.Major {
|
||||||
|
return v.Major < other.Major
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Minor != other.Minor {
|
||||||
|
return v.Minor < other.Minor
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.Patch < other.Patch
|
||||||
|
}
|
||||||
|
|
||||||
func FindRepoRoot(start string) (string, error) {
|
func FindRepoRoot(start string) (string, error) {
|
||||||
current := start
|
current := start
|
||||||
for {
|
for {
|
||||||
@@ -340,3 +353,91 @@ func DesktopInfoPlist(version, minMacOSVersion string) (string, error) {
|
|||||||
|
|
||||||
return content + "\n", nil
|
return content + "\n", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tagRegex = regexp.MustCompile(`^v(\d+\.\d+\.\d+)$`)
|
||||||
|
|
||||||
|
func ListGitTags(root string) ([]string, error) {
|
||||||
|
cmd := exec.Command("git", "-C", root, "tag", "--list", "--merge", "HEAD")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取 git tag 列表失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags []string
|
||||||
|
for _, line := range strings.Split(strings.TrimSpace(string(output)), "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line != "" {
|
||||||
|
tags = append(tags, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckNoRegression(newVersion Version, tags []string) error {
|
||||||
|
var maxVersion Version
|
||||||
|
found := false
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
parts := tagRegex.FindStringSubmatch(tag)
|
||||||
|
if parts == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := Parse(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found || maxVersion.Less(v) {
|
||||||
|
maxVersion = v
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if newVersion == maxVersion {
|
||||||
|
return fmt.Errorf("版本号 %s 已存在(tag v%s)", newVersion, maxVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newVersion.Less(maxVersion) {
|
||||||
|
return fmt.Errorf("版本号 %s 小于已有 tag v%s,不允许倒退", newVersion, maxVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bump(root, arg string) (Version, error) {
|
||||||
|
current, err := Read(root)
|
||||||
|
if err != nil {
|
||||||
|
return Version{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVersion Version
|
||||||
|
|
||||||
|
switch arg {
|
||||||
|
case "major":
|
||||||
|
newVersion = Version{Major: current.Major + 1, Minor: 0, Patch: 0}
|
||||||
|
case "minor":
|
||||||
|
newVersion = Version{Major: current.Major, Minor: current.Minor + 1, Patch: 0}
|
||||||
|
case "patch":
|
||||||
|
newVersion = Version{Major: current.Major, Minor: current.Minor, Patch: current.Patch + 1}
|
||||||
|
default:
|
||||||
|
parsed, parseErr := Parse(arg)
|
||||||
|
if parseErr != nil {
|
||||||
|
return Version{}, fmt.Errorf("参数 %q 既非 major|minor|patch 也非合法版本号: %w", arg, parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
newVersion = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
versionPath := filepath.Join(root, versionFileName)
|
||||||
|
if err := os.WriteFile(versionPath, []byte(newVersion.String()+"\n"), 0o600); err != nil {
|
||||||
|
return Version{}, fmt.Errorf("写入 VERSION 失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVersion, nil
|
||||||
|
}
|
||||||
@@ -111,3 +111,105 @@ func TestDesktopInfoPlist(t *testing.T) {
|
|||||||
_, err = DesktopInfoPlist("1.2.3", "")
|
_, err = DesktopInfoPlist("1.2.3", "")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLess(t *testing.T) {
|
||||||
|
assert.True(t, Version{1, 0, 0}.Less(Version{2, 0, 0}))
|
||||||
|
assert.True(t, Version{1, 1, 0}.Less(Version{1, 2, 0}))
|
||||||
|
assert.True(t, Version{1, 0, 1}.Less(Version{1, 0, 2}))
|
||||||
|
assert.False(t, Version{2, 0, 0}.Less(Version{1, 0, 0}))
|
||||||
|
assert.False(t, Version{1, 0, 0}.Less(Version{1, 0, 0}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBump(t *testing.T) {
|
||||||
|
setupRoot := func(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
root := t.TempDir()
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(root, "VERSION"), []byte("0.1.0\n"), 0o600))
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("major", func(t *testing.T) {
|
||||||
|
root := setupRoot(t)
|
||||||
|
v, err := Bump(root, "major")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, Version{1, 0, 0}, v)
|
||||||
|
|
||||||
|
read, readErr := ReadString(root)
|
||||||
|
require.NoError(t, readErr)
|
||||||
|
assert.Equal(t, "1.0.0", read)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("minor", func(t *testing.T) {
|
||||||
|
root := setupRoot(t)
|
||||||
|
v, err := Bump(root, "minor")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, Version{0, 2, 0}, v)
|
||||||
|
|
||||||
|
read, readErr := ReadString(root)
|
||||||
|
require.NoError(t, readErr)
|
||||||
|
assert.Equal(t, "0.2.0", read)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("patch", func(t *testing.T) {
|
||||||
|
root := setupRoot(t)
|
||||||
|
v, err := Bump(root, "patch")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, Version{0, 1, 1}, v)
|
||||||
|
|
||||||
|
read, readErr := ReadString(root)
|
||||||
|
require.NoError(t, readErr)
|
||||||
|
assert.Equal(t, "0.1.1", read)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("specific version", func(t *testing.T) {
|
||||||
|
root := setupRoot(t)
|
||||||
|
v, err := Bump(root, "1.0.0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, Version{1, 0, 0}, v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("same version as current", func(t *testing.T) {
|
||||||
|
root := setupRoot(t)
|
||||||
|
v, err := Bump(root, "0.1.0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, Version{0, 1, 0}, v)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid argument", func(t *testing.T) {
|
||||||
|
root := setupRoot(t)
|
||||||
|
_, err := Bump(root, "invalid")
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckNoRegression(t *testing.T) {
|
||||||
|
t.Run("greater than existing tag", func(t *testing.T) {
|
||||||
|
err := CheckNoRegression(Version{0, 2, 0}, []string{"v0.1.0"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("equal to existing tag", func(t *testing.T) {
|
||||||
|
err := CheckNoRegression(Version{0, 1, 0}, []string{"v0.1.0"})
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("less than existing tag", func(t *testing.T) {
|
||||||
|
err := CheckNoRegression(Version{0, 1, 5}, []string{"v0.2.0"})
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no tags", func(t *testing.T) {
|
||||||
|
err := CheckNoRegression(Version{0, 1, 0}, nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("skips non-semver tags", func(t *testing.T) {
|
||||||
|
err := CheckNoRegression(Version{0, 2, 0}, []string{"v0.1.0", "some-other-tag"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("picks max tag", func(t *testing.T) {
|
||||||
|
err := CheckNoRegression(Version{0, 1, 5}, []string{"v0.1.0", "v0.2.0", "v0.0.5"})
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user