Skip to content

feat: Dev2alpha#165

Merged
mkdir700 merged 2 commits intoalphafrom
dev2alpha
Sep 14, 2025
Merged

feat: Dev2alpha#165
mkdir700 merged 2 commits intoalphafrom
dev2alpha

Conversation

@mkdir700
Copy link
Owner

@mkdir700 mkdir700 commented Sep 14, 2025

🚀 Dev2Alpha: GitCode 发布同步自动化

将 dev 分支的最新功能合并到 alpha 分支,主要包含 GitCode 发布同步的完整自动化解决方案。

✨ 新增功能

🤖 GitCode 发布同步工作流

  • 文件: .github/workflows/sync-release-to-gitcode.yml
  • 触发器:
    • 自动触发:GitHub release 发布或编辑时
    • 手动触发:通过 workflow_dispatch 支持测试和手动同步
  • 功能特性:
    • 🔄 自动同步仓库分支(main, dev, alpha, beta)和所有标签到 GitCode
    • 📦 自动下载并上传 release 资产到 GitCode
    • 🎯 智能检测:避免重复创建 release 和上传已存在的资产
    • 🧪 测试模式:支持 dry-run 测试,确保流程正确性
    • ⚠️ 错误处理:完整的错误处理和重试机制
    • 📊 详细日志:提供完整的执行摘要和状态报告

📤 GitCode 资产上传脚本

  • 文件: scripts/upload-assets.js
  • 功能: 专用的 GitCode 资产上传工具
  • 特性:
    • 🚀 并发上传:支持多文件并发上传,提高效率
    • 🔄 智能重试:自动重试失败的上传,确保可靠性
    • 📋 去重检查:自动检查已存在的资产,避免重复上传
    • 🎛️ 可配置:支持自定义并发数、重试次数等参数
    • 📝 详细日志:提供上传进度和结果统计

🔧 技术实现

  • API 集成: 使用 GitCode API v5 进行 release 管理和资产上传
  • 认证方式: 支持 OAuth2 token 认证
  • 错误容错: 完整的 HTTP 错误处理和网络超时处理
  • 状态管理: 智能的状态检查和条件执行
  • 资源优化: 高效的文件处理和内存管理

📋 测试说明

可以通过以下方式测试此功能:

  1. 手动触发测试:

    gh workflow run "Sync Release to GitCode" \
      --field tag_name="v1.0.0-test" \
      --field release_name="Test Release" \
      --field test_mode=true
  2. 实际 Release 同步: 直接发布 GitHub release,工作流将自动执行

🎯 使用场景

  • ✅ 新 release 发布时自动同步到 GitCode
  • ✅ 现有 release 更新时自动同步变更
  • ✅ 历史 release 的手动补充同步
  • ✅ 发布流程的测试和验证

🔒 安全考虑

  • 使用 GitHub Secrets 安全存储 GitCode access token
  • 支持组织级别的 GitCode 所有者配置
  • 完整的权限验证和错误处理

测试状态: ✅ 已通过手动测试和 dry-run 验证
兼容性: ✅ 兼容现有的 release 流程,无破坏性变更
文档: ✅ 包含完整的内联文档和使用说明

* feat(ffmpeg): add China mirror support for FFmpeg downloads

- Add IP-based region detection using ipinfo.io API
- Support China mainland, Hong Kong, Macau, Taiwan regions
- Add dedicated China mirror URLs from gitcode.com
- Implement automatic fallback from China to global mirrors
- Add comprehensive test coverage for new functionality
- Default to China mirror on detection failure for better UX

Breaking change: Service now defaults to China mirror for
better performance in Chinese regions

* fix(test): remove unused parameter in FFmpegDownloadService test

- Fix TypeScript error TS6133 for unused 'url' parameter
- Replace unused 'url' with underscore in mock implementation
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 14, 2025

Walkthrough

Adds a GitHub Actions workflow to sync releases to GitCode, introduces a Node.js asset uploader for GitCode, and updates FFmpegDownloadService to support region-based (China/global) mirrors with detection, manual override, and fallback. Includes new tests covering IP-based region detection, mirror APIs, URL selection, and fallback behavior.

Changes

