fix(desktop): bridge Electron users on Windows and Linux - #9079
Conversation
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
Code reviewNo blockers on the current head. Four commits landed since the last review pass, all hardening, and each one holds up:
Carried over from the prior pass and unchanged at this head: the Windows migration GUID is the exact Non-blocking note, unchanged from last time: the docs rewrite drops the macOS-era signing-credential fallback and draft/prerelease detail — the new text is accurate, but if those operational details still matter they deserve a home somewhere. Testing evidence (unattended CI run)Unattended run — no local build or tmux. Evidence below is the PR's own CI on the reviewed commit, fetched via API. The Not verified: end-to-end Electron → Tauri installation on a real Windows/Linux machine. Naming the lanes for completeness: neither
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 中文说明代码审查:当前 head 无阻塞项。上一轮审查后落地的四个提交全部是加固,且每一项都经得起推敲:NSIS 钩子新增的 测试证据(无人值守 CI 运行):未本地构建、无 tmux;证据来自被审提交上 PR 自身 CI(API 拉取)。 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean across every stage; the one remaining item (the signed-installation rehearsal) is intrinsically a release-time gate, not something a merge review can settle. Stepping back: my independent proposal for this hardening round was exactly what landed — guard the uninstaller invocation against stale registry state, make a bridge run that cannot replace a newer feed fail loudly instead of skipping silently, and pin each behavior in the contract suite. The PR matches it move for move, nothing extra. The part that could have been dangerous (a third-party uninstaller invoked from an NSIS pre-install hook) was verified last round against electron-builder's own sources; this round I additionally checked the new The new commits also closed the only red flag from the first round — the PR's own ESLint failure — and the full One standing note for whoever runs the release: the signed Electron → Tauri rehearsal on a disposable Windows VM and the published-AppImage hop on Linux are the PR's own declared preconditions — keep them as hard gates before dispatching 中文说明回顾全局:这一轮加固如果让我独立提方案,正是 PR 落地的样子——为陈旧注册表状态加卸载器存在性守卫、让无法替换更新 feed 的桥接运行显式失败而非静默跳过、每项行为都用契约套件锁定。PR 逐步吻合且毫无多余。唯一可能有风险的部分(NSIS 预安装钩子调用第三方卸载器)上一轮已对照 electron-builder 自身源码验证;本轮又将新增的 — Qwen Code · qwen3.8-max Reviewed at |
|
Thanks for the PR! Template looks good ✓ Problem: observed, not theoretical. Issue #9074 documents the live feed gap with concrete versions — Direction: aligned. This extends the macOS bridge that already shipped in #8392 using the same one-time mechanism; the issue is P1 and on the platform-distribution roadmap. Release infrastructure is a sensitive area, so I'm saying this plainly: the change is authored by a maintainer with admin access against their own P1 item, which is the right ownership for it — but it still gets the full review below rather than a rubber stamp. CHANGELOG: no direct reference, area is qwen-code-specific and clearly relevant. Size: not applicable — no core module paths touched ( Approach: scope is right. Every file serves the stated goal — per-platform manifest generation, feed upload of the matching payloads, the Windows NSIS pre-install migration hook, contract tests, and a docs refresh. No drive-by changes. The four commits added since the last review pass are hardening of the same mechanism, not scope growth: a Risk: no elevated risk signals — none of the changed files match the revert-correlated paths. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:已观测到,不是理论问题。Issue #9074 用具体版本号记录了线上 feed 的缺口—— 方向:对齐。这是沿用 #8392 已上线的一次性桥接机制扩展到 macOS 之外;issue 是 P1 且在 platform-distribution 路线图上。发布基础设施属于敏感区域,这里直说:改动由具有 admin 权限的维护者针对自己的 P1 事项提交,归属合理——但仍会走完整审查,而不是走过场。CHANGELOG:无直接引用,该领域是 qwen-code 特有且明确相关。 规模:不适用——未触及核心模块路径(仅 方案:范围合理。每个文件都服务于目标——按平台生成清单、向 feed 上传对应载荷、Windows NSIS 预安装迁移钩子、契约测试和文档更新,无夹带改动。上一轮审查后新增的四个提交全部是同一机制的加固而非范围扩张:迁移钩子增加 风险:无升级风险信号——变更文件均未命中与 revert 相关的高风险路径。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not explored to full depth (tool budget reached): "PR #9079 extends the one-time Electron→Tauri update bridge…": none — all checks above were completed within budget.; "PR #9079 extends the one-time Electron→Tauri update bridge…": none — all checks above completed within budget.; "PR #9079 extends the one-time Electron→Tauri update bridge…": did not locate the exact definition site of electron-builder's ${isUpdated} runtime flag (generated outside the fetched templates); relied instead on electron…; "PR #9079 extends the one-time Electron→Tauri update bridge…": verify LogicLib.nsh _!= empty-string-vs-0 semantics against NSIS source (affects the outcome branch of finding 1); "PR #9079 extends the one-time Electron→Tauri update bridge…": inspect tauri-bundler 2.8.5's installer.nsh for NSIS_HOOK_PREINSTALL call site and $R2 residue, and 7 more.
Not reviewed: reverse audit — stopped before round 4 by the review time budget.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/desktop-release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
仅完成部分审查,审查缺口已披露。
未探索到全部深度(达到工具调用预算):"PR #9079 extends the one-time Electron→Tauri update bridge…":none — all checks above were completed within budget.;"PR #9079 extends the one-time Electron→Tauri update bridge…":none — all checks above completed within budget.;"PR #9079 extends the one-time Electron→Tauri update bridge…":did not locate the exact definition site of electron-builder's ${isUpdated} runtime flag (generated outside the fetched templates); relied instead on electron…;"PR #9079 extends the one-time Electron→Tauri update bridge…":verify LogicLib.nsh _!= empty-string-vs-0 semantics against NSIS source (affects the outcome branch of finding 1);"PR #9079 extends the one-time Electron→Tauri update bridge…":inspect tauri-bundler 2.8.5's installer.nsh for NSIS_HOOK_PREINSTALL call site and $R2 residue,另有 7 条。
未审查:反向审计——评审时间预算不足,未能开始第 4 轮。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/desktop-release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max via Qwen Code /review (v0.21.11)
yiliang114
left a comment
There was a problem hiding this comment.
Verified the unresolved Critical against this head — it still stands.
Independently confirmed against the desktop-v0.0.5 tag: packages/desktop/apps/electron/electron-builder.yml sets productName: Qwen Code Desktop with no uninstallDisplayName override, so the shipped uninstaller wrote DisplayName = Qwen Code Desktop 0.0.5. The hook's ${AndIf} $R1 == "Qwen Code Desktop" is an exact NSIS compare and never matches, so the migration ExecWait is silently skipped on every real installation. The prefix-match suggestion in the thread (truncate to 17 chars) covers both 0.0.4 and 0.0.5.
Everything else checked out on this head: the manifest generator emits the version/files/path/sha512/releaseDate shape electron-updater expects on all three platforms, the publish job guards exactly one *-setup.exe and one *.AppImage before touching the fixed feed, and the contract tests exercise each platform plus the missing-artifact failure path.
|
Closeout for this round:
|
The migration ExecWait target is assembled from the HKCU InstallLocation registry value; require the uninstaller executable to exist before running it, and pin the conjunct in the release contract suite.
|
@qwen-code /review --timeout=360 |
|
Qwen Code review request accepted. Review is queued in workflow run. |
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed at c6e282d6 — approving. The blocking Critical (the dead DisplayName guard) is fixed, c6e282d6 also closes the ExecWait gap I was about to raise, and I re-derived every load-bearing fact in the Windows migration path from the electron-builder / tauri-bundler sources rather than taking the earlier thread on trust.
Verified
- Registry keys are the real ones.
uuid5("com.alibaba.qwen-code", 50e065bc-3134-11e6-9bab-38c9862bdaf3)=821b18a9-7c63-5bb4-9e20-51ba63d5ecc3, matching both!defines.registryAddInstallInfoinapp-builder-lib/templates/nsis/include/installer.nshwritesInstallLocationunderSoftware\<guid>, so the$R0read is correct. - The
DisplayNamefix is correct, not just plausible.UNINSTALL_DISPLAY_NAMEdefaults to"${productName} ${version}"(NsisTarget.ts) and the appended$1is""in both install-mode branches, so the shipped value is exactlyQwen Code Desktop 0.0.5.StrCpy $R1 $R1 17truncates to the 17-character product name and therefore matches 0.0.4 and 0.0.5 alike. - The new
${FileExists}conjunct closes a real hole. NSIS documents that whenExecWaitis given an output variable, "only sets the error flag if an error occurs; if an error occurs the contents of the user variable are undefined". So with stale HKCU keys but a hand-deleted install directory,$R2would have kept""on the silent updater-driven path, and because LogicLib!=is a string compare"" != "0"is true — every subsequent Tauri install, including a plain fresh one, would have aborted with "Could not remove the previous Qwen Code Desktop installation" and no way out but the registry. Guarding on the uninstaller's existence removes the reachable path; the residual (file vanishes between the check and the spawn, or a launch denial) is not worth an error-flag read. --updatedreally does preserve user data. The uninstaller only setsisDeleteAppDatafromDELETE_APP_DATA_ON_UNINSTALLinside${ifNot} ${isUpdated}, sodeleteAppDataOnUninstall: trueis neutralised./currentuserand/Sare both parsed (initMultiUser,${GetOptions} $R0 "/S"), and the one-click uninstaller exits throughquitSuccess=SetErrorLevel 0+Quit, so a successful removal returns 0.- The bridge is genuinely one-shot. That same uninstall section runs
DeleteRegKeyon bothUNINSTALL_REGISTRY_KEYandINSTALL_REGISTRY_KEYunconditionally — outside theisUpdatedbranch. So after a successful migration the hook's guard can never re-fire, and later Tauri upgrade installs cannot try to run a now-deleted uninstaller. This was the failure mode I most wanted to rule out. - Hook name and placement. tauri-bundler emits
!ifmacrodef NSIS_HOOK_PREINSTALL/!insertmacro NSIS_HOOK_PREINSTALLas the first statement ofSection Install(right afterSetOutPath $INSTDIR);MUI2.nshpulls in LogicLib and the template already!includesFileFunc.nsh, so${If}/${AndIf}/${FileExists}all compile; and nothing in Tauri'sSection Installreads$R0/$R1/$R2after the hook, so the register use is safe in both directions. - The new manifests actually reach legacy clients.
desktop-v0.0.5shipsprovider: genericwithurl: https://github.com/QwenLM/qwen-code/releases/download/desktop-latest, so electron-updater fetcheslatest.ymlon Windows andlatest-linux.ymlon Linux from exactly the tag this workflow uploads to. And the legacywinconfig setsverifyUpdateCodeSignature: false, so the Tauri installer's different (or absent) Authenticode publisher will not be rejected — the other way this change could have shipped silently inert. - Artifact selection can't cross platforms. The collect step normalises spaces (
name="${name// /-}"), so no- url:/path:entry can carry a space, and its per-OS allow-lists (*-setup.exeonly,*.AppImage/*.debonly) keep the mac-x64.zip/-arm64.zipregexes from matching a Windows or Linux payload. - The new hard-fail when an
electron_bridge=truerelease would not replace a newer feed is the right call — silently uploading nothing was the worse outcome. node scripts/test-release.jspasses in a clean tree at this head, and the new${FileExists}conjunct is pinned by its own assertion intestLegacyApplicationIdentity.
No remaining findings. The Windows and Linux signed-installer rehearsals in your Test Plan are still the gating step before publishing, and that is correctly disclosed.
中文说明
在 c6e282d6 上完成审查,同意合并。阻塞性 Critical(失效的 DisplayName 守卫)已修复,c6e282d6 还顺带补上了我原本要提的 ExecWait 缺口;我没有沿用此前评论的结论,而是直接从 electron-builder / tauri-bundler 源码重新推导了 Windows 迁移路径上所有起关键作用的事实:
- 注册表键正确:
uuid5("com.alibaba.qwen-code", 50e065bc-…)正是821b18a9-…;InstallLocation确实写在Software\<guid>下。 DisplayName修复正确:默认值就是Qwen Code Desktop 0.0.5,$1在两个分支都是空串,StrCpy $R1 $R1 17能同时覆盖 0.0.4 与 0.0.5。- 新增的
${FileExists}条件堵住了一个真实漏洞:NSIS 文档明确写道,ExecWait指定输出变量时"仅在发生错误时设置 error flag;发生错误时该用户变量的内容是未定义的"。因此在注册表键仍在、安装目录被手工删除的情况下,静默/更新器路径上$R2会保持"",而 LogicLib 的!=是字符串比较,"" != "0"为真——此后每一次 Tauri 安装(包括全新安装)都会以"Could not remove the previous Qwen Code Desktop installation"中止,只能手改注册表。加上卸载器存在性判断后,可达路径已消除;剩余情形(检查与启动之间文件消失、或启动被拒)不值得再加 error flag 判断。 --updated确实保留用户数据:isDeleteAppData只在${ifNot} ${isUpdated}内被置位;/currentuser、/S均被解析;成功卸载通过quitSuccess返回 0。- 桥接确实是一次性的:卸载器无条件删除两个注册表键,因此迁移成功后守卫不可能再次命中,后续 Tauri 升级不会去执行已删除的卸载器——这是我最担心的失败模式,已排除。
- Hook 宏名与插入位置正确;MUI2 引入 LogicLib,模板本身已
!include FileFunc.nsh,因此${FileExists}可用;Tauri 的Section Install在 hook 之后不读$R0/$R1/$R2。 - 新清单确实能被旧客户端读到:0.0.5 用的是
genericprovider,地址正是desktop-latest;且旧 win 配置verifyUpdateCodeSignature: false,Tauri 安装包签名差异不会被拒绝。 - 构件选择不会跨平台串台:收集步骤把空格替换成
-,并按 OS 白名单过滤。 - 干净树上
node scripts/test-release.js通过,且新增的${FileExists}条件在testLegacyApplicationIdentity中有对应断言固定。
没有遗留问题。Test Plan 里 Windows / Linux 已签名安装器的演练仍是发布前的把关步骤,这一点你已如实披露。
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 124 passed · 0 failed · 124 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:124 通过 · 0 失败 · 124 总计 Verification reportPR #9079 Deep Verification —
|
| Cell | Environment | Observable oracle | Base | Head |
|---|---|---|---|---|
| A1–A3 | full fixture set (4 mac + exe + AppImage + .sig/.deb/.tar.gz decoys, names taken from the workflow's own collect/rename step) |
manifests exist; selected names exact; sha512/size match independent recomputation; path:/sha512: = primary; ISO releaseDate |
n/a | 3/3 platforms, 21/21 checks |
| A4 | base script invoked with the head workflow's exact args (--platform windows/linux) |
entries matching the requested platform's shape | 0 windows, 0 linux entries (silently writes the mac manifest) | 1 each |
| A5 | windows/linux-only asset set (no mac artifacts) | exit code + stderr | fails Missing Electron bridge artifact (as predicted — counted as pass) |
exit 0 on both platforms |
| A6 | mac-only fixture, both arms | byte-compare excluding releaseDate: |
— | byte-identical to base (no macOS regression) |
| A7 | head error matrix | exit codes + messages | n/a | duplicate exe → found 2; missing AppImage → found 0; invalid/missing --platform and bad semver all rejected (5/5) |
| A8 | base mac-only contract | exit 0, 4 entries | green (control is live) | — |
A/B table 2 — publish-job workflow replay (harness/workflow-replay.mjs, witness: 02-workflow-replay-head-vs-base.png)
Both run: blocks of the publish job were extracted verbatim with a YAML parser at head and base, then executed under bash --noprofile --norc with the steps' own set -euo pipefail, a recording gh stub, jq from the image, and fixture release-assets/ named exactly as the build job's Collect artifacts step produces them.
| Cell | Scenario | Oracle (recorded gh calls / exit / stderr) |
Base | Head |
|---|---|---|---|---|
| G1/G2 | generate step, bridge=true | manifest files present + latest.yml advertises the NSIS exe, latest-linux.yml the AppImage; SHA256SUMS covers them |
only latest-mac.yml (the gap) |
all 3 yml + json (8/8) |
| F1/F2 | feed upload, current feed 0.2.1 (older) | exact uploaded file set from the stub log | 6 assets | 10 assets (Δ = latest.yml, latest-linux.yml, exe, AppImage) |
| F3/F4 | bridge 0.3.0 vs feed 0.4.0 (newer) | exit code + message + no upload | exit 0, silent notice | exit 1, Electron bridge 0.3.0 cannot replace newer stable feed 0.4.0, no upload |
| F5/F6 | missing AppImage / two -setup.exe |
layering of the two guards | n/a | generate step fails first (found 0 / found 2); the feed-level count guard also rejects when driven directly (belt-and-braces, see Findings) |
| F7 | bridge=false parity | upload set | 1 file (json) | 1 file (json) — non-bridge path unchanged on both arms |
| F8 | feed release does not exist | gh release create args |
n/a | create carries all 10 assets + --latest=false |
Reachability of the "exactly one installer/AppImage" precondition: the build matrix has exactly one Windows target (x86_64-pc-windows-msvc, --bundles nsis) and one Linux target (x86_64-unknown-linux-gnu, --bundles appimage,deb), so a real release produces exactly one of each (.sig/.deb files do not match the globs) — asserted in harness/sync-and-semantics.mjs S4.
Contract suite + mutation matrix (witnesses: 03-mutation-matrix.png, 04-contract-suite-head.png)
| Mutation | Expected | Result |
|---|---|---|
| M0 unmutated head | suite green | green |
M1 remove ${FileExists} conjunct (reverts the final commit validate Electron uninstaller path) |
fail in testLegacyApplicationIdentity, message names FileExists |
killed exactly there |
M2 remove installerHooks from tauri.conf.json |
fail | killed, message names electron-migration.nsh |
| M3 remove bridge-cannot-replace-newer-feed guard from workflow | fail in testElectronBridgeWorkflow |
killed there |
| M4 corrupt the Electron GUID in the hook | fail | killed, message names 821b18a9 |
M5 drop --updated from the ExecWait line |
fail | killed, message names --updated |
| restore after each | green | green ×5 |
| base-era suite at base | green | green (attribution baseline) |
No mutant regressed from killed to survived; every kill landed on the assertion the commits claim to pin. The fix introduced by the final commit is load-bearing in the test suite (M1) and its runtime purpose is analyzed in Findings §1.
Corrections
None required — no earlier review claims about this PR were found to be inaccurate.
Findings (none blocking)
- (Low / pre-release item, already owned by the author) The Electron GUID and the electron-builder registry/filename contract are untrusted inputs this sandbox cannot falsify. The hook keys on
Software\821b18a9-7c63-5bb4-9e20-51ba63d5ecc3, an uninstaller namedUninstall Qwen Code Desktop.exe, and flags/currentuser /S --updated _?=$R0. The Electron-era builder config is no longer in the repo, so the GUID's provenance cannot be re-derived here; the runtime behavior depends on electron-builder's documented conventions (per-user installs writeSoftware\<appId>InstallLocation, the uninstaller isUninstall <productName>.exe,--updatedpreserves user data). The failure mode of a wrong GUID is a silent no-op (Electron stays, a second uninstall entry remains) — visible, but only in the signed-installer rehearsal. The PR's own test plan already marks that rehearsal⚠️ -required before release, which is the correct gate; treat it as a hard release prerequisite, not a merge blocker. - (Info) The hook cannot misfire on Tauri's own installation — verified against the pinned bundler, not assumed. From the
@tauri-apps/cli2.11.4 binary (the exact version locked inpackages/desktop-shell/package-lock.json): the template includes the hook via{{#if installer_hooks}} !include …, invokes it through!ifmacrodef NSIS_HOOK_PREINSTALLinsideSection Installbefore anyFilecopy (matching the docstring "before copying files, setting registry key values and creating shortcuts"), writes Tauri's uninstaller as$INSTDIR\uninstall.exe(so the hook'sFileExists "…\Uninstall Qwen Code Desktop.exe"can never match a Tauri install), and writes Tauri's uninstall registry data under…\Uninstall\${PRODUCTNAME}— a different key from the Electron GUID key. So Tauri→Tauri updates run the hook as a no-op, and after migration only one uninstall entry remains, as claimed. The hook contains noMessageBox, so silent installs cannot hang.StrCpy $R1 $R1 17+ compare against the 17-charQwen Code Desktopcovers both electron-builder DisplayName conventions (<name>and<name> <version>). - (Info) The feed-level "one Windows installer and one Linux AppImage" guard is defense-in-depth, not an independent gate. In a real run the manifest-generation step fails first on the same conditions (
found 0/found 2, verified in F5/F6 on both layers), andset -estops the job before the feed step. The guard only becomes decisive if the generate step is ever removed or reordered — harmless redundancy, classified as intentional, not dead code. - (Info) Per-user scope only. The hook reads HKCU exclusively. An elevated/all-users Electron 0.0.5 installation (if any were ever shipped that way) would not be migrated; the PR description explicitly scopes to per-user installs. Not falsifiable here.
- (Nit) Design-doc rewrite is a condensation, not information loss. The removed "Signing credentials" and "Validation" content is carried by
packages/desktop-shell/README.md(checked:TAURI_SIGNING_PRIVATE_KEY,MAC_CSC_*/APPLE_NOTARY_*fallbacks, and the bridge usage note are all there).
Not covered
- Reviewer Test Plan steps 3 and 4 are structurally unrunnable in this container (no Windows VM, no signed bridge installer, no published Electron 0.0.5 AppImage/ZIP clients). The author flags both as
⚠️ rehearsal-required before release; this verification substitutes harness-level proof for them and does not replace them. - The publish-step replay is uncalibrated: first round, no GitHub token, no
previous-report.md, and no retrievable real emitted artifact to reproduce byte-for-byte. What would have calibrated it: the livedesktop-latestrelease's asset list from the earlier macOS bridge run, or a prior verify report. Partial corroboration: the base arm's 6-file upload set matches the documented and previously shipped macOS bridge contract (json +latest-mac.yml+ 4 mac payloads). - Per-commit attribution (6 commits per the snapshot; the depth-2 checkout makes only the aggregate
HEAD^1..HEADdiff reachable —git rev-list --count HEAD^1..HEAD^2returns 1 at the shallow boundary, andgit rev-parse --is-shallow-repositoryis true). The aggregate diff was verified; the final commit's claim was additionally isolated via mutation M1. - NSIS compilation of the hook — no
makensis, no cargo/tauri toolchain in the container; template semantics were verified from the pinned binary instead (Finding §2). An actualtauri buildon Windows remains the first real compile of the.nsh. - Runtime behavior of the electron-builder uninstaller (
--updatedpreserving user data, exit-code semantics) — depends on the legacy installer's embedded electron-builder version; covered only by rehearsal. - Repo-wide test/lint gates,
sync-desktop-to-oss.yml(unchanged; mirrors onlydesktop-latest.json, consistent with the existing macOS bridge behavior of not mirroring the Electron yml manifests),yamllint(install blocked:pip3: Permission deniedin this container — actionlint and shellcheck ran and were proven live instead), OSS/China-mirror consumption of the bridge manifests. - PR claims about the live release state (
latest.ymladvertising Electron 0.0.5, feed serving Tauri 0.2.1) — not observable without network/token; treated as motivation only.
Methodology
Environment: the CI verify container (node:22-bookworm), repo at the refs/pull/9079/merge checkout (depth 2, npm ci + npm run build pre-run; the desktop-shell workspace's dev deps are not in the root lockfile, which is why the tauri CLI binary was fetched via npm pack for template inspection). Four harnesses drove the code mock-free: (1) harness/ab-manifest.mjs ran the real head script and the exact HEAD^1 module over fixture asset directories whose names were derived from the workflow's own Collect artifacts rename rules, verifying manifest contents against independently recomputed sha512/size; (2) harness/workflow-replay.mjs executed the publish job's two bash blocks verbatim (YAML-parsed, bash --noprofile --norc, the steps' own set -euo pipefail) against a recording gh stub and real jq; (3) harness/mutation-matrix.mjs applied five point mutations in a scratch worktree and ran the real contract suite (packages/desktop-shell/scripts/test-release.js, no mocks) against each; (4) harness/sync-and-semantics.mjs checked cross-file regex sync and extracted template facts from the pinned tauri CLI binary via strings. Gates: contract suite at head and base, bash -n on all four extracted blocks, actionlint 1.7.12 on the workflow, shellcheck 0.11.0 on the blocks, git diff --check — each linter proven live by planting a violation it caught. Raw logs in logs/, per-cell harness code in harness/, extracted bash in scratch/blocks-{head,base}/, assertion ledger in assertion-parts/ (36 + 36 + 22 + 17 + 13 = 124). No PR content was executed with credentials; no GitHub writes were attempted.
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
Released in v0.21.12. |




What this PR does
This PR extends the existing one-time Electron-to-Tauri update bridge from macOS to Windows and Linux. A bridge release now publishes the legacy Electron manifests and the matching Tauri installer payloads for all three supported platforms while retaining the normal Tauri updater feed.
On Windows, the Tauri installer recognizes the matching per-user Electron installation and runs its registered uninstaller in update mode before copying Tauri files. This preserves user data and avoids leaving a second uninstall entry that could later remove the new installation. The release documentation now describes the cross-platform contract and operator flow.
Why it's needed
The live
desktop-latestrelease currently serves Tauri0.2.1, butlatest.ymlandlatest-linux.ymlstill advertise Electron0.0.5. Windows and Linux users installed from the Electron release therefore cannot discover the Tauri desktop through their existing updater. This closes that gap while reusing the release artifacts and bridge mechanism already proven on macOS.Reviewer Test Plan
How to verify
electron_bridge=trueand confirm the fixeddesktop-latestfeed receiveslatest-mac.yml,latest.yml,latest-linux.yml, their matching payloads, anddesktop-latest.json.0.0.5, install a signed bridge build and confirm Electron is removed without deleting user data, only one Qwen Code Desktop uninstall entry remains, and the Tauri app can update again through its JSON feed.Evidence (Before & After)
N/A — release infrastructure and installer migration only; no UI change.
Tested on
Environment (optional)
macOS arm64, Node.js 22.22.0.
npm run test:release --prefix packages/desktop-shell,actionlint .github/workflows/desktop-release.yml, andgit diff --checkpassed.Risk & Scope
electron_bridge=true; later Tauri-only releases leave the fixed Electron bridge assets intact.Linked Issues
Resolves #9074
中文说明
这个 PR 做了什么
这个 PR 将现有的一次性 Electron → Tauri 更新桥接从 macOS 扩展到 Windows 和 Linux。桥接版本现在会为三个受支持平台发布旧 Electron 更新器可识别的清单及相应的 Tauri 安装包,同时保留正常的 Tauri 更新源。
在 Windows 上,Tauri 安装器会识别同一应用的当前用户 Electron 安装,并在复制 Tauri 文件前以更新模式运行其已注册的卸载程序。这样既保留用户数据,也避免遗留第二个卸载项,防止以后卸载旧项时误删新安装。发布文档现在也声明了跨平台兼容契约和操作方式。
为什么需要
线上
desktop-latestRelease 当前已经提供 Tauri0.2.1,但latest.yml和latest-linux.yml仍然发布 Electron0.0.5。因此从 Electron 版本安装的 Windows 和 Linux 用户无法通过现有更新器发现 Tauri 桌面端。这个改动复用已经在 macOS 验证过的发布资产和桥接机制,补齐这一缺口。Reviewer 测试计划
如何验证
electron_bridge=true检查稳定版 dry run,确认固定desktop-latestfeed 收到latest-mac.yml、latest.yml、latest-linux.yml、对应载荷以及desktop-latest.json。0.0.5的一次性 Windows 虚拟机中安装已签名桥接版,确认 Electron 被移除但用户数据保留、系统中只剩一个 Qwen Code Desktop 卸载项,且 Tauri 应用之后可以继续通过 JSON feed 更新。证据(Before & After)
N/A——仅涉及发布基础设施和安装器迁移,没有 UI 变化。
测试平台
环境(可选)
macOS arm64,Node.js 22.22.0。
npm run test:release --prefix packages/desktop-shell、actionlint .github/workflows/desktop-release.yml和git diff --check均通过。风险与范围
electron_bridge=true执行一次;后续仅发布 Tauri 的版本会保留固定 Electron 桥接资产。关联 Issue
Resolves #9074