README.zh.md 31 KB

DeepSeek Harness 桌面端

English | 中文

桌面应用是完整 dsh Web 应用外的一层 Electron 壳。Electron RunAsNode 子进程启动共享 profile runner,Electron 立即从 dsh-app://app/ 加载打包内的 Web 入口。共享加载页等待 Host 启动注入,然后在同一文档中启动客户端。Electron 将应用 HTTP 请求转发给已认证的 Web Host;WebSocket 流连接到该 Host,仅为归属的应用窗口附加凭据。Node IPC 承载启动注入、就绪与关闭。Desktop 默认使用端口 19387,与 Web 的 3080 分开;可通过 webserver.config.port patch 覆盖。

关键技术决策

设计师原稿位于 resources/icon.png 和 resources/icon.svg;平台适配保留鲸鱼与渐变,分别位于 resources/icon-windows.* 和 resources/icon-macos.*。将各平台 SVG 导出为透明的 1024×1024 PNG。electron-builder 为 Windows 应用、安装程序和卸载程序生成多尺寸 ICO(Windows 图标要求)。安装页面在两种主题下使用匹配的图案;卸载程序的欢迎和完成页共用 installer/assets/uninstaller-sidebar.png,准备阶段将其转换为 164×314 BMP。

macOS PNG 使用带留白的圆角底板,供传统 ICNS 打包使用,包含最高 1024 像素的表示。它是扁平图标,并非 Icon Composer 文档。Apple 的应用图标指南要求向 Icon Composer 提供未遮罩的图层;这些输入需要在 macOS 上单独导出,不能复用已做圆角的 ICNS 图案。发布前须在支持的 macOS 版本中验收 Finder 和 Dock 的显示效果。

内置工作区依赖

当前 Windows Python 产物包含未签名的原生扩展。本机验证中,Smart App Control 阻止了 _decimal、pyexpat、_lzma 和 _uuid;该主机上的 XML 和 LZMA 操作失败。numpy/pandas 冒烟检查通过,不代表所有扩展都兼容。

Desktop 携带独立的 Python、Node.js 和 pnpm 分发包,并在 Python 的 site-packages 中预装 numpy 和 pandas。load_workspace_dependencies 工具首次使用时,将该产物离线安装到 $DSH_HOME/dsh-runtimes/dsh-primary-runtime(通常为 ~/.dsh/dsh-runtimes/dsh-primary-runtime),并返回解释器、pnpm 脚本和库目录的绝对路径。pnpm 脚本通过返回的 Node 可执行文件运行。返回的 Node 库目录为随包交付的库预留,不是 pnpm 的全局安装目录。

该产物随 Desktop 版本发布。runtime.json 记录 Desktop 版本、目标平台和组件版本;匹配的安装会被复用,版本不同时在完整暂存副本完成后替换目录。添加到该目录的 Python 包在同一版本内保留,升级时随应用基线一起替换。目录替换失败时保留之前的安装;解释器仍在运行时,Windows 可能拒绝替换。

该工具不修改 PATH、环境变量或用户包管理器配置。pnpm 的全局包、命令入口和 store 保留自身默认值及用户设置,包括环境不支持全局安装时的原生错误。不提供独立依赖更新器。第一方 Runtime 决策记录这些选择。

Node 准备内置解释器和 Python 库,无需系统 Python 或 pip。下载锁固定解释器压缩包及目标平台 wheel 的 URL 和哈希;pnpm 使用 Desktop 构建依赖锁。支持的库 wheel 直接解压到 site-packages;需要其他安装目录的 wheel 会被拒绝,不生成包的命令行包装器。本机目标检查在清理暂存目录后以及 macOS 签名后执行内置解释器及 numpy/pandas 运算。独立 Node 可执行文件获得 V8 所需的 JIT 权限。跨目标执行和签名安装需要对应的发布主机。dev:desktop 和 start:desktop 都会在启动 Electron 前准备 .desktop-build/targets/<target>/runtime/primary-runtime;首次准备可能需要下载锁定的依赖。

