feat: v1.3.0 - About menu, Sparkle auto-update, GitHub Actions CI/CD#20
Conversation
…ion improvements About Menu: - Create AboutView with app info, version, and license details - Create AcknowledgementsView for open source library credits - Create AboutWindowController for managing the about window - Add About menu item to MenuBarController - Add localization for about window (en/zh-Hans) GitHub Actions: - Add release.yml workflow for automated DMG builds on tag push - Configure Sparkle appcast generation for auto-updates Documentation: - Translate README to English (main README.md) - Keep Chinese version as README_CN.md - Update docs/README.md with current features and structure - Add link between English and Chinese versions Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Import Sparkle module in AppDelegate - Add SPUStandardUpdaterController for managing updates - Connect About window "Check for Updates" button to Sparkle - Listen for checkForUpdates notification Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (10)
📝 WalkthroughWalkthrough此 PR 为 ScreenTranslate macOS 应用添加了自动发布工作流、Sparkle 更新框架集成、"关于"窗口 UI、应用内更新检查功能,并将 README 从中文翻译为英文,同时创建了中文版本的 README。 Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (1)
ScreenTranslate/App/AppDelegate.swift (1)
25-25: 建议将updaterController改为惰性非可选,降低时序崩溃风险当前使用 IUO,若未来有更早触发
checkForUpdates的路径,会直接崩溃。这里可以改成惰性初始化并移除启动阶段的手动赋值。♻️ 建议修改
- private var updaterController: SPUStandardUpdaterController! + private lazy var updaterController = SPUStandardUpdaterController( + startingUpdater: true, + updaterDelegate: nil, + userDriverDelegate: nil + ) ... - // Initialize Sparkle updater - updaterController = SPUStandardUpdaterController( - startingUpdater: true, - updaterDelegate: nil, - userDriverDelegate: nil - )Also applies to: 56-60, 197-200
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ScreenTranslate/App/AppDelegate.swift` at line 25, 将隐式展开可选的 updaterController 改为惰性非可选并移除启动阶段的手动赋值:把 "private var updaterController: SPUStandardUpdaterController!" 改为一个 lazy 非可选属性(例如 lazy private var updaterController: SPUStandardUpdaterController = { ... }())并在该闭包内完成原本在启动流程中创建/配置的逻辑;从 applicationDidFinishLaunching(或当前手动赋值的位置)删除对 updaterController 的显式赋值,确保所有对 updaterController 的调用(例如 checkForUpdates)不再使用强制解包或 IUO,以避免启动时的时序崩溃风险。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/release.yml:
- Around line 11-14: 工作流缺少 permissions 声明,导致使用 softprops/action-gh-release@v2
的发布步骤可能因无写权限而失败;在顶层添加 permissions: with contents: write(即在 workflow 根对象下新增
permissions: { contents: write })以确保 release/upload 相关步骤有写入权限,参考 jobs/build 和使用的
action 名称 softprops/action-gh-release@v2 来定位需要权限保障的发布步骤并提交修改。
- Around line 47-74: The appcast currently writes enclosure url="${DMG_URL}"
with sparkle:edSignature="" and length="0", which will be rejected by Sparkle;
update the CI step that produces appcast.xml to sign the built DMG and set the
real byte size before writing the appcast: after building the DMG
(VERSION/DMS_URL), run your signing tool (e.g., sign_update or ed25519-cli) to
produce a Base64 Ed25519 signature and compute the file size (e.g., stat -f%z or
wc -c), then substitute those values into the enclosure attributes
(sparkle:edSignature and length) when creating appcast.xml (or switch to using
generate_appcast which does this automatically); also ensure the app bundle’s
Info.plist contains the matching SUPublicEDKey public key so Sparkle can verify
updates.
- Line 66: Replace the static use of github.event.head_commit.timestamp for the
<pubDate> element by generating an RFC-2822 timestamp at runtime (e.g., using
date -Ru) in a preceding step and injecting that generated value into the
<pubDate> field; add a step (e.g., "Generate pubDate" or similar) that runs date
-Ru and exposes the result as an output (e.g., pubDate) and then reference that
output where <pubDate>${{ github.event.head_commit.timestamp }}</pubDate>
currently appears so Sparkle can reliably parse the appcast.
In `@README_CN.md`:
- Around line 122-146: The code block in README_CN.md lacks a language specifier
and triggers MD040; update the fenced block that contains the project tree (the
block showing "ScreenTranslate/ ... Resources/") to use a plain text language
tag by changing the opening fence from ``` to ```text so the directory tree
renders consistently and lints cleanly; locate the block in README_CN.md that
contains the tree (the multi-line block starting with "ScreenTranslate/") and
replace its opening fence accordingly.
- Line 12: README_CN.md still references version "1.1.0" in the badges and
changelog; update all occurrences of "1.1.0" and "v1.1.0" to "1.3.0" / "v1.3.0"
respectively so the badge (the markdown image/link containing version text) and
the changelog heading (the "v..." entry in the release notes) match the PR
target; search for the literal tokens "1.1.0" and "v1.1.0" and replace them with
"1.3.0" and "v1.3.0" in README_CN.md (ensure both the badge alt/text and the
changelog headings are updated).
In `@README.md`:
- Line 12: Update the README.md version strings from 1.2.0 to 1.3.0: replace the
version badge text and URL (the markdown anchor/img snippet showing
"version-1.2.0" and alt text "Version") and update the changelog heading/entries
currently labeled "v1.2.0" (the changelog section starting at the "v1.2.0"
heading) so they reflect "v1.3.0"; ensure both the badge link and any changelog
headings/labels are consistently changed to 1.3.0.
- Around line 182-183: Update the terminology "translate and insert feature" to
the hyphenated form "translate-and-insert" throughout the README to match
existing docs; locate the text string "translate and insert feature" (and any
other occurrences of "translate and insert") and replace them with
"translate-and-insert" so the term is consistent.
- Around line 126-150: The fenced project-structure code block that starts with
"ScreenTranslate/" should declare a language to satisfy MD040; update the
opening fence from ``` to ```text for that block (the block containing the tree
listing like "ScreenTranslate/ ├── App/ ...") so markdownlint passes and
readability improves.
In `@ScreenTranslate/Features/About/AboutView.swift`:
- Around line 36-40: AboutView contains hardcoded user-facing strings (e.g.,
Text("ScreenTranslate"), Text("Version \(appVersion) (\(buildNumber))") and
other literals at the noted locations) which must be localized; replace each
literal in the AboutView SwiftUI view with localized keys using
LocalizedStringKey or NSLocalizedString (e.g., use
Text(LocalizedStringKey("about.title")) for the title and use String(format:
NSLocalizedString("about.version_format", comment: ""), appVersion, buildNumber)
for the version line), add corresponding keys to Localizable.strings (including
zh-Hans) and update any other hardcoded texts in AboutView to use those keys so
all user-facing strings are localized.
In `@ScreenTranslate/Features/About/AboutWindowController.swift`:
- Around line 44-47: In windowWillClose(_:) avoid clearing self.window
unconditionally from the queued async Task; instead, inside the Task (on
`@MainActor`) first obtain the window from notification.object (e.g. guard let
closed = notification.object as? NSWindow) and compare by identity to the
controller's current window (closed == self.window), and only set self.window =
nil if they match; this prevents an earlier close event's task from nulling a
newly opened window.
In `@ScreenTranslate/Features/About/AcknowledgementsView.swift`:
- Around line 73-80: Replace the forced unwrap of URL(string: item.url)! in the
Link initializer with a safe conditional: attempt to construct URL from item.url
and, if successful, use Link(destination: url) { Text(item.url) ... }. If URL
construction fails, render the same Text(item.url) (with .font(.caption),
.foregroundStyle(.primary or .secondary), .lineLimit(1),
.truncationMode(.middle)) and keep .buttonStyle(.plain) behavior so invalid URLs
degrade to plain text instead of crashing; update references in
AcknowledgementsView where Link and Text(item.url) are used.
In `@ScreenTranslate/Supporting` Files/Info.plist:
- Around line 35-36: The SUFeedURL value in Info.plist is using the wrong GitHub
path (/releases/download/latest/appcast.xml) so Sparkle cannot find the appcast;
update the SUFeedURL entry to use the correct GitHub releases pattern by
replacing the path segment to /releases/latest/download/appcast.xml so the
appcast.xml is served correctly (locate the SUFeedURL key in Info.plist and
change its string value accordingly).
---
Nitpick comments:
In `@ScreenTranslate/App/AppDelegate.swift`:
- Line 25: 将隐式展开可选的 updaterController 改为惰性非可选并移除启动阶段的手动赋值:把 "private var
updaterController: SPUStandardUpdaterController!" 改为一个 lazy 非可选属性(例如 lazy
private var updaterController: SPUStandardUpdaterController = { ...
}())并在该闭包内完成原本在启动流程中创建/配置的逻辑;从 applicationDidFinishLaunching(或当前手动赋值的位置)删除对
updaterController 的显式赋值,确保所有对 updaterController 的调用(例如 checkForUpdates)不再使用强制解包或
IUO,以避免启动时的时序崩溃风险。
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
.github/workflows/release.ymlREADME.mdREADME_CN.mdScreenTranslate.xcodeproj/project.pbxprojScreenTranslate/App/AppDelegate.swiftScreenTranslate/Features/About/AboutView.swiftScreenTranslate/Features/About/AboutWindowController.swiftScreenTranslate/Features/About/AcknowledgementsView.swiftScreenTranslate/Features/MenuBar/MenuBarController.swiftScreenTranslate/Resources/en.lproj/Localizable.stringsScreenTranslate/Resources/zh-Hans.lproj/Localizable.stringsScreenTranslate/Supporting Files/Info.plistdocs/README.md
| - name: Generate Sparkle Appcast | ||
| run: | | ||
| # Install generate_appcast if needed | ||
| # brew install --cask sparkle | ||
|
|
||
| # For now, create a basic appcast.xml | ||
| VERSION=${GITHUB_REF#refs/tags/v} | ||
| DMG_URL="https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/${{ env.PROJECT_NAME }}-${VERSION}.dmg" | ||
|
|
||
| cat > appcast.xml << EOF | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/"> | ||
| <channel> | ||
| <title>${{ env.PROJECT_NAME }} Updates</title> | ||
| <link>https://github.com/${{ github.repository }}/releases</link> | ||
| <description>Most recent changes with links to updates.</description> | ||
| <language>en</language> | ||
| <item> | ||
| <title>Version ${VERSION}</title> | ||
| <pubDate>${{ github.event.head_commit.timestamp }}</pubDate> | ||
| <sparkle:version>${VERSION}</sparkle:version> | ||
| <sparkle:shortVersionString>${VERSION}</sparkle:shortVersionString> | ||
| <sparkle:minimumSystemVersion>13.0</sparkle:minimumSystemVersion> | ||
| <enclosure url="${DMG_URL}" sparkle:edSignature="" length="0" type="application/octet-stream" /> | ||
| </item> | ||
| </channel> | ||
| </rss> | ||
| EOF |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# 找到并查看 release.yml 文件
find . -name "release.yml" -type fRepository: hubo1989/ScreenTranslate
Length of output: 99
🏁 Script executed:
# 获取 .github/workflows 目录的文件列表
git ls-files .github/workflows/Repository: hubo1989/ScreenTranslate
Length of output: 97
🏁 Script executed:
# 读取 release.yml 文件内容,查看 47-74 行
cat -n .github/workflows/release.yml | sed -n '40,80p'Repository: hubo1989/ScreenTranslate
Length of output: 2165
🌐 Web query:
Sparkle macOS edSignature signature verification requirements appcast
💡 Result:
For Sparkle (macOS) EdDSA / sparkle:edSignature verification, the practical requirements are:
-
Each update archive referenced by an
<enclosure>must include:sparkle:edSignature="…"(Base64 Ed25519 / EdDSA signature of the exact archive bytes you publish), andlength="…"(byte size of that archive). Sparkle’ssign_updatetool outputs exactly these attributes, andgenerate_appcastcan generate them automatically. [1]
-
Your app must ship the matching public key in
Info.plistasSUPublicEDKey(Sparkle uses this to verify thesparkle:edSignature). [1][2] -
The new app inside the update must also contain
SUPublicEDKey(Sparkle supports key rotation but not key removal; missing the key in the new bundle causes validation failure). [2] -
If you enable
SURequireSignedFeed:- you must also sign the appcast and any external release notes (and include the corresponding
sparkle:edSignature/sparkle:lengthwhere applicable). [1]
- you must also sign the appcast and any external release notes (and include the corresponding
-
DSA signatures are legacy/deprecated; EdDSA (Ed25519) signatures are the current recommended mechanism. [3]
Sources: Sparkle “Publishing an update” docs (includes sign_update output examples and signed-feed notes) [1]; Sparkle GitHub discussion clarifying SUPublicEDKey presence in the updated bundle and generate_appcast behavior [2]; Ed25519/EdDSA compatibility note (WinSparkle) [3].
🌐 Web query:
Sparkle framework sparkle:edSignature length attribute requirements
💡 Result:
For Sparkle appcasts, when you add sparkle:edSignature="..." to an <enclosure>, you should also include the standard RSS length="..." attribute set to the exact size of the update archive in bytes (an integer). Sparkle’s sign_update tool outputs both values together specifically for you to copy into the <enclosure> element. [1]
Example output / usage (from Sparkle docs): sparkle:edSignature="…==" length="1623481" → add both attributes to <enclosure … />. [1]
If you’re signing release notes files (when using SURequireSignedFeed), sign_update outputs sparkle:edSignature="..." plus sparkle:length="..." (namespaced) for the sparkle:releaseNotesLink element. [1]
Sources: [1]
🏁 Script executed:
# 查看整个 release.yml 文件,检查是否有其他与签名相关的步骤
wc -l .github/workflows/release.yml && cat -n .github/workflows/release.yml | tail -20Repository: hubo1989/ScreenTranslate
Length of output: 922
Sparkle appcast 缺少有效签名与真实文件大小,自动更新会失败。
第 70 行当前 sparkle:edSignature="" 且 length="0" 会导致 Sparkle 框架无法验证更新。根据 Sparkle 规范,每个更新存档必须包含有效的 Ed25519 EdDSA 签名(Base64 编码)和准确的字节大小,否则客户端会拒绝应用更新。
应在 CI 流程中对构建的 DMG 进行签名,并获取实际文件大小:
- 使用
sign_update工具生成签名和大小,或 - 使用
generate_appcast自动生成有效的 appcast.xml,或 - 手动计算 DMG 大小(
stat -f%z)并使用 ed25519-cli 等工具签名
此外,应确保应用的 Info.plist 包含对应的 SUPublicEDKey 公钥,以便 Sparkle 进行验证。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/release.yml around lines 47 - 74, The appcast currently
writes enclosure url="${DMG_URL}" with sparkle:edSignature="" and length="0",
which will be rejected by Sparkle; update the CI step that produces appcast.xml
to sign the built DMG and set the real byte size before writing the appcast:
after building the DMG (VERSION/DMS_URL), run your signing tool (e.g.,
sign_update or ed25519-cli) to produce a Base64 Ed25519 signature and compute
the file size (e.g., stat -f%z or wc -c), then substitute those values into the
enclosure attributes (sparkle:edSignature and length) when creating appcast.xml
(or switch to using generate_appcast which does this automatically); also ensure
the app bundle’s Info.plist contains the matching SUPublicEDKey public key so
Sparkle can verify updates.
| - ✨ Added translate and insert feature | ||
| - ✨ Menu bar shortcuts synced with settings |
There was a problem hiding this comment.
术语建议统一为 “translate-and-insert”。
Line [182] 当前写法为 “translate and insert feature”,建议加连字符以与文档中既有术语保持一致。
🧰 Tools
🪛 LanguageTool
[grammar] ~182-~182: Use a hyphen to join words.
Context: ... translation feature - ✨ Added translate and insert feature - ✨ Menu bar shortcut...
(QB_NEW_EN_HYPHEN)
[grammar] ~182-~182: Use a hyphen to join words.
Context: ...nslation feature - ✨ Added translate and insert feature - ✨ Menu bar shortcuts sy...
(QB_NEW_EN_HYPHEN)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@README.md` around lines 182 - 183, Update the terminology "translate and
insert feature" to the hyphenated form "translate-and-insert" throughout the
README to match existing docs; locate the text string "translate and insert
feature" (and any other occurrences of "translate and insert") and replace them
with "translate-and-insert" so the term is consistent.
GitHub Actions: - Add permissions: contents: write for release workflow - Generate proper RFC-2822 pubDate using date -Ru - Calculate real DMG file size for appcast enclosure README: - Update version badge from 1.2.0 to 1.3.0 - Add v1.3.0 changelog entry - Add language specifier to code blocks (text) AboutView: - Localize all hardcoded user-facing strings - Add new localization keys for app name, version format, etc. AboutWindowController: - Fix race condition in windowWillClose by checking window identity - Use weak self capture to prevent retain cycles AcknowledgementsView: - Handle invalid URLs gracefully without forced unwrap Info.plist: - Fix SUFeedURL path to use correct /releases/latest/download/ format AppDelegate: - Make updaterController lazy to avoid initialization timing issues Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Summary
This PR implements all features planned for v1.3.0:
About Menu
AboutViewwith app icon, version, copyright, license, and GitHub linkAcknowledgementsViewfor open source library creditsAboutWindowControllerfor managing the about window lifecycleSparkle Auto-Update Framework
SPUStandardUpdaterControllerin AppDelegateSUFeedURLto Info.plist for update feedGitHub Actions CI/CD
release.ymlworkflow triggered by version tags (v*)Documentation
Test Plan
Files Changed
ScreenTranslate/Features/About/AboutView.swift(new)ScreenTranslate/Features/About/AboutWindowController.swift(new)ScreenTranslate/Features/About/AcknowledgementsView.swift(new)ScreenTranslate/Features/MenuBar/MenuBarController.swift(modified)ScreenTranslate/App/AppDelegate.swift(modified)ScreenTranslate/Resources/en.lproj/Localizable.strings(modified)ScreenTranslate/Resources/zh-Hans.lproj/Localizable.strings(modified).github/workflows/release.yml(new)README.md(translated to English)README_CN.md(new - Chinese version)docs/README.md(updated)Summary by CodeRabbit
发布说明
新功能
文档
Chores