mcpField
M
macos-auto-update

macos-auto-update

github.com/fayazara开发685574 次安装22 周前更新
代码仓库

关于 macos-auto-update

这项技能用于为原生 macOS 应用添加 Sparkle 自动更新,介绍依赖配置、更新管理器、应用配置及更新界面集成。

主要功能

  1. 01通过 SPM 添加 Sparkle
  2. 02指导实现 UpdaterManager
  3. 03配置 Info.plist 更新项
  4. 04设置与菜单中的更新控件

适用场景

  1. 01为 macOS 应用添加更新检查
  2. 02配置 Sparkle 更新源和签名密钥

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/fayazara/macos-app-skills --skill macos-auto-update

安装后在 Claude Code 中输入/macos-auto-update确认已加载。

附带文件references/UpdaterManager.swift

详细介绍

macOS 应用的 Sparkle 自动更新

此技能可为原生 macOS 应用添加 Sparkle 自动更新支持。Sparkle 是 Mac App Store 之外用于 macOS 应用更新的标准开源框架。

概述

实现分为 4 个部分:

  1. SPM 依赖项 -- 将 Sparkle 软件包添加到 Xcode 项目
  2. UpdaterManager.swift -- 封装 SPUStandardUpdaterController 的单例
  3. Info.plist 键 -- SUFeedURL 和 SUPublicEDKey
  4. UI 集成 -- 设置中或菜单栏中的“检查更新”按钮

步骤 1:通过 SPM 添加 Sparkle

在 Xcode 中,依次选择 File > Add Package Dependencies > 输入:

https://github.com/sparkle-project/Sparkle

使用“Up to Next Major Version”规则,并指定 2.0.0。将 Sparkle 框架添加到应用目标。

如果项目使用 Package.swift,也可以将其添加到该文件中:

.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.0.0")

步骤 2:创建 UpdaterManager.swift

将 references/UpdaterManager.swift 复制到项目中。这是一个单例,它会:

  • 提前创建 SPUStandardUpdaterController(在 applicationDidFinishLaunching 返回之前)
  • 发布 canCheckForUpdates,以便 UI 绑定
  • 提供 automaticallyChecksForUpdates 开关
  • 在 DEBUG 构建中跳过所有更新逻辑(这样开发期间就不会收到更新提示)
  • 对于仅使用菜单栏的应用:在显示更新窗口前,临时将激活策略切换为 .regular

该文件中的关键设计决策:

  • 初始化器中设置 startingUpdater: false,然后在 applicationDidFinishLaunching 中显式调用 start()。这样可以控制调用时机。
  • 在 start() 和 checkForUpdates() 中使用 DEBUG 守卫。Sparkle 绝不应在调试构建中运行,否则它会尝试用发行版构建更新调试版应用。
  • 使用带有 @Published 的 ObservableObject(而不是 @Observable),因为我们需要通过 Combine 的 publisher(for:) 桥接 Sparkle 的 KVO。

步骤 3:配置 Info.plist

将以下键添加到应用的 Info.plist 中:

<key>SUFeedURL</key>
<string>https://raw.githubusercontent.com/OWNER/REPO/main/appcast.xml</string>

<key>SUPublicEDKey</key>
<string>YOUR_PUBLIC_EDDSA_KEY_HERE</string>

<key>SUEnableInstallerLauncherService</key>
<true/>
生成 EdDSA 密钥

Sparkle 使用 EdDSA(Ed25519)签名。生成密钥对:

# Find generate_keys in your DerivedData after building the project with Sparkle
find ~/Library/Developer/Xcode/DerivedData -name "generate_keys" -type f 2>/dev/null | head -1

运行:

/path/to/generate_keys

此命令会输出公钥,并将私钥存储在钥匙串中。将公钥填入 Info.plist 中的 SUPublicEDKey。私钥保留在钥匙串中,供发行时的 sign_update 使用。

步骤 4:接入应用

App Delegate
final class AppDelegate: NSObject, NSApplicationDelegate {
    private let updaterManager = UpdaterManager.shared

    func applicationDidFinishLaunching(_ notification: Notification) {
        updaterManager.start()
    }
}

必须尽早访问 UpdaterManager.shared 属性,以便在应用完成启动前创建 SPUStandardUpdaterController。在 AppDelegate 属性中引用它即可确保这一点。

设置 UI(关于面板)
struct AboutSettingsPane: View {
    @ObservedObject private var updaterManager = UpdaterManager.shared

    var body: some View {
        Form {
            Section("Updates") {
                Toggle(isOn: Binding(
                    get: { updaterManager.automaticallyChecksForUpdates },
                    set: { updaterManager.automaticallyChecksForUpdates = $0 }
                )) {
                    Text("Automatically check for updates")
                }

                Button("Check for Updates...") {
                    updaterManager.checkForUpdates()
                }
                .disabled(!updaterManager.canCheckForUpdates)
            }
        }
        .formStyle(.grouped)
        .scrollContentBackground(.hidden)
    }
}
菜单栏(可选)
Button {
    updaterManager.checkForUpdates()
} label: {
    Label("Check for Updates...", systemImage: "arrow.down.circle")
}
.disabled(!updaterManager.canCheckForUpdates)

步骤 5:创建初始 Appcast

在代码仓库根目录创建 appcast.xml。该文件初始为空,并由发行流程填充:

<?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>YourApp</title>
    <description>Most recent changes for YourApp.</description>
    <language>en</language>
  </channel>
</rss>

将此文件托管在 GitHub(原始文件 URL)或任意静态文件主机上。该 URL 必须与 Info.plist 中的 SUFeedURL 一致。

仅使用菜单栏的应用

如果应用以 .accessory 运行(没有 Dock 图标),除非临时切换到 .regular,否则 Sparkle 的更新窗口不会显示。参考实现 UpdaterManager 会在 checkForUpdates() 中处理这一点:

func checkForUpdates() {
    NSApp.setActivationPolicy(.regular)
    NSApp.activate(ignoringOtherApps: true)
    controller.checkForUpdates(nil)
}

更新窗口关闭时,应用会恢复为 .accessory(由现有的激活策略管理器处理)。

Hardened Runtime 权限

如果应用使用 Hardened Runtime(公证所必需),则不需要 Sparkle 专用权限。Sparkle 2.x 可在标准的 Hardened Runtime 配置下运行。

Appcast 条目格式

appcast 中的每个发行版本格式如下(构建发行工具时可供参考):

<item>
  <title>Version 1.2 (Build 5)</title>
  <pubDate>Mon, 26 May 2026 12:00:00 +0000</pubDate>
  <sparkle:version>5</sparkle:version>
  <sparkle:shortVersionString>1.2</sparkle:shortVersionString>
  <sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
  <description><![CDATA[<ul><li>New feature</li><li>Bug fix</li></ul>]]></description>
  <enclosure url="https://github.com/OWNER/REPO/releases/download/v1.2/YourApp.dmg"
             type="application/octet-stream"
             sparkle:edSignature="BASE64_EDDSA_SIGNATURE"
             length="FILE_SIZE_BYTES" />
</item>
  • sparkle:version = CFBundleVersion(构建号)
  • sparkle:shortVersionString = CFBundleShortVersionString(营销版本号)
  • sparkle:edSignature = sign_update YourApp.dmg 的输出
  • length = 文件大小(字节)

评论

支持 Markdown