diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 174a166..bb4f9fd 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -579,8 +579,12 @@ jobs: out_name = "GoNavi-DriverAgents.zip" index_name = "GoNavi-DriverAgents-Index.json" base = Path("drivers") - out_path = Path(out_name) - index_path = Path(index_name) + driver_release_dir = Path("../driver-release-assets") + if driver_release_dir.exists(): + shutil.rmtree(driver_release_dir) + driver_release_dir.mkdir(parents=True, exist_ok=True) + out_path = driver_release_dir / out_name + index_path = driver_release_dir / index_name if out_path.exists(): out_path.unlink() if index_path.exists(): @@ -597,7 +601,7 @@ jobs: raise RuntimeError(f"driver asset name conflict: {p.name}") zf.write(p, arcname) size_index[p.name] = p.stat().st_size - standalone_path = Path(p.name) + standalone_path = driver_release_dir / p.name if standalone_path.exists(): raise RuntimeError(f"release asset already exists: {standalone_path}") shutil.copy2(p, standalone_path) @@ -632,6 +636,22 @@ jobs: sha256sum "${FILES[@]}" > SHA256SUMS fi + - name: Generate Driver SHA256SUMS + shell: bash + run: | + cd driver-release-assets + FILES=() + while IFS= read -r file; do + if [ -n "$file" ]; then + FILES+=("$file") + fi + done < <(find . -maxdepth 1 -type f ! -name SHA256SUMS -exec basename {} \; | sort) + if [ ${#FILES[@]} -eq 0 ]; then + echo "❌ 未找到驱动发布资产" + exit 1 + fi + sha256sum "${FILES[@]}" > SHA256SUMS + - name: Generate Dev Version id: version run: | @@ -697,6 +717,68 @@ jobs: } } + - name: Reset Previous Driver Dev Release + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.DRIVER_RELEASE_TOKEN }} + script: | + const tag = 'dev-latest'; + const ref = `tags/${tag}`; + const [owner, repo] = 'Syngnat/GoNavi-DriverAgents'.split('/'); + const releases = await github.paginate(github.rest.repos.listReleases, { + owner, + repo, + per_page: 100, + }); + + const matchedReleases = releases.filter((release) => release.tag_name === tag); + if (matchedReleases.length === 0) { + core.info(`No existing driver releases found for tag ${tag}`); + } else { + for (const release of matchedReleases) { + core.info(`Deleting driver release ${release.id} (${release.name || 'unnamed'}) for tag ${tag}`); + await github.rest.repos.deleteRelease({ + owner, + repo, + release_id: release.id, + }); + } + } + + try { + await github.rest.git.deleteRef({ + owner, + repo, + ref, + }); + core.info(`Deleted driver ref ${ref}`); + } catch (error) { + if (error.status === 404) { + core.info(`No existing driver ref found for ${ref}`); + } else { + throw error; + } + } + + - name: Create Dev Driver Agents Pre-release + uses: softprops/action-gh-release@v2 + with: + repository: Syngnat/GoNavi-DriverAgents + tag_name: dev-latest + name: "GoNavi Driver Agents (${{ steps.version.outputs.version }})" + files: driver-release-assets/* + fail_on_unmatched_files: true + prerelease: true + draft: false + make_latest: false + body: | + GoNavi dev driver-agent assets. + + **版本**: `${{ steps.version.outputs.version }}` + **来源仓库**: `${{ github.repository }}` + **提交**: [`${{ github.sha }}`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) + token: ${{ secrets.DRIVER_RELEASE_TOKEN }} + - name: Create Dev Pre-release uses: softprops/action-gh-release@v2 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6551e0a..4f1e192 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -620,8 +620,12 @@ jobs: out_name = "GoNavi-DriverAgents.zip" index_name = "GoNavi-DriverAgents-Index.json" base = Path("drivers") - out_path = Path(out_name) - index_path = Path(index_name) + driver_release_dir = Path("../driver-release-assets") + if driver_release_dir.exists(): + shutil.rmtree(driver_release_dir) + driver_release_dir.mkdir(parents=True, exist_ok=True) + out_path = driver_release_dir / out_name + index_path = driver_release_dir / index_name if out_path.exists(): out_path.unlink() if index_path.exists(): @@ -638,7 +642,7 @@ jobs: raise RuntimeError(f"driver asset name conflict: {p.name}") zf.write(p, arcname) size_index[p.name] = p.stat().st_size - standalone_path = Path(p.name) + standalone_path = driver_release_dir / p.name if standalone_path.exists(): raise RuntimeError(f"release asset already exists: {standalone_path}") shutil.copy2(p, standalone_path) @@ -654,7 +658,7 @@ jobs: print(f"published standalone driver assets={len(standalone_assets)}") PY - # Release 同时发布单驱动资产与总包:应用优先下载单驱动,失败后仍可用总包兜底。 + # GoNavi 主仓库只保留主程序包;驱动资产发布到独立仓库。 rm -rf drivers - name: Generate SHA256SUMS @@ -674,6 +678,36 @@ jobs: sha256sum "${FILES[@]}" > SHA256SUMS fi + - name: Generate Driver SHA256SUMS + shell: bash + run: | + cd driver-release-assets + FILES=() + while IFS= read -r file; do + if [ -n "$file" ]; then + FILES+=("$file") + fi + done < <(find . -maxdepth 1 -type f ! -name SHA256SUMS -exec basename {} \; | sort) + if [ ${#FILES[@]} -eq 0 ]; then + echo "❌ 未找到驱动发布资产" + exit 1 + fi + sha256sum "${FILES[@]}" > SHA256SUMS + + - name: Create Driver Agents Release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + repository: Syngnat/GoNavi-DriverAgents + tag_name: ${{ github.ref_name }} + name: "GoNavi Driver Agents ${{ github.ref_name }}" + files: driver-release-assets/* + fail_on_unmatched_files: true + make_latest: true + body: | + GoNavi driver-agent assets for `${{ github.ref_name }}`. + token: ${{ secrets.DRIVER_RELEASE_TOKEN }} + - name: Checkout code for changelog uses: actions/checkout@v4 with: diff --git a/internal/app/methods_driver.go b/internal/app/methods_driver.go index ba65d52..a06d021 100644 --- a/internal/app/methods_driver.go +++ b/internal/app/methods_driver.go @@ -293,6 +293,9 @@ type driverBundleAssetIndex struct { const ( // 默认使用内置 manifest,避免依赖网络与外部仓库 404。 defaultDriverManifestURLValue = "builtin://manifest" + driverReleaseRepo = "Syngnat/GoNavi-DriverAgents" + driverReleaseLatestAPIURL = "https://api.github.com/repos/" + driverReleaseRepo + "/releases/latest" + driverReleaseDevTag = "dev-latest" optionalDriverBundleAssetName = "GoNavi-DriverAgents.zip" optionalDriverBundleIndexAssetName = "GoNavi-DriverAgents-Index.json" optionalDriverBundleDownloadTimeout = 45 * time.Minute @@ -883,7 +886,7 @@ func (a *App) CheckDriverNetworkStatus() connection.QueryResult { }, { Name: "GitHub 驱动发布", - URL: fmt.Sprintf("https://github.com/%s/releases/latest/download/%s", updateRepo, optionalDriverBundleAssetName), + URL: driverReleaseLatestDownloadURL(optionalDriverBundleAssetName), }, { Name: "GitHub Release 资产域名", @@ -1873,18 +1876,26 @@ func optionalDriverSourceBuildAvailable(definition driverDefinition, selectedVer } func resolvePublishedDriverDownloadURL(definition driverDefinition, version string) (string, bool) { - driverType := normalizeDriverType(definition.Type) versionText := normalizeVersion(strings.TrimSpace(version)) - if driverType == "" || versionText == "" { + if versionText == "" { return "", false } - tag := "v" + versionText - assetName, ok := resolvePublishedDriverReleaseAssetName(driverType, versionText, tag) + return resolvePublishedDriverDownloadURLForTag(definition, versionText, "v"+versionText) +} + +func resolvePublishedDriverDownloadURLForTag(definition driverDefinition, selectedVersion string, tag string) (string, bool) { + driverType := normalizeDriverType(definition.Type) + tagName := strings.TrimSpace(tag) + if driverType == "" || tagName == "" { + return "", false + } + + assetName, ok := resolvePublishedDriverReleaseAssetName(driverType, selectedVersion, tagName) if !ok { return "", false } - return fmt.Sprintf("https://github.com/%s/releases/download/%s/%s", updateRepo, tag, assetName), true + return driverReleaseDownloadURL(tagName, assetName), true } func resolvePublishedDriverReleaseAssetName(driverType string, version string, tag string) (string, bool) { @@ -2265,7 +2276,7 @@ func resolveDriverVersionOptionsFromReleases(definition driverDefinition) []driv } result = append(result, driverVersionOptionItem{ Version: version, - DownloadURL: fmt.Sprintf("https://github.com/%s/releases/download/%s/%s", updateRepo, tag, assetName), + DownloadURL: driverReleaseDownloadURL(tag, assetName), Source: "release", }) } @@ -2311,7 +2322,7 @@ func loadDriverReleaseListCached() ([]githubRelease, error) { } func fetchDriverReleaseList() ([]githubRelease, error) { - apiURL := fmt.Sprintf("https://api.github.com/repos/%s/releases?per_page=30", updateRepo) + apiURL := fmt.Sprintf("https://api.github.com/repos/%s/releases?per_page=30", driverReleaseRepo) client := newHTTPClientWithGlobalProxy(driverReleaseListProbeTimeout) req, err := http.NewRequest(http.MethodGet, apiURL, nil) if err != nil { @@ -4175,6 +4186,34 @@ func optionalDriverReleaseAssetNameForVersion(driverType string, selectedVersion return names[0] } +func currentDriverReleaseTag() string { + currentVersion := normalizeVersion(getCurrentVersion()) + if currentVersion == "" || currentVersion == "0.0.0" { + return "" + } + if strings.HasPrefix(strings.ToLower(currentVersion), "dev-") { + return driverReleaseDevTag + } + return "v" + currentVersion +} + +func driverReleaseDownloadURL(tag string, assetName string) string { + tagName := strings.TrimSpace(tag) + asset := strings.TrimSpace(assetName) + if tagName == "" || asset == "" { + return "" + } + return fmt.Sprintf("https://github.com/%s/releases/download/%s/%s", driverReleaseRepo, url.PathEscape(tagName), url.PathEscape(asset)) +} + +func driverReleaseLatestDownloadURL(assetName string) string { + asset := strings.TrimSpace(assetName) + if asset == "" { + return "" + } + return fmt.Sprintf("https://github.com/%s/releases/latest/download/%s", driverReleaseRepo, url.PathEscape(asset)) +} + func optionalDriverBundlePlatformDir(goos string) string { switch strings.ToLower(strings.TrimSpace(goos)) { case "windows": @@ -4261,11 +4300,10 @@ func resolveOptionalDriverBundleDownloadURLs() []string { candidates = append(candidates, trimmed) } - currentVersion := normalizeVersion(getCurrentVersion()) - if currentVersion != "" && currentVersion != "0.0.0" { - appendURL(fmt.Sprintf("https://github.com/Syngnat/GoNavi/releases/download/v%s/%s", currentVersion, optionalDriverBundleAssetName)) + if tag := currentDriverReleaseTag(); tag != "" { + appendURL(driverReleaseDownloadURL(tag, optionalDriverBundleAssetName)) } - appendURL(fmt.Sprintf("https://github.com/Syngnat/GoNavi/releases/latest/download/%s", optionalDriverBundleAssetName)) + appendURL(driverReleaseLatestDownloadURL(optionalDriverBundleAssetName)) return candidates } @@ -4483,9 +4521,8 @@ func resolveOptionalDriverAgentDownloadURLs(definition driverDefinition, rawURL return candidates } - currentVersion := normalizeVersion(getCurrentVersion()) - if currentVersion != "" && currentVersion != "0.0.0" { - if publishedURL, ok := resolvePublishedDriverDownloadURL(definition, currentVersion); ok { + if tag := currentDriverReleaseTag(); tag != "" { + if publishedURL, ok := resolvePublishedDriverDownloadURLForTag(definition, selectedVersion, tag); ok { appendURL(publishedURL) } } @@ -4907,11 +4944,7 @@ func preloadOptionalDriverPackageSizes(definitions []driverDefinition) map[strin return result } - currentVersion := normalizeVersion(getCurrentVersion()) - tag := "" - if currentVersion != "" && currentVersion != "0.0.0" { - tag = "v" + currentVersion - } + tag := currentDriverReleaseTag() fillFromSizes := func(sizeByAsset map[string]int64, driverTypes []string) []string { missing := make([]string, 0, len(driverTypes)) @@ -5098,7 +5131,7 @@ func fetchDriverBundleAssetSizeIndex(release *githubRelease) (map[string]int64, } func fetchLatestReleaseForDriverAssets() (*githubRelease, error) { - return fetchDriverReleaseByURL(updateAPIURL) + return fetchDriverReleaseByURL(driverReleaseLatestAPIURL) } func resolveLatestPublishedDriverDownloadURL(definition driverDefinition) (string, bool) { @@ -5114,7 +5147,7 @@ func resolveLatestPublishedDriverDownloadURL(definition driverDefinition) (strin if sizeByAsset, publishedAssets, ok := readReleaseAssetSizesFromCache("latest"); ok { for _, assetName := range assetNames { if publishedAssets[assetName] && sizeByAsset[assetName] > 0 { - return fmt.Sprintf("https://github.com/%s/releases/latest/download/%s", updateRepo, assetName), true + return driverReleaseLatestDownloadURL(assetName), true } } return "", false @@ -5126,7 +5159,7 @@ func resolveLatestPublishedDriverDownloadURL(definition driverDefinition) (strin } for _, assetName := range assetNames { if publishedAssets[assetName] && sizeByAsset[assetName] > 0 { - return fmt.Sprintf("https://github.com/%s/releases/latest/download/%s", updateRepo, assetName), true + return driverReleaseLatestDownloadURL(assetName), true } } return "", false @@ -5137,7 +5170,7 @@ func fetchReleaseByTag(tag string) (*githubRelease, error) { if tagName == "" { return nil, fmt.Errorf("Tag 为空") } - apiURL := fmt.Sprintf("https://api.github.com/repos/%s/releases/tags/%s", updateRepo, url.PathEscape(tagName)) + apiURL := fmt.Sprintf("https://api.github.com/repos/%s/releases/tags/%s", driverReleaseRepo, url.PathEscape(tagName)) return fetchDriverReleaseByURL(apiURL) } diff --git a/internal/app/methods_driver_version_test.go b/internal/app/methods_driver_version_test.go index 9370b57..8bc07a9 100644 --- a/internal/app/methods_driver_version_test.go +++ b/internal/app/methods_driver_version_test.go @@ -36,12 +36,42 @@ func TestResolveVersionedDriverOptionUsesPublishedMongoV1Release(t *testing.T) { t.Fatalf("expected version %q, got %q", version, gotVersion) } - wantURL := fmt.Sprintf("https://github.com/%s/releases/download/v%s/%s", updateRepo, version, assetName) + wantURL := fmt.Sprintf("https://github.com/%s/releases/download/v%s/%s", driverReleaseRepo, version, assetName) if gotURL != wantURL { t.Fatalf("expected published release URL %q, got %q", wantURL, gotURL) } } +func TestCurrentDriverReleaseTagUsesDevLatestForDevBuild(t *testing.T) { + originalVersion := AppVersion + AppVersion = "dev-abc1234" + t.Cleanup(func() { + AppVersion = originalVersion + }) + + if got := currentDriverReleaseTag(); got != driverReleaseDevTag { + t.Fatalf("expected dev driver release tag %q, got %q", driverReleaseDevTag, got) + } +} + +func TestResolveOptionalDriverBundleDownloadURLsUsesDriverReleaseRepo(t *testing.T) { + originalVersion := AppVersion + AppVersion = "0.7.4" + t.Cleanup(func() { + AppVersion = originalVersion + }) + + urls := resolveOptionalDriverBundleDownloadURLs() + wantTagged := driverReleaseDownloadURL("v0.7.4", optionalDriverBundleAssetName) + wantLatest := driverReleaseLatestDownloadURL(optionalDriverBundleAssetName) + if len(urls) != 2 { + t.Fatalf("expected tagged and latest bundle URLs, got %v", urls) + } + if urls[0] != wantTagged || urls[1] != wantLatest { + t.Fatalf("unexpected driver bundle URLs: got %v want [%q %q]", urls, wantTagged, wantLatest) + } +} + func TestDriverVersionSupportRangeForMongoDB(t *testing.T) { definition, ok := resolveDriverDefinition("mongodb") if !ok { @@ -119,7 +149,7 @@ func TestResolveOptionalDriverAgentDownloadURLsDoesNotFallbackForHistoricalVersi t.Fatal("expected mongodb driver definition") } - explicitURL := fmt.Sprintf("https://github.com/Syngnat/GoNavi/releases/download/v1.17.4/%s", mongoVersionedReleaseAssetName(1)) + explicitURL := driverReleaseDownloadURL("v1.17.4", mongoVersionedReleaseAssetName(1)) urls := resolveOptionalDriverAgentDownloadURLs( definition, explicitURL, @@ -364,7 +394,7 @@ func TestShouldForceSourceBuildForResolvedDownload(t *testing.T) { t.Fatal("expected mongodb v1 builtin install to keep source build mode") } - explicitURL := fmt.Sprintf("https://github.com/%s/releases/download/v1.17.4/%s", updateRepo, mongoVersionedReleaseAssetName(1)) + explicitURL := driverReleaseDownloadURL("v1.17.4", mongoVersionedReleaseAssetName(1)) if shouldForceSourceBuildForResolvedDownload("mongodb", "1.17.4", explicitURL) { t.Fatal("expected mongodb v1 published asset install to skip forced source build") }