Cohort / File(s) Summary
CI: Sync Release to GitCode
.github/workflows/sync-release-to-gitcode.yml
New workflow to sync branches, tags, releases, and assets to GitCode; supports release and manual triggers, dry-run mode, and detailed step summaries.
Tools: GitCode Asset Uploader
scripts/upload-assets.js
Adds GitCodeUploader Node script with concurrent uploads, retries, asset existence checks, and CLI; exports class for reuse.
FFmpeg: Region-Aware Mirror & Fallback
src/main/services/FFmpegDownloadService.ts, src/main/services/__tests__/FFmpegDownloadService.test.ts
Implements IP-based region detection, China/global mirror configs, manual mirror control, mirror-aware version queries, and download fallback; adds comprehensive tests for detection, APIs, URL patterns, and behavior.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant GH as GitHub Actions
  participant GHAPI as GitHub API
  participant GC as GitCode Git
  participant GCAPI as GitCode API
  participant Upl as upload-assets.js

  Dev->>GH: Release published/edited or workflow_dispatch
  GH->>GH: Determine release metadata (tag, name, body, flags)
  alt Not test_mode
    GH->>GC: git push --force branches (main/dev/alpha/beta) if present
    GH->>GC: git push --force --tags
  else test_mode
    GH->>GH: Log intended git operations
  end

  alt Manual trigger
    GH->>GHAPI: Fetch release by tag
    GHAPI-->>GH: Release + assets (if any)
  else Release event
    GH-->>GH: Use event payload assets
  end

  GH->>GCAPI: Check tag and release existence
  alt Release not exists
    GH->>GCAPI: POST create release
  else Release exists
    GH->>GCAPI: PATCH update release
  end

  alt Assets present and not test_mode
    GH->>Upl: Run uploader with token/owner/repo/tag/files
    Upl->>GCAPI: Get release + upload URLs
    Upl->>GCAPI: Upload assets (concurrent, retries)
  else No assets or test_mode
    GH->>GH: Log asset handling
  end

  GH-->>Dev: Step Summary (actions, results)
Loading
sequenceDiagram
  autonumber
  participant App as App
  participant FF as FFmpegDownloadService
  participant IP as ipinfo.io
  participant CN as China Mirror
  participant GL as Global Mirror

  App->>FF: downloadFFmpeg()
  par Region detection (with timeout)
    FF->>IP: fetch country (<=5s)
    alt Success (CN/HK/MO/TW)
      FF-->>FF: useChinaMirror = true
    else Failure/timeout
      FF-->>FF: useChinaMirror = true (default)
    end
  and Await (<=10s)
    FF-->>FF: wait for detection or timeout
  end

  alt useChinaMirror
    FF->>CN: Attempt download
    alt Success
      FF-->>App: Download complete
    else Failure
      FF->>GL: Fallback download
      GL-->>FF: Result
      FF-->>App: Completion or error
    end
  else global mirror
    FF->>GL: Download
    FF-->>App: Completion or error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

A bunny taps the tags in moonlit code,
Syncs releases down the GitCode road.
With mirrors that shimmer—CN or wide—
It sniffs the breeze, then picks a side.
Assets hop swiftly, retries in queue;
Carrots for logs, and CI hops through. 🥕🚀

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The current title "feat: Dev2alpha" is terse and primarily reflects the branch name rather than summarizing the primary work in this PR; it does not communicate the main changes (adding a GitHub→GitCode release sync workflow and uploader, plus FFmpeg China/global mirror logic and tests), so it is unhelpful for reviewers scanning history. Please replace the title with a short, descriptive sentence that highlights the main change, for example "feat: add sync-release-to-gitcode workflow and asset uploader; add China/global FFmpeg mirror support", or split into separate PRs if you want each logical change isolated.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev2alpha

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mkdir700 mkdir700 merged commit 5cbf492 into alpha Sep 14, 2025
4 of 5 checks passed
@mkdir700 mkdir700 deleted the dev2alpha branch September 14, 2025 06:07
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/services/FFmpegDownloadService.ts (3)

411-511: Always clean tempDir on failure to avoid disk bloat

cleanupTempDir is only called on success. Ensure it also runs on errors/cancel.