决策 原因 直接结果
发布身份 桌面壳 API、Web 客户端、后端与插件依赖图作为一个组合完成验证;独立版本会产生未经验证的组合,并让更新可用性含糊不清。 Electron 与 @deepseek-ai/dsh 始终使用同一精确版本。即使桌面壳代码不变,升级 dsh 也必须发布新 Desktop 版本。
运行时 应用必须能够在没有系统 Node.js 或 pnpm 的机器上运行。 dsh 通过设置 ELECTRON_RUN_AS_NODE=1 和 --expose-internals 的 Electron 运行,所有包操作都使用内置 pnpm。包管理器配置和 Host 环境遵循用户设置。包脚本通过 node shell 启动器转发给 Electron。
包来源 即使离线,启动时安装核心依赖也会增加开销。 app.asar/dsh 携带完整生产依赖树;profile 只安装外部插件。
共享模块 Host API 可能依赖模块身份。 共享 profile runner 在 Desktop profile 内补全安装包与 bundle 缺失的依赖;pnpm 管理的包优先。
状态归属 共享可执行依赖图会让 CLI(命令行界面)与 Desktop 相互改变 dsh、Cordis、插件或原生模块版本,而两个桌面进程还可能争用同一个 profile。 Electron 在访问任何 profile 前获取进程生命周期单实例锁,并独占 $DSH_HOME/profiles/desktop 及其包管理器状态。CLI 与 Desktop 共享 $DSH_HOME 下受支持的产品数据,但绝不共享可执行包、插件激活、锁文件或 node_modules。
传输 复用 Web 服务与认证,让应用行为由同一份实现负责。 Electron 直接加载 Host 的认证 HTTP URL;子进程 IPC 承载生命周期消息,本地壳协议提供启动和管理页面。
插件变更 包安装和 Host 启动可能失败。 Desktop 停止 Host 后直接修改当前 profile。失败保留部分修改供用户修复,不自动回滚 profile。
更新 桌面壳与 dsh 独立更新会重新产生版本分裂,而桌面壳未变化的数据块不应强制完整传输。 Electron 壳、匹配的 dsh 运行时与 pnpm 组成一个已签名更新单元。平台更新产物可以复用未变化的数据块,但运行时版本选择绝不脱离 Desktop 发布。

薄壳决策负责共享 Web 行为与 Desktop 适配。Electron 打包与更新决策负责发布身份、签名及更新验收。

安装归属

Electron 拥有 $DSH_HOME/profiles/desktop。其 dependencies 包含 pnpm 安装的包;dsh.profile.bundles 包含内置 bundle,后接已启用插件。签名应用从 resources/app.asar/dsh 提供 dsh、私有 Desktop Host 及其生产依赖。打包应用选择 runtime profile 解析,不创建包链接;开发 profile 使用文件系统链接。宿主与插件在同一个 Electron Node 模式进程中执行;Desktop 不启用 --preserve-symlinks。CLI 不能启动或修改此 profile。

本地启动页面展示启动状态及可用恢复操作。产品渲染进程使用 Web 应用的 HTTP API。独立插件窗口接收结构化的列表、安装、移除、更新和检查更新操作;两个渲染进程都不会获得文件系统、原始 Electron IPC、shell 或任意 pnpm 参数访问权。

产品 UI 保留 Web 操作,包括通过共享认证 HTTP 路由执行的“打开方式…”。Desktop 使用 Web 的自动目录选择机制,并以共享 Web 模板的 bundle 列表和 patch 重载策略初始化新 profile。

Electron 根据应用 locale 选择类型化的英文或中文桌面壳文案,并以英文作为 fallback。菜单、原生对话框、启动页与插件管理渲染进程使用同一 locale 数据;仓库的 Client UI i18n gate 会检查这些桌面源文件。

Electron 原生“编辑”菜单为当前聚焦窗口提供撤销、重做、剪切、复制、粘贴和全选命令及平台快捷键。右键点击可编辑输入区域会打开不带快捷键标注的这些命令,其可用状态由 Chromium 提供;选中的只读文本提供“复制”命令。

运行时与插件激活

签名资源中的 resources/app.asar/dsh/desktop-runtime.json 绑定 shell 版本、Electron 的 Node 版本、平台、架构、共享包版本和最终文件清单。启动读取元数据,并检查共享包记录。发布 schema、shell 版本、目标兼容性和文件完整性在打包时验证。首次启动不会把核心包复制到 profile 存储或通过 pnpm 安装核心包。

  1. 主窗口在 profile 准备或后端启动前,从打包静态资源显示共享 Web 加载页。共享 profile 初始化创建缺失的 manifest、空用户 patch 与 pnpm workspace 文件,不覆盖现有文件。实际 Host 仅启动一次,并通过共享 profile runner 补全缺失的模块链接。
  2. 应用升级时,共享 profile runner 刷新其拥有的模块链接,不检查插件 peer 要求。插件文件、配置、版本与锁文件保留原位;不运行 pnpm。
  3. Electron 的 Node 版本、平台或架构变化时保留已安装插件。原生兼容性问题在加载时报错,可通过 pnpm 修复。
  4. 插件添加、更新和删除使用内置 pnpm 及其正常的用户和 profile 配置。Desktop 不覆盖 registry、npmrc、缓存或 store,新 profile 不添加构建许可列表或严格构建设置。插件管理页提供可取消的行内版本表单;版本和范围交给 pnpm,也允许提交已安装版本以重装。包规格交给 pnpm,包括本地目录、Git、tarball 和别名。相对路径从 Desktop profile 目录解析。声明 dsh.bundle.patch 的包作为 bundle 启用;普通依赖安装后不自动启用。Desktop 不扫描插件依赖图,也不在 Host 启动前验证 patch 文件。自定义 profile 元数据和 bundle 顺序会保留。已安装元数据不可读时,仍能列出、禁用和删除依赖;无法读取已安装版本时,列表使用依赖规格。
  5. 插件变更在直接修改当前 profile 前停止后端。准备成功后启动 Host。包操作或 Host 启动失败会保留已修改文件并报告错误。Desktop 不创建 staging 目录、激活日志或回滚副本。

CLI 与 Desktop 共用已安装依赖清单及 bundle 列表协调逻辑。bundle 声明遵循与启动一致的安装目录优先解析顺序。CLI 操作自动启用已安装 bundle;Desktop 更新后保留通过 UI 禁用的 bundle 状态。两条路径都不要求已安装元数据可读才能列出或移除依赖。

加载页不依赖 Host。错误页提供重启和重装指导。运行时资源支持 profile 恢复时,即可禁用插件和重置 Desktop,包括开发模式;早期初始化失败只提供重启。应用菜单仍提供插件管理器入口。插件修改不自动回滚。

Host 错误诊断仅保留 stderr 输出的最后 64 Ki 个字符。更早的输出会被丢弃,避免长期运行的 Host 使壳的诊断缓冲区无限增长。

重置删除 $DSH_HOME/profiles/desktop 中除所持事务锁外的所有条目,然后初始化内置 profile。它删除 Desktop 配置和已安装第三方包,不保留备份。共享任务、设置和 Harness-home .env 保持不变。壳资源和 preload 失败时使用独立文档显示可用恢复操作和诊断;其控件不依赖 preload。

包事务独占 $DSH_HOME/profiles/desktop/lock 直到 pnpm 进程退出。pnpm 运行前,共享模块补全 helper 仅移除其拥有的链接,并保留 pnpm 管理的目录;Host 在启动时重新创建所需链接。重置保留 profile 目录与锁,直到初始化和 Host 启动结束。链接清理保留目标目录。原生构建遵循 pnpm 配置的构建策略;发布准备负责独立的构建时许可列表。

开发

dev:desktop 会构建当前 Host、客户端 bundle、Web 前端和 Electron 壳,把已构建的 CLI 包、私有 Desktop Host 包及其 workspace 依赖投影为一次性桌面 npm 项目,然后直接启动 Electron;这条路径不从 npm 解析 dsh:

pnpm run dev:desktop

开发 Harness 状态默认写入 apps/desktop/.desktop-build/development/home,一次性 npm 项目位于 apps/desktop/.desktop-build/development/project,Electron 浏览器数据则位于 apps/desktop/.desktop-build/development/electron-user-data。因此,会话、设置、凭据、包链接和浏览器数据都不会进入用户正常使用的 Harness home;显式 DSH_HOME 只会替换开发 Harness home。Renderer DevTools 默认自动打开,Main、Renderer 和 dsh Host 调试端口依次为 9229、9222 和 9230。DSH_DESKTOP_MAIN_INSPECT_PORT、DSH_DESKTOP_RENDERER_DEBUG_PORT 与 DSH_DESKTOP_HOST_INSPECT_PORT 可以替换这些端口,DSH_DESKTOP_OPEN_DEVTOOLS=0 则保持 Renderer 调试窗口关闭。

显式构建完成后,start:desktop 会重新生成一次性项目,并跳过构建直接启动已有产物:

pnpm run start:desktop

Workspace 开发使用 Electron RunAsNode 运行当前 CLI 与私有 Desktop Host 包,插件管理和恢复使用 $DSH_HOME/profiles/desktop,与一次性工作区运行时分离。开发与打包 profile 都使用正常的 bundle 解析,包括链接包。需要验证 Electron RunAsNode、内置 pnpm、内置 dsh 资源、插件安装和修复时,应运行未封装安装器的应用目录。

打包

打包、上传以及手动 macOS 签名检查使用 apps/desktop/.env.windows 或 .env.macos,由目标平台选择。复制对应的 Windows 模板 或 macOS 模板,填写本机配置;Git 忽略这两个本地文件,安装产物也不包含它们。发布字段只从目标文件读取,不回退到系统或 shell 中的同名变量;PATH、代理和构建工具环境仍保留。文件使用 UTF-8,支持 BOM;相对证书、SignTool、Apple API Key 和钥匙串路径以 apps/desktop 为基准,变量值不做 shell 展开,包含 # 或空格的密码需要引号。CI 同样在运行前生成目标文件。

每条打包命令在构建与下载前检查应用 ID、更新地址和该模式需要的签名配置。macOS 检查身份、Team ID、一套完整公证凭据以及引用的 API Key 和钥匙串文件;Windows 检查公开代码签名证书、SignTool 文件、容器名称和 PIN 格式。仅准备 Windows 资源或显式未签名打包不要求签名凭据。配置检查不验证 PIN 是否正确、Token 是否登录、钥匙串是否解锁或 Apple 是否接受凭据;实际签名与公证负责这些检查。单独运行相同检查:

pnpm --dir apps/desktop run check:package

无需提前执行 prepare:desktop:

pnpm run package:desktop

发布自动化使用固定目标命令,确保运行时准备、dsh 准备与 electron-builder 接收相同的平台和架构:

pnpm run package:desktop:mac:arm64
pnpm run package:desktop:mac:x64
pnpm run package:desktop:win:x64

macOS arm64 命令要求 Apple Silicon。macOS x64 命令可以在 Intel macOS 或带 Rosetta 的 Apple Silicon 上运行。Windows x64 命令要求 Windows x64。Linux 不是受支持的 Desktop 发布目标。

每个目标都在 apps/desktop/.desktop-build/targets/<target>/ 下持有自己的打包输入、已准备运行时、包集合、dsh 依赖树、pnpm 准备状态、未打包应用、更新元数据和最终产物。Electron 归档缓存继续由 .desktop-build/downloads 共享,因为每个归档文件名都包含版本、平台和架构,并且在解包前经过验证。目标构建绝不读取其他目标的可变准备状态。

运行时文件筛选

生产包首先经过 npm 发布规则和依赖安装。桌面文件规则随后在签名和完整性封存之前过滤不可变的 resources/app.asar/dsh/node_modules 副本。它排除 TypeScript 声明、明确属于 JavaScript/CSS/TypeScript 的 source map、TypeScript 构建缓存、Domino 测试目录、指定的原生编译产物,以及其他平台的 node-pty 预构建文件。它保留运行时 JavaScript、原生模块及其 DLL/EXE 辅助程序、WASM、未知资源、许可证和声明。规则不会修改 npm tarball、内置包管理器或用户安装的插件文件。

打包应用运行编译后的 JavaScript 和预生成的 Typert 元数据,不编译 TypeScript 插件。源码级调试导航和编辑器声明仍可从开发包中获取。复制规则测试覆盖排除项和保留资源;prepare:dsh 在 Host smoke 和最终清单验证之前,使用 Electron RunAsNode 执行产物 smoke。

Windows 发布验收还需在 Desktop 构建后手动运行原生清理和替换检查。将 $Electron 设为已准备的 Electron 可执行文件,将 $Makensis、$SevenZip 和 $PluginDir 分别设为锁定版本构建器的 NSIS 编译器、7-Zip 可执行文件和 x86-unicode NSIS 插件目录。从仓库根目录运行以下命令。它验证 Electron junction 清理、目录替换与回滚和两种文件占用替换方式;不属于单元测试通道。

pwsh -NoProfile -File apps/desktop/scripts/smoke-windows.ps1 -Electron $Electron -Makensis $Makensis -SevenZip $SevenZip -PluginDir $PluginDir

Windows 安装器先将新版本解压到安装目录旁边,再退出旧应用并通过同卷目录改名完成替换。同路径升级在替换成功前保留旧目录;解压失败时旧版不变,替换失败时尝试恢复旧目录。安装器在启动前清理旧版备份。强制结束安装器或断电可能留下 .new-* 或 .old-* 目录;不同安装位置或安装范围迁移仍使用 electron-builder 的旧卸载器流程。

上传更新

DSH_DESKTOP_AUTO_UPDATE_ENV 同时选择打包时写入的更新 URL 与后续 COS 上传目标,可取 test 或 production;未设置时使用 test。测试打包必须通过 DOWNLOAD_TEST_ORIGIN 提供 HTTPS origin,生产 origin 仍为 https://download.deepseek.com。上传还必须通过 DOWNLOAD_TEST_COS_BUCKET 或 DOWNLOAD_PROD_COS_BUCKET 提供所选环境的 COS bucket。目标路径为 _/harness/desktop/stable/<target>/,其中 target 为 mac-arm64、mac-x64 或 win-x64。

更新目标与上传凭据都与所选环境对应:

环境 公开 origin COS bucket COS 凭据
test 或未设置 DOWNLOAD_TEST_ORIGIN DOWNLOAD_TEST_COS_BUCKET DOWNLOAD_TEST_COS_SECRET_ID、DOWNLOAD_TEST_COS_SECRET_KEY
production https://download.deepseek.com DOWNLOAD_PROD_COS_BUCKET DOWNLOAD_PROD_COS_SECRET_ID、DOWNLOAD_PROD_COS_SECRET_KEY

在目标 .env 中配置更新地址与所选 COS bucket、SecretId、SecretKey,再打包并上传同一个目标:

pnpm run package:desktop:mac:arm64
pnpm run upload:mac:arm64

