Cline 桌面应用双渠道发布实战:Tag 契约、CI 签名公证流程与 Tauri 自动更新源
Cline 桌面应用双渠道发布实战Tag 契约、CI 签名公证流程与 Tauri 自动更新源【免费下载链接】clineAutonomous coding agent as an SDK, IDE extension, or CLI assistant.项目地址: https://gitcode.com/GitHub_Trending/cl/cline本文以 Cline 仓库中publish-desktop发布技能文档SKILL.md为主体完整梳理 Cline 桌面应用apps/examples/desktop-app基于 Tauri从打 tag 到发布的双渠道stable / beta发布契约、逐步操作流程并结合 desktop-publish.yml 工作流与配套脚本深入解析 macOS 通用二进制签名公证、Windows Azure Trusted Signing 签名以及 Tauri 自动更新源desktop-latest/desktop-beta的生成与防护机制。读完本文你可以独立完成一次桌面版本发布并理解其中每一道安全门禁环境密钥隔离、渠道 fail-closed 校验、编译期内嵌更新地址断言的设计原因。发布契约两个渠道一个工作流桌面应用发布完全在 GitHub Actions 中完成没有本地发布路径。发布产物包含两个平台macOS单个签名 公证的 universal DMG原生同时支持 Apple Silicon 与 IntelWindows经 Authenticode 签名的 NSIS 安装器Product_version_x64-setup.exe在build-windows作业中通过 Azure Trusted Signing 签名jsign 经 Tauri 的signCommand调用见 tauri-sign-windows.ps1需要仓库级AZURE_*密钥其中包括AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_DESKTOP以及cline-cli-signingEntra 应用上针对PublishDesktop环境的联邦凭据。已安装的 App 通过 Tauri updater 自动发现新版本因此发布即更新——发布动作会把新版本推送给该渠道下的所有存量用户。渠道、标签与更新源映射两个渠道共用同一个工作流desktop-publish.yml的channel输入渠道标签格式来源分支滚动更新源feed产品名 / 包标识stabledesktop-vX.Y.Z无后缀工作流拒绝 prerelease 后缀maindesktop-latestCline /bot.cline.appbetadesktop-vX.Y.Z-beta.Ndesktop-experimentaldesktop-betaCline Beta /bot.cline.app.beta与 stable 并排安装beta 构建额外叠加src-tauri/tauri.beta.conf.json配置层。beta 流程的完整背景见 EXPERIMENTAL.md。版本来源与 beta 版本号规则版本号的来源有两处二者必须互相对齐、且与 tag 一致apps/examples/desktop-app/package.jsonapps/examples/desktop-app/src-tauri/tauri.conf.jsonsrc-tauri/Cargo.toml有自己的版本号但会被tauri.conf.json覆盖无需改动。beta 版本是下一个stable 版本的 prereleasestable0.0.13→ beta0.0.14-beta.1、-beta.2……一旦发出了版本 ≥ beta 基座的 stable下一个 beta 就要抬升基座stable0.0.14发布后下一个 beta 是0.0.15-beta.1。beta 的X.Y.Z基座绝不能与已发布的 stable 相同。发布准备包含三部分已批准的发布说明、两处版本文件升版、以及 CHANGELOG.md 的更新——stable 提交到mainbeta 提交到desktop-experimental。关键安全不变量两个渠道都从 main 派发Both channels dispatch frommain。这不是便利性设计而是安全不变量运行执行的是main上的工作流副本只有 checkout 指向 tag。因此签名密钥的两道门禁——github.ref main检查与PublishDesktop环境仅允许 main 部署分支策略——对 beta 同样成立。永远不要把desktop-experimental加入 PublishDesktop 的 deployment-branch 策略否则在实验分支上编辑过的工作流文件就能接触到签名密钥。工作流会为 tag 创建 GitHub releaseuniversal DMG macOS updater 制品 带 updater 签名的 Windows NSIS 安装器 latest.jsonbeta 标记为 prerelease并刷新渠道的滚动 feed release——这是该渠道所有已安装 App 轮询的静态自动更新源。永远不要删除desktop-latest或desktop-beta的 release 或 tagfeed URL 被编译进了二进制updater 没有回退端点删除会使所有存量安装永久失联。CHANGELOG.md中## version小节精确匹配而非顶部小节会被逐字提取到 GitHub release 正文、Slack 公告和 updater manifest 的 notes 中。另外推送 commit 或 tag 之前始终先询问。逐步操作流程以下命令都从仓库根目录执行SKILL.md 明确要求 working directory 为 repo root。第 0 步确认渠道若用户未说明先询问本次发布是stable 还是 beta。后续所有步骤都以此为分支条件绝不要猜测。第 1 步收集上下文git status --short --branch git fetch origin --tags git tag --list desktop-v* --sort-v:refname | head -10 node -p require(./apps/examples/desktop-app/package.json).version node -p require(./apps/examples/desktop-app/src-tauri/tauri.conf.json).version如果尚不存在任何desktop-v*tag这是首个发布以桌面应用的第一个 commit 作为基线并明确告知基线是推断的。对于beta发布在desktop-experimental上工作checkoutorigin/desktop-experimental若它落后于 main先把origin/main合入——冲突处理策略见 EXPERIMENTAL.md并从该分支读取版本文件。上一个 tag 基线是两条渠道中最新的、且为当前分支祖先的desktop-v*tag。第 2 步收集发布区间内的提交# stable在 main 上 git log last-desktop-tag..HEAD --oneline --no-merges -- apps/examples/desktop-app sdk/packages .github/workflows/desktop-publish.yml # beta在 desktop-experimental 上 git log last-desktop-tag..origin/desktop-experimental --oneline --no-merges -- apps/examples/desktop-app sdk/packages .github/workflows/desktop-publish.yml注意日志路径包含sdk/packages桌面应用的 sidecar 会把 monorepo 里的cline/core等 SDK 包打包进去因此 SDK 变更也会随桌面应用一起发布。用户可见的 SDK 变更provider、模型、行为修复应并入发布说明纯内部变更可以跳过。第 3 步起草面向用户的发布说明扁平 bullet 列表、面向用户的语言。先呈现草稿并等待批准再动任何文件。第 4 步确定版本升版Stable询问本次是 patch、minor、major 还是显式版本号。用户没说清楚时不要猜。Beta按版本号规则计算——基座 下一个 stable 版本N递增0.0.14-beta.1→0.0.14-beta.2stable0.0.14发布后则变为0.0.15-beta.1。把计算出的版本与用户确认。第 5 步更新发布文件stable 在mainbeta 在desktop-experimentalpackage.json → 新版本号tauri.conf.json → 相同版本号在 CHANGELOG.md 顶部插入## X.Y.Z不写日期beta 为## X.Y.Z-beta.N内容为已批准的说明第 6 步提交前验证bun -F cline/code typecheck bun test apps/examples/desktop-app/scripts/generate-update-manifest.test.ts完整的桌面 bundle 只能在 macOS 上构建工作流的 build 作业才是真正的构建验证。在 Mac 上想要额外信心时可在应用目录执行bun run package:desktop:mac --allow-unsigned-mac。第 7 步提交发布变更git add apps/examples/desktop-app/package.json apps/examples/desktop-app/src-tauri/tauri.conf.json apps/examples/desktop-app/CHANGELOG.md git commit -m chore(desktop): release vX.Y.Z推送发布 commit 前询问一次创建并推送 tag 前再询问一次git push origin HEAD git tag -a desktop-vX.Y.Z -m Desktop vX.Y.Z # beta: desktop-vX.Y.Z-beta.N / Desktop vX.Y.Z-beta.N git push origin refs/tags/desktop-vX.Y.Z第 8 步派发工作流前提发布 commit 已在渠道分支上stable 为mainbeta 为desktop-experimental且 tag 先推送。两个渠道都从main派发原因见上文安全不变量# stable gh workflow run desktop-publish.yml --ref main -f git_tagdesktop-vX.Y.Z -f channelstable -f confirm_publishpublish # beta gh workflow run desktop-publish.yml --ref main -f git_tagdesktop-vX.Y.Z-beta.N -f channelbeta -f confirm_publishpublish gh run list --workflowdesktop-publish.yml --limit1 --json url,status,conclusion,createdAt --jq .[0]运行会暂停等待审批。validate立即执行随后build作业等待PublishDesktop环境——直到必需审阅人批准运行停留在waiting这是预期行为而非卡死。可在运行的 Web UIReview deployments批准或gh api repos/cline/cline/actions/runs/run-id/pending_deployments \ --method POST -f stateapproved -f commentdesktop vX.Y.Z \ -F environment_ids[]19152605990 # PublishDesktop在此之前validate之后什么都不会执行——签名密钥也读不到。工作流随后执行的内容与 desktop-publish.yml 对应构建一个 universal macOS bundletauri build --target universal-apple-darwin对 aarch64 x86_64 的 Rust 二进制做 lipoBun sidecar 由build-sidecar-bin.ts做 lipobeta 叠加tauri.beta.conf.json验证 bundle 内每个 Mach-O 都携带两个架构切片并验证编译后的二进制恰好内嵌本渠道的 feed URLVerify updater feed endpoint步骤用strings/grep断言stable 必须含desktop-latest/latest.json且不含desktop-beta反之亦然用 Developer ID 证书签名、用 App Store Connect API key 公证、用 Tauri updater key 签名 updater 制品并行地build-windows在 Windows runner 上构建 x64 NSIS 安装器通过 Azure Trusted Signing 对每个二进制做 Authenticode 签名TaurisignCommand→ tauri-sign-windows.ps1执行同样的 feed 端点与遥测护栏并用Get-AuthenticodeSignature验证最终安装器release 作业创建 GitHub releasebeta 为 prerelease、刷新渠道 feeddesktop-latest/latest.json或desktop-beta/latest.json、发 Slack 公告。公证通常额外增加 2–10 分钟。如果工作流因缺少凭据失败见下文Publish 密钥一次性配置。第 9 步发布成功后验证更新源curl -sL https://github.com/cline/cline/releases/download/desktop-latest/latest.json | head -30 # stable curl -sL https://github.com/cline/cline/releases/download/desktop-beta/latest.json | head -30 # betaversion字段必须等于新版本darwin-aarch64和darwin-x86_64两个条目都必须指向该 release tag 下同一个新的 universal.app.tar.gz资产因为 fat 二进制的每个切片在运行时各自请求自己的架构 key所以两个 key 服务同一制品windows-x86_64条目必须指向新的*_x64-setup.exe资产。该渠道的已安装 App包括旧的按架构安装会在下次启动或 2 小时内拿到更新。beta 发布后还要确认 stable feed 未被触碰desktop-latest/latest.json仍应服务上一个 stable 版本。工作流对此有 fail-closed 防护但验证成本极低、漏检后果极重——updater 的比较器是朴素的 semver大于beta manifest 落到desktop-latest会让所有 stable 安装自动更新到 beta。第 10 步汇报报告内容渠道、版本、tag、changelog 是否更新、commit hash、推送了什么、工作流 URL、feed 验证结果。工作流内部机制解析desktop-publish.yml.github/workflows/desktop-publish.yml 由四个作业组成validate→buildmacOS与build-windowsWindows并行 →release。几个值得关注的实现细节validate 作业fail-closed 的渠道映射渠道映射是承重墙每个渠道定义自己的 tag 形状、祖先分支、feed 与产品名未知渠道直接失败。源码中的注释点明了原因——updater 比较器是朴素的 semvernewer thanbeta manifest 落到desktop-latest会让所有 stable 安装自动更新到 beta。stable 的正则拒绝 prerelease 后缀也是同一原因。validate 还做三件事密钥作用域检查Verify signing secrets are not repository-scoped该作业不声明任何 environment因此凡是能在此解析出的签名密钥只可能是 repository/organization 级密钥——意味着全仓库任何工作流都能读到它。这一步与 build 作业的存在性检查配合共同证明密钥确实来自PublishDesktop环境tag 与版本一致性checkout tag 后比对package.json与tauri.conf.json的版本与 tag 剥离desktop-v前缀后的值祖先检查tag 指向的 commit 必须可从渠道分支origin/main或origin/desktop-experimental到达。build 作业macOS universal 构建与三道护栏build作业声明environment: PublishDesktop且带if: github.ref refs/heads/main。工作流注释明确说明该if是咨询性的——真正强制的门禁是环境的 deployment-branch 策略因为被派发的分支会运行自己的工作流副本。作业内的关键步骤密钥存在性前置检查Tauri 在APPLE_CERTIFICATE为空时会静默跳过代码签名、在APPLE_API_KEY为空时静默跳过公证构建照样成功并产出一个未签名未公证的 bundle——所以必须在任何构建前 fail up frontuniversal 校验Tauri 自己 lipo 主二进制但 sidecar 由自研的build-sidecar-bin.ts合并所以逐一把Contents/MacOS/*过lipo -archs任一不是双架构即失败——单架构 sidecar 会发布顺利、另一架构上崩溃feed 端点断言updater 端点由 tauri-build 以字符串字面量编入主二进制合并配置经 codegen 内嵌stringsgrep断言 bundle 恰好内嵌本渠道 feed URL捕获--config叠加层静默未生效的场景遥测自检sidecar 以--telemetry-selfcheck运行断言enabled:true且 OTLP endpoint host 可解析——--define内联是打包后 App从 Finder/Dock 启动、无运行时环境变量唯一能上报遥测的途径刻意不使用 Rust 构建缓存这是唯一能读到 Apple 证书与 updater key 的作业Actions 缓存一旦中毒恢复的缓存归档就是攻击者可控的。产物收集阶段把 DMG 与 updater tarball 及其.sig复制为dist/publish/下的PREFIX_VERSION_universal.dmg/.app.tar.gz/.sigCline→ClineCline Beta→Cline-Beta。build-windows 作业Azure Trusted Signing 全链签名该作业需要id-token: write通过PublishDesktop环境的联邦凭据cline-cli-signingEntra 应用subjectrepo:cline/cline:environment:PublishDesktop走 OIDC 登录 Azure。设计要点全有或全无未签名的 Windows 桌面构建绝不可接受Smart App Control / WDAC 拦截未签名 exeSmartScreen 标记未签名安装器且 Tauri 在 updater key 缺失时会静默跳过 updater 制品签名——与 CLI 管线不同这里没有未签名回退作业内所有 action 均SHA 钉死它们持有id-token: write与 updater 签名密钥被劫持的上游 tag 不得触达签名身份签名配置是运行时生成的 overlaysignCommand需要签名脚本的绝对路径指向 tauri-sign-windows.ps1。脚本本身下载 jsign 7.5 并校验 SHA-256、每次调用时Tauri 对主 exe、sidecar、NSIS 卸载器、安装器各调一次signCommand从az account get-access-token现取短时效 token 经环境变量而非 argv传给 jsign、以SHA-256算法 RFC3161 微软时间戳服务签名并在签完立即Get-AuthenticodeSignature自验独立的Verify Authenticode signatures步骤对dist/publish/*.exe和 sidecar exe 再验一遍——即使某个安装器完全跳过了signCommand也能被捕获sidecar 必须在场WDAC 锁定的机器会在运行时拦截它即使安装器本身完好。release 作业changelog 提取、manifest 生成与 feed 刷新changelog 精确匹配用 awk 抓取## version到下一个## 数字之间的内容并逐字写入 release 正文。注释解释了为何必须是精确匹配而非顶部小节main与desktop-experimental交叉合并后stable 与 beta 小节会按时间交错顶部小节可能属于另一渠道Slack 截断Slack section 块拒绝超过 3000 字符的文本且不报错因此对超长 changelog 发送带Read the full release notes链接的截断副本而 GitHub release 正文与 updater manifest 保持完整updater manifest由 generate-update-manifest.ts 生成。它扫描制品目录把*_universal.app.tar.gz同时映射到darwin-aarch64与darwin-x86_64两个平台 key指向同一资产与同一签名*_x64-setup.exe映射到windows-x86_64并拒绝同一平台 key 被多个制品声称feed 二次校验Update auto-update feed步骤从 channel 重新计算期望 feed 并要求与 validate 输出一致belt and braces防止单点线程化 bug 把发布指到另一渠道的 feed再确保滚动 release 存在不存在则创建--latestfalse——仓库级 latest release 归 CLI 发布所有最后gh release upload $FEED dist/desktop/latest.json --clobber刷新滚动资产创建 release 时make_latest: falsebeta 加prerelease。自动更新源的配置与生成原理stable 的 updater 配置在 tauri.conf.json 中plugins.updater.pubkeyminisign 公钥用于校验 updater 制品签名与endpoints指向https://github.com/cline/cline/releases/download/desktop-latest/latest.json。beta 由 tauri.beta.conf.json 覆盖productNameCline Beta、identifierbot.cline.app.beta与 updater 端点desktop-beta/latest.json。Tauri 按顺序合并重复的--config标志因此 beta 叠加层产品名、包标识、beta 更新源覆盖在 release 叠加层之上而不必复制它工作流的 build 步骤据此为 beta 传两个--config。滚动 feed 的运作方式desktop-latest/desktop-beta是滚动 release其latest.json资产由 release 作业--clobber上传指向最新的按 tag 不可变资产。generate-update-manifest.ts中每个平台条目的url形如https://github.com/repo/releases/download/tag/artifact——即 manifest 每发版刷新一次下载 URL 永远指向对应版本的 release tag。EXPERIMENTAL.md 特别强调命名不对称的陷阱desktop-latest就是stable feed不要改名成desktop-stable——URL 被编译进每一个已发布的 stable 二进制updater 没有回退端点改名会让所有改名前安装永久停在死 feed 上desktop-beta在首个 beta 发出后同理。Publish 密钥一次性配置以下密钥必须配置在PublishDesktop环境Settings → Environments → PublishDesktop → Environment secrets而不是仓库级使得只有build作业能读到、且只在审批之后能读到。该环境同时限制部署来源为main并强制审阅人。把它们配成仓库级密钥是常见错误。环境门禁的作业同样会解析仓库级密钥环境值只是优先构建照样成功——凭据就此静默暴露在整个仓库。validate作业专门在任何无 environment 的作业中探测这些密钥一旦发现解析成功即失败整次运行。遇到该报错时删除仓库级副本而不是再复制一份。若某处都缺失build的前置检查会点名缺失项使运行失败。Apple 值来自用于手工签名的同一 Apple Developer 账户获取方式见桌面应用 README 的 macOS signing notarization 一节密钥值APPLE_CERTIFICATE从 Keychain Access 导出的Developer ID Application身份.p12须含私钥的 Base64base64 -i certificate.p12 \| pbcopyAPPLE_CERTIFICATE_PASSWORD导出.p12时设定的密码APPLE_SIGNING_IDENTITYDeveloper ID Application: Team Name (TEAMID)——来自security find-identity -v -p codesigningAPPLE_API_KEYApp Store Connect APIKey ID用于公证APPLE_API_KEY_CONTENTAuthKey_KEYID.p8文件内容APPLE_API_ISSUERApp Store ConnectIssuer IDUsers and Access → Integrations 的 UUIDTAURI_SIGNING_PRIVATE_KEYTauri updater 私钥内容tauri signer generate。此钥一旦丢失已发布的 App 将无法再验证更新——务必妥善保管TAURI_SIGNING_PRIVATE_KEY_PASSWORD该密钥的密码Windows 侧还需仓库级AZURE_CLIENT_ID、AZURE_TENANT_ID、AZURE_SUBSCRIPTION_ID、AZURE_TRUSTED_SIGNING_ENDPOINT、AZURE_TRUSTED_SIGNING_ACCOUNT_NAME、AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_DESKTOP工作流注释AZURE_*为 repository secretTAURI_*在 PublishDesktop 环境。Slack 与遥测类密钥SLACK_RELEASE_BOT_TOKEN、TELEMETRY_SERVICE_API_KEY、ERROR_SERVICE_API_KEY、OTEL 配置与 CLI、SDK、扩展发布工作流共享且已配置。不要把它们移入PublishDesktop——一旦作用域限定到该环境其他所有发布工作流中的这些值会被静默清空除了遥测缺失 Slack 发不出之外不会有任何报错。Beta 渠道的分支模型与合并冲突策略EXPERIMENTAL.md 要点EXPERIMENTAL.md 补充了 beta 渠道的流程背景发布时需要知道beta 是独立 App 而非 stable 的模式产品名、包标识不同两者并排安装以便直接对比两个 App 共享~/.clineprovider 凭据、全局设置、hub daemonbeta 需要更新版 hub 时可能触发 hub-update-required 流程——这是预期的版本偏斜分支模型特性 PR 目标desktop-experimental并在其上迭代对main的原始 PR 保持 draft 状态积累后续工作毕业 向main提全新 PR按正常 main PR 标准走评审。同步方向单向定期把main合入desktop-experimental至少每个 stable 桌面版本发布后一次绝不整支反合合并冲突策略package.json/tauri.conf.json版本号冲突时保留分支的 beta 版本CHANGELOG.md保留双方小节、按版本新者优先stable 与 beta 小节按时间交错特性代码在已毕业内容上以 main 为准无自动降级退出 beta 就是删掉 beta Appstable 从未被触碰已毕业功能的 stable 版本通过 stable App 的正常发布获得tauri.beta.conf.json必须存在于被 tag 的 commit 上build 作业 checkout 的是 tag因此要在main与desktop-experimental两个分支上都保留它。发布前自检清单汇总 SKILL.md 全文的门禁要求发布动作完成后可按此核对版本号在 package.json 与 tauri.conf.json 中一致且与 tag 一致CHANGELOG.md 存在精确的## version小节beta 为## X.Y.Z-beta.N无日期发布 commit 在渠道分支上tag 已推送且可从渠道分支到达工作流从main派发PublishDesktop审批通过渠道 feed 的latest.json已刷新且版本正确另一渠道 feed 未被触碰beta 基座未与任何已发布 stable 的X.Y.Z相同报告含渠道、版本、tag、commit hash、工作流 URL 与 feed 验证结果。以上流程与护栏均以当前仓库中的 desktop-publish.yml、generate-update-manifest.ts、tauri-sign-windows.ps1 及两份 tauri 配置的实际实现为准当前桌面应用版本号为0.0.22可作为流程理解时的现实参照。【免费下载链接】clineAutonomous coding agent as an SDK, IDE extension, or CLI assistant.项目地址: https://gitcode.com/GitHub_Trending/cl/cline创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考