Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 407d008e19 | |||
| a2751eab31 | |||
| 5655fc5560 | |||
| 49b47a1ae0 | |||
| bcf82d42bc | |||
| 394025c8ea | |||
| 34bd749741 | |||
| 290f299e22 | |||
| 859dec8ada |
42
.github/workflows/release.yml
vendored
42
.github/workflows/release.yml
vendored
@@ -136,7 +136,7 @@ jobs:
|
|||||||
make release-assets-check
|
make release-assets-check
|
||||||
|
|
||||||
- name: Build Linux release assets
|
- name: Build Linux release assets
|
||||||
run: make release-assets-linux TARGET_ARCH=${{ matrix.arch }}
|
run: make release-assets-linux
|
||||||
|
|
||||||
- name: Upload Linux release assets
|
- name: Upload Linux release assets
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -148,22 +148,19 @@ jobs:
|
|||||||
build-windows:
|
build-windows:
|
||||||
name: Build Windows ${{ matrix.arch }} Assets
|
name: Build Windows ${{ matrix.arch }} Assets
|
||||||
needs: prepare
|
needs: prepare
|
||||||
runs-on: windows-latest
|
runs-on: ${{ matrix.runner }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- arch: amd64
|
- arch: amd64
|
||||||
|
runner: windows-latest
|
||||||
msystem: MINGW64
|
msystem: MINGW64
|
||||||
|
cc: gcc
|
||||||
|
cxx: g++
|
||||||
packages: >-
|
packages: >-
|
||||||
make
|
make
|
||||||
mingw-w64-x86_64-gcc
|
mingw-w64-x86_64-gcc
|
||||||
- arch: arm64
|
|
||||||
msystem: CLANGARM64
|
|
||||||
packages: >-
|
|
||||||
make
|
|
||||||
mingw-w64-clang-aarch64-clang
|
|
||||||
mingw-w64-clang-aarch64-llvm
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
@@ -193,6 +190,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Preflight Windows release toolchain
|
- name: Preflight Windows release toolchain
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
|
env:
|
||||||
|
CC: ${{ matrix.cc }}
|
||||||
|
CXX: ${{ matrix.cxx }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
command -v go
|
command -v go
|
||||||
@@ -201,21 +201,12 @@ jobs:
|
|||||||
bun --version
|
bun --version
|
||||||
command -v make
|
command -v make
|
||||||
make --version
|
make --version
|
||||||
if [ "${{ matrix.arch }}" = "arm64" ]; then
|
command -v "$CC"
|
||||||
command -v clang
|
"$CC" --version
|
||||||
clang --version
|
command -v "$CXX"
|
||||||
if command -v llvm-windres >/dev/null 2>&1; then
|
"$CXX" --version
|
||||||
llvm-windres --version
|
command -v windres
|
||||||
else
|
windres --version
|
||||||
command -v windres
|
|
||||||
windres --version
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
command -v gcc
|
|
||||||
gcc --version
|
|
||||||
command -v windres
|
|
||||||
windres --version
|
|
||||||
fi
|
|
||||||
if command -v powershell.exe >/dev/null 2>&1; then
|
if command -v powershell.exe >/dev/null 2>&1; then
|
||||||
powershell.exe -NoProfile -Command '$PSVersionTable.PSVersion.ToString()'
|
powershell.exe -NoProfile -Command '$PSVersionTable.PSVersion.ToString()'
|
||||||
else
|
else
|
||||||
@@ -226,7 +217,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Windows release assets
|
- name: Build Windows release assets
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
run: make release-assets-windows TARGET_ARCH=${{ matrix.arch }}
|
env:
|
||||||
|
CC: ${{ matrix.cc }}
|
||||||
|
CXX: ${{ matrix.cxx }}
|
||||||
|
run: make release-assets-windows
|
||||||
|
|
||||||
- name: Upload Windows release assets
|
- name: Upload Windows release assets
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
130
Makefile
130
Makefile
@@ -1,5 +1,5 @@
|
|||||||
.PHONY: \
|
.PHONY: \
|
||||||
lint test clean \
|
lint test clean hooks-install hooks-check hooks-test \
|
||||||
version-sync version-check version-bump \
|
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 \
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
_backend-lint _backend-test _backend-clean _backend-build \
|
_backend-lint _backend-test _backend-clean _backend-build \
|
||||||
_versionctl-lint _versionctl-test \
|
_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 \
|
||||||
|
_hooks-pre-commit _check-clean-worktree \
|
||||||
_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 \
|
||||||
_check-linux-target-arch _check-windows-target-arch _ensure-appimagetool \
|
_check-linux-target-arch _check-windows-target-arch _ensure-appimagetool \
|
||||||
@@ -35,16 +36,16 @@ ifeq ($(TARGET_ARCH),arm64)
|
|||||||
APPIMAGE_ARCH := aarch64
|
APPIMAGE_ARCH := aarch64
|
||||||
DEB_ARCH := arm64
|
DEB_ARCH := arm64
|
||||||
RPM_ARCH := aarch64
|
RPM_ARCH := aarch64
|
||||||
WINDOWS_WINDRES_FORMAT := pe-aarch64
|
|
||||||
WINDOWS_RESOURCE := rsrc_windows_arm64.syso
|
|
||||||
else
|
else
|
||||||
APPIMAGE_ARCH := x86_64
|
APPIMAGE_ARCH := x86_64
|
||||||
DEB_ARCH := amd64
|
DEB_ARCH := amd64
|
||||||
RPM_ARCH := x86_64
|
RPM_ARCH := x86_64
|
||||||
WINDOWS_WINDRES_FORMAT := pe-x86-64
|
|
||||||
WINDOWS_RESOURCE := rsrc_windows_amd64.syso
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
WINDOWS_WINDRES_FORMAT_BFD := pe-x86-64
|
||||||
|
WINDOWS_WINDRES_FORMAT_LLVM := x86_64-w64-mingw32
|
||||||
|
WINDOWS_RESOURCE := rsrc_windows_amd64.syso
|
||||||
|
|
||||||
APPIMAGETOOL_PATH := build/tools/appimagetool-$(APPIMAGE_ARCH).AppImage
|
APPIMAGETOOL_PATH := build/tools/appimagetool-$(APPIMAGE_ARCH).AppImage
|
||||||
APPIMAGETOOL_URL ?= https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(APPIMAGE_ARCH).AppImage
|
APPIMAGETOOL_URL ?= https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(APPIMAGE_ARCH).AppImage
|
||||||
APPIMAGETOOL ?= $(APPIMAGETOOL_PATH)
|
APPIMAGETOOL ?= $(APPIMAGETOOL_PATH)
|
||||||
@@ -62,6 +63,82 @@ test: _backend-test _frontend-test _desktop-test _versionctl-test
|
|||||||
clean: _backend-clean _frontend-clean _desktop-clean
|
clean: _backend-clean _frontend-clean _desktop-clean
|
||||||
@printf 'Clean complete\n'
|
@printf 'Clean complete\n'
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Git hooks
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
hooks-install:
|
||||||
|
@hooks_dir=$$(git rev-parse --git-path hooks); \
|
||||||
|
mkdir -p "$$hooks_dir"; \
|
||||||
|
cp scripts/git-hooks/pre-commit "$$hooks_dir/pre-commit"; \
|
||||||
|
cp scripts/git-hooks/commit-msg "$$hooks_dir/commit-msg"; \
|
||||||
|
chmod +x "$$hooks_dir/pre-commit" "$$hooks_dir/commit-msg"; \
|
||||||
|
printf 'Installed Git hooks to %s\n' "$$hooks_dir"
|
||||||
|
|
||||||
|
hooks-check:
|
||||||
|
@hooks_dir=$$(git rev-parse --git-path hooks); \
|
||||||
|
status=0; \
|
||||||
|
for hook in pre-commit commit-msg; do \
|
||||||
|
if [ -x "$$hooks_dir/$$hook" ]; then \
|
||||||
|
printf 'OK: %s\n' "$$hook"; \
|
||||||
|
else \
|
||||||
|
printf 'MISSING: %s (%s/%s)\n' "$$hook" "$$hooks_dir" "$$hook"; \
|
||||||
|
status=1; \
|
||||||
|
fi; \
|
||||||
|
done; \
|
||||||
|
exit $$status
|
||||||
|
|
||||||
|
hooks-test:
|
||||||
|
@scripts/git-hooks/test-hooks.sh
|
||||||
|
|
||||||
|
_hooks-pre-commit:
|
||||||
|
@set -e; \
|
||||||
|
staged_files=$$(git diff --cached --name-only --diff-filter=ACM); \
|
||||||
|
if [ -z "$$staged_files" ]; then \
|
||||||
|
printf 'No staged files to check\n'; \
|
||||||
|
exit 0; \
|
||||||
|
fi; \
|
||||||
|
printf '%s\n' "$$staged_files" | while IFS= read -r file; do \
|
||||||
|
[ -n "$$file" ] || continue; \
|
||||||
|
case "$$file" in scripts/git-hooks/*) continue ;; esac; \
|
||||||
|
if git show ":$$file" 2>/dev/null | grep -Eq '^(<<<<<<<|=======|>>>>>>>)'; then \
|
||||||
|
printf 'Found conflict markers in staged file: %s\n' "$$file" >&2; \
|
||||||
|
printf 'Resolve conflict markers before committing.\n' >&2; \
|
||||||
|
exit 1; \
|
||||||
|
fi; \
|
||||||
|
size=$$(git cat-file -s ":$$file" 2>/dev/null || printf '0'); \
|
||||||
|
if [ "$$size" -gt 512000 ] 2>/dev/null; then \
|
||||||
|
if git show ":$$file" 2>/dev/null | LC_ALL=C grep -Iq .; then \
|
||||||
|
printf 'Warning: large staged text file (%s bytes): %s\n' "$$size" "$$file" >&2; \
|
||||||
|
fi; \
|
||||||
|
fi; \
|
||||||
|
case "$$file" in \
|
||||||
|
backend/*.go) \
|
||||||
|
rel=$${file#backend/}; \
|
||||||
|
printf 'Go lint: backend/%s\n' "$$rel"; \
|
||||||
|
(cd backend && go tool golangci-lint run "$$rel"); \
|
||||||
|
;; \
|
||||||
|
versionctl/*.go) \
|
||||||
|
rel=$${file#versionctl/}; \
|
||||||
|
printf 'Go lint: versionctl/%s\n' "$$rel"; \
|
||||||
|
(cd versionctl && go tool golangci-lint run "$$rel"); \
|
||||||
|
;; \
|
||||||
|
frontend/*.ts|frontend/*.tsx) \
|
||||||
|
rel=$${file#frontend/}; \
|
||||||
|
printf 'Frontend lint: frontend/%s\n' "$$rel"; \
|
||||||
|
(cd frontend && bunx eslint "$$rel"); \
|
||||||
|
printf 'Frontend format: frontend/%s\n' "$$rel"; \
|
||||||
|
(cd frontend && bunx prettier --check "$$rel"); \
|
||||||
|
;; \
|
||||||
|
frontend/*.scss) \
|
||||||
|
rel=$${file#frontend/}; \
|
||||||
|
printf 'Frontend format: frontend/%s\n' "$$rel"; \
|
||||||
|
(cd frontend && bunx prettier --check "$$rel"); \
|
||||||
|
;; \
|
||||||
|
esac; \
|
||||||
|
done; \
|
||||||
|
printf 'Pre-commit checks passed\n'
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# 版本管理
|
# 版本管理
|
||||||
# ============================================
|
# ============================================
|
||||||
@@ -73,13 +150,21 @@ version-check:
|
|||||||
go run ./versionctl check
|
go run ./versionctl check
|
||||||
|
|
||||||
version-bump: BUMP ?= patch
|
version-bump: BUMP ?= patch
|
||||||
version-bump:
|
version-bump: lint test _check-clean-worktree
|
||||||
$(eval _BUMP_ARG := $(if $(SET_VERSION),$(SET_VERSION),$(BUMP)))
|
@set -e; \
|
||||||
$(eval _NEW_VERSION := $(shell go run ./versionctl bump $(_BUMP_ARG)))
|
bump_arg="$(if $(SET_VERSION),$(SET_VERSION),$(BUMP))"; \
|
||||||
git add VERSION frontend/
|
new_version=$$(go run ./versionctl bump "$$bump_arg"); \
|
||||||
git commit -m "chore: 版本升迁 v$(_NEW_VERSION)"
|
git add VERSION frontend/; \
|
||||||
git tag "v$(_NEW_VERSION)"
|
git commit -m "chore: 版本升迁 v$$new_version"; \
|
||||||
@printf '版本升迁完成: v%s\n' "$(_NEW_VERSION)"
|
git tag "v$$new_version"; \
|
||||||
|
printf '版本升迁完成: v%s\n' "$$new_version"
|
||||||
|
|
||||||
|
_check-clean-worktree:
|
||||||
|
@if [ -n "$$(git status --porcelain)" ]; then \
|
||||||
|
printf '工作区不干净,请先提交或清理改动后再执行版本升迁。\n' >&2; \
|
||||||
|
git status --short; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Server 模式
|
# Server 模式
|
||||||
@@ -116,6 +201,7 @@ desktop-build-mac: version-check _desktop-prepare-frontend _desktop-prepare-embe
|
|||||||
cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-mac-amd64 ./cmd/desktop
|
cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-mac-amd64 ./cmd/desktop
|
||||||
lipo -create build/nex-mac-arm64 build/nex-mac-amd64 -output build/nex-mac-universal
|
lipo -create build/nex-mac-arm64 build/nex-mac-amd64 -output build/nex-mac-universal
|
||||||
lipo -info build/nex-mac-universal | grep -q 'x86_64 arm64'
|
lipo -info build/nex-mac-universal | grep -q 'x86_64 arm64'
|
||||||
|
rm -f build/nex-mac-arm64 build/nex-mac-amd64
|
||||||
@printf 'Packaging macOS app bundle...\n'
|
@printf 'Packaging macOS app bundle...\n'
|
||||||
rm -rf build/Nex.app
|
rm -rf build/Nex.app
|
||||||
mkdir -p build/Nex.app/Contents/MacOS build/Nex.app/Contents/Resources
|
mkdir -p build/Nex.app/Contents/MacOS build/Nex.app/Contents/Resources
|
||||||
@@ -160,7 +246,7 @@ _desktop-test:
|
|||||||
cd backend && go test ./cmd/desktop/... -v
|
cd backend && go test ./cmd/desktop/... -v
|
||||||
|
|
||||||
_desktop-clean:
|
_desktop-clean:
|
||||||
rm -rf build/ embedfs/assets embedfs/frontend-dist backend/cmd/desktop/rsrc_windows_amd64.syso backend/cmd/desktop/rsrc_windows_arm64.syso
|
rm -rf build/ embedfs/assets embedfs/frontend-dist backend/cmd/desktop/rsrc_windows_amd64.syso
|
||||||
|
|
||||||
_desktop-prepare-frontend: _frontend-install
|
_desktop-prepare-frontend: _frontend-install
|
||||||
@printf 'Preparing frontend for desktop...\n'
|
@printf 'Preparing frontend for desktop...\n'
|
||||||
@@ -176,13 +262,16 @@ _desktop-prepare-embedfs:
|
|||||||
|
|
||||||
_desktop-prepare-windows-resource: _check-windows-target-arch
|
_desktop-prepare-windows-resource: _check-windows-target-arch
|
||||||
@printf 'Preparing Windows $(TARGET_ARCH) executable icon...\n'
|
@printf 'Preparing Windows $(TARGET_ARCH) executable icon...\n'
|
||||||
@if [ "$(TARGET_ARCH)" = "arm64" ] && [ "$(WINDRES)" = "windres" ] && command -v llvm-windres >/dev/null 2>&1; then \
|
@WINDRES_CMD="$(WINDRES)"; \
|
||||||
|
WINDRES_FMT="$(WINDOWS_WINDRES_FORMAT_BFD)"; \
|
||||||
|
if command -v llvm-windres >/dev/null 2>&1; then \
|
||||||
WINDRES_CMD=llvm-windres; \
|
WINDRES_CMD=llvm-windres; \
|
||||||
else \
|
WINDRES_FMT="$(WINDOWS_WINDRES_FORMAT_LLVM)"; \
|
||||||
WINDRES_CMD="$(WINDRES)"; \
|
elif "$$WINDRES_CMD" --version 2>&1 | grep -qi LLVM; then \
|
||||||
|
WINDRES_FMT="$(WINDOWS_WINDRES_FORMAT_LLVM)"; \
|
||||||
fi; \
|
fi; \
|
||||||
command -v "$$WINDRES_CMD" >/dev/null 2>&1 || { printf 'Missing windres tool: %s\n' "$$WINDRES_CMD"; exit 1; }; \
|
command -v "$$WINDRES_CMD" >/dev/null 2>&1 || { printf 'Missing windres tool: %s\n' "$$WINDRES_CMD"; exit 1; }; \
|
||||||
cd backend/cmd/desktop && "$$WINDRES_CMD" -O coff -F $(WINDOWS_WINDRES_FORMAT) -i icon_windows.rc -o $(WINDOWS_RESOURCE)
|
cd backend/cmd/desktop && "$$WINDRES_CMD" -O coff -F "$$WINDRES_FMT" -i icon_windows.rc -o $(WINDOWS_RESOURCE)
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# 发布资产
|
# 发布资产
|
||||||
@@ -241,6 +330,7 @@ release-assets-server-macos: version-check
|
|||||||
tar -C build -czf "$(RELEASE_DIR)/$$asset" nex-server-macos-arm64
|
tar -C build -czf "$(RELEASE_DIR)/$$asset" nex-server-macos-arm64
|
||||||
asset=$$(go run ./versionctl asset-name server macos universal tar.gz); \
|
asset=$$(go run ./versionctl asset-name server macos universal tar.gz); \
|
||||||
tar -C build -czf "$(RELEASE_DIR)/$$asset" nex-server-macos-universal
|
tar -C build -czf "$(RELEASE_DIR)/$$asset" nex-server-macos-universal
|
||||||
|
rm -f build/nex-server-macos-amd64 build/nex-server-macos-arm64 build/nex-server-macos-universal
|
||||||
|
|
||||||
release-assets-desktop-linux: version-check release-assets-check desktop-build-linux _package-linux-tar _package-linux-appimage _package-linux-deb _package-linux-rpm
|
release-assets-desktop-linux: version-check release-assets-check desktop-build-linux _package-linux-tar _package-linux-appimage _package-linux-deb _package-linux-rpm
|
||||||
|
|
||||||
@@ -251,6 +341,7 @@ release-assets-desktop-windows: version-check release-assets-check desktop-build
|
|||||||
"$$POWERSHELL" -NoProfile -Command "Compress-Archive -LiteralPath '$(WINDOWS_DESKTOP_BINARY)' -DestinationPath '$(RELEASE_DIR)/$$asset' -Force"
|
"$$POWERSHELL" -NoProfile -Command "Compress-Archive -LiteralPath '$(WINDOWS_DESKTOP_BINARY)' -DestinationPath '$(RELEASE_DIR)/$$asset' -Force"
|
||||||
|
|
||||||
release-assets-desktop-macos: version-check release-assets-check desktop-build-mac _package-macos-zip _package-macos-dmg
|
release-assets-desktop-macos: version-check release-assets-check desktop-build-mac _package-macos-zip _package-macos-dmg
|
||||||
|
rm -rf build/Nex.app build/dmg
|
||||||
|
|
||||||
release-assets-checksums:
|
release-assets-checksums:
|
||||||
@cd "$(RELEASE_DIR)" && \
|
@cd "$(RELEASE_DIR)" && \
|
||||||
@@ -275,7 +366,7 @@ _check-linux-target-arch:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
_check-windows-target-arch:
|
_check-windows-target-arch:
|
||||||
@if [ "$(TARGET_ARCH)" != "amd64" ] && [ "$(TARGET_ARCH)" != "arm64" ]; then \
|
@if [ "$(TARGET_ARCH)" != "amd64" ]; then \
|
||||||
printf 'Unsupported Windows TARGET_ARCH: %s\n' "$(TARGET_ARCH)"; \
|
printf 'Unsupported Windows TARGET_ARCH: %s\n' "$(TARGET_ARCH)"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
@@ -366,7 +457,8 @@ _package-macos-dmg:
|
|||||||
ln -s /Applications "$$dmgdir/Applications"; \
|
ln -s /Applications "$$dmgdir/Applications"; \
|
||||||
asset=$$(go run ./versionctl asset-name desktop macos universal dmg); \
|
asset=$$(go run ./versionctl asset-name desktop macos universal dmg); \
|
||||||
hdiutil create -volname Nex -srcfolder "$$dmgdir" -ov -format UDZO "$(RELEASE_DIR)/$$asset"; \
|
hdiutil create -volname Nex -srcfolder "$$dmgdir" -ov -format UDZO "$(RELEASE_DIR)/$$asset"; \
|
||||||
hdiutil verify "$(RELEASE_DIR)/$$asset" >/dev/null
|
hdiutil verify "$(RELEASE_DIR)/$$asset" >/dev/null && \
|
||||||
|
rm -rf "$$dmgdir"
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# 共享 helper targets
|
# 共享 helper targets
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -109,9 +109,6 @@ make desktop-build-mac
|
|||||||
# Windows
|
# Windows
|
||||||
make desktop-build-win
|
make desktop-build-win
|
||||||
|
|
||||||
# Windows arm64
|
|
||||||
make desktop-build-win TARGET_ARCH=arm64
|
|
||||||
|
|
||||||
# Linux
|
# Linux
|
||||||
make desktop-build-linux
|
make desktop-build-linux
|
||||||
|
|
||||||
@@ -120,7 +117,7 @@ make desktop-build-linux TARGET_ARCH=arm64
|
|||||||
```
|
```
|
||||||
|
|
||||||
**使用桌面应用**:
|
**使用桌面应用**:
|
||||||
- 双击启动应用(macOS: Nex.app,Windows: nex-win-amd64.exe / nex-win-arm64.exe,Linux: nex-linux-amd64 / nex-linux-arm64)
|
- 双击启动应用(macOS: Nex.app,Windows: nex-win-amd64.exe,Linux: nex-linux-amd64 / nex-linux-arm64)
|
||||||
- 系统托盘图标出现,浏览器自动打开管理界面
|
- 系统托盘图标出现,浏览器自动打开管理界面
|
||||||
- 点击托盘图标显示菜单,可打开管理界面或退出
|
- 点击托盘图标显示菜单,可打开管理界面或退出
|
||||||
- 关闭浏览器后服务继续运行,可通过托盘重新打开
|
- 关闭浏览器后服务继续运行,可通过托盘重新打开
|
||||||
@@ -175,7 +172,6 @@ make server-build
|
|||||||
| macOS arm64 | `nex-server_<version>_macos_arm64.tar.gz` |
|
| macOS arm64 | `nex-server_<version>_macos_arm64.tar.gz` |
|
||||||
| macOS universal | `nex-server_<version>_macos_universal.tar.gz` |
|
| macOS universal | `nex-server_<version>_macos_universal.tar.gz` |
|
||||||
| Windows amd64 | `nex-server_<version>_windows_amd64.zip` |
|
| Windows amd64 | `nex-server_<version>_windows_amd64.zip` |
|
||||||
| Windows arm64 | `nex-server_<version>_windows_arm64.zip` |
|
|
||||||
|
|
||||||
**web 产物**:
|
**web 产物**:
|
||||||
|
|
||||||
@@ -191,7 +187,6 @@ make server-build
|
|||||||
| Linux arm64 | `nex-desktop_<version>_linux_arm64.tar.gz`、`.AppImage`、`.deb`、`.rpm` |
|
| Linux arm64 | `nex-desktop_<version>_linux_arm64.tar.gz`、`.AppImage`、`.deb`、`.rpm` |
|
||||||
| macOS universal | `nex-desktop_<version>_macos_universal.zip`、`nex-desktop_<version>_macos_universal.dmg` |
|
| macOS universal | `nex-desktop_<version>_macos_universal.zip`、`nex-desktop_<version>_macos_universal.dmg` |
|
||||||
| Windows amd64 | `nex-desktop_<version>_windows_amd64.zip` |
|
| Windows amd64 | `nex-desktop_<version>_windows_amd64.zip` |
|
||||||
| Windows arm64 | `nex-desktop_<version>_windows_arm64.zip` |
|
|
||||||
|
|
||||||
Linux deb 包声明 `libgtk-3-0`、`libayatana-appindicator3-1`、`xdg-utils` 运行依赖;rpm 包声明 `gtk3`、`libayatana-appindicator-gtk3`、`xdg-utils` 运行依赖。Rocky Linux 9 等发行版可能需要启用 EPEL 才能解析 Ayatana AppIndicator 依赖。
|
Linux deb 包声明 `libgtk-3-0`、`libayatana-appindicator3-1`、`xdg-utils` 运行依赖;rpm 包声明 `gtk3`、`libayatana-appindicator-gtk3`、`xdg-utils` 运行依赖。Rocky Linux 9 等发行版可能需要启用 EPEL 才能解析 Ayatana AppIndicator 依赖。
|
||||||
|
|
||||||
@@ -333,7 +328,13 @@ backend 分类测试、MySQL 专项测试和前端 E2E 测试请分别查看 `ba
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 首次克隆后安装 Git hooks
|
# 首次克隆后安装 Git hooks
|
||||||
lefthook install
|
make hooks-install
|
||||||
|
|
||||||
|
# 检查 Git hooks 安装状态
|
||||||
|
make hooks-check
|
||||||
|
|
||||||
|
# 运行 Git hooks 回归测试
|
||||||
|
make hooks-test
|
||||||
|
|
||||||
# 全局命令
|
# 全局命令
|
||||||
make lint # 前后端共享检查
|
make lint # 前后端共享检查
|
||||||
@@ -356,6 +357,11 @@ make desktop-test # desktop 专属测试
|
|||||||
make desktop-clean # 清理 desktop 产物
|
make desktop-clean # 清理 desktop 产物
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Git hooks 使用仓库内 `scripts/git-hooks/` 的原生脚本,不依赖额外 hook 框架。当前 hooks 包含:
|
||||||
|
|
||||||
|
- pre-commit:检查 staged files 的冲突标记、Go lint、前端 lint/格式和大文件告警
|
||||||
|
- commit-msg:校验提交信息格式为 `类型: 简短描述`,描述需使用中文
|
||||||
|
|
||||||
## 版本与发布
|
## 版本与发布
|
||||||
|
|
||||||
### 统一版本源
|
### 统一版本源
|
||||||
@@ -366,7 +372,7 @@ make desktop-clean # 清理 desktop 产物
|
|||||||
### 本地版本演进
|
### 本地版本演进
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 递增版本(自动 sync + check + commit + tag)
|
# 递增版本(自动 lint + test + 工作区检查 + sync/check + commit + tag)
|
||||||
make version-bump BUMP=minor
|
make version-bump BUMP=minor
|
||||||
|
|
||||||
# 或指定具体版本号
|
# 或指定具体版本号
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
VITE_API_BASE=
|
VITE_API_BASE=
|
||||||
VITE_APP_VERSION=0.1.3
|
VITE_APP_VERSION=0.1.7
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
VITE_API_BASE=
|
VITE_API_BASE=
|
||||||
VITE_APP_VERSION=0.1.3
|
VITE_APP_VERSION=0.1.7
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
VITE_API_BASE=/api
|
VITE_API_BASE=/api
|
||||||
VITE_APP_VERSION=0.1.3
|
VITE_APP_VERSION=0.1.7
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.3",
|
"version": "0.1.7",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
pre-commit:
|
|
||||||
commands:
|
|
||||||
backend-lint:
|
|
||||||
glob: "backend/**/*.go"
|
|
||||||
run: cd backend && go tool golangci-lint run --new-from-rev HEAD ./...
|
|
||||||
@@ -152,9 +152,9 @@ TBD - 提供跨平台桌面应用支持,将后端服务与前端静态资源
|
|||||||
#### Scenario: Windows 构建
|
#### Scenario: Windows 构建
|
||||||
|
|
||||||
- **WHEN** 执行 Windows desktop 构建命令且当前版本为 `1.2.3`
|
- **WHEN** 执行 Windows desktop 构建命令且当前版本为 `1.2.3`
|
||||||
- **THEN** 系统 SHALL 生成 Windows amd64 和 arm64 desktop 可执行文件
|
- **THEN** 系统 SHALL 生成 Windows amd64 desktop 可执行文件
|
||||||
- **AND** Windows desktop 构建 SHALL 使用 `-H=windowsgui` linker flag 隐藏控制台窗口
|
- **AND** Windows desktop 构建 SHALL 使用 `-H=windowsgui` linker flag 隐藏控制台窗口
|
||||||
- **AND** 最终 Windows desktop 发布资产文件名 SHALL 包含 `1.2.3`、`windows` 和对应架构标识
|
- **AND** 最终 Windows desktop 发布资产文件名 SHALL 包含 `1.2.3`、`windows` 和 `amd64`
|
||||||
|
|
||||||
#### Scenario: Linux 构建
|
#### Scenario: Linux 构建
|
||||||
|
|
||||||
|
|||||||
167
openspec/specs/git-hooks/spec.md
Normal file
167
openspec/specs/git-hooks/spec.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# git-hooks
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
定义仓库原生 Git hooks 的安装、校验、测试与跨平台执行规则,确保提交前快速检查和提交信息格式校验符合项目规范。
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement: pre-commit hook 快速检查
|
||||||
|
|
||||||
|
pre-commit hook SHALL 在 `git commit` 执行前对 staged files 进行快速检查,仅检查本次提交涉及的文件。
|
||||||
|
|
||||||
|
#### Scenario: 无 Go 和前端文件变更时跳过
|
||||||
|
|
||||||
|
- **WHEN** staged files 中既无 `.go` 文件也无 `.ts`/`.tsx`/`.scss` 文件
|
||||||
|
- **THEN** pre-commit hook SHALL 直接通过,不运行任何 linter
|
||||||
|
|
||||||
|
#### Scenario: 冲突标记检测
|
||||||
|
|
||||||
|
- **WHEN** staged files 中包含 `<<<<<<<`、`=======` 或 `>>>>>>>` 冲突标记
|
||||||
|
- **THEN** pre-commit hook SHALL 报告错误并列出包含冲突的文件名
|
||||||
|
- **THEN** commit SHALL 被阻止
|
||||||
|
|
||||||
|
#### Scenario: Go 文件 lint 检查
|
||||||
|
|
||||||
|
- **WHEN** staged files 中包含 `.go` 文件
|
||||||
|
- **THEN** pre-commit hook SHALL 对 staged `.go` 文件运行 `golangci-lint run`(复用 `backend/.golangci.yml` 配置)
|
||||||
|
- **THEN** 若 lint 报告任何错误,commit SHALL 被阻止
|
||||||
|
|
||||||
|
#### Scenario: 前端文件 lint 检查
|
||||||
|
|
||||||
|
- **WHEN** staged files 中包含 `.ts` 或 `.tsx` 文件
|
||||||
|
- **THEN** pre-commit hook SHALL 对 staged 前端文件运行 ESLint(复用 `frontend/eslint.config.js` 配置)
|
||||||
|
- **THEN** 若 ESLint 报告任何错误,commit SHALL 被阻止
|
||||||
|
|
||||||
|
#### Scenario: 前端文件格式检查
|
||||||
|
|
||||||
|
- **WHEN** staged files 中包含 `.ts`、`.tsx` 或 `.scss` 文件
|
||||||
|
- **THEN** pre-commit hook SHALL 对 staged 前端文件运行 Prettier 格式检查(复用 `frontend/.prettierrc` 配置)
|
||||||
|
- **THEN** 若存在格式不符合规范的文件,commit SHALL 被阻止
|
||||||
|
|
||||||
|
#### Scenario: 大文件告警
|
||||||
|
|
||||||
|
- **WHEN** staged files 中存在超过 500KB 的文本文件
|
||||||
|
- **THEN** pre-commit hook SHALL 输出警告信息(不阻止提交),提示检查是否误提交
|
||||||
|
|
||||||
|
#### Scenario: commit 被阻止时显示修复提示
|
||||||
|
|
||||||
|
- **WHEN** pre-commit hook 检查失败
|
||||||
|
- **THEN** hook SHALL 输出明确的修复提示(如 `bun run fix`、手动解决冲突标记等)
|
||||||
|
|
||||||
|
### Requirement: commit-msg hook 校验提交信息格式
|
||||||
|
|
||||||
|
commit-msg hook SHALL 在 `git commit` 输入提交信息后校验格式,确保符合项目规范。提交描述 SHALL 使用中文;版本号、英文专有名词可与中文描述混用。
|
||||||
|
|
||||||
|
#### Scenario: 合法格式通过
|
||||||
|
|
||||||
|
- **WHEN** 提交信息首行格式为 `<类型>: <描述>`,类型为 `feat`、`fix`、`refactor`、`docs`、`style`、`test`、`chore` 之一
|
||||||
|
- **THEN** commit-msg hook SHALL 通过,commit 正常执行
|
||||||
|
|
||||||
|
#### Scenario: 非法类型被拒绝
|
||||||
|
|
||||||
|
- **WHEN** 提交信息首行使用的类型不在允许列表中(如 `update: xxx`)
|
||||||
|
- **THEN** commit-msg hook SHALL 报告错误,显示允许的类型列表,commit SHALL 被阻止
|
||||||
|
|
||||||
|
#### Scenario: 英文描述被拒绝
|
||||||
|
|
||||||
|
- **WHEN** 提交信息首行为 `feat: add auth`
|
||||||
|
- **THEN** commit-msg hook SHALL 报告错误,提示提交描述需使用中文
|
||||||
|
- **THEN** commit SHALL 被阻止
|
||||||
|
|
||||||
|
#### Scenario: 缺少冒号空格被拒绝
|
||||||
|
|
||||||
|
- **WHEN** 提交信息首行为 `feat:xxx`(冒号后无空格)或 `feat xxx`
|
||||||
|
- **THEN** commit-msg hook SHALL 报告格式错误,commit SHALL 被阻止
|
||||||
|
|
||||||
|
#### Scenario: 首行过长告警
|
||||||
|
|
||||||
|
- **WHEN** 提交信息首行超过 72 个字符
|
||||||
|
- **THEN** commit-msg hook SHALL 输出警告(不阻止提交),提示首行应简短
|
||||||
|
|
||||||
|
#### Scenario: Merge commit 自动放行
|
||||||
|
|
||||||
|
- **WHEN** 提交信息首行以 `Merge` 开头
|
||||||
|
- **THEN** commit-msg hook SHALL 直接通过,不进行格式校验
|
||||||
|
|
||||||
|
#### Scenario: 格式错误时显示示例
|
||||||
|
|
||||||
|
- **WHEN** commit-msg hook 检查失败
|
||||||
|
- **THEN** hook SHALL 输出包含正确格式示例的错误信息(如 `feat: 添加供应商批量管理功能`)
|
||||||
|
|
||||||
|
### Requirement: hooks-install 安装命令
|
||||||
|
|
||||||
|
`make hooks-install` SHALL 将 `scripts/git-hooks/` 下的 hook 脚本安装到 `.git/hooks/`,不覆盖 Git LFS 管理的 hook。
|
||||||
|
|
||||||
|
#### Scenario: 安装 pre-commit 和 commit-msg
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make hooks-install`
|
||||||
|
- **THEN** `scripts/git-hooks/pre-commit` SHALL 被复制到 `.git/hooks/pre-commit`
|
||||||
|
- **THEN** `scripts/git-hooks/commit-msg` SHALL 被复制到 `.git/hooks/commit-msg`
|
||||||
|
- **THEN** 两个文件 SHALL 被设置为可执行(`chmod +x`)
|
||||||
|
|
||||||
|
#### Scenario: 不覆盖 LFS 管理的 hook
|
||||||
|
|
||||||
|
- **WHEN** `.git/hooks/post-checkout`、`.git/hooks/post-commit`、`.git/hooks/post-merge`、`.git/hooks/pre-push` 已由 Git LFS 管理
|
||||||
|
- **THEN** `make hooks-install` SHALL NOT 覆盖或修改这些文件
|
||||||
|
|
||||||
|
#### Scenario: 重复安装幂等
|
||||||
|
|
||||||
|
- **WHEN** `make hooks-install` 被执行多次
|
||||||
|
- **THEN** hook 文件 SHALL 被正确覆盖更新,不会产生重复或损坏
|
||||||
|
|
||||||
|
#### Scenario: hooks-check 验证安装状态
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make hooks-check`
|
||||||
|
- **THEN** 命令 SHALL 检查 `.git/hooks/pre-commit` 和 `.git/hooks/commit-msg` 是否存在且可执行
|
||||||
|
- **THEN** SHALL 输出每个 hook 的安装状态
|
||||||
|
|
||||||
|
### Requirement: hooks-test 回归测试命令
|
||||||
|
|
||||||
|
`make hooks-test` SHALL 运行仓库内 hook 回归测试,覆盖 commit-msg 格式校验和 pre-commit staged-file 检查,不污染真实 git index。
|
||||||
|
|
||||||
|
#### Scenario: 运行 hook 回归测试
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make hooks-test`
|
||||||
|
- **THEN** SHALL 运行 `scripts/git-hooks/test-hooks.sh`
|
||||||
|
- **THEN** 测试 SHALL 使用临时 `GIT_INDEX_FILE` 构造 staged fixture
|
||||||
|
- **THEN** 若任一 hook 行为不符合预期,命令 SHALL 返回非零退出码
|
||||||
|
|
||||||
|
### Requirement: 跨平台可用
|
||||||
|
|
||||||
|
pre-commit 和 commit-msg hook 脚本 SHALL 可在 macOS 和 Windows(Git Bash)上正常执行。
|
||||||
|
|
||||||
|
#### Scenario: macOS 上正常执行
|
||||||
|
|
||||||
|
- **WHEN** hook 脚本在 macOS 上被 git 调用
|
||||||
|
- **THEN** `#!/bin/sh` shebang SHALL 被系统正确解析
|
||||||
|
- **THEN** `exec make` SHALL 正确调用 Makefile target
|
||||||
|
|
||||||
|
#### Scenario: Windows Git Bash 上正常执行
|
||||||
|
|
||||||
|
- **WHEN** hook 脚本在 Windows 的 Git Bash 环境中被 git 调用
|
||||||
|
- **THEN** Git for Windows 自带的 sh.exe SHALL 正确解析 `#!/bin/sh`
|
||||||
|
- **THEN** `exec make` SHALL 正确调用 Makefile target(依赖 Git Bash/MINGW64 环境中 `make` 可用)
|
||||||
|
- **THEN** Go 和 Bun 工具链 SHALL 通过 PATH 可被 Makefile 调用
|
||||||
|
|
||||||
|
### Requirement: pre-commit 核心逻辑在 Makefile 中复用
|
||||||
|
|
||||||
|
pre-commit hook 的检查逻辑 SHALL 通过 Makefile target 调用项目已有工具链,不重复实现 hook 框架逻辑。commit-msg hook SHALL 在脚本内直接完成格式校验。
|
||||||
|
|
||||||
|
#### Scenario: Go lint 复用后端配置
|
||||||
|
|
||||||
|
- **WHEN** pre-commit 需要检查 Go 文件
|
||||||
|
- **THEN** SHALL 调用 Makefile 逻辑,在 `backend/` 目录对 staged `.go` 文件运行 `go tool golangci-lint run`
|
||||||
|
- **THEN** SHALL 复用 `backend/.golangci.yml` 中的 lint 配置
|
||||||
|
|
||||||
|
#### Scenario: 前端 lint 使用 staged 文件参数
|
||||||
|
|
||||||
|
- **WHEN** pre-commit 需要检查前端文件
|
||||||
|
- **THEN** SHALL 调用 Makefile 逻辑,在 `frontend/` 目录对 staged 前端文件运行 ESLint 和 Prettier 的文件参数模式
|
||||||
|
- **THEN** SHALL NOT 在 pre-commit 阶段运行全量 `bun run check`
|
||||||
|
|
||||||
|
#### Scenario: 终端直接调试
|
||||||
|
|
||||||
|
- **WHEN** 开发者执行 `make _hooks-pre-commit`
|
||||||
|
- **THEN** SHALL 执行与 pre-commit hook 完全相同的检查逻辑
|
||||||
|
- **THEN** 输出 SHALL 与 hook 触发时一致
|
||||||
@@ -43,7 +43,6 @@
|
|||||||
- **AND** 系统 SHALL 生成 `nex-server_<version>_macos_arm64.tar.gz`
|
- **AND** 系统 SHALL 生成 `nex-server_<version>_macos_arm64.tar.gz`
|
||||||
- **AND** 系统 SHALL 生成 `nex-server_<version>_macos_universal.tar.gz`
|
- **AND** 系统 SHALL 生成 `nex-server_<version>_macos_universal.tar.gz`
|
||||||
- **AND** 系统 SHALL 生成 `nex-server_<version>_windows_amd64.zip`
|
- **AND** 系统 SHALL 生成 `nex-server_<version>_windows_amd64.zip`
|
||||||
- **AND** 系统 SHALL 生成 `nex-server_<version>_windows_arm64.zip`
|
|
||||||
|
|
||||||
#### Scenario: web 发布构建
|
#### Scenario: web 发布构建
|
||||||
|
|
||||||
@@ -57,22 +56,33 @@
|
|||||||
- **WHEN** 发布流水线执行 Linux desktop 发布构建
|
- **WHEN** 发布流水线执行 Linux desktop 发布构建
|
||||||
- **THEN** 系统 SHALL 在可访问 Go、Bun、CGO、GTK3、Ayatana AppIndicator 和 Linux 打包工具链的环境中构建
|
- **THEN** 系统 SHALL 在可访问 Go、Bun、CGO、GTK3、Ayatana AppIndicator 和 Linux 打包工具链的环境中构建
|
||||||
- **AND** 系统 SHALL 为 `amd64` 和 `arm64` 分别生成 tar.gz、AppImage、deb 和 rpm desktop 发布资产
|
- **AND** 系统 SHALL 为 `amd64` 和 `arm64` 分别生成 tar.gz、AppImage、deb 和 rpm desktop 发布资产
|
||||||
- **AND** Linux arm64 desktop 发布构建 SHALL 使用原生 arm64 runner 或等价的 arm64 Linux 构建环境
|
- **AND** Linux amd64 desktop 发布构建 SHALL 在 `ubuntu-latest` runner 上执行
|
||||||
|
- **AND** Linux arm64 desktop 发布构建 SHALL 在 `ubuntu-24.04-arm` runner 上执行
|
||||||
|
- **AND** 系统 SHALL NOT 在构建步骤中显式传递 TARGET_ARCH 参数
|
||||||
|
|
||||||
#### Scenario: Windows desktop 发布构建
|
#### Scenario: Windows desktop 发布构建
|
||||||
|
|
||||||
- **WHEN** 发布流水线执行 Windows desktop 发布构建
|
- **WHEN** 发布流水线执行 Windows desktop 发布构建
|
||||||
- **THEN** 系统 SHALL 在包含对应架构 MSYS2/MinGW 或等价 CGO 工具链的环境中构建
|
- **THEN** 系统 SHALL 在包含对应架构 MSYS2/MinGW 或等价 CGO 工具链的环境中构建
|
||||||
|
- **AND** Windows amd64 desktop 发布构建 SHALL 在 `windows-latest` runner 上的 MSYS2 MINGW64 环境中执行
|
||||||
- **AND** 系统 SHALL 生成 `nex-desktop_<version>_windows_amd64.zip`
|
- **AND** 系统 SHALL 生成 `nex-desktop_<version>_windows_amd64.zip`
|
||||||
- **AND** 系统 SHALL 生成 `nex-desktop_<version>_windows_arm64.zip`
|
- **AND** 系统 SHALL NOT 在构建步骤中显式传递 TARGET_ARCH 参数
|
||||||
|
|
||||||
#### Scenario: macOS desktop 发布构建
|
#### Scenario: macOS desktop 发布构建
|
||||||
|
|
||||||
- **WHEN** 发布流水线执行 macOS desktop 发布构建
|
- **WHEN** 发布流水线执行 macOS desktop 发布构建
|
||||||
- **THEN** 系统 SHALL 在可访问 Go、Bun、Xcode 命令行工具、`lipo`、`hdiutil` 和 zip 打包工具的 macOS 环境中构建
|
- **THEN** 系统 SHALL 在可访问 Go、Bun、Xcode 命令行工具、`lipo`、`hdiutil` 和 zip 打包工具的 macOS 环境中构建
|
||||||
|
- **AND** 系统 SHALL 在 ARM64 macOS runner 上编译 amd64 和 arm64 双架构二进制并使用 `lipo` 合并为 universal binary
|
||||||
- **AND** 系统 SHALL 生成 `nex-desktop_<version>_macos_universal.zip`
|
- **AND** 系统 SHALL 生成 `nex-desktop_<version>_macos_universal.zip`
|
||||||
- **AND** 系统 SHALL 生成 `nex-desktop_<version>_macos_universal.dmg`
|
- **AND** 系统 SHALL 生成 `nex-desktop_<version>_macos_universal.dmg`
|
||||||
|
|
||||||
|
#### Scenario: 原生架构构建
|
||||||
|
|
||||||
|
- **WHEN** 发布流水线执行 Linux 或 Windows 的 server/desktop 构建步骤
|
||||||
|
- **THEN** 系统 SHALL NOT 显式传递 TARGET_ARCH 参数
|
||||||
|
- **AND** Makefile SHALL 通过 `go env GOARCH` 自动检测目标架构
|
||||||
|
- **AND** 原生 runner 的实际架构 SHALL 与 `go env GOARCH` 返回值一致
|
||||||
|
|
||||||
### Requirement: 三平台发布构建预检
|
### Requirement: 三平台发布构建预检
|
||||||
|
|
||||||
系统 SHALL 在正式执行各平台 release 构建前验证对应 job 的关键工具链可用性,并在环境不完整时快速失败且输出明确诊断。
|
系统 SHALL 在正式执行各平台 release 构建前验证对应 job 的关键工具链可用性,并在环境不完整时快速失败且输出明确诊断。
|
||||||
@@ -166,7 +176,7 @@
|
|||||||
- **WHEN** 当前发布版本为 `1.2.3`
|
- **WHEN** 当前发布版本为 `1.2.3`
|
||||||
- **THEN** Linux server 发布资产文件名 SHALL 为 `nex-server_1.2.3_linux_amd64.tar.gz` 和 `nex-server_1.2.3_linux_arm64.tar.gz`
|
- **THEN** Linux server 发布资产文件名 SHALL 为 `nex-server_1.2.3_linux_amd64.tar.gz` 和 `nex-server_1.2.3_linux_arm64.tar.gz`
|
||||||
- **AND** macOS server 发布资产文件名 SHALL 为 `nex-server_1.2.3_macos_amd64.tar.gz`、`nex-server_1.2.3_macos_arm64.tar.gz` 和 `nex-server_1.2.3_macos_universal.tar.gz`
|
- **AND** macOS server 发布资产文件名 SHALL 为 `nex-server_1.2.3_macos_amd64.tar.gz`、`nex-server_1.2.3_macos_arm64.tar.gz` 和 `nex-server_1.2.3_macos_universal.tar.gz`
|
||||||
- **AND** Windows server 发布资产文件名 SHALL 为 `nex-server_1.2.3_windows_amd64.zip` 和 `nex-server_1.2.3_windows_arm64.zip`
|
- **AND** Windows server 发布资产文件名 SHALL 为 `nex-server_1.2.3_windows_amd64.zip`
|
||||||
|
|
||||||
#### Scenario: web 资产命名
|
#### Scenario: web 资产命名
|
||||||
|
|
||||||
@@ -178,7 +188,7 @@
|
|||||||
|
|
||||||
- **WHEN** 当前发布版本为 `1.2.3`
|
- **WHEN** 当前发布版本为 `1.2.3`
|
||||||
- **THEN** Linux desktop 发布资产文件名 SHALL 使用 `nex-desktop_1.2.3_linux_<arch>.<format>` 格式
|
- **THEN** Linux desktop 发布资产文件名 SHALL 使用 `nex-desktop_1.2.3_linux_<arch>.<format>` 格式
|
||||||
- **AND** Windows desktop 发布资产文件名 SHALL 为 `nex-desktop_1.2.3_windows_amd64.zip` 和 `nex-desktop_1.2.3_windows_arm64.zip`
|
- **AND** Windows desktop 发布资产文件名 SHALL 为 `nex-desktop_1.2.3_windows_amd64.zip`
|
||||||
- **AND** macOS desktop 发布资产文件名 SHALL 为 `nex-desktop_1.2.3_macos_universal.zip` 和 `nex-desktop_1.2.3_macos_universal.dmg`
|
- **AND** macOS desktop 发布资产文件名 SHALL 为 `nex-desktop_1.2.3_macos_universal.zip` 和 `nex-desktop_1.2.3_macos_universal.dmg`
|
||||||
- **AND** 发布资产文件名中的 macOS 平台字段 SHALL 使用 `macos` 而非 `darwin`
|
- **AND** 发布资产文件名中的 macOS 平台字段 SHALL 使用 `macos` 而非 `darwin`
|
||||||
|
|
||||||
|
|||||||
@@ -90,22 +90,34 @@
|
|||||||
|
|
||||||
### Requirement: 版本升迁 Makefile 编排
|
### Requirement: 版本升迁 Makefile 编排
|
||||||
|
|
||||||
`make version-bump` SHALL 编排完整的版本升迁流程:工作区干净检查 → `version bump`(含 sync/check/倒退检查)→ git add → git commit → git tag。不传 `BUMP` 参数时 SHALL 默认执行 `BUMP=patch`。
|
`make version-bump` SHALL 编排完整的版本升迁流程:全量 lint 检查 → 全量单元测试 → 工作区干净检查 → `version bump`(含 sync/check/倒退检查)→ git add → git commit → git tag。不传 `BUMP` 参数时 SHALL 默认执行 `BUMP=patch`。lint/test 前置检查 SHALL NOT 替代工作区干净检查。
|
||||||
|
|
||||||
#### Scenario: 完整升迁流程
|
#### Scenario: 完整升迁流程
|
||||||
|
|
||||||
- **WHEN** 执行 `make version-bump BUMP=minor`,工作区干净,当前版本 `0.1.0`
|
- **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`
|
- **THEN** Makefile SHALL 依次执行:`make lint` → `make test` → 工作区检查 → `version bump minor` → `git add VERSION frontend/` → `git commit -m "chore: 版本升迁 v0.2.0"` → `git tag v0.2.0`
|
||||||
|
|
||||||
#### Scenario: 不传 BUMP 默认 patch
|
#### Scenario: 不传 BUMP 默认 patch
|
||||||
|
|
||||||
- **WHEN** 执行 `make version-bump`,工作区干净,当前版本 `0.1.0`
|
- **WHEN** 执行 `make version-bump`,工作区干净,当前版本 `0.1.0`
|
||||||
- **THEN** Makefile SHALL 等效于执行 `make version-bump BUMP=patch`,将版本更新为 `0.1.1`
|
- **THEN** Makefile SHALL 等效于执行 `make version-bump BUMP=patch`,将版本更新为 `0.1.1`
|
||||||
|
|
||||||
|
#### Scenario: lint 失败时终止
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make version-bump`,但 `make lint` 报告错误
|
||||||
|
- **THEN** Makefile SHALL 以非零退出码失败,SHALL NOT 执行 `version bump`、git commit、git tag
|
||||||
|
- **THEN** SHALL 输出错误信息提示修复 lint 问题后重试
|
||||||
|
|
||||||
|
#### Scenario: test 失败时终止
|
||||||
|
|
||||||
|
- **WHEN** 执行 `make version-bump`,但 `make test` 报告测试失败
|
||||||
|
- **THEN** Makefile SHALL 以非零退出码失败,SHALL NOT 执行 `version bump`、git commit、git tag
|
||||||
|
- **THEN** SHALL 输出错误信息提示修复测试失败后重试
|
||||||
|
|
||||||
#### Scenario: 工作区不干净
|
#### Scenario: 工作区不干净
|
||||||
|
|
||||||
- **WHEN** 执行 `make version-bump BUMP=minor`,但工作区有未提交的改动
|
- **WHEN** 执行 `make version-bump BUMP=minor`,但工作区有未提交的改动
|
||||||
- **THEN** Makefile SHALL 以非零退出码失败并提示先提交或暂存改动
|
- **THEN** Makefile SHALL 以非零退出码失败并提示先提交或清理改动
|
||||||
|
|
||||||
#### Scenario: 支持指定版本号
|
#### Scenario: 支持指定版本号
|
||||||
|
|
||||||
|
|||||||
42
scripts/git-hooks/commit-msg
Executable file
42
scripts/git-hooks/commit-msg
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
MSG_FILE=$1
|
||||||
|
|
||||||
|
if [ ! -f "$MSG_FILE" ]; then
|
||||||
|
printf '%s\n' '提交信息文件不存在。' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS= read -r FIRST_LINE < "$MSG_FILE" || FIRST_LINE=
|
||||||
|
|
||||||
|
case "$FIRST_LINE" in
|
||||||
|
Merge*)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if ! printf '%s\n' "$FIRST_LINE" | grep -Eq '^(feat|fix|refactor|docs|style|test|chore): .+$'; then
|
||||||
|
cat >&2 <<'EOF'
|
||||||
|
提交信息格式错误。
|
||||||
|
|
||||||
|
格式: <类型>: <简短描述>
|
||||||
|
类型: feat / fix / refactor / docs / style / test / chore
|
||||||
|
|
||||||
|
示例:
|
||||||
|
feat: 添加供应商批量管理功能
|
||||||
|
fix: 修复流式响应断连问题
|
||||||
|
chore: 版本升迁 v0.2.0
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DESCRIPTION=${FIRST_LINE#*: }
|
||||||
|
if printf '%s\n' "$DESCRIPTION" | LC_ALL=C grep -Eq '^[ -~]+$'; then
|
||||||
|
printf '%s\n' '提交描述需使用中文。' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#FIRST_LINE} -gt 72 ]; then
|
||||||
|
printf '%s\n' '警告: 提交信息首行超过 72 个字符,建议保持简短。' >&2
|
||||||
|
fi
|
||||||
12
scripts/git-hooks/pre-commit
Executable file
12
scripts/git-hooks/pre-commit
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
ROOT_DIR=$(git rev-parse --show-toplevel)
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
command -v make >/dev/null 2>&1 || {
|
||||||
|
printf '%s\n' '缺少 make 命令,请先安装 Make 或使用 Git Bash/MINGW64 环境。' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
exec make _hooks-pre-commit
|
||||||
134
scripts/git-hooks/test-hooks.sh
Executable file
134
scripts/git-hooks/test-hooks.sh
Executable file
@@ -0,0 +1,134 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT_DIR=$(git rev-parse --show-toplevel)
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
TMP_DIR=${TMPDIR:-/tmp}/nex-hooks-test.$$
|
||||||
|
mkdir -p "$TMP_DIR"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -f \
|
||||||
|
backend/pkg/buildinfo/hook_bad_test_fixture.go \
|
||||||
|
frontend/src/hook_bad_fixture.ts \
|
||||||
|
frontend/src/hook_format_fixture.ts \
|
||||||
|
docs/hook-doc-fixture.md \
|
||||||
|
docs/hook-conflict-fixture.md \
|
||||||
|
docs/hook-large-fixture.txt
|
||||||
|
rm -rf "$TMP_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
pass() {
|
||||||
|
printf 'OK: %s\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'FAIL: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
write_msg() {
|
||||||
|
file=$1
|
||||||
|
shift
|
||||||
|
printf '%s\n' "$*" > "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_success() {
|
||||||
|
name=$1
|
||||||
|
shift
|
||||||
|
if "$@" > "$TMP_DIR/out" 2>&1; then
|
||||||
|
pass "$name"
|
||||||
|
else
|
||||||
|
cat "$TMP_DIR/out" >&2
|
||||||
|
fail "$name"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_failure() {
|
||||||
|
name=$1
|
||||||
|
shift
|
||||||
|
if "$@" > "$TMP_DIR/out" 2>&1; then
|
||||||
|
cat "$TMP_DIR/out" >&2
|
||||||
|
fail "$name"
|
||||||
|
fi
|
||||||
|
pass "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_precommit_for() {
|
||||||
|
index=$TMP_DIR/index
|
||||||
|
rm -f "$index"
|
||||||
|
GIT_INDEX_FILE=$index git read-tree HEAD
|
||||||
|
for file in "$@"; do
|
||||||
|
GIT_INDEX_FILE=$index git add -f "$file"
|
||||||
|
done
|
||||||
|
GIT_INDEX_FILE=$index make _hooks-pre-commit
|
||||||
|
}
|
||||||
|
|
||||||
|
MSG_FILE=$TMP_DIR/commit-msg.txt
|
||||||
|
write_msg "$MSG_FILE" 'feat: 添加 hook 测试'
|
||||||
|
expect_success 'commit-msg accepts Chinese description' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||||
|
|
||||||
|
write_msg "$MSG_FILE" 'feat: add hook tests'
|
||||||
|
expect_failure 'commit-msg rejects English-only description' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||||
|
|
||||||
|
write_msg "$MSG_FILE" 'update: 添加 hook 测试'
|
||||||
|
expect_failure 'commit-msg rejects invalid type' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||||
|
|
||||||
|
write_msg "$MSG_FILE" 'Merge branch feature'
|
||||||
|
expect_success 'commit-msg accepts merge commits' scripts/git-hooks/commit-msg "$MSG_FILE"
|
||||||
|
|
||||||
|
cat > backend/pkg/buildinfo/hook_bad_test_fixture.go <<'EOF'
|
||||||
|
package buildinfo
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func hookBadTestFixture() {
|
||||||
|
fmt.Println("bad")
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
expect_failure 'pre-commit rejects Go lint errors' run_precommit_for backend/pkg/buildinfo/hook_bad_test_fixture.go
|
||||||
|
rm -f backend/pkg/buildinfo/hook_bad_test_fixture.go
|
||||||
|
|
||||||
|
cat > frontend/src/hook_bad_fixture.ts <<'EOF'
|
||||||
|
console.log('bad')
|
||||||
|
EOF
|
||||||
|
expect_failure 'pre-commit rejects frontend lint errors' run_precommit_for frontend/src/hook_bad_fixture.ts
|
||||||
|
rm -f frontend/src/hook_bad_fixture.ts
|
||||||
|
|
||||||
|
cat > frontend/src/hook_format_fixture.ts <<'EOF'
|
||||||
|
const hookFormatFixture={foo:"bar"}
|
||||||
|
export { hookFormatFixture }
|
||||||
|
EOF
|
||||||
|
expect_failure 'pre-commit rejects frontend format errors' run_precommit_for frontend/src/hook_format_fixture.ts
|
||||||
|
rm -f frontend/src/hook_format_fixture.ts
|
||||||
|
|
||||||
|
cat > docs/hook-doc-fixture.md <<'EOF'
|
||||||
|
hook doc fixture
|
||||||
|
EOF
|
||||||
|
expect_success 'pre-commit skips non-code staged files' run_precommit_for docs/hook-doc-fixture.md
|
||||||
|
rm -f docs/hook-doc-fixture.md
|
||||||
|
|
||||||
|
cat > docs/hook-conflict-fixture.md <<'EOF'
|
||||||
|
<<<<<<< HEAD
|
||||||
|
conflict
|
||||||
|
=======
|
||||||
|
other
|
||||||
|
>>>>>>> branch
|
||||||
|
EOF
|
||||||
|
expect_failure 'pre-commit rejects conflict markers' run_precommit_for docs/hook-conflict-fixture.md
|
||||||
|
rm -f docs/hook-conflict-fixture.md
|
||||||
|
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt 40000 ]; do
|
||||||
|
printf 'large hook fixture line\n'
|
||||||
|
i=$((i + 1))
|
||||||
|
done > docs/hook-large-fixture.txt
|
||||||
|
if run_precommit_for docs/hook-large-fixture.txt > "$TMP_DIR/out" 2>&1 && grep -q 'Warning: large staged text file' "$TMP_DIR/out"; then
|
||||||
|
pass 'pre-commit warns for large text files'
|
||||||
|
else
|
||||||
|
cat "$TMP_DIR/out" >&2
|
||||||
|
fail 'pre-commit warns for large text files'
|
||||||
|
fi
|
||||||
|
rm -f docs/hook-large-fixture.txt
|
||||||
@@ -288,7 +288,6 @@ func serverAssetName(version, platform, arch, format string) (string, error) {
|
|||||||
{platform: "macos", arch: "arm64", format: "tar.gz"},
|
{platform: "macos", arch: "arm64", format: "tar.gz"},
|
||||||
{platform: "macos", arch: "universal", format: "tar.gz"},
|
{platform: "macos", arch: "universal", format: "tar.gz"},
|
||||||
{platform: "windows", arch: "amd64", format: "zip"},
|
{platform: "windows", arch: "amd64", format: "zip"},
|
||||||
{platform: "windows", arch: "arm64", format: "zip"},
|
|
||||||
}) {
|
}) {
|
||||||
return "", fmt.Errorf("不支持的 server 资产目标 %s/%s/%s", platform, arch, format)
|
return "", fmt.Errorf("不支持的 server 资产目标 %s/%s/%s", platform, arch, format)
|
||||||
}
|
}
|
||||||
@@ -321,7 +320,6 @@ func desktopAssetName(version, platform, arch, format string) (string, error) {
|
|||||||
{platform: "macos", arch: "universal", format: "zip"},
|
{platform: "macos", arch: "universal", format: "zip"},
|
||||||
{platform: "macos", arch: "universal", format: "dmg"},
|
{platform: "macos", arch: "universal", format: "dmg"},
|
||||||
{platform: "windows", arch: "amd64", format: "zip"},
|
{platform: "windows", arch: "amd64", format: "zip"},
|
||||||
{platform: "windows", arch: "arm64", format: "zip"},
|
|
||||||
}) {
|
}) {
|
||||||
return "", fmt.Errorf("不支持的 desktop 资产目标 %s/%s/%s", platform, arch, format)
|
return "", fmt.Errorf("不支持的 desktop 资产目标 %s/%s/%s", platform, arch, format)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ func TestAssetNames(t *testing.T) {
|
|||||||
{"server macos arm64", "server", "macos", "arm64", "tar.gz", "nex-server_1.2.3_macos_arm64.tar.gz"},
|
{"server macos arm64", "server", "macos", "arm64", "tar.gz", "nex-server_1.2.3_macos_arm64.tar.gz"},
|
||||||
{"server macos universal", "server", "macos", "universal", "tar.gz", "nex-server_1.2.3_macos_universal.tar.gz"},
|
{"server macos universal", "server", "macos", "universal", "tar.gz", "nex-server_1.2.3_macos_universal.tar.gz"},
|
||||||
{"server windows amd64", "server", "windows", "amd64", "zip", "nex-server_1.2.3_windows_amd64.zip"},
|
{"server windows amd64", "server", "windows", "amd64", "zip", "nex-server_1.2.3_windows_amd64.zip"},
|
||||||
{"server windows arm64", "server", "windows", "arm64", "zip", "nex-server_1.2.3_windows_arm64.zip"},
|
|
||||||
{"web", "web", "", "", "tar.gz", "nex-web_1.2.3.tar.gz"},
|
{"web", "web", "", "", "tar.gz", "nex-web_1.2.3.tar.gz"},
|
||||||
{"desktop linux amd64 tar", "desktop", "linux", "amd64", "tar.gz", "nex-desktop_1.2.3_linux_amd64.tar.gz"},
|
{"desktop linux amd64 tar", "desktop", "linux", "amd64", "tar.gz", "nex-desktop_1.2.3_linux_amd64.tar.gz"},
|
||||||
{"desktop linux amd64 appimage", "desktop", "linux", "amd64", "AppImage", "nex-desktop_1.2.3_linux_amd64.AppImage"},
|
{"desktop linux amd64 appimage", "desktop", "linux", "amd64", "AppImage", "nex-desktop_1.2.3_linux_amd64.AppImage"},
|
||||||
@@ -110,7 +109,6 @@ func TestAssetNames(t *testing.T) {
|
|||||||
{"desktop macos zip", "desktop", "macos", "universal", "zip", "nex-desktop_1.2.3_macos_universal.zip"},
|
{"desktop macos zip", "desktop", "macos", "universal", "zip", "nex-desktop_1.2.3_macos_universal.zip"},
|
||||||
{"desktop macos dmg", "desktop", "macos", "universal", "dmg", "nex-desktop_1.2.3_macos_universal.dmg"},
|
{"desktop macos dmg", "desktop", "macos", "universal", "dmg", "nex-desktop_1.2.3_macos_universal.dmg"},
|
||||||
{"desktop windows amd64", "desktop", "windows", "amd64", "zip", "nex-desktop_1.2.3_windows_amd64.zip"},
|
{"desktop windows amd64", "desktop", "windows", "amd64", "zip", "nex-desktop_1.2.3_windows_amd64.zip"},
|
||||||
{"desktop windows arm64", "desktop", "windows", "arm64", "zip", "nex-desktop_1.2.3_windows_arm64.zip"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|||||||
Reference in New Issue
Block a user