生产发布需在打包前在目标 .env 中设置 DSH_DESKTOP_AUTO_UPDATE_ENV=production,再在执行 upload:mac:arm64、upload:mac:x64 或 upload:win:x64 前提供 DOWNLOAD_PROD_COS_BUCKET 与生产凭据对。打包不要求 COS bucket 或凭据。它会明确禁止 electron-builder 发布,从其子进程中删除全部四个 COS 凭据字段,并且只有在 electron-builder 以及全部签名或公证钩子成功后才写入目标完成记录。上传会先要求该记录与所选环境、目标、公开 URL 和当前 dsh 版本一致,再要求根 dsh 版本、Desktop 版本、频道元数据版本、产物名称、大小与 SHA-512 全部一致,之后才读取所选 COS 凭据对。它只上传该目标不可变且带版本的产物,最后以 no-cache 上传根据版本得出的频道元数据,并且不会删除历史对象。稳定版本使用 latest-mac.yml 或 latest.yml;alpha 等预发布版本则使用 alpha-mac.yml 或 alpha.yml,与 electron-builder 生成的文件名一致。

macOS 配置使用必填发布环境,不会接受钥匙串中最先发现的证书。空值、格式错误的 Team ID、包含 electron-builder 不支持的 Developer ID Application: 前缀的签名身份,以及不完整的公证凭据都会被拒绝。macOS 打包要求已配置的身份及其私钥可用。运行时准备会把该身份、安全时间戳与 hardened runtime 应用到每个内嵌 Mach-O 文件;应用签名完成后,深度严格检查会拒绝其他叶证书 Authority 或 Team ID,验证通过才生成发布产物。macOS 固定目标安装包命令为已签名应用创建独立副本,并发执行两条产物流。一路先公证 App 并钉票,再生成 ZIP 及其更新元数据。另一路把已签名 App 副本封装进签名 DMG,再公证 DMG、钉票并验证;其中的 App 不单独附加票据。只有两路均成功结束,产物才会移入最终目录并写入发布完成记录。仅生成目录的命令同样需要公证凭据,并等待 Apple 公证和 App 钉票完成。并行公证决策负责副本隔离与容器票据语义。私钥可以来自登录钥匙串或 electron-builder 的标准 CSC_LINK 输入;环境中的 CSC_NAME 与证书发现顺序都不能选择发布所有者。公证凭据也可以使用 electron-builder 支持的完整 Apple ID 或钥匙串 profile 方式。手动执行 pnpm --dir apps/desktop run verify:mac-signature -- <path-to-app> 重复应用检查时,也必须提供两个 macOS 身份变量。

macOS 签名遍历真实文件,不跟随 Framework 的软链接别名。PAK 资源保留全部随附语言,由外层 Framework 或应用签名记录完整性,不逐个签名。发布策略负责依赖补丁和验证要求。

可通过公司代理加速向 Apple 公证服务上传。代理配置参见公司内部文档。

未签名 Windows 测试安装包

在 Windows x64 上,使用完整的未签名打包命令进行本地安装测试:

pnpm run package:desktop:win:x64:unsigned

该命令要求设置 DSH_DESKTOP_APP_ID 并具备常规构建依赖,包括编译原生模块所需的 Python 和 Visual C++ 构建工具。Python 不在 PATH 中时,将 PYTHON 设置为其可执行文件路径。命令将安装包写入 .desktop-build/targets/win-x64/unsigned-artifacts/,省略自动更新配置,清除签名凭据,且不生成发布完成记录。它不需要 EV 凭据或更新源地址。签名打包和上传命令仍遵循正式发布要求。

Windows 安装界面

Windows 安装程序使用原生 NSIS 页面,提供亮暗配色、系统阴影、可编辑的安装目录,以及默认勾选立即启动的完成页。安装仅面向当前用户。点击安装或按 Enter 均校验当前路径;新安装位置必须为空,非空位置必须是已登记的安装目录。受影响安装路径中的程序运行时显示系统提示,并保持应用运行;其他目录中的同名应用不阻止安装。静默更新最多等待受影响应用退出十秒,若仍在运行则以退出码 2 结束。

主题在启动时跟随 Windows;可用 /THEME=light、/THEME=dark 和 /THEME=auto 显式选择配色。窗口在品牌控件准备完成后显示。进度读取锁定版本的 7-Zip 解压器百分比;目录替换、注册和清理仍使用有界估算。加权百分比不代表剩余时间。NSIS 报告成功后,进度条用 600 毫秒补满并短暂显示 100%,再显示完成页;切换目标时长为 750 毫秒。完成页保留窗口位置。点击完成后,安装程序先隐藏窗口,再启动已安装的可执行文件;启动失败会恢复页面以供重试。目录替换和失败恢复遵循上文描述的安装流程。首次启动的配置档案准备仍属于独立的 Desktop 操作。

Windows 打包使用 Visual C++ Build Tools 和 Windows SDK 编译 x86 Win32/GDI+ 辅助库;签名构建通过已配置的 Windows 签名器对该库签名。准备钩子在所有平台上均由 electron-builder 继续负责收集生产依赖。安装界面决策记录 NSIS 接入方式和发布验证要求。

在有交互式桌面的 Windows x64 上,从仓库根目录运行 pnpm --dir apps/desktop run test:installer,可将小型原生测试载荷接入正式安装配置并执行验证。每次运行使用独立产品身份,依次验证仅英文和仅中文的安装器变体,并根据实际显示的欢迎页按钮选择测试文案。两个变体均安装到私有目录并在测试后卸载;截图和结果保留在 .desktop-build/installer-tests/ 下。检查包含末尾带分隔符的已登记路径升级,以及磁盘根目录拒绝。可选的 --signed 标志使用下文的 Windows EV 配置,在嵌入前对测试程序和辅助库签名;它不会启用更新源。

Windows EV 签名

本项目使用的 SafeNet Token 出现 SignTool Error: No private key is available. 时,说明 PIN(密码)错误。立即停止所有签名尝试,等待用户处理 PIN 后再继续。PIN 输错达到五次会锁定 Token。遇到该错误后,不得重试打包或签名探针。签名器串行执行 Token 操作,首次失败后拒绝所有排队任务。

Windows 打包将 7-Zip 过滤器固定为 BCJ,以兼容内置的 NSIS 解码器。这样可以保留 x64 安装包中由依赖携带的 ARM64 二进制文件;自动 ARM64 过滤会生成该解码器无法解压的条目。

NSIS 在安装阶段清理临时解压目录,完成后才显示完成页或自动启动应用。已安装的生产依赖保持为普通文件;启动时不会再次解压。安装仍会写入完整的应用目录树。

在 .env.windows 中填写 DSH_DESKTOP_WINDOWS_CER_FILE(公开 EV 叶证书)、DSH_DESKTOP_WINDOWS_SIGNTOOL(SafeNet 兼容的 SignTool)、DSH_DESKTOP_WINDOWS_KEY_CONTAINER(匹配的私钥容器)和 DSH_DESKTOP_WINDOWS_TOKEN_PIN(Token Password)。私钥仍保留在 USB Token;不要把证书或本地凭据文件提交到 Git。

pnpm run package:desktop:win:x64

打包前插入并解锁 Token。electron-builder 钩子把每个产物交给采用 CRLF 的 scripts/windows-sign.cmd;该 CMD 只调用一次已配置的 SignTool,并指定 /f、SafeNet /kc "[{{PIN}}]=容器"、/csp "eToken Base Cryptographic Provider"、SHA-256 文件摘要和 DigiCert SHA-256 RFC 3161 时间戳。钩子不会改用 electron-builder 内置的 SignTool,也不会重试失败的签名请求。SignTool、证书、容器、PIN、Token 或签名不可用时,Windows 发布打包会失败,不会生成未签名产物。

PIN 不能包含 ]、引号或换行,因为这些字符用于分隔 SafeNet /kc 值或对应的 CMD 参数。CMD 会禁用延迟展开,因此包含 ! 的 PIN 可以原样到达 SafeNet。打包流程不会把任何 DSH_DESKTOP_WINDOWS_* 字段传给构建与 运行时准备子进程;它只向 electron-builder 提供四个配置输入,在其他字段已经清理的环境中只向签名 CMD 提供经过校验的签名字段,在 SignTool 启动前清除这些字段,并遮盖 SignTool 诊断。SafeNet 仍要求 PIN 出现在 SignTool 进程命令行中。本地 .env.windows 明文保存 PIN,应限制文件访问权限;CI 使用临时文件并在任务结束后删除。不要提交或分享文件内容,也不要把凭据写入日志。配置检查不会消耗 Token 的 PIN 尝试次数;签名仍在首次失败后停止整批任务。

使用对应的 :dir 命令可以生成可直接运行的应用目录,而不是安装包,例如:

pnpm run package:desktop:dir
pnpm run package:desktop:mac:arm64:dir

需要检查或诊断为宿主目标准备的资源而不调用 electron-builder 时,可以让同一流水线在准备完成后停止:

pnpm run prepare:desktop

这条诊断命令是另一种停止位置,并非两条命令构建流程的前半段。之后执行 package:desktop* 时仍会重新完成正式构建与准备,避免使用陈旧的 dsh 包、运行时文件或 dsh 内容。

每条打包命令都会构建仓库,打包以 dsh 和私有 Desktop Host 为根的第一方生产依赖闭包,并准备目标专用的 Electron 分发包与 pnpm CLI。prepare:dsh 在构建时安装一次生产依赖图,准备物化包供 electron-builder 归档到 app.asar/dsh,移除包管理器元数据,并生成包含共享包版本和最终文件哈希的 desktop-runtime.json。在 macOS 上,它先签名并验证原生文件,再生成清单;electron-builder 不对已签名的此目录重复进行嵌套签名。资源映射明确包含默认根目录过滤器会忽略的 dsh/node_modules;准备完成的运行时清单在原生签名后检查。原生可执行文件及库解包到 ASAR 旁;Python、独立 Node 和 pnpm 保留在外部 runtime 资源中。签名安装包、公证、已安装应用升级和各目标原生模块的验收需要发布环境。

未压缩产物包含 Electron、物化后的 dsh 生产依赖树、pnpm,以及壳应用。安装包大小与文件系统占用不同;发布验收需要测量两者,以及 profile 插件存储和首次启动耗时。此布局用更多应用内文件换取消除用户机器上的核心包安装过程。

更新

打包应用会在主窗口打开十秒后检查目标专用的发布流;本地化的 检查更新… 菜单项会手动触发同一检查。发现可用版本时,应用打开一个原生确认弹窗。用户确认后,应用等待正在进行的检查完成,下载并验证已签名的 Desktop 发布、停止 dsh 子进程,并把安装与重启交给 electron-updater。下次启动在显示本地加载页的同时校准版本绑定的运行时。

签名打包为 DSH_DESKTOP_AUTO_UPDATE_ENV 选择的部署生成 generic-provider 频道元数据。NSIS 差分包与 macOS ZIP 目标让 electron-updater 可以复用未变化的数据块;供手动安装的 DMG 经过公证,但不生成 blockmap,因为它不是 macOS updater 的载荷。运行时与桌面壳仍属于同一个签名 Desktop 发布。macOS 签名与公证凭据使用 electron-builder 的标准环境变量;Windows EV 签名使用上文所述的公开证书、已验证 SignTool、SafeNet 容器和 runner PIN。必填 Desktop 发布环境选择构建所验证的应用身份与平台签名身份。

底层开发覆盖项

未打包的 Electron 进程使用应用目录下的 .desktop-build/development/project 作为开发项目。DSH_DESKTOP_PNPM_ENTRY 和 DSH_DESKTOP_DSH_DIR 用于选择明确的运行时资源。打包应用会忽略这些变量,从 process.resourcesPath 解析签名资源,并使用受管 Desktop profile。

已知限制

  • 发布签名、公证、更新托管和跨上一版本的已安装产物验证需要生产发布环境。
  • 依赖的生命周期脚本遵循 pnpm 的构建权限;Desktop 不提供单独的审批对话框。
  • 桌面壳与 CLI dsh 共享 $DSH_HOME 下的会话、设置、凭据、工作区和存储,但可执行包、插件激活和锁文件彼此隔离。