From 5ad07df8eefc2c77a03b3ca917222f8bdca54e5b Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Wed, 5 Aug 2026 17:44:19 +0800 Subject: [PATCH 1/2] fix(ci): recover from stalled GitCode asset uploads --- .github/workflows/gitcode-mirror.yml | 3 ++- scripts/publish-gitcode-release.mjs | 31 ++++++++++++++++------- tests/gitcodeRelease.test.ts | 38 ++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gitcode-mirror.yml b/.github/workflows/gitcode-mirror.yml index a7cc1c5..c4ba34c 100644 --- a/.github/workflows/gitcode-mirror.yml +++ b/.github/workflows/gitcode-mirror.yml @@ -33,7 +33,7 @@ jobs: || github.event.workflow_run.conclusion == 'success' ) runs-on: ubuntu-24.04 - timeout-minutes: 60 + timeout-minutes: 35 steps: - uses: actions/checkout@v7 - name: Resolve release tag @@ -75,6 +75,7 @@ jobs: run: gh release download "${{ steps.release.outputs.tag }}" --dir artifacts - name: Publish GitCode mirror if: steps.release.outputs.should_mirror == 'true' + timeout-minutes: 30 env: ARTIFACT_DIRECTORY: artifacts GITCODE_ACCESS_TOKEN: ${{ secrets.GITCODE_ACCESS_TOKEN }} diff --git a/scripts/publish-gitcode-release.mjs b/scripts/publish-gitcode-release.mjs index 3cc245e..d6fb237 100644 --- a/scripts/publish-gitcode-release.mjs +++ b/scripts/publish-gitcode-release.mjs @@ -4,6 +4,8 @@ import { basename, join, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; const API_BASE = 'https://api.gitcode.com/api/v5'; +const API_TIMEOUT_MS = 30_000; +const UPLOAD_TIMEOUT_SECONDS = 180; function parseRepository(value) { const repository = String(value ?? '').trim(); @@ -36,6 +38,7 @@ async function apiRequest(path, { method, headers: body ? { 'Content-Type': 'application/json' } : undefined, body: body ? JSON.stringify(body) : undefined, + signal: AbortSignal.timeout(API_TIMEOUT_MS), }); } catch (error) { throw new Error(`GitCode API request failed for ${path}: ${error instanceof Error ? error.message : error}`); @@ -53,11 +56,10 @@ export function uploadFileWithCurl({ url, headers, path }) { '--silent', '--show-error', '--location', - '--retry', '3', - '--retry-delay', '5', - '--retry-all-errors', '--connect-timeout', '30', - '--max-time', '1800', + '--speed-limit', '1', + '--speed-time', '30', + '--max-time', String(UPLOAD_TIMEOUT_SECONDS), '--request', 'PUT', '--upload-file', path, '--output', process.platform === 'win32' ? 'NUL' : '/dev/null', @@ -147,11 +149,22 @@ export async function publishGitcodeRelease({ const headers = Array.isArray(rawHeaders) ? Object.fromEntries(rawHeaders.map(({ key, name, value }) => [key ?? name, value])) : rawHeaders && typeof rawHeaders === 'object' ? rawHeaders : {}; - await uploadImpl({ - url: uploadUrl, - headers, - path: join(directory, entry.name), - }); + console.log(`Uploading GitCode asset: ${entry.name}`); + try { + await uploadImpl({ + url: uploadUrl, + headers, + path: join(directory, entry.name), + }); + } catch (error) { + console.warn(`GitCode upload command did not finish cleanly; verifying asset: ${entry.name}`); + const refreshedRelease = await apiRequest(releaseByTagPath, { token, fetchImpl }); + const uploadedDespiteError = (refreshedRelease.assets ?? []) + .some((asset) => asset.name === entry.name); + if (!uploadedDespiteError) throw error; + console.log(`GitCode asset exists despite upload error: ${entry.name}`); + } + existingAssets.add(entry.name); console.log(`Uploaded GitCode asset: ${entry.name}`); } } diff --git a/tests/gitcodeRelease.test.ts b/tests/gitcodeRelease.test.ts index f4f813d..c038d28 100644 --- a/tests/gitcodeRelease.test.ts +++ b/tests/gitcodeRelease.test.ts @@ -53,4 +53,42 @@ describe('GitCode release mirror', () => { await rm(directory, { recursive: true, force: true }); } }); + + test('continues when GitCode stores an asset but the upload response times out', async () => { + const directory = await mkdtemp(join(tmpdir(), 'easycli-gitcode-release-')); + const artifactName = 'EasyCLIProxyAPI-v1.2.3-Windows-amd64.zip'; + let releaseRequests = 0; + try { + await writeFile(join(directory, artifactName), 'artifact'); + const fetchImpl = async (input: URL | RequestInfo) => { + const url = String(input); + if (url.includes('/releases/tags/')) { + releaseRequests += 1; + return Response.json({ + tag_name: 'v1.2.3', + assets: releaseRequests === 1 ? [] : [{ name: artifactName }], + }); + } + if (url.includes('/upload_url?')) { + return Response.json({ upload_url: 'https://file-cdn.gitcode.com/presigned-upload' }); + } + return new Response('', { status: 500 }); + }; + + await expect(publishGitcodeRelease({ + directory, + repository: 'mirror-owner/EasyCLIProxyAPI', + token: 'secret-token', + tag: 'v1.2.3', + fetchImpl, + uploadImpl: async () => { + throw new Error('upload response timed out'); + }, + })).resolves.toBeUndefined(); + + expect(releaseRequests).toBe(2); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); }); From 7c2dab9fc21c2783307165718050437a27a899b2 Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Wed, 5 Aug 2026 18:40:59 +0800 Subject: [PATCH 2/2] build: enable GitCode fallback for local builds --- build.ps1 | 16 +++++++++++++++- build.sh | 4 +++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/build.ps1 b/build.ps1 index a958702..160403a 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,6 +1,8 @@ [CmdletBinding()] param( - [int]$BuildJobs + [int]$BuildJobs, + + [string]$GitCodeRepository = 'lzt404/EasyCLIProxyAPI' ) Set-StrictMode -Version Latest @@ -25,12 +27,16 @@ if (-not $PSBoundParameters.ContainsKey('BuildJobs')) { if ($BuildJobs -lt 1 -or $BuildJobs -gt 256) { throw 'BuildJobs must be between 1 and 256.' } +if ($GitCodeRepository -notmatch '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$') { + throw 'GitCodeRepository must use the owner/repository format.' +} if (-not (Get-Command bun -ErrorAction SilentlyContinue)) { throw 'bun is not installed or not in PATH.' } Write-Host "Cargo build jobs: $BuildJobs" +Write-Host "GitCode fallback repository: $GitCodeRepository" & bun install if ($LASTEXITCODE -ne 0) { @@ -38,8 +44,10 @@ if ($LASTEXITCODE -ne 0) { } $PreviousBuildJobs = $env:CARGO_BUILD_JOBS +$PreviousGitCodeRepository = $env:GITCODE_REPOSITORY try { $env:CARGO_BUILD_JOBS = [string]$BuildJobs + $env:GITCODE_REPOSITORY = $GitCodeRepository & bun tauri build --no-bundle if ($LASTEXITCODE -ne 0) { throw "Tauri build failed with exit code $LASTEXITCODE." @@ -52,6 +60,12 @@ finally { else { $env:CARGO_BUILD_JOBS = $PreviousBuildJobs } + if ($null -eq $PreviousGitCodeRepository) { + Remove-Item Env:GITCODE_REPOSITORY -ErrorAction SilentlyContinue + } + else { + $env:GITCODE_REPOSITORY = $PreviousGitCodeRepository + } } if (-not (Test-Path -LiteralPath $AppBin -PathType Leaf)) { diff --git a/build.sh b/build.sh index 10146c8..e711a6b 100755 --- a/build.sh +++ b/build.sh @@ -5,6 +5,7 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$ROOT_DIR" BUILD_JOBS="${CARGO_BUILD_JOBS:-20}" +GITCODE_REPOSITORY="${GITCODE_REPOSITORY:-lzt404/EasyCLIProxyAPI}" APP_BIN="$ROOT_DIR/src-tauri/target/release/cpa-gui" BIN_DIR="$ROOT_DIR/bin-work" BIN_OUT="$BIN_DIR/EasyCLIProxyAPI" @@ -15,8 +16,9 @@ if ! command -v bun >/dev/null 2>&1; then fi echo "Cargo build jobs: $BUILD_JOBS" +echo "GitCode fallback repository: $GITCODE_REPOSITORY" bun install -CARGO_BUILD_JOBS="$BUILD_JOBS" bun tauri build --no-bundle +CARGO_BUILD_JOBS="$BUILD_JOBS" GITCODE_REPOSITORY="$GITCODE_REPOSITORY" bun tauri build --no-bundle if [ ! -x "$APP_BIN" ]; then echo "Build finished, but executable not found: $APP_BIN"