feat: 扩展发布打包支持多组件多架构多格式产物
- 新增 web 组件独立发布为 nex-web_<version>.tar.gz
- server 新增 arm64 架构、macOS universal、Windows arm64 产物
- desktop 新增 arm64 架构支持(Linux/Windows)
- Linux desktop 新增 AppImage、deb、rpm 安装包格式
- macOS desktop 新增 unsigned DMG 安装包
- 统一发布资产命名为 {component}_{version}_{platform}_{arch}.{ext}
- 新增 SHA256SUMS 校验和清单覆盖全部发布资产
- versionctl 新增 asset-name CLI 支持按参数生成资产文件名
- Makefile release target 重构为组件/平台/架构参数化
- GitHub Actions release workflow 扩展多组件多架构构建矩阵
- 同步更新 openspec 主规范(desktop-app/release-pipeline/workspace-command-flows)
This commit is contained in:
@@ -263,44 +263,86 @@ func ReadEnvVar(content, key string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
func ServerAssetName(version, goos, arch string) (string, error) {
|
||||
func ReleaseAssetName(version, component, platform, arch, format string) (string, error) {
|
||||
if _, err := Parse(version); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch goos {
|
||||
case "linux", "windows", "darwin":
|
||||
switch component {
|
||||
case "server":
|
||||
return serverAssetName(version, platform, arch, format)
|
||||
case "web":
|
||||
return webAssetName(version, platform, arch, format)
|
||||
case "desktop":
|
||||
return desktopAssetName(version, platform, arch, format)
|
||||
default:
|
||||
return "", fmt.Errorf("不支持的 server 平台 %q", goos)
|
||||
return "", fmt.Errorf("不支持的资产组件 %q", component)
|
||||
}
|
||||
|
||||
if arch == "" {
|
||||
return "", errors.New("server 资产命名缺少架构")
|
||||
}
|
||||
|
||||
ext := ".tar.gz"
|
||||
if goos == "windows" {
|
||||
ext = ".zip"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("nex-server_%s_%s_%s%s", version, goos, arch, ext), nil
|
||||
}
|
||||
|
||||
func DesktopAssetName(version, platform string) (string, error) {
|
||||
if _, err := Parse(version); err != nil {
|
||||
return "", err
|
||||
func serverAssetName(version, platform, arch, format string) (string, error) {
|
||||
if !validCombination(platform, arch, format, []releaseAssetTarget{
|
||||
{platform: "linux", arch: "amd64", format: "tar.gz"},
|
||||
{platform: "linux", arch: "arm64", format: "tar.gz"},
|
||||
{platform: "macos", arch: "amd64", format: "tar.gz"},
|
||||
{platform: "macos", arch: "arm64", format: "tar.gz"},
|
||||
{platform: "macos", arch: "universal", format: "tar.gz"},
|
||||
{platform: "windows", arch: "amd64", format: "zip"},
|
||||
{platform: "windows", arch: "arm64", format: "zip"},
|
||||
}) {
|
||||
return "", fmt.Errorf("不支持的 server 资产目标 %s/%s/%s", platform, arch, format)
|
||||
}
|
||||
|
||||
switch platform {
|
||||
case "linux":
|
||||
return fmt.Sprintf("Nex_%s_linux_amd64.tar.gz", version), nil
|
||||
case "windows":
|
||||
return fmt.Sprintf("Nex_%s_windows_amd64.zip", version), nil
|
||||
case "macos":
|
||||
return fmt.Sprintf("Nex_%s_macOS_universal.zip", version), nil
|
||||
default:
|
||||
return "", fmt.Errorf("不支持的 desktop 平台 %q", platform)
|
||||
return fmt.Sprintf("nex-server_%s_%s_%s.%s", version, platform, arch, format), nil
|
||||
}
|
||||
|
||||
func webAssetName(version, platform, arch, format string) (string, error) {
|
||||
if platform != "" || arch != "" {
|
||||
return "", errors.New("web 资产命名不支持平台或架构参数")
|
||||
}
|
||||
|
||||
if format != "tar.gz" {
|
||||
return "", fmt.Errorf("不支持的 web 资产格式 %q", format)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("nex-web_%s.tar.gz", version), nil
|
||||
}
|
||||
|
||||
func desktopAssetName(version, platform, arch, format string) (string, error) {
|
||||
if !validCombination(platform, arch, format, []releaseAssetTarget{
|
||||
{platform: "linux", arch: "amd64", format: "tar.gz"},
|
||||
{platform: "linux", arch: "amd64", format: "AppImage"},
|
||||
{platform: "linux", arch: "amd64", format: "deb"},
|
||||
{platform: "linux", arch: "amd64", format: "rpm"},
|
||||
{platform: "linux", arch: "arm64", format: "tar.gz"},
|
||||
{platform: "linux", arch: "arm64", format: "AppImage"},
|
||||
{platform: "linux", arch: "arm64", format: "deb"},
|
||||
{platform: "linux", arch: "arm64", format: "rpm"},
|
||||
{platform: "macos", arch: "universal", format: "zip"},
|
||||
{platform: "macos", arch: "universal", format: "dmg"},
|
||||
{platform: "windows", arch: "amd64", format: "zip"},
|
||||
{platform: "windows", arch: "arm64", format: "zip"},
|
||||
}) {
|
||||
return "", fmt.Errorf("不支持的 desktop 资产目标 %s/%s/%s", platform, arch, format)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("nex-desktop_%s_%s_%s.%s", version, platform, arch, format), nil
|
||||
}
|
||||
|
||||
type releaseAssetTarget struct {
|
||||
platform string
|
||||
arch string
|
||||
format string
|
||||
}
|
||||
|
||||
func validCombination(platform, arch, format string, targets []releaseAssetTarget) bool {
|
||||
for _, target := range targets {
|
||||
if target.platform == platform && target.arch == arch && target.format == format {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func DesktopInfoPlist(version, minMacOSVersion string) (string, error) {
|
||||
|
||||
@@ -83,20 +83,72 @@ func TestVerifyTag(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAssetNames(t *testing.T) {
|
||||
linuxServer, err := ServerAssetName("1.2.3", "linux", "amd64")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "nex-server_1.2.3_linux_amd64.tar.gz", linuxServer)
|
||||
testCases := []struct {
|
||||
name string
|
||||
component string
|
||||
platform string
|
||||
arch string
|
||||
format string
|
||||
want string
|
||||
}{
|
||||
{"server linux amd64", "server", "linux", "amd64", "tar.gz", "nex-server_1.2.3_linux_amd64.tar.gz"},
|
||||
{"server linux arm64", "server", "linux", "arm64", "tar.gz", "nex-server_1.2.3_linux_arm64.tar.gz"},
|
||||
{"server macos amd64", "server", "macos", "amd64", "tar.gz", "nex-server_1.2.3_macos_amd64.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 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"},
|
||||
{"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 deb", "desktop", "linux", "amd64", "deb", "nex-desktop_1.2.3_linux_amd64.deb"},
|
||||
{"desktop linux amd64 rpm", "desktop", "linux", "amd64", "rpm", "nex-desktop_1.2.3_linux_amd64.rpm"},
|
||||
{"desktop linux arm64 tar", "desktop", "linux", "arm64", "tar.gz", "nex-desktop_1.2.3_linux_arm64.tar.gz"},
|
||||
{"desktop linux arm64 appimage", "desktop", "linux", "arm64", "AppImage", "nex-desktop_1.2.3_linux_arm64.AppImage"},
|
||||
{"desktop linux arm64 deb", "desktop", "linux", "arm64", "deb", "nex-desktop_1.2.3_linux_arm64.deb"},
|
||||
{"desktop linux arm64 rpm", "desktop", "linux", "arm64", "rpm", "nex-desktop_1.2.3_linux_arm64.rpm"},
|
||||
{"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 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"},
|
||||
}
|
||||
|
||||
macServer, err := ServerAssetName("1.2.3", "darwin", "arm64")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "nex-server_1.2.3_darwin_arm64.tar.gz", macServer)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := ReleaseAssetName("1.2.3", tc.component, tc.platform, tc.arch, tc.format)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
macDesktop, err := DesktopAssetName("1.2.3", "macos")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Nex_1.2.3_macOS_universal.zip", macDesktop)
|
||||
invalidCases := []struct {
|
||||
name string
|
||||
component string
|
||||
platform string
|
||||
arch string
|
||||
format string
|
||||
}{
|
||||
{"invalid version", "server", "linux", "amd64", "tar.gz"},
|
||||
{"invalid component", "mobile", "linux", "amd64", "tar.gz"},
|
||||
{"darwin platform", "server", "darwin", "arm64", "tar.gz"},
|
||||
{"server unsupported format", "server", "linux", "amd64", "zip"},
|
||||
{"server unsupported arch", "server", "windows", "universal", "zip"},
|
||||
{"web with platform", "web", "linux", "amd64", "tar.gz"},
|
||||
{"web unsupported format", "web", "", "", "zip"},
|
||||
{"desktop unsupported platform", "desktop", "ios", "arm64", "zip"},
|
||||
{"desktop unsupported format", "desktop", "macos", "universal", "tar.gz"},
|
||||
}
|
||||
|
||||
_, err = DesktopAssetName("1.2.3", "ios")
|
||||
assert.Error(t, err)
|
||||
for _, tc := range invalidCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
version := "1.2.3"
|
||||
if tc.name == "invalid version" {
|
||||
version = "1.2"
|
||||
}
|
||||
_, err := ReleaseAssetName(version, tc.component, tc.platform, tc.arch, tc.format)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDesktopInfoPlist(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user