-  private async performDownload(
+  private async performDownload(
     platform: Platform,
     arch: Arch,
     version: FFmpegVersion,
     onProgress?: (progress: DownloadProgress) => void
   ): Promise<boolean> {
-    const key = `${platform}-${arch}`
+    const key = `${platform}-${arch}`
     const controller = new AbortController()
     this.downloadController.set(key, controller)
@@
-    try {
-      // 创建目标目录
-      const platformDir = `${version.version}-${platform}-${arch}`
-      const targetDir = path.join(this.binariesDir, platformDir)
-      const tempDir = path.join(this.binariesDir, '.temp', key)
+    // 提前声明以便 finally 中清理
+    const platformDir = `${version.version}-${platform}-${arch}`
+    const targetDir = path.join(this.binariesDir, platformDir)
+    const tempDir = path.join(this.binariesDir, '.temp', key)
+    try {
       this.ensureDir(targetDir)
       this.ensureDir(tempDir)
@@
-      return false
-    } finally {
+      return false
+    } finally {
       this.downloadProgress.delete(key)
       this.downloadController.delete(key)
+      // 成功或失败均清理临时目录
+      this.cleanupTempDir(tempDir)
     }
   }

589-692: Handle more redirect codes and relative Location headers

Support 303/307/308 and resolve relative redirects.

-            if (response.statusCode === 301 || response.statusCode === 302) {
-              const redirectUrl = response.headers.location
+            if ([301, 302, 303, 307, 308].includes(response.statusCode || 0)) {
+              const location = response.headers.location
-              if (redirectUrl) {
-                download(redirectUrl, redirectCount + 1)
+              if (location) {
+                const next = new URL(location, currentUrl).toString()
+                download(next, redirectCount + 1)
                 return
               }
             }

704-718: Extraction dependency: unzip may be missing on Linux

unzip isn’t guaranteed in minimal distros. Consider falling back to 7z or using a Node zip lib.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 872c8db and e29dbed.

📒 Files selected for processing (4)
  • .github/workflows/sync-release-to-gitcode.yml (1 hunks)
  • scripts/upload-assets.js (1 hunks)
  • src/main/services/FFmpegDownloadService.ts (4 hunks)
  • src/main/services/__tests__/FFmpegDownloadService.test.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: 统一使用 loggerService 记录日志,禁止使用 console
logger 使用示例:logger.error('Error in :', { error }); 第二个参数必须是对象字面量 {}

Files:

  • scripts/upload-assets.js
  • src/main/services/FFmpegDownloadService.ts
  • src/main/services/__tests__/FFmpegDownloadService.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: 在组件/Hook 中避免硬编码尺寸和时长,优先使用 useTheme() 的 token(如 motionDurationMid、borderRadiusSM/MD 等)或集中定义的样式变量
定制 antd 组件样式时优先使用 styled-components(styled(Component) 包装),避免通过全局 SCSS 与全局 className 覆盖
项目使用 Zustand 结合 Immer 中间件与自定义中间件栈(持久化、DevTools、订阅选择器)进行状态管理

Files:

  • src/main/services/FFmpegDownloadService.ts
  • src/main/services/__tests__/FFmpegDownloadService.test.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,ts}: Zustand 必须在组件/Hook 顶层使用 selector(useStore(selector));禁止在 useMemo/useEffect 内部调用 store Hook
避免使用返回对象的 useStore 选择器(如 useStore(s => ({ a: s.a, b: s.b }))),应使用单字段选择器或配合 shallow 比较器

Files:

  • src/main/services/FFmpegDownloadService.ts
  • src/main/services/__tests__/FFmpegDownloadService.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

测试使用 vitest 作为测试框架

Files:

  • src/main/services/__tests__/FFmpegDownloadService.test.ts
🧬 Code graph analysis (2)
src/main/services/FFmpegDownloadService.ts (1)
src/renderer/src/services/Logger.ts (1)
  • error (422-424)
src/main/services/__tests__/FFmpegDownloadService.test.ts (1)
scripts/upload-assets.js (1)
  • options (360-367)
🪛 YAMLlint (1.37.1)
.github/workflows/sync-release-to-gitcode.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[warning] 49-49: too few spaces before comment: expected 2

(comments)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (ubuntu-latest, 20)
  • GitHub Check: test (windows-latest, 20)
  • GitHub Check: test (macos-latest, 20)
🔇 Additional comments (3)
src/main/services/__tests__/FFmpegDownloadService.test.ts (2)

105-109: Good: explicit mirror selection for deterministic tests

Explicitly forcing the global mirror removes flakiness from region defaults.


271-338: AbortSignal type in expectations may fail under TS config without DOM libs

Expect.any(AbortSignal) depends on lib.dom or Node 18+ types. If CI lacks DOM, use globalThis.AbortSignal or loosen to expect.anything().

- signal: expect.any(AbortSignal),
+ signal: expect.any((globalThis as any).AbortSignal ?? Object),

If needed, ensure tsconfig includes appropriate lib for fetch/AbortController.

.github/workflows/sync-release-to-gitcode.yml (1)

36-40: Align API base with uploader script

You export GITCODE_API_BASE=https://gitcode.com/api/v5; ensure the uploader reads this (after script change) so both paths hit the same host.

- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for all branches and tags
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

YAML lint: add two spaces before comment

Minor formatting nit only.

-          fetch-depth: 0 # Fetch all history for all branches and tags
+          fetch-depth: 0  # Fetch all history for all branches and tags
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fetch-depth: 0 # Fetch all history for all branches and tags
fetch-depth: 0 # Fetch all history for all branches and tags
🧰 Tools
🪛 YAMLlint (1.37.1)

[warning] 49-49: too few spaces before comment: expected 2

(comments)

🤖 Prompt for AI Agents
.github/workflows/sync-release-to-gitcode.yml around line 49: the inline comment
after the fetch-depth key uses only one space before the '#' which fails the
project's YAML lint rule; update the line so there are two spaces before the
comment (i.e., ensure one space after the value and an additional leading space
before '#') to satisfy linting.

Comment on lines +83 to +143
- name: Sync repository to GitCode
if: steps.release.outputs.test_mode != 'true'
run: |
echo "🔄 Syncing repository to GitCode using HTTPS..."

# Configure git with token authentication
git config user.name "GitHub Actions"
git config user.email "actions@github.com"

# Construct GitCode repository URL with token authentication
GITCODE_REPO_URL="https://oauth2:${{ secrets.GITCODE_ACCESS_TOKEN }}@gitcode.com/$GITCODE_OWNER/$GITCODE_REPO.git"

echo "Repository: $GITCODE_OWNER/$GITCODE_REPO"

# Add GitCode remote (remove if exists)
if git remote | grep -q "gitcode"; then
echo "Removing existing gitcode remote"
git remote remove gitcode
fi

echo "Adding GitCode remote with HTTPS authentication"
git remote add gitcode "$GITCODE_REPO_URL"

echo "📤 Force pushing branches to GitCode..."

# Show available branches
echo "Available branches:"
git branch -a | grep -E "(main|dev|alpha|beta)" || echo "Target branches not found"

# Force push main branches to GitCode
for branch in main dev alpha beta; do
if git show-ref --verify --quiet refs/heads/$branch || git show-ref --verify --quiet refs/remotes/origin/$branch; then
echo "Pushing branch: $branch"
if git show-ref --verify --quiet refs/heads/$branch; then
git push --force gitcode $branch:$branch || {
echo "❌ Failed to push local branch $branch"
exit 1
}
else
git push --force gitcode origin/$branch:$branch || {
echo "❌ Failed to push remote branch $branch"
exit 1
}
fi
echo "✅ Successfully pushed branch: $branch"
else
echo "⚠️ Branch $branch not found, skipping"
fi
done

echo "🏷️ Pushing all tags to GitCode..."
echo "Available tags (last 10):"
git tag | tail -10 || echo "No tags found"

git push --force gitcode --tags || {
echo "❌ Failed to push tags"
exit 1
}

echo "✅ Repository sync completed successfully"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Force-pushing branches and tags is risky

--force on branches and tags can rewrite history on GitCode. Prefer --force-with-lease for branches and push tags without force unless necessary.

- git push --force gitcode $branch:$branch
+ git push --force-with-lease gitcode $branch:$branch
@@
- git push --force gitcode --tags
+ git push gitcode --tags

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
.github/workflows/sync-release-to-gitcode.yml around lines 83 to 143: the
workflow currently uses --force for branch and tag pushes which can rewrite
remote history; change branch pushes to use --force-with-lease instead of
--force (both when pushing local branch and when pushing origin/branch) and
remove the --force flag when pushing tags (use a normal git push --tags) so tags
are not forcibly rewritten unless absolutely required.

Comment on lines +198 to +258
- name: Check if release exists on GitCode
id: check-release
run: |
if [ "${{ steps.release.outputs.test_mode }}" = "true" ]; then
echo "🧪 Test mode enabled - skipping GitCode API check"
echo "exists=false" >> $GITHUB_OUTPUT
echo "Test mode: Simulating release does not exist on GitCode"
else
# First check if tag exists using GitCode tags API
echo "Checking if tag exists..."
tags_response=$(curl -s -w "%{http_code}" \
-H "Accept: application/json" \
"$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/tags?access_token=${{ secrets.GITCODE_ACCESS_TOKEN }}")

tags_http_code="${tags_response: -3}"
tags_response_body="${tags_response%???}"

echo "Tags API HTTP Code: $tags_http_code"

tag_exists=false
if [ "$tags_http_code" = "200" ] || [ "$tags_http_code" = "201" ]; then
echo "Available tags (first 20):"
echo "$tags_response_body" | jq -r '.[] | .name' 2>/dev/null | head -20 || echo "Failed to parse tags"

# Check if our target tag exists
if echo "$tags_response_body" | jq -e --arg tag "${{ steps.release.outputs.tag_name }}" '.[] | select(.name == $tag)' > /dev/null 2>&1; then
tag_exists=true
echo "✅ Tag ${{ steps.release.outputs.tag_name }} exists on GitCode"
else
echo "❌ Tag ${{ steps.release.outputs.tag_name }} does not exist on GitCode"
fi
else
echo "❌ Failed to fetch tags from GitCode (HTTP $tags_http_code): $tags_response_body"
fi

# Then check if release exists (only if tag exists)
if [ "$tag_exists" = "true" ]; then
echo "Checking if release exists..."
response=$(curl -s -w "%{http_code}" \
-H "Accept: application/json" \
"$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/releases/tags/${{ steps.release.outputs.tag_name }}?access_token=${{ secrets.GITCODE_ACCESS_TOKEN }}")
else
echo "⚠️ Skipping release check since tag does not exist"
response="404Not Found"
fi

http_code="${response: -3}"
response_body="${response%???}"

echo "HTTP Code: $http_code"
echo "Response: $response_body"

if [ "$http_code" = "200" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Release already exists on GitCode"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Release does not exist on GitCode"
fi
fi

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Token in query string in GitCode API calls

curl requests pass access_token via query. Prefer Authorization header to avoid leakage.

- "$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/tags?access_token=${{ secrets.GITCODE_ACCESS_TOKEN }}")
+ -H "Authorization: token ${{ secrets.GITCODE_ACCESS_TOKEN }}" \
+ "$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/tags")

Apply similarly to subsequent releases API calls (create/update/check).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Check if release exists on GitCode
id: check-release
run: |
if [ "${{ steps.release.outputs.test_mode }}" = "true" ]; then
echo "🧪 Test mode enabled - skipping GitCode API check"
echo "exists=false" >> $GITHUB_OUTPUT
echo "Test mode: Simulating release does not exist on GitCode"
else
# First check if tag exists using GitCode tags API
echo "Checking if tag exists..."
tags_response=$(curl -s -w "%{http_code}" \
-H "Accept: application/json" \
"$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/tags?access_token=${{ secrets.GITCODE_ACCESS_TOKEN }}")
tags_http_code="${tags_response: -3}"
tags_response_body="${tags_response%???}"
echo "Tags API HTTP Code: $tags_http_code"
tag_exists=false
if [ "$tags_http_code" = "200" ] || [ "$tags_http_code" = "201" ]; then
echo "Available tags (first 20):"
echo "$tags_response_body" | jq -r '.[] | .name' 2>/dev/null | head -20 || echo "Failed to parse tags"
# Check if our target tag exists
if echo "$tags_response_body" | jq -e --arg tag "${{ steps.release.outputs.tag_name }}" '.[] | select(.name == $tag)' > /dev/null 2>&1; then
tag_exists=true
echo "✅ Tag ${{ steps.release.outputs.tag_name }} exists on GitCode"
else
echo "❌ Tag ${{ steps.release.outputs.tag_name }} does not exist on GitCode"
fi
else
echo "❌ Failed to fetch tags from GitCode (HTTP $tags_http_code): $tags_response_body"
fi
# Then check if release exists (only if tag exists)
if [ "$tag_exists" = "true" ]; then
echo "Checking if release exists..."
response=$(curl -s -w "%{http_code}" \
-H "Accept: application/json" \
"$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/releases/tags/${{ steps.release.outputs.tag_name }}?access_token=${{ secrets.GITCODE_ACCESS_TOKEN }}")
else
echo "⚠️ Skipping release check since tag does not exist"
response="404Not Found"
fi
http_code="${response: -3}"
response_body="${response%???}"
echo "HTTP Code: $http_code"
echo "Response: $response_body"
if [ "$http_code" = "200" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Release already exists on GitCode"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Release does not exist on GitCode"
fi
fi
- name: Check if release exists on GitCode
id: check-release
run: |
if [ "${{ steps.release.outputs.test_mode }}" = "true" ]; then
echo "🧪 Test mode enabled - skipping GitCode API check"
echo "exists=false" >> $GITHUB_OUTPUT
echo "Test mode: Simulating release does not exist on GitCode"
else
# First check if tag exists using GitCode tags API
echo "Checking if tag exists..."
tags_response=$(curl -s -w "%{http_code}" \
-H "Accept: application/json" \
-H "Authorization: token ${{ secrets.GITCODE_ACCESS_TOKEN }}" \
"$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/tags")
tags_http_code="${tags_response: -3}"
tags_response_body="${tags_response%???}"
echo "Tags API HTTP Code: $tags_http_code"
tag_exists=false
if [ "$tags_http_code" = "200" ] || [ "$tags_http_code" = "201" ]; then
echo "Available tags (first 20):"
echo "$tags_response_body" | jq -r '.[] | .name' 2>/dev/null | head -20 || echo "Failed to parse tags"
# Check if our target tag exists
if echo "$tags_response_body" | jq -e --arg tag "${{ steps.release.outputs.tag_name }}" '.[] | select(.name == $tag)' > /dev/null 2>&1; then
tag_exists=true
echo "✅ Tag ${{ steps.release.outputs.tag_name }} exists on GitCode"
else
echo "❌ Tag ${{ steps.release.outputs.tag_name }} does not exist on GitCode"
fi
else
echo "❌ Failed to fetch tags from GitCode (HTTP $tags_http_code): $tags_response_body"
fi
# Then check if release exists (only if tag exists)
if [ "$tag_exists" = "true" ]; then
echo "Checking if release exists..."
response=$(curl -s -w "%{http_code}" \
-H "Accept: application/json" \
"$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/releases/tags/${{ steps.release.outputs.tag_name }}?access_token=${{ secrets.GITCODE_ACCESS_TOKEN }}")
else
echo "⚠️ Skipping release check since tag does not exist"
response="404Not Found"
fi
http_code="${response: -3}"
response_body="${response%???}"
echo "HTTP Code: $http_code"
echo "Response: $response_body"
if [ "$http_code" = "200" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Release already exists on GitCode"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Release does not exist on GitCode"
fi
fi
🤖 Prompt for AI Agents
.github/workflows/sync-release-to-gitcode.yml around lines 198-258: the curl
calls include the access token in the query string which can leak secrets;
change the requests to send the token in an Authorization header (e.g.,
Authorization: token ${{ secrets.GITCODE_ACCESS_TOKEN }}) instead of appending
?access_token=... to the URL, update both the tags API and releases/tags
requests (and any subsequent create/update calls) to remove the access_token
query param, add the header to each curl -H call, and verify the URLs and
response parsing remain unchanged.

Comment on lines +259 to +307
- name: Create release on GitCode
if: steps.check-release.outputs.exists == 'false'
id: create-release
run: |
payload=$(jq -n \
--arg tag_name "${{ steps.release.outputs.tag_name }}" \
--arg name "${{ steps.release.outputs.release_name }}" \
--arg body "${{ steps.release.outputs.release_body }}" \
--argjson prerelease "${{ steps.release.outputs.prerelease }}" \
--argjson draft "${{ steps.release.outputs.draft }}" \
'{
tag_name: $tag_name,
name: $name,
body: $body,
prerelease: $prerelease,
draft: $draft
}')

echo "Creating release with payload:"
echo "$payload" | jq .

if [ "${{ steps.release.outputs.test_mode }}" = "true" ]; then
echo "🧪 Test mode enabled - skipping release creation on GitCode"
echo "✅ Test mode: Would create release successfully on GitCode"
echo "created=true" >> $GITHUB_OUTPUT
else
response=$(curl -s -w "%{http_code}" \
-X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "$payload" \
"$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/releases?access_token=${{ secrets.GITCODE_ACCESS_TOKEN }}")

http_code="${response: -3}"
response_body="${response%???}"

echo "Create Release Response Code: $http_code"
echo "Create Release Response: $response_body"

if [ "$http_code" = "201" ] || [ "$http_code" = "200" ]; then
echo "✅ Release created successfully on GitCode (HTTP $http_code)"
echo "created=true" >> $GITHUB_OUTPUT
else
echo "❌ Failed to create release on GitCode (HTTP $http_code)"
echo "Response: $response_body"
echo "created=false" >> $GITHUB_OUTPUT
exit 1
fi
fi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use Authorization header for release create/update too

Keep auth out of URL for all POST/PATCH calls.

- "$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/releases?access_token=${{ secrets.GITCODE_ACCESS_TOKEN }}")
+ -H "Authorization: token ${{ secrets.GITCODE_ACCESS_TOKEN }}" \
+ "$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/releases")
- "$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/releases/${{ steps.release.outputs.tag_name }}?access_token=${{ secrets.GITCODE_ACCESS_TOKEN }}")
+ -H "Authorization: token ${{ secrets.GITCODE_ACCESS_TOKEN }}" \
+ "$GITCODE_API_BASE/repos/$GITCODE_OWNER/$GITCODE_REPO/releases/${{ steps.release.outputs.tag_name }}")

Also applies to: 309-355

🤖 Prompt for AI Agents
.github/workflows/sync-release-to-gitcode.yml lines 259-307 (and similarly
309-355): the release create/update curl calls currently include the access
token in the request URL; move the token to an Authorization header instead to
avoid leaking credentials in URLs. Modify the curl commands to remove the
?access_token=... query parameter and add a header -H "Authorization: Bearer
${GITCODE_ACCESS_TOKEN}" (or use the secrets variable) while keeping
Content-Type/Accept headers and the POST/PATCH method and body handling the
same; ensure the workflow uses the GitHub secret (secrets.GITCODE_ACCESS_TOKEN)
for the header and that response handling (http_code/response_body) remains
unchanged.

Comment on lines +24 to +25
this.baseUrl = 'https://api.gitcode.com/api/v5'
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

API base mismatch with workflow env; allow override via env/option

Workflow uses GITCODE_API_BASE=https://gitcode.com/api/v5, but script hardcodes https://api.gitcode.com/api/v5. This will break in one of the paths. Read from options/env and default to the workflow value.

-    this.baseUrl = 'https://api.gitcode.com/api/v5'
+    this.baseUrl = options.baseUrl || process.env.GITCODE_API_BASE || 'https://gitcode.com/api/v5'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.baseUrl = 'https://api.gitcode.com/api/v5'
}
this.baseUrl = options.baseUrl || process.env.GITCODE_API_BASE || 'https://gitcode.com/api/v5'
}
🤖 Prompt for AI Agents
In scripts/upload-assets.js around lines 24 to 25, the API base URL is hardcoded
to https://api.gitcode.com/api/v5 which conflicts with the workflow's
GITCODE_API_BASE (https://gitcode.com/api/v5); change this to allow overriding
by reading an option or environment variable and falling back to the current
hardcoded value — e.g. check options.apiBase then process.env.GITCODE_API_BASE
and assign the first defined value to this.baseUrl, defaulting to the existing
literal if neither is set.

Comment on lines +359 to +367
// 解析命令行参数
const options = {
accessToken: process.env.GITCODE_ACCESS_TOKEN,
owner: process.env.GITCODE_OWNER,
repo: process.env.GITCODE_REPO,
tag: process.env.GITCODE_TAG,
concurrency: 3,
retryAttempts: 3
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

CLI should accept/propagate API base to match workflow env

Allow --api-base or read env already set by workflow to avoid mismatch.

   const options = {
     accessToken: process.env.GITCODE_ACCESS_TOKEN,
     owner: process.env.GITCODE_OWNER,
     repo: process.env.GITCODE_REPO,
     tag: process.env.GITCODE_TAG,
+    baseUrl: process.env.GITCODE_API_BASE,
     concurrency: 3,
     retryAttempts: 3
   }
-    } else if (!arg.startsWith('--')) {
+    } else if (arg === '--api-base' && i + 1 < args.length) {
+      options.baseUrl = args[++i]
+    } else if (!arg.startsWith('--')) {
       filePaths.push(arg)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 解析命令行参数
const options = {
accessToken: process.env.GITCODE_ACCESS_TOKEN,
owner: process.env.GITCODE_OWNER,
repo: process.env.GITCODE_REPO,
tag: process.env.GITCODE_TAG,
concurrency: 3,
retryAttempts: 3
}
// 解析命令行参数
const options = {
accessToken: process.env.GITCODE_ACCESS_TOKEN,
owner: process.env.GITCODE_OWNER,
repo: process.env.GITCODE_REPO,
tag: process.env.GITCODE_TAG,
baseUrl: process.env.GITCODE_API_BASE,
concurrency: 3,
retryAttempts: 3
}
🤖 Prompt for AI Agents
In scripts/upload-assets.js around lines 359 to 367, the CLI/options block
doesn't accept or propagate the API base URL used by the workflow; add support
for an --api-base CLI flag and/or read a GITCODE_API_BASE (or similar)
environment variable and include it in the options object (e.g., apiBase:
process.env.GITCODE_API_BASE || parsedFlag). Then pass this options.apiBase
through to the code that constructs the GitCode API client so the script uses
the same API base as the workflow.

Comment on lines +28 to +30
// Mock global fetch for IP detection tests
global.fetch = vi.fn()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Use vi.stubGlobal for fetch and restore after tests

Directly assigning global.fetch can leak across suites. Prefer vi.stubGlobal('fetch', fn) and restore in afterAll.

-// Mock global fetch for IP detection tests
-global.fetch = vi.fn()
+// Mock global fetch for IP detection tests
+vi.stubGlobal('fetch', vi.fn())
+afterAll(() => {
+  // @ts-ignore
+  global.fetch = undefined
+})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Mock global fetch for IP detection tests
global.fetch = vi.fn()
// Mock global fetch for IP detection tests
vi.stubGlobal('fetch', vi.fn())
afterAll(() => {
// @ts-ignore
global.fetch = undefined
})
🤖 Prompt for AI Agents
In src/main/services/__tests__/FFmpegDownloadService.test.ts around lines 28 to
30, the test directly assigns global.fetch = vi.fn(), which can leak the mock
across test suites; replace that assignment with vi.stubGlobal('fetch', vi.fn())
and add an afterAll hook that calls vi.unstubAllGlobals() to restore the global
fetch after the tests complete.

Comment on lines +480 to +489
it('should fallback to global mirror when China mirror not supported', () => {
service.setMirrorSource(true)

// 假设有一个平台在中国镜像源中不支持(这里只是测试逻辑)
// 实际上所有平台都支持,所以这个测试更多是为了测试回退逻辑的代码结构
const version = service.getFFmpegVersion('darwin', 'arm64')
expect(version).toBeDefined()
expect(version?.platform).toBe('darwin')
expect(version?.arch).toBe('arm64')
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add a real fallback test covering China→global retry path

Currently this test doesn’t simulate a China-mirror failure. Spy performDownload to fail once then succeed on fallback via global version.

 it('should fallback to global mirror when China mirror not supported', async () => {
   service.setMirrorSource(true)
-  const version = service.getFFmpegVersion('darwin', 'arm64')
-  expect(version).toBeDefined()
-  expect(version?.platform).toBe('darwin')
-  expect(version?.arch).toBe('arm64')
+  const spy = vi.spyOn(service as any, 'performDownload')
+    .mockResolvedValueOnce(false) // China mirror fails
+    .mockResolvedValueOnce(true)  // Global mirror succeeds
+  const ok = await (service as any).downloadFFmpegWithFallback('darwin','arm64')
+  expect(ok).toBe(true)
+  expect(spy).toHaveBeenCalledTimes(2)
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should fallback to global mirror when China mirror not supported', () => {
service.setMirrorSource(true)
// 假设有一个平台在中国镜像源中不支持(这里只是测试逻辑)
// 实际上所有平台都支持,所以这个测试更多是为了测试回退逻辑的代码结构
const version = service.getFFmpegVersion('darwin', 'arm64')
expect(version).toBeDefined()
expect(version?.platform).toBe('darwin')
expect(version?.arch).toBe('arm64')
})
it('should fallback to global mirror when China mirror not supported', async () => {
service.setMirrorSource(true)
// 假设有一个平台在中国镜像源中不支持(这里只是测试逻辑)
// 实际上所有平台都支持,所以这个测试更多是为了测试回退逻辑的代码结构
const spy = vi.spyOn(service as any, 'performDownload')
.mockResolvedValueOnce(false) // China mirror fails
.mockResolvedValueOnce(true) // Global mirror succeeds
const ok = await (service as any).downloadFFmpegWithFallback('darwin','arm64')
expect(ok).toBe(true)
expect(spy).toHaveBeenCalledTimes(2)
})

Comment on lines +195 to +198
logger.warn('无法检测用户地区,使用默认镜像源', { error })
this.useChinaMirror = true // 检测失败时默认使用中国镜像源
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Defaulting to China mirror on detection failure: add opt-out

Hard-defaulting to CN impacts non-CN users offline. Provide an env/config override (e.g., ECHOPLAYER_FFMPEG_DEFAULT_MIRROR=global).

I can add a small config check in constructor if desired.

🤖 Prompt for AI Agents
In src/main/services/FFmpegDownloadService.ts around lines 195-198, the code
currently hard-defaults to the China mirror on region-detection failure; add an
environment/config override so callers can opt out. Before setting
this.useChinaMirror = true in the catch path, read a config/env var (e.g.
process.env.ECHOPLAYER_FFMPEG_DEFAULT_MIRROR) in the constructor and use it to
decide the default: accept values like "global" or "china" (or "auto" to
preserve current behavior), validate the value and then, on detection failure,
set this.useChinaMirror based on that config instead of unconditionally true;
keep the existing logger call but include which default was applied. Ensure
invalid values fall back to the previous behavior (or a safe default) and
document the new env var in code comments.

Comment on lines +203 to +224
private async getIpCountry(): Promise<string> {
try {
// 使用 AbortController 设置 5 秒超时
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000)

const response = await fetch('https://ipinfo.io/json', {
signal: controller.signal,
headers: {
'User-Agent': 'EchoPlayer-FFmpeg-Downloader/2.0',
'Accept-Language': 'en-US,en;q=0.9'
}
})

clearTimeout(timeoutId)
const data = await response.json()
return data.country || 'CN' // 默认返回 CN,这样中国用户即使检测失败也能使用中国镜像源
} catch (error) {
logger.warn('获取IP地理位置失败,默认使用中国镜像源', { error })
return 'CN' // 默认返回 CN
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

IP geo lookup privacy note; allow disabling

Calling ipinfo.io leaks user IP to a third party. Provide a toggle to skip detection (env or setting) and default to global if disabled.

🤖 Prompt for AI Agents
In src/main/services/FFmpegDownloadService.ts around lines 203 to 224, the IP
geolocation lookup calls ipinfo.io which leaks user IP to a third party; add a
configurable opt-out so fetching is skipped when disabled (prefer an environment
variable like FFMEPG_GEO_LOOKUP_ENABLED or a passed-in service setting) and
default to "GLOBAL" when disabled. Modify getIpCountry to first check the
config/ENV: if lookup is disabled, immediately return 'GLOBAL' (and log a
debug/info line noting lookup is skipped); otherwise proceed with the existing
abort-controller fetch logic. Ensure the new config is type-safe, documented
where the service is instantiated, and that unit tests or callers are updated to
set the env/setting in test scenarios.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant