Qwen Code 桌面客户端品牌化构建白标实操:Tauri 壳 + brandId + Logo 快速生成品牌安装包
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
客户提需求:“把 Qwen Code 桌面客户端装进我们自己的产品,换名字、换图标、换启动页,要改多少东西?”Qwen Code 仓库内置的 Desktop Brand Builder 技能把桌面客户端品牌化构建压缩成了一件两分钟的事:给一个brandId加一张 logo,基于 Tauri 的桌面壳就能被白标/换肤成带自己名字的应用、一整套图标、品牌启动页,以及对应的安装包。
全景图:3 个品牌挂载点 × 1 个零依赖脚本
Qwen Code 的桌面实现是 packages/desktop-shell 下的 Tauri 壳(Electron 时代的packages/desktop已移除),品牌信息全部集中在三处:
| 挂载点 | 承载内容 | 品牌化时改动什么 |
|---|---|---|
| src-tauri/tauri.conf.json | productName、identifier、updater 配置 | 应用名、bundle identifier、更新源 |
src-tauri/icons/ | 应用图标全家桶 | 用品牌 logo 重建整套尺寸 |
| bootstrap/ 启动 UI | index.html、bootstrap.js、logo svg | 启动页标题、启动文案、logo 引用 |
驱动这三个挂载点的只有一个零依赖脚本 brand-create.mjs(Node ≥ 18,无外部依赖):输入--shell-root加一份brand.json,输出 JSON 报告。技能全文定义在 SKILL.md。壳内部跑的是 Qwen Code Web Shell:
开工前准备:依赖与独立构建克隆
- Node ≥ 18:品牌脚本零依赖;打包阶段还需要 Tauri 自己的构建链(Rust 工具链)。
- 两处
npm install:build:runtime会回调仓库根部的构建(依赖根部 devDependencies 如cross-env、esbuild),所以根部依赖与 desktop-shell 依赖缺一不可:
npm install # 根部依赖 cd packages/desktop-shell npm install --workspaces=false # desktop-shell 依赖 cd ../..- 独立克隆、独立目录:品牌补丁不可逆,日常仓库不能拿来直接改,每个品牌给一个全新克隆:
BUILD_ROOT="$PWD/brand-builds/acme-ai-20260919" mkdir -p "$BUILD_ROOT" git clone --branch main --single-branch \ https://gitcode.com/GitHub_Trending/qw/qwen-code \ "$BUILD_ROOT/qwen-code" cd "$BUILD_ROOT/qwen-code" git checkout -B brand-acme-ai origin/mainclone 或 checkout 失败就停在这里报告,不要假装分支已创建继续往下走。
实操流程:从 brand.json 到安装包
第一步,写 brand.json。必填只有两项:brandId(须匹配^[a-z][a-z0-9-]*$)和logo(存在的本地文件,推荐 ≥ 1024px 方形 PNG)。其余字段都有确定性推导,全程拿acme-ai走一遍:
{ "brandId": "acme-ai", "logo": "/absolute/path/to/logo.png", "website": "https://acme.ai", "appName": "Acme AI", "appId": "ai.acme.desktop", "artifactPrefix": "Acme-AI", "updaterEndpoints": [], "updaterPubkey": "" }- appName:按短横线切段、逐段首字母大写、空格连接 →
Acme AI; - artifactPrefix:同样大写处理但用短横线连接 →
Acme-AI。ai这类词在内置缩写集(ai/api/cli/ide/sdk/ui/url)里会整体大写,所以是Acme AI而不是Acme Ai,acme-cli会得到Acme CLI; - appId:取
website的 host、去掉www.前缀、标签反转再追加.desktop→https://acme.ai变ai.acme.desktop;host 不足两段时回退为app.acme-ai.desktop; - updaterEndpoints:默认空数组,即关闭应用内更新——这是刻意的设计,品牌包与官方更新源彻底隔离(见踩坑第 3 条)。
第二步,跑品牌脚本:
node packages/desktop-shell/.agents/skills/desktop-brand-builder/scripts/brand-create.mjs \ --shell-root /absolute/path/to/qwen-code/packages/desktop-shell \ --config /absolute/path/to/brand.json脚本按顺序完成三个补丁:改写tauri.conf.json(应用名、identifier、短描述、updater 端点)、从 logo 重建整套图标、替换 bootstrap 的品牌字符串与 logo 引用。成功时打印一份 JSON 报告(appName、appId、artifactPrefix、icons、bootstrapPatched等),直接拿它当交付摘要。脚本在手就不要手工去改tauri.conf.json和图标文件——它是打补丁的唯一权威。
第三步,打包:
cd packages/desktop-shell npm run build:runtime --workspaces=false # 按宿主平台捆绑 Node 运行时 npx tauri build # 当前平台产物落在src-tauri/target/release/bundle/下,子目录dmg/(macOS)、nsis/(Windows)、appimage/与deb/(Linux),对应 tauri.conf.json 里声明的 bundle targets;交叉编译则是src-tauri/target/<triple>/release/bundle/。
三个补丁背后的设计动机
脚本可信,是因为它把几个容易出事的边角都提前处理了。
pubkey 置空字符串,而不是删字段。tauri-plugin-updater的 schema 声明pubkey: String且没有 serde 默认值——把字段整个删掉,应用启动时反序列化直接失败;而端点为空时,空字符串 pubkey 意味着根本不会跑更新检查,无害。
缺 updater 段时 fail closed。如果品牌提供了端点或公钥,但目标tauri.conf.json里没有plugins.updater段(fork 或手改过的 shell-root 就可能缺),脚本在任何文件写盘前就报错退出。否则报告会显示“已应用”,实际交付的却是一个永远无法更新的品牌包。
logo 以 argv 传递,不经过 shell。脚本先用require.resolve直接定位 Tauri CLI 入口,再以spawnSync(process.execPath, [...])调用,logo 路径只是普通 argv 元素,不经过任何命令解释器——文件名里带$(cmd)或反引号也不会触发注入。
JS/HTML 两种语境分别转义。bootstrap.js里应用名在单引号字符串内,先经JSON.stringify完整转义(只转单引号不够:Bob's App\这种以反斜杠结尾的名字会“逃逸”掉结束引号);index.html里则按&→<→>→"→'做 HTML 实体转义,先转&避免二次转义。两处替换都用函数式 replacer,防止$&之类的模式被展开。
汇总成一张输入 → 校验 → 行为表:
| 输入 | 校验规则 | 不满足时行为 |
|---|---|---|
brandId | 匹配^[a-z][a-z0-9-]*$ | 立即报错,不写任何文件 |
logo | 文件存在,扩展名在 png/jpg/jpeg/svg/ico/webp 内 | 立即报错 |
updaterEndpoints非空 | 必须同时提供updaterPubkey | 立即报错——否则每次更新签名校验都失败 |
appName | 不得等于Qwen Code Desktop | 立即报错——等于默认值会使单次使用守卫失效 |
提供 updater 配置,但目标无plugins.updater段 | — | fail closed,一个文件都不写 |
| Tauri icon CLI 不可用 | logo 为 PNG 时兜底 | 只替换icons/icon.png,警告其余尺寸仍是旧 logo |
踩坑手册:四个容易翻车的点
交叉编译为什么必须逐目标重跑 build:runtime
- 现象:交叉编译出的安装包能装上,启动即报 exec format error;
- 原因:prepare-runtime.js 按
QWEN_DESKTOP_TARGET环境变量(默认宿主平台)下载并捆绑对应平台的 Node 运行时。换目标后不重跑,装进产物的是错误架构的 Node 二进制; - 解法:每次
tauri build --target前都带着环境变量重跑一次:
QWEN_DESKTOP_TARGET=aarch64-apple-darwin npm run build:runtime --workspaces=false npx tauri build --target aarch64-apple-darwin该变量接受aarch64-apple-darwin等 Rust triple(共五个受支持目标,不支持的直接抛错)。target: all就按“build:runtime → tauri build”逐目标迭代,且只跑当前机器或 CI 支持的目标——文件真实存在之前,不要声称产出了跨平台产物。
单次使用守卫:同一克隆里不能重跑脚本
- 现象:第二次运行被拒,提示 shell 已品牌化;
- 原因:守卫检测
productName是否仍为Qwen Code Desktop。bootstrap 补丁依赖原始字面量,pubkey/endpoints 的变更也无法回滚,重跑会在品牌文件里重复拼接字符串; - 解法:每个品牌一个全新克隆;
brand.json写错了就丢弃整个克隆重来,而不是在补丁过的树上“修补”。
品牌包与官方更新源彻底隔离
- 现象:默认配置的品牌包完全不会轮询更新;
- 原因:设计上品牌包永远不得轮询官方 Qwen Code 更新源,官方更新源也永远不得更新品牌包;端点为空时脚本还会同步关掉
bundle.createUpdaterArtifacts,避免 Tauri bundler 继续产出没人消费的签名更新产物; - 解法:需要应用内更新时自建更新源,把
updaterEndpoints指向它,updaterPubkey配签名用的公钥。
签名与自建更新源:密钥对必须自备
- 现象:品牌构建默认不签名,直接分发就是未签名包;
- 原因:上游发布流水线的 Apple/Windows 签名密钥与更新私钥只属于官方 Qwen Code 发布,复用等于拿别人的身份发自己的包;
- 解法:自己生成一对:
npx @tauri-apps/cli signer generate -w ~/.tauri/my-brand.key.key是私钥,放进构建 CI 作为TAURI_SIGNING_PRIVATE_KEY;.pub里的 base64 公钥填入brand.json的updaterPubkey。两者必须配对——更新器用这把公钥校验每个更新签名,配错或缺失都会让应用永远无法更新。
交付前验收:产物、指纹与上报项
| 验收项 | 动作 |
|---|---|
| 产物位置 | 确认在src-tauri/target/release/bundle/(交叉编译为src-tauri/target/<triple>/release/bundle/),子目录应为dmg/、nsis/、appimage/或deb/ |
| 指纹 | 对每个产物算sha256sum(macOS 用shasum -a 256) |
| DMG 完整性 | macOS 上对 DMG 跑hdiutil verify |
| 上报内容 | 产物路径、SHA-256、应用名、appId、构建目录 |
| 构建失败时 | 保留构建目录供事后排查,返回最后有用的错误行与完整日志路径;不删目录,也不在同一克隆里重试品牌步骤 |
写在最后
白标换肤场景,一份brand.json加一个零依赖脚本就能覆盖“名字、图标、启动页”三个挂载点;需要应用内更新时,再叠加自备的签名密钥对和自建更新源即可——而整个流程不会触碰官方更新分发的任何一个字节。
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考