fix: 修复发布流水线 LFS 资产校验
This commit is contained in:
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
@@ -19,6 +19,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
@@ -44,6 +46,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
@@ -73,6 +77,7 @@ jobs:
|
|||||||
command -v pkg-config
|
command -v pkg-config
|
||||||
pkg-config --modversion ayatana-appindicator3-0.1
|
pkg-config --modversion ayatana-appindicator3-0.1
|
||||||
pkg-config --modversion gtk+-3.0
|
pkg-config --modversion gtk+-3.0
|
||||||
|
make release-assets-check
|
||||||
|
|
||||||
- name: Build Linux release assets
|
- name: Build Linux release assets
|
||||||
run: make release-assets-linux
|
run: make release-assets-linux
|
||||||
@@ -92,6 +97,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
@@ -134,6 +141,7 @@ jobs:
|
|||||||
command -v powershell
|
command -v powershell
|
||||||
powershell -NoProfile -Command '$PSVersionTable.PSVersion.ToString()'
|
powershell -NoProfile -Command '$PSVersionTable.PSVersion.ToString()'
|
||||||
fi
|
fi
|
||||||
|
make release-assets-check
|
||||||
|
|
||||||
- name: Build Windows release assets
|
- name: Build Windows release assets
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
@@ -154,6 +162,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
@@ -176,6 +186,7 @@ jobs:
|
|||||||
command -v ditto
|
command -v ditto
|
||||||
xcrun --find lipo
|
xcrun --find lipo
|
||||||
xcrun --find vtool
|
xcrun --find vtool
|
||||||
|
make release-assets-check
|
||||||
|
|
||||||
- name: Build macOS release assets
|
- name: Build macOS release assets
|
||||||
run: make release-assets-macos
|
run: make release-assets-macos
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -4,7 +4,7 @@
|
|||||||
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-check 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 \
|
_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 \
|
||||||
@@ -183,14 +183,18 @@ endif
|
|||||||
# 发布资产
|
# 发布资产
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
release-assets-linux: version-check desktop-build-linux
|
release-assets-check:
|
||||||
|
go run ./versionctl release-assets-check
|
||||||
|
@printf 'Release assets check passed\n'
|
||||||
|
|
||||||
|
release-assets-linux: version-check release-assets-check desktop-build-linux
|
||||||
rm -rf "$(RELEASE_DIR)"
|
rm -rf "$(RELEASE_DIR)"
|
||||||
mkdir -p "$(RELEASE_DIR)"
|
mkdir -p "$(RELEASE_DIR)"
|
||||||
cd backend && CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-server-linux-amd64 ./cmd/server
|
cd backend && CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-server-linux-amd64 ./cmd/server
|
||||||
tar -C build -czf "$(RELEASE_DIR)/$(SERVER_LINUX_ASSET)" nex-server-linux-amd64
|
tar -C build -czf "$(RELEASE_DIR)/$(SERVER_LINUX_ASSET)" nex-server-linux-amd64
|
||||||
tar -C build -czf "$(RELEASE_DIR)/$(DESKTOP_LINUX_ASSET)" nex-linux-amd64
|
tar -C build -czf "$(RELEASE_DIR)/$(DESKTOP_LINUX_ASSET)" nex-linux-amd64
|
||||||
|
|
||||||
release-assets-windows: version-check desktop-build-win
|
release-assets-windows: version-check release-assets-check desktop-build-win
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
powershell -NoProfile -Command "Remove-Item -LiteralPath '$(RELEASE_DIR)' -Recurse -Force -ErrorAction SilentlyContinue; New-Item -ItemType Directory -Path '$(RELEASE_DIR)' -Force | Out-Null"
|
powershell -NoProfile -Command "Remove-Item -LiteralPath '$(RELEASE_DIR)' -Recurse -Force -ErrorAction SilentlyContinue; New-Item -ItemType Directory -Path '$(RELEASE_DIR)' -Force | Out-Null"
|
||||||
cd backend && set "CGO_ENABLED=1"&& set "GOOS=windows"&& set "GOARCH=amd64"&& go build -ldflags "$(GO_LDFLAGS_WIN)" -o ../build/nex-server-win-amd64.exe ./cmd/server
|
cd backend && set "CGO_ENABLED=1"&& set "GOOS=windows"&& set "GOARCH=amd64"&& go build -ldflags "$(GO_LDFLAGS_WIN)" -o ../build/nex-server-win-amd64.exe ./cmd/server
|
||||||
@@ -201,7 +205,7 @@ else
|
|||||||
@exit 1
|
@exit 1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
release-assets-macos: version-check desktop-build-mac
|
release-assets-macos: version-check release-assets-check desktop-build-mac
|
||||||
rm -rf "$(RELEASE_DIR)"
|
rm -rf "$(RELEASE_DIR)"
|
||||||
mkdir -p "$(RELEASE_DIR)"
|
mkdir -p "$(RELEASE_DIR)"
|
||||||
cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-server-darwin-amd64 ./cmd/server
|
cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-server-darwin-amd64 ./cmd/server
|
||||||
|
|||||||
@@ -84,6 +84,37 @@
|
|||||||
- **THEN** 发布流水线 SHALL 在正式构建前失败
|
- **THEN** 发布流水线 SHALL 在正式构建前失败
|
||||||
- **AND** 系统 SHALL 在日志中标识缺失的工具链名称
|
- **AND** 系统 SHALL 在日志中标识缺失的工具链名称
|
||||||
|
|
||||||
|
### Requirement: 发布流水线 LFS 资产拉取
|
||||||
|
|
||||||
|
发布流水线 SHALL 在所有会 checkout 仓库并参与版本校验或平台构建的 job 中拉取 Git LFS 真实文件,确保发布构建读取到真实二进制资产而非 LFS pointer 文本。
|
||||||
|
|
||||||
|
#### Scenario: 发布 job 获取真实 LFS 图标资产
|
||||||
|
|
||||||
|
- **WHEN** 发布流水线执行 `prepare`、`build-linux`、`build-windows` 或 `build-macos` job 的 checkout 步骤
|
||||||
|
- **THEN** checkout 步骤 SHALL 拉取 Git LFS 文件
|
||||||
|
- **AND** `assets/icon.ico`、`assets/icon.icns`、`assets/icon.png` 和 `frontend/public/icon.png` SHALL 在后续步骤中表现为真实图标文件而非 LFS pointer 文本
|
||||||
|
|
||||||
|
### Requirement: 发布资产图标预检
|
||||||
|
|
||||||
|
发布流水线 SHALL 在正式执行各平台发布构建前校验关键图标资产可用,并在检测到 LFS pointer 或错误格式时快速失败且输出明确诊断。
|
||||||
|
|
||||||
|
#### Scenario: 图标资产为 LFS pointer
|
||||||
|
|
||||||
|
- **WHEN** 发布资产预检发现关键图标文件内容为 Git LFS pointer 文本
|
||||||
|
- **THEN** 发布流水线 SHALL 在执行平台发布构建前失败
|
||||||
|
- **AND** 系统 SHALL 在日志中标识对应图标文件需要拉取 Git LFS 真实内容
|
||||||
|
|
||||||
|
#### Scenario: 图标资产格式无效
|
||||||
|
|
||||||
|
- **WHEN** 发布资产预检发现关键图标文件不是对应格式的有效资源
|
||||||
|
- **THEN** 发布流水线 SHALL 在执行平台发布构建前失败
|
||||||
|
- **AND** 系统 SHALL 在日志中标识格式无效的图标文件路径
|
||||||
|
|
||||||
|
#### Scenario: 图标资产预检通过
|
||||||
|
|
||||||
|
- **WHEN** `assets/icon.ico`、`assets/icon.icns`、`assets/icon.png` 和 `frontend/public/icon.png` 均为真实且格式可用的图标资产
|
||||||
|
- **THEN** 发布流水线 SHALL 继续执行对应平台的 `make release-assets-*` 构建
|
||||||
|
|
||||||
### Requirement: 发布流水线运行时兼容性
|
### Requirement: 发布流水线运行时兼容性
|
||||||
|
|
||||||
系统 SHALL 保持与 GitHub-hosted runner 当前受支持的 workflow runtime 约束兼容,避免发布流程依赖已声明弃用的 runtime 或执行约束。
|
系统 SHALL 保持与 GitHub-hosted runner 当前受支持的 workflow runtime 约束兼容,避免发布流程依赖已声明弃用的 runtime 或执行约束。
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ func run(args []string) error {
|
|||||||
return printMacOSPlist(root, args[1])
|
return printMacOSPlist(root, args[1])
|
||||||
case "asset-name":
|
case "asset-name":
|
||||||
return printAssetName(root, args[1:])
|
return printAssetName(root, args[1:])
|
||||||
|
case "release-assets-check":
|
||||||
|
if len(args) != 1 {
|
||||||
|
return fmt.Errorf("release-assets-check 不需要额外参数")
|
||||||
|
}
|
||||||
|
return projectversion.CheckReleaseAssets(root)
|
||||||
default:
|
default:
|
||||||
return usageError()
|
return usageError()
|
||||||
}
|
}
|
||||||
@@ -147,5 +152,5 @@ func mustGetwd() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func usageError() error {
|
func usageError() error {
|
||||||
return fmt.Errorf("用法: version <print|sync|check|verify-tag|bump|macos-plist|asset-name>")
|
return fmt.Errorf("用法: version <print|sync|check|verify-tag|bump|macos-plist|asset-name|release-assets-check>")
|
||||||
}
|
}
|
||||||
|
|||||||
69
versionctl/projectversion/release_assets.go
Normal file
69
versionctl/projectversion/release_assets.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package projectversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var releaseAssetChecks = []releaseAssetCheck{
|
||||||
|
{
|
||||||
|
path: "assets/icon.ico",
|
||||||
|
description: "Windows ICO 图标",
|
||||||
|
magic: []byte{0x00, 0x00, 0x01, 0x00},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "assets/icon.icns",
|
||||||
|
description: "macOS ICNS 图标",
|
||||||
|
magic: []byte("icns"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "assets/icon.png",
|
||||||
|
description: "PNG 图标",
|
||||||
|
magic: []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "frontend/public/icon.png",
|
||||||
|
description: "前端 PNG 图标",
|
||||||
|
magic: []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var gitLFSPointerPrefix = []byte("version https://git-lfs.github.com/spec/v1")
|
||||||
|
|
||||||
|
type releaseAssetCheck struct {
|
||||||
|
path string
|
||||||
|
description string
|
||||||
|
magic []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckReleaseAssets(root string) error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
for _, check := range releaseAssetChecks {
|
||||||
|
if err := checkReleaseAsset(root, check); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkReleaseAsset(root string, check releaseAssetCheck) error {
|
||||||
|
content, err := os.ReadFile(filepath.Join(root, check.path))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s 不可读取: %w", check.path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.HasPrefix(content, gitLFSPointerPrefix) {
|
||||||
|
return fmt.Errorf("%s 是 Git LFS pointer,请先拉取 Git LFS 真实内容", check.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.HasPrefix(content, check.magic) {
|
||||||
|
return fmt.Errorf("%s 不是有效的%s", check.path, check.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
58
versionctl/projectversion/release_assets_test.go
Normal file
58
versionctl/projectversion/release_assets_test.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package projectversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckReleaseAssets(t *testing.T) {
|
||||||
|
t.Run("valid assets", func(t *testing.T) {
|
||||||
|
root := setupReleaseAssetRoot(t)
|
||||||
|
|
||||||
|
require.NoError(t, CheckReleaseAssets(root))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("lfs pointer", func(t *testing.T) {
|
||||||
|
root := setupReleaseAssetRoot(t)
|
||||||
|
writeReleaseAsset(t, root, "assets/icon.ico", []byte("version https://git-lfs.github.com/spec/v1\noid sha256:abc\nsize 123\n"))
|
||||||
|
|
||||||
|
err := CheckReleaseAssets(root)
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "assets/icon.ico 是 Git LFS pointer")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid format", func(t *testing.T) {
|
||||||
|
root := setupReleaseAssetRoot(t)
|
||||||
|
writeReleaseAsset(t, root, "frontend/public/icon.png", []byte("not a png"))
|
||||||
|
|
||||||
|
err := CheckReleaseAssets(root)
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "frontend/public/icon.png 不是有效的前端 PNG 图标")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupReleaseAssetRoot(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
writeReleaseAsset(t, root, "assets/icon.ico", []byte{0x00, 0x00, 0x01, 0x00, 0x01})
|
||||||
|
writeReleaseAsset(t, root, "assets/icon.icns", []byte("icnsdata"))
|
||||||
|
writeReleaseAsset(t, root, "assets/icon.png", []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00})
|
||||||
|
writeReleaseAsset(t, root, "frontend/public/icon.png", []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00})
|
||||||
|
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeReleaseAsset(t *testing.T, root, relPath string, content []byte) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
fullPath := filepath.Join(root, relPath)
|
||||||
|
require.NoError(t, os.MkdirAll(filepath.Dir(fullPath), 0o755))
|
||||||
|
require.NoError(t, os.WriteFile(fullPath, content, 0o600))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user