mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-09 04:02:35 +00:00
feat(desktop): update packaging and auto-update paths for target-specific builds
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md
|
||||
2026-08-25-electron-desktop-packaging-and-updates.md: dc21fc568c2aa416ade9792823becd087a9dff4b
|
||||
2026-08-25-electron-desktop-packaging-and-updates.zh.md: 6928e86db7f184b90aa47005f9c16ed86e0730e7
|
||||
2026-08-25-electron-desktop-packaging-and-updates.md: 3a0a23074615cec6fc4f30c995679b4670660e98
|
||||
2026-08-25-electron-desktop-packaging-and-updates.zh.md: 0b520aeb7fa341e4980c7955f645a6e45b116d23
|
||||
|
||||
+2
-2
@@ -70,7 +70,7 @@ The installer never mutates the active profile in place. It copies profile metad
|
||||
|
||||
The process-lifetime Electron lock is the authoritative Desktop owner. The package transaction lock is depth defense and records the process that can still mutate package state: Electron between package operations and the spawned pnpm PID while pnpm runs. The owner change is truncated, written, and synchronized through the already-open exclusive lock file. If Electron terminates during pnpm execution, a later process observes the live worker and refuses to start a competing store or staging transaction; after that worker exits, the stale PID can be recovered.
|
||||
|
||||
The packaged seed is an offline installation kit, not an executable dsh tree. It contains the release identity, initial desktop-project manifest, a descriptor and immutable tarballs for the first-party package closure rooted at dsh, lockfile, integrity inventory, and required store subset. The release build requires the Electron package and root dsh package to have the same version, creates final npm tarballs from the official source build, selects the reachable dsh and vendored packages plus the Landlock entry, and verifies the dsh tarball's `lib/desktop-host.js` entry and `config/desktop.cordis.patch.yml` overlay. The overlay is the only CLI configuration file published specifically for Desktop; example configurations remain outside the tarball. These tarballs remain the official `pnpm pack` results governed by each package's `files` manifest; Desktop does not remove published declarations or otherwise create a second package-content policy. The manifest lists every selected package as a local direct dependency, automatic peer installation is disabled, and the workspace file overrides every selected first-party name to its local tarball. The build rejects any lockfile that resolves one of those names by registry version. Bundled pnpm disables its global virtual store, materializes external production dependencies from npm without lifecycle scripts, deletes `node_modules` and every temporary pnpm cache, config, and state directory, then performs a clean offline installation from the final store alone and checks both Desktop Host files. Inventory generation follows removal of that second `node_modules` tree and temporary pnpm project registrations. Requiring both files before copying the package set and after offline installation prevents a release whose Host entry loads but cannot compose its required overlay from reaching application signing.
|
||||
The packaged seed is an offline installation kit, not an executable dsh tree. It contains the release identity, initial desktop-project manifest, a descriptor and immutable tarballs for the first-party package closure rooted at dsh, lockfile, integrity inventory, and required store subset. Each `mac-arm64`, `mac-x64`, and `win-x64` build owns its packed packages, runtime, package set, seed, pnpm preparation state, unpacked application, update metadata, and final artifacts under `.desktop-build/targets/<target>`; only the immutable, checksum-verified Node.js download cache is shared. The release build requires the Electron package and root dsh package to have the same version, creates final npm tarballs from the official source build, selects the reachable dsh and vendored packages plus the Landlock entry, and verifies the dsh tarball's `lib/desktop-host.js` entry and `config/desktop.cordis.patch.yml` overlay. The overlay is the only CLI configuration file published specifically for Desktop; example configurations remain outside the tarball. These tarballs remain the official `pnpm pack` results governed by each package's `files` manifest; Desktop does not remove published declarations or otherwise create a second package-content policy. The manifest lists every selected package as a local direct dependency, automatic peer installation is disabled, and the workspace file overrides every selected first-party name to its local tarball. The target Node.js executes bundled pnpm, so pnpm's operating-system and CPU selection makes the materialized dependency graph and seed target-specific. Bundled pnpm disables its global virtual store, materializes external production dependencies from npm without lifecycle scripts, deletes `node_modules` and every temporary pnpm cache, config, and state directory, then performs a clean offline installation from the final store alone and checks both Desktop Host files. The build rejects any lockfile that resolves one of the local first-party names by registry version. Inventory generation follows removal of that second `node_modules` tree and temporary pnpm project registrations. Requiring both files before copying the package set and after offline installation prevents a release whose Host entry loads but cannot compose its required overlay from reaching application signing.
|
||||
|
||||
The seed stores pnpm content in 16 deterministic uncompressed tar shards selected by normalized store path. Apple notarization inspects Mach-O code inside those archives, so macOS seed preparation stages every referenced Mach-O content-addressed object and runs at most four independent Developer ID signers concurrently with a secure timestamp and hardened runtime. A signer failure is observed only after every active signer exits and leaves the original CAS objects and package index unchanged. After all signers succeed, preparation writes each object at its new SHA-512 path and transactionally rewrites every base and side-effects file reference in pnpm's MessagePack SQLite index. A second offline installation proves that pnpm resolves the rewritten store; preparation then shards it, extracts the final archives, and verifies every embedded signature. Package paths and non-native bytes remain unchanged, and the seed retains bundled architecture variants because removing files would create a Desktop-specific package file set. Seed integrity covers the shard manifest and every archive before extraction. Startup validates archive paths, entry types, uniqueness, and counts, extracts every shard into a unique Desktop-owned staging directory, replaces matching immutable store files, and transactionally merges each pnpm store version's SQLite `package_index` into `.dsh/desktop/pnpm/store`. Seed records replace matching keys while records downloaded for Desktop plugins remain. An interrupted file merge may leave valid immutable cache content, but each SQLite merge is atomic, and profile installation and activation still require pnpm integrity and the complete health check.
|
||||
|
||||
@@ -86,7 +86,7 @@ Electron update uses one `electron-updater` release stream and signed `electron-
|
||||
|
||||
Before the new release opens a window, startup reconciles dsh from its packaged seed while retaining installed desktop plugins. The health check covers dependency resolution, native modules, shell API compatibility, backend startup and shutdown, Web assets, and the client boot graph. An incompatible plugin blocks activation and leaves the previous project available for rollback. Startup fails visibly rather than launching a shell and dsh version that do not match.
|
||||
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` selects the test deployment by default or the production deployment for both the target-specific generic-provider URL and COS destination. Release automation supplies the test HTTPS origin through `DOWNLOAD_TEST_ORIGIN` and each deployment's bucket through `DOWNLOAD_TEST_COS_BUCKET` or `DOWNLOAD_PROD_COS_BUCKET`; keeping mutable test routing and COS storage identities out of source lets deployment infrastructure change without a code release, while the public production origin remains fixed. Packaging resolves only the public updater URL, disables electron-builder publishing, removes every COS credential field from its subprocess environment, and writes a completion record only after electron-builder and every signing or notarization hook succeeds. Target upload additionally requires the selected bucket, then requires the completion record, root dsh version, Desktop version, channel metadata version, artifact names, sizes, and SHA-512 values to agree before it reads the selected credentials or sends data. It uploads immutable versioned updater payloads and any separate blockmaps before replacing `latest-mac.yml` or `latest.yml`, and it never deletes historical objects. NSIS embeds its blockmap in the signed executable; the macOS ZIP carries a separate blockmap. Both let electron-updater download changed blocks when supported, while application replacement and the local pnpm staging transaction remain separate operations.
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` selects the test deployment by default or the production deployment for both the target-specific generic-provider URL and COS destination. Release automation supplies the test HTTPS origin through `DOWNLOAD_TEST_ORIGIN` and each deployment's bucket through `DOWNLOAD_TEST_COS_BUCKET` or `DOWNLOAD_PROD_COS_BUCKET`; keeping mutable test routing and COS storage identities out of source lets deployment infrastructure change without a code release, while the public production origin remains fixed. Packaging resolves only the public updater URL, disables electron-builder publishing, removes every COS credential field from its subprocess environment, and writes a completion record only after electron-builder and every signing or notarization hook succeeds. Target upload additionally requires the selected bucket, then requires the completion record, root dsh version, Desktop version, version-derived channel metadata, artifact names, sizes, and SHA-512 values to agree before it reads the selected credentials or sends data. It uploads immutable versioned updater payloads and any separate blockmaps before replacing the channel metadata emitted by electron-builder, and it never deletes historical objects. Stable versions use the `latest` metadata name; prereleases use the first semantic-version prerelease identifier. NSIS embeds its blockmap in the signed executable; the macOS ZIP carries a separate blockmap. Both let electron-updater download changed blocks when supported, while application replacement and the local pnpm staging transaction remain separate operations.
|
||||
|
||||
## Security and release policy
|
||||
|
||||
|
||||
+2
-2
@@ -70,7 +70,7 @@ Electron 拥有保留 profile `.dsh/profiles/desktop`。其中精确的 `@deepse
|
||||
|
||||
进程生命周期 Electron 锁是 Desktop 的权威 owner。包事务锁用于纵深防御,并记录仍能修改包状态的进程:包操作之间记录 Electron,pnpm 运行期间记录已生成的 pnpm PID。Owner 变更通过已经打开的排他锁文件完成截断、写入与同步。如果 Electron 在 pnpm 执行期间终止,后续进程会发现仍存活的 worker,并拒绝启动并发的 store 或 staging 事务;该 worker 退出后,陈旧 PID 才可以恢复。
|
||||
|
||||
打包种子是离线安装包,而不是可执行 dsh 目录。它包含发布身份、初始桌面项目 manifest、以 dsh 为根的第一方包闭包描述文件及不可变 tarball、lockfile、完整性清单和所需 store 子集。发布构建要求 Electron 包与根 dsh 包使用相同版本,从正式源码构建生成最终 npm tarball,选择可达的 dsh 与 vendored 包以及 Landlock 入口,并验证 dsh tarball 中的 `lib/desktop-host.js` 入口与 `config/desktop.cordis.patch.yml` overlay。该 overlay 是唯一为了 Desktop 而发布的 CLI 配置文件;示例配置仍留在 tarball 之外。这些 tarball 保持为由各包 `files` manifest 决定内容的正式 `pnpm pack` 结果;Desktop 不删除已发布的声明文件,也不建立第二套包内容策略。manifest 把每个选中的包列为本地直接依赖,关闭对等依赖自动安装,workspace 文件再把每个选中的第一方包 override 到对应本地 tarball。构建会拒绝任何通过 registry 版本解析这些包名的 lockfile。内置 pnpm 关闭全局 virtual store,在禁用生命周期脚本的情况下从 npm 物化外部生产依赖,删除 `node_modules` 以及所有临时 pnpm cache、config 和 state 目录,然后只使用最终 store 执行一次干净的离线安装,并检查两个 Desktop Host 文件。生成清单前会删除第二次生成的 `node_modules` 和临时 pnpm 项目注册。在复制 package set 前与离线安装后都要求两个文件,可防止 Host 入口本身能够加载、却无法组合所需 overlay 的发布进入应用签名阶段。
|
||||
打包种子是离线安装包,而不是可执行 dsh 目录。它包含发布身份、初始桌面项目 manifest、以 dsh 为根的第一方包闭包描述文件及不可变 tarball、lockfile、完整性清单和所需 store 子集。每个 `mac-arm64`、`mac-x64` 和 `win-x64` 构建都在 `.desktop-build/targets/<target>` 下持有自己的打包输入、运行时、包集合、seed、pnpm 准备状态、未打包应用、更新元数据和最终产物;只有不可变且经过校验和验证的 Node.js 下载缓存会被共享。发布构建要求 Electron 包与根 dsh 包使用相同版本,从正式源码构建生成最终 npm tarball,选择可达的 dsh 与 vendored 包以及 Landlock 入口,并验证 dsh tarball 中的 `lib/desktop-host.js` 入口与 `config/desktop.cordis.patch.yml` overlay。该 overlay 是唯一为了 Desktop 而发布的 CLI 配置文件;示例配置仍留在 tarball 之外。这些 tarball 保持为由各包 `files` manifest 决定内容的正式 `pnpm pack` 结果;Desktop 不删除已发布的声明文件,也不建立第二套包内容策略。manifest 把每个选中的包列为本地直接依赖,关闭对等依赖自动安装,workspace 文件再把每个选中的第一方包 override 到对应本地 tarball。目标 Node.js 执行内置 pnpm,因此 pnpm 的操作系统和 CPU 选择会使物化的依赖图与 seed 成为目标专用内容。内置 pnpm 关闭全局 virtual store,在禁用生命周期脚本的情况下从 npm 物化外部生产依赖,删除 `node_modules` 以及所有临时 pnpm cache、config 和 state 目录,然后只使用最终 store 执行一次干净的离线安装,并检查两个 Desktop Host 文件。构建会拒绝任何通过 registry 版本解析本地第一方包名的 lockfile。生成清单前会删除第二次生成的 `node_modules` 和临时 pnpm 项目注册。在复制 package set 前与离线安装后都要求两个文件,可防止 Host 入口本身能够加载、却无法组合所需 overlay 的发布进入应用签名阶段。
|
||||
|
||||
种子根据规范化 store 路径,把 pnpm 内容放入 16 个确定性的未压缩 tar 分片。Apple 公证会检查这些归档内的 Mach-O 代码,因此 macOS seed 会 staging 每个被引用的内容寻址 Mach-O 对象,最多并发四个独立的 Developer ID 签名进程,并带上安全时间戳与 hardened runtime。任一签名失败后,准备过程会等待已启动的签名进程全部退出,原始 CAS 对象与包索引保持不变。所有签名成功后,准备过程把每个对象写到新的 SHA-512 路径,并以事务方式重写 pnpm MessagePack SQLite 索引内全部基础文件和 side-effects 文件引用。第二次离线安装证明 pnpm 可以解析重写后的 store;准备过程随后完成分片、解包最终归档并验证每个内嵌签名。包路径和非原生字节保持不变;种子保留包内附带的架构变体,因为删除文件会创建 Desktop 专属的包文件集。种子完整性覆盖分片 manifest 和解包前的每个归档。启动时验证归档路径、条目类型、唯一性和数量,把所有分片解包到唯一且由 Desktop 拥有的 staging 目录,替换匹配的不可变 store 文件,并以事务方式把各 pnpm store 版本的 SQLite `package_index` 合并进 `.dsh/desktop/pnpm/store`。Seed 记录替换匹配的键,为 Desktop 插件下载的记录继续保留。中断的文件合并可能留下有效的不可变缓存内容,但每次 SQLite 合并都是原子的,profile 安装与激活仍必须通过 pnpm 完整性与完整健康检查。
|
||||
|
||||
@@ -86,7 +86,7 @@ Electron 更新只使用一个 `electron-updater` 发布流和签名 `electron-b
|
||||
|
||||
新发布在打开窗口前从安装包种子校准 dsh,同时保留已安装桌面插件。健康检查覆盖依赖解析、原生模块、壳 API 兼容性、后端启停、Web 资源和客户端启动图。不兼容插件会阻止激活,并保留上一个项目用于回滚。启动过程会明确失败,而不会运行版本不匹配的壳与 dsh。
|
||||
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` 默认为测试部署,也可以选择生产部署,并同时决定目标专用的 generic-provider URL 与 COS 目标。发布自动化通过 `DOWNLOAD_TEST_ORIGIN` 提供测试 HTTPS origin,并通过 `DOWNLOAD_TEST_COS_BUCKET` 或 `DOWNLOAD_PROD_COS_BUCKET` 提供各部署的 bucket;可变的测试路由与 COS 存储身份不写入源码,部署基础设施变更时无需发布新代码,而公开的生产 origin 仍固定。打包只解析公开更新 URL、禁止 electron-builder 发布、从子进程环境中删除每个 COS 凭据字段,并且只有在 electron-builder 以及每个签名或公证 hook 成功后才写入完成记录。目标上传还必须提供所选 bucket,随后会先要求完成记录、根 dsh 版本、Desktop 版本、频道元数据版本、产物名称、大小与 SHA-512 全部一致,再读取所选凭据或发送数据。它先上传不可变且带版本的更新载荷与所有独立 blockmap,最后替换 `latest-mac.yml` 或 `latest.yml`,并且不会删除历史对象。NSIS 把 blockmap 嵌入已签名的可执行文件,macOS ZIP 则使用独立 blockmap;两者都让 electron-updater 在平台支持时只下载变化的数据块,而应用替换与本地 pnpm staging 事务仍是两个独立操作。
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` 默认为测试部署,也可以选择生产部署,并同时决定目标专用的 generic-provider URL 与 COS 目标。发布自动化通过 `DOWNLOAD_TEST_ORIGIN` 提供测试 HTTPS origin,并通过 `DOWNLOAD_TEST_COS_BUCKET` 或 `DOWNLOAD_PROD_COS_BUCKET` 提供各部署的 bucket;可变的测试路由与 COS 存储身份不写入源码,部署基础设施变更时无需发布新代码,而公开的生产 origin 仍固定。打包只解析公开更新 URL、禁止 electron-builder 发布、从子进程环境中删除每个 COS 凭据字段,并且只有在 electron-builder 以及每个签名或公证 hook 成功后才写入完成记录。目标上传还必须提供所选 bucket,随后会先要求完成记录、根 dsh 版本、Desktop 版本、根据版本得出的频道元数据、产物名称、大小与 SHA-512 全部一致,再读取所选凭据或发送数据。它先上传不可变且带版本的更新载荷与所有独立 blockmap,最后替换 electron-builder 生成的频道元数据,并且不会删除历史对象。稳定版本使用 `latest` 元数据名称,预发布版本则使用语义化版本的第一个预发布标识符。NSIS 把 blockmap 嵌入已签名的可执行文件,macOS ZIP 则使用独立 blockmap;两者都让 electron-updater 在平台支持时只下载变化的数据块,而应用替换与本地 pnpm staging 事务仍是两个独立操作。
|
||||
|
||||
## 安全与发布策略
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write apps/desktop/README.md
|
||||
README.md: 40c0508ed5102908edd12601596fc016f39323e7
|
||||
README.zh.md: 78c3a34e729c242969a6d85f3d20170078c2720d
|
||||
README.md: 8377a26dc1348e943954f898acc7a13001d9802a
|
||||
README.zh.md: f6e14bd213f386f1f4047ef9cdf61e1f73a6b64b
|
||||
|
||||
@@ -98,6 +98,8 @@ pnpm run package:desktop:win:x64
|
||||
|
||||
The macOS arm64 command requires Apple Silicon. The macOS x64 command runs on Intel macOS or Apple Silicon with Rosetta. The Windows x64 command requires Windows x64. Linux is not a supported Desktop release target.
|
||||
|
||||
Each target owns its packed package inputs, prepared runtime, package set, seed, pnpm preparation state, unpacked application, update metadata, and final artifacts under `apps/desktop/.desktop-build/targets/<target>/`. The Node.js archive cache remains shared under `.desktop-build/downloads` because every archive name includes its version, platform, and architecture and is verified before extraction. A target build never consumes another target's mutable preparation state.
|
||||
|
||||
### Upload updates
|
||||
|
||||
`DSH_DESKTOP_AUTO_UPDATE_ENV` selects `test` or `production` for both the URL embedded during packaging and the later COS upload; an absent value selects `test`. Test packaging requires its HTTPS origin in `DOWNLOAD_TEST_ORIGIN`, while the production origin remains `https://download.deepseek.com`. Upload additionally requires the selected deployment's COS bucket in `DOWNLOAD_TEST_COS_BUCKET` or `DOWNLOAD_PROD_COS_BUCKET`. The target path is `_/harness/desktop/stable/<target>/`, where `target` is `mac-arm64`, `mac-x64`, or `win-x64`.
|
||||
@@ -121,7 +123,7 @@ export DOWNLOAD_TEST_COS_SECRET_KEY='<test COS SecretKey>'
|
||||
pnpm run upload:mac:arm64
|
||||
```
|
||||
|
||||
Set `DSH_DESKTOP_AUTO_UPDATE_ENV=production` before packaging, then provide `DOWNLOAD_PROD_COS_BUCKET` and the production credential pair before running `upload:mac:arm64`, `upload:mac:x64`, or `upload:win:x64`. Packaging does not require a COS bucket or credentials. It explicitly disables electron-builder publishing, strips all four COS credential fields from its subprocesses, and writes a target completion record only after electron-builder and every signing or notarization hook succeeds. Upload requires that record to match the selected environment, target, public URL, and current dsh version; it also requires the root dsh version, Desktop version, `latest*.yml` version, artifact names, sizes, and SHA-512 values to agree before it reads the selected COS credential pair. It uploads only that target's immutable versioned artifacts, uploads `latest-mac.yml` or `latest.yml` last with `no-cache`, and never deletes historical objects.
|
||||
Set `DSH_DESKTOP_AUTO_UPDATE_ENV=production` before packaging, then provide `DOWNLOAD_PROD_COS_BUCKET` and the production credential pair before running `upload:mac:arm64`, `upload:mac:x64`, or `upload:win:x64`. Packaging does not require a COS bucket or credentials. It explicitly disables electron-builder publishing, strips all four COS credential fields from its subprocesses, and writes a target completion record only after electron-builder and every signing or notarization hook succeeds. Upload requires that record to match the selected environment, target, public URL, and current dsh version; it also requires the root dsh version, Desktop version, channel metadata version, artifact names, sizes, and SHA-512 values to agree before it reads the selected COS credential pair. It uploads only that target's immutable versioned artifacts, uploads the version-derived channel metadata last with `no-cache`, and never deletes historical objects. Stable releases use `latest-mac.yml` or `latest.yml`; a prerelease such as `alpha` uses `alpha-mac.yml` or `alpha.yml`, matching electron-builder's emitted filename.
|
||||
|
||||
The macOS configuration uses the required release environment instead of accepting whichever certificate appears first in a keychain. It rejects empty values, a malformed Team ID, a signing identity that includes electron-builder's unsupported `Developer ID Application:` prefix, and incomplete notarization credentials. macOS packaging requires the configured identity and its private key. Seed preparation applies that identity, a secure timestamp, and hardened runtime to every embedded Mach-O file; after signing the application, a deep strict check rejects any other leaf authority or Team ID before artifact creation. Electron-builder notarizes and staples the application before packaging and signs the DMG. The DMG artifact-completion hook then notarizes and staples it before requiring its exact identity, ticket, and Gatekeeper acceptance; only after the hook succeeds can electron-builder publish the file. The private key can come from the login keychain or electron-builder's standard `CSC_LINK` input; ambient `CSC_NAME` and certificate discovery order do not select the release owner. Notary credentials may instead use electron-builder's complete Apple ID or keychain-profile strategy. The two macOS identity variables are also required when repeating the application check manually with `pnpm --dir apps/desktop run verify:mac-signature -- <path-to-app>`.
|
||||
|
||||
@@ -156,7 +158,7 @@ pnpm run prepare:desktop
|
||||
|
||||
This diagnostic command is an alternative stopping point, not the first half of a two-command build. A later `package:desktop*` command repeats the official build and preparation so it cannot consume stale dsh packages, runtime files, or seed content.
|
||||
|
||||
Every package command performs the official repository build, packs the dsh and vendored package families, and packs the Landlock entry before preparing release resources. `prepare:packages` selects the first-party production closure rooted at `@deepseek-ai/dsh`, verifies that its tarball contains both `lib/desktop-host.js` and `config/desktop.cordis.patch.yml`, copies the selected tarballs into the seed input, and records their sizes and SHA-512 integrity. The overlay is the only CLI configuration file published specifically for Desktop; example configurations remain outside the tarball. These are the official `pnpm pack` outputs, so each package's `files` manifest controls its published contents: Desktop adds no second filter, retains published declarations such as `lib/types`, and neither strips nor adds source maps independently. Registry packages likewise retain their published package bytes in pnpm's content-addressed store. The root dsh package and Electron package must have the same version, but dsh does not need to be published to npm before the Desktop application is built. `prepare:runtime` downloads Node.js 24.17.0 from the official Node.js release service, verifies its SHA-256 entry before extraction, and executes the prepared binary on a compatible build host to verify its reported version. It copies the pnpm version declared by the desktop package and records both runtime versions in the release seed. `prepare:seed` generates local core-package mappings, uses bundled pnpm with the global virtual store disabled to materialize external production dependencies from npm without lifecycle scripts, deletes `node_modules` and all temporary pnpm cache, config, and state, proves the complete graph installs offline with both Desktop Host files, performs the macOS rewrite when applicable, proves the rewritten store with another offline installation, removes temporary pnpm project registrations, and replaces the loose store with 16 deterministic uncompressed tar shards. It extracts those final shards and verifies every embedded macOS signature before inventory generation. Later GUI plugin operations retain the local core mappings while resolving plugin packages and their external dependencies from the fixed Desktop npm registry. `electron-builder` emits platform artifacts under `apps/desktop/.desktop-build/artifacts`.
|
||||
Every package command performs the official repository build, packs the dsh and vendored package families, and packs the Landlock entry before preparing release resources. `prepare:packages` selects the first-party production closure rooted at `@deepseek-ai/dsh`, verifies that its tarball contains both `lib/desktop-host.js` and `config/desktop.cordis.patch.yml`, copies the selected tarballs into the seed input, and records their sizes and SHA-512 integrity. The overlay is the only CLI configuration file published specifically for Desktop; example configurations remain outside the tarball. These are the official `pnpm pack` outputs, so each package's `files` manifest controls its published contents: Desktop adds no second filter, retains published declarations such as `lib/types`, and neither strips nor adds source maps independently. Registry packages likewise retain their published package bytes in pnpm's content-addressed store. The root dsh package and Electron package must have the same version, but dsh does not need to be published to npm before the Desktop application is built. `prepare:runtime` downloads Node.js 24.17.0 from the official Node.js release service, verifies its SHA-256 entry before extraction, and executes the prepared target binary on a compatible build host to verify its reported version. It copies the pnpm version declared by the desktop package and records both runtime versions in the release seed. `prepare:seed` runs that target Node.js and bundled pnpm, so platform- and CPU-filtered optional dependencies make the pnpm store and seed target-specific. It generates local core-package mappings, disables the global virtual store, materializes external production dependencies from npm without lifecycle scripts, deletes `node_modules` and all temporary pnpm cache, config, and state, proves the complete graph installs offline with both Desktop Host files, performs the macOS rewrite when applicable, proves the rewritten store with another offline installation, removes temporary pnpm project registrations, and replaces the loose store with 16 deterministic uncompressed tar shards. It extracts those final shards and verifies every embedded macOS signature before inventory generation. Later GUI plugin operations retain the local core mappings while resolving plugin packages and their external dependencies from the fixed Desktop npm registry. `electron-builder` emits each target's platform artifacts under `apps/desktop/.desktop-build/targets/<target>/artifacts`; a later version keeps differently named immutable installers and blockmaps while replacing that target's unpacked application, diagnostics, completion record, and channel metadata.
|
||||
|
||||
An unpacked artifact contains four independent size contributors: Electron, the offline seed store shards and local dsh tarballs, the upstream Node.js and pnpm runtime, and the small shell application. The shards are uncompressed so the outer DMG, ZIP, or NSIS compressor and differential updater can operate on stable ranges. Filesystem size is not installer download size, so measure both separately. First packaged startup also extracts the seed store into `$DSH_HOME/desktop/pnpm/store` before installing the writable profile, so release qualification must measure both application and Harness-home disk use.
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ pnpm run package:desktop:win:x64
|
||||
|
||||
macOS arm64 命令要求 Apple Silicon。macOS x64 命令可以在 Intel macOS 或带 Rosetta 的 Apple Silicon 上运行。Windows x64 命令要求 Windows x64。Desktop 尚不支持 Linux 发布目标。
|
||||
|
||||
每个目标都在 `apps/desktop/.desktop-build/targets/<target>/` 下持有自己的打包输入、已准备运行时、包集合、seed、pnpm 准备状态、未打包应用、更新元数据和最终产物。Node.js 归档缓存继续由 `.desktop-build/downloads` 共享,因为每个归档文件名都包含版本、平台和架构,并且在解包前经过验证。目标构建绝不读取其他目标的可变准备状态。
|
||||
|
||||
### 上传更新
|
||||
|
||||
`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`。
|
||||
@@ -121,7 +123,7 @@ export DOWNLOAD_TEST_COS_SECRET_KEY='<test COS SecretKey>'
|
||||
pnpm run upload:mac:arm64
|
||||
```
|
||||
|
||||
生产发布需在打包前设置 `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 以及全部签名或公证 hook 成功后才写入目标完成记录。上传会先要求该记录与所选环境、目标、公开 URL 和当前 dsh 版本一致,再要求根 dsh 版本、Desktop 版本、`latest*.yml` 版本、产物名称、大小与 SHA-512 全部一致,之后才读取所选 COS 凭据对。它只上传该目标不可变且带版本的产物,最后以 `no-cache` 上传 `latest-mac.yml` 或 `latest.yml`,并且不会删除历史对象。
|
||||
生产发布需在打包前设置 `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 以及全部签名或公证 hook 成功后才写入目标完成记录。上传会先要求该记录与所选环境、目标、公开 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 打包要求已配置的身份及其私钥可用。Seed 准备会把该身份、安全时间戳与 hardened runtime 应用到每个内嵌 Mach-O 文件;应用签名完成后,深度严格检查会拒绝其他叶证书 Authority 或 Team ID,验证通过才生成发布产物。Electron-builder 会在封装前公证应用并钉票,然后签署 DMG。DMG 的 artifact-completion hook 随后会公证它并钉票,再要求其身份、票据与 Gatekeeper 验证全部通过;只有 hook 成功,electron-builder 才能发布该文件。私钥可以来自登录钥匙串或 electron-builder 的标准 `CSC_LINK` 输入;环境中的 `CSC_NAME` 与证书发现顺序都不能选择发布所有者。公证凭据也可以使用 electron-builder 支持的完整 Apple ID 或钥匙串 profile 方式。手动执行 `pnpm --dir apps/desktop run verify:mac-signature -- <path-to-app>` 重复应用检查时,也必须提供两个 macOS 身份变量。
|
||||
|
||||
@@ -156,7 +158,7 @@ pnpm run prepare:desktop
|
||||
|
||||
这条诊断命令是另一种停止位置,并非两条命令构建流程的前半段。之后执行 `package:desktop*` 时仍会重新完成正式构建与准备,避免使用陈旧的 dsh 包、运行时文件或 seed 内容。
|
||||
|
||||
每条打包命令都会先执行仓库的正式构建,打包 dsh 与 vendored 包族,并打包 Landlock 入口,然后再准备发布资源。`prepare:packages` 选择以 `@deepseek-ai/dsh` 为根的第一方生产依赖闭包,验证 dsh tarball 同时包含 `lib/desktop-host.js` 与 `config/desktop.cordis.patch.yml`,把选中的 tarball 复制到种子输入,并记录其大小与 SHA-512 完整性。该 overlay 是唯一为了 Desktop 而发布的 CLI 配置文件;示例配置仍留在 tarball 之外。这些 tarball 是正式的 `pnpm pack` 输出,因此各包的 `files` manifest 决定发布内容:Desktop 不增加第二套过滤规则,会保留 `lib/types` 等已发布声明,也不会独立删除或增加 source map。Registry 包同样在 pnpm 内容寻址 store 中保留其发布的包字节。根 dsh 包与 Electron 包必须使用同一版本,但构建 Desktop 应用前不再要求 dsh 已发布到 npm。`prepare:runtime` 从 Node.js 官方发行服务下载 Node.js 24.17.0,在解压前验证其 SHA-256 条目,并在兼容的构建宿主上执行准备完成的二进制文件以验证其报告版本。它复制桌面包声明的 pnpm 版本,并把两个运行时版本记录进发布种子。`prepare:seed` 生成本地核心包映射,使用禁用全局 virtual store 的内置 pnpm 从 npm 物化外部生产依赖并禁用生命周期脚本,删除 `node_modules` 以及所有临时 pnpm cache、config 和 state,证明完整依赖图可以离线安装并包含两个 Desktop Host 文件,在适用时执行 macOS 重写,再通过一次离线安装证明重写后的 store,删除临时 pnpm 项目注册,然后把松散 store 替换为 16 个确定性的未压缩 tar 分片。它会解包这些最终分片,并在生成清单前验证每个内嵌 macOS 签名。后续 GUI 插件操作保留本地核心包映射,同时从固定的 Desktop npm registry 解析插件包及其外部依赖。`electron-builder` 把平台产物写到 `apps/desktop/.desktop-build/artifacts`。
|
||||
每条打包命令都会先执行仓库的正式构建,打包 dsh 与 vendored 包族,并打包 Landlock 入口,然后再准备发布资源。`prepare:packages` 选择以 `@deepseek-ai/dsh` 为根的第一方生产依赖闭包,验证 dsh tarball 同时包含 `lib/desktop-host.js` 与 `config/desktop.cordis.patch.yml`,把选中的 tarball 复制到种子输入,并记录其大小与 SHA-512 完整性。该 overlay 是唯一为了 Desktop 而发布的 CLI 配置文件;示例配置仍留在 tarball 之外。这些 tarball 是正式的 `pnpm pack` 输出,因此各包的 `files` manifest 决定发布内容:Desktop 不增加第二套过滤规则,会保留 `lib/types` 等已发布声明,也不会独立删除或增加 source map。Registry 包同样在 pnpm 内容寻址 store 中保留其发布的包字节。根 dsh 包与 Electron 包必须使用同一版本,但构建 Desktop 应用前不再要求 dsh 已发布到 npm。`prepare:runtime` 从 Node.js 官方发行服务下载 Node.js 24.17.0,在解压前验证其 SHA-256 条目,并在兼容的构建宿主上执行准备完成的目标二进制文件以验证其报告版本。它复制桌面包声明的 pnpm 版本,并把两个运行时版本记录进发布种子。`prepare:seed` 运行该目标 Node.js 与内置 pnpm,因此按平台和 CPU 过滤的可选依赖会使 pnpm store 与 seed 成为目标专用内容。它生成本地核心包映射、禁用全局 virtual store、从 npm 物化外部生产依赖并禁用生命周期脚本、删除 `node_modules` 以及所有临时 pnpm cache、config 和 state,证明完整依赖图可以离线安装并包含两个 Desktop Host 文件,在适用时执行 macOS 重写,再通过一次离线安装证明重写后的 store,删除临时 pnpm 项目注册,然后把松散 store 替换为 16 个确定性的未压缩 tar 分片。它会解包这些最终分片,并在生成清单前验证每个内嵌 macOS 签名。后续 GUI 插件操作保留本地核心包映射,同时从固定的 Desktop npm registry 解析插件包及其外部依赖。`electron-builder` 把各目标的平台产物写到 `apps/desktop/.desktop-build/targets/<target>/artifacts`;后续版本会保留不同名称的不可变安装包与 blockmap,但会替换该目标的未打包应用、诊断文件、完成记录与频道元数据。
|
||||
|
||||
未压缩产物包含四块相互独立的体积:Electron、离线 seed store 分片与本地 dsh tarball、上游 Node.js 与 pnpm 运行时,以及很小的桌面壳应用。分片不压缩,使外层 DMG、ZIP 或 NSIS 压缩器与差分更新器可以处理稳定的数据区间。文件系统占用不等于安装包下载大小,因此必须分别测量。打包应用首次启动时还会先把 seed store 解包到 `$DSH_HOME/desktop/pnpm/store`,再安装可写 profile,因此发布验证必须同时测量应用与 Harness home 的磁盘占用。
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
/** Electron-builder fields asserted by the Desktop release tests. */
|
||||
export interface DesktopElectronBuilderConfig {
|
||||
readonly appId: string
|
||||
readonly directories: {
|
||||
readonly output: string
|
||||
}
|
||||
readonly extraResources: readonly [
|
||||
{ readonly from: string, readonly to: 'runtime' },
|
||||
{ readonly from: string, readonly to: 'seed' },
|
||||
]
|
||||
readonly mac: {
|
||||
readonly identity: string | undefined
|
||||
readonly forceCodeSigning: boolean
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
installWindowsNsisBootstrapSigner,
|
||||
} from './scripts/windows-sign.mjs'
|
||||
import { resolveDesktopAutoUpdateConfig } from './scripts/desktop-auto-update-environment.mjs'
|
||||
import { desktopTargetBuildPaths } from './scripts/desktop-build-paths.mjs'
|
||||
|
||||
/**
|
||||
* Create electron-builder configuration from one release environment.
|
||||
@@ -43,11 +44,12 @@ export function createElectronBuilderConfig(
|
||||
installWindowsNsisBootstrapSigner({ sign: windowsSigner })
|
||||
}
|
||||
const update = resolveDesktopAutoUpdateConfig(env, resolvedPlatform, resolvedArch)
|
||||
const buildPaths = desktopTargetBuildPaths(update.target)
|
||||
return {
|
||||
appId,
|
||||
productName: 'DeepSeek Harness',
|
||||
artifactName: 'deepseek-harness-${version}-${os}-${arch}.${ext}',
|
||||
directories: { output: '.desktop-build/artifacts' },
|
||||
directories: { output: buildPaths.artifacts },
|
||||
asar: true,
|
||||
files: [
|
||||
'lib/*.js',
|
||||
@@ -56,8 +58,8 @@ export function createElectronBuilderConfig(
|
||||
'package.json',
|
||||
],
|
||||
extraResources: [
|
||||
{ from: '.desktop-build/runtime', to: 'runtime' },
|
||||
{ from: '.desktop-build/seed', to: 'seed' },
|
||||
{ from: buildPaths.runtime, to: 'runtime' },
|
||||
{ from: buildPaths.seed, to: 'seed' },
|
||||
],
|
||||
mac: {
|
||||
category: 'public.app-category.developer-tools',
|
||||
|
||||
@@ -50,6 +50,17 @@ export function resolveDesktopAutoUpdateTarget(
|
||||
*/
|
||||
export function desktopBuildRecordFilename(target: DesktopAutoUpdateTarget): string
|
||||
|
||||
/**
|
||||
* Return the electron-builder channel metadata filename for an application version.
|
||||
* @param version - Desktop semantic version.
|
||||
* @param platform - Target platform.
|
||||
* @returns Channel metadata filename emitted for the target.
|
||||
*/
|
||||
export function desktopUpdateMetadataFilename(
|
||||
version: string,
|
||||
platform: NodeJS.Platform,
|
||||
): string
|
||||
|
||||
/**
|
||||
* Resolve the public updater URL for one release target.
|
||||
* @param env - Packaging or upload environment.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/** Resolve the Desktop auto-update channel and its Tencent COS destination. */
|
||||
|
||||
import { prerelease, valid } from 'semver'
|
||||
|
||||
/** Environment variable that selects the Desktop update deployment. */
|
||||
export const DESKTOP_AUTO_UPDATE_ENV = 'DSH_DESKTOP_AUTO_UPDATE_ENV'
|
||||
|
||||
@@ -62,6 +64,24 @@ export function desktopBuildRecordFilename(target) {
|
||||
return `${target}-release.json`
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the electron-builder channel metadata filename for an application version.
|
||||
* @param {string} version - Desktop semantic version.
|
||||
* @param {NodeJS.Platform} platform - Target platform.
|
||||
* @returns {string} Channel metadata filename emitted for the target.
|
||||
*/
|
||||
export function desktopUpdateMetadataFilename(version, platform) {
|
||||
if (valid(version) === null) {
|
||||
throw new Error(`desktop auto-update: invalid Desktop version ${JSON.stringify(version)}`)
|
||||
}
|
||||
if (platform !== 'darwin' && platform !== 'win32') {
|
||||
throw new Error(`desktop auto-update: unsupported metadata platform ${platform}`)
|
||||
}
|
||||
const release = prerelease(version)
|
||||
const channel = release === null ? 'latest' : String(release[0])
|
||||
return `${channel}${platform === 'darwin' ? '-mac' : ''}.yml`
|
||||
}
|
||||
|
||||
/**
|
||||
* Read one required release setting without accepting whitespace-only values.
|
||||
* @param {NodeJS.ProcessEnv} env - Packaging or upload environment.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { DesktopAutoUpdateTarget } from './desktop-auto-update-environment.mjs'
|
||||
|
||||
/** Mutable target directories plus the shared immutable download cache. */
|
||||
export interface DesktopTargetBuildPaths {
|
||||
readonly root: string
|
||||
readonly artifacts: string
|
||||
readonly runtime: string
|
||||
readonly packageSet: string
|
||||
readonly seed: string
|
||||
readonly seedPnpm: string
|
||||
readonly nodeExtract: string
|
||||
readonly packedDsh: string
|
||||
readonly packedVendor: string
|
||||
readonly packedLandlock: string
|
||||
readonly downloads: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the fixed build target selected by a packaging environment.
|
||||
* @param env - Packaging environment.
|
||||
* @param hostPlatform - Build-host platform used when no target override exists.
|
||||
* @param hostArch - Build-host architecture used when no target override exists.
|
||||
* @returns Supported Desktop target name.
|
||||
*/
|
||||
export function resolveDesktopBuildTarget(
|
||||
env?: NodeJS.ProcessEnv,
|
||||
hostPlatform?: NodeJS.Platform,
|
||||
hostArch?: string,
|
||||
): DesktopAutoUpdateTarget
|
||||
|
||||
/**
|
||||
* Return the mutable preparation and artifact directories owned by one release target.
|
||||
* @param target - Supported Desktop target name.
|
||||
* @returns Target paths plus the shared immutable download cache.
|
||||
*/
|
||||
export function desktopTargetBuildPaths(target: DesktopAutoUpdateTarget): DesktopTargetBuildPaths
|
||||
|
||||
/**
|
||||
* Resolve the paths owned by the target selected in a packaging environment.
|
||||
* @param env - Packaging environment.
|
||||
* @param hostPlatform - Build-host platform used when no target override exists.
|
||||
* @param hostArch - Build-host architecture used when no target override exists.
|
||||
* @returns Selected target paths.
|
||||
*/
|
||||
export function resolveDesktopTargetBuildPaths(
|
||||
env?: NodeJS.ProcessEnv,
|
||||
hostPlatform?: NodeJS.Platform,
|
||||
hostArch?: string,
|
||||
): DesktopTargetBuildPaths
|
||||
@@ -0,0 +1,70 @@
|
||||
/** Resolve build-owned Desktop paths without sharing mutable state across release targets. */
|
||||
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
const APP_ROOT = resolve(import.meta.dirname, '..')
|
||||
const BUILD_ROOT = join(APP_ROOT, '.desktop-build')
|
||||
const SUPPORTED_TARGETS = new Set(['mac-arm64', 'mac-x64', 'win-x64'])
|
||||
|
||||
/**
|
||||
* Resolve the fixed build target selected by a packaging environment.
|
||||
* @param {NodeJS.ProcessEnv} env - Packaging environment.
|
||||
* @param {NodeJS.Platform} hostPlatform - Build-host platform used when no target override exists.
|
||||
* @param {string} hostArch - Build-host architecture used when no target override exists.
|
||||
* @returns {'mac-arm64' | 'mac-x64' | 'win-x64'} Supported Desktop target name.
|
||||
*/
|
||||
export function resolveDesktopBuildTarget(
|
||||
env = process.env,
|
||||
hostPlatform = process.platform,
|
||||
hostArch = process.arch,
|
||||
) {
|
||||
const platform = env.DSH_DESKTOP_TARGET_PLATFORM ?? env.npm_config_platform ?? hostPlatform
|
||||
const arch = env.DSH_DESKTOP_TARGET_ARCH ?? env.npm_config_arch ?? hostArch
|
||||
const os = platform === 'darwin' ? 'mac' : platform === 'win32' || platform === 'win' ? 'win' : platform
|
||||
const target = `${os}-${arch}`
|
||||
if (!SUPPORTED_TARGETS.has(target)) {
|
||||
throw new Error(`desktop build paths: unsupported target ${target}`)
|
||||
}
|
||||
return /** @type {'mac-arm64' | 'mac-x64' | 'win-x64'} */ (target)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mutable preparation and artifact directories owned by one release target.
|
||||
* @param {'mac-arm64' | 'mac-x64' | 'win-x64'} target - Supported Desktop target name.
|
||||
* @returns {{ root: string, artifacts: string, runtime: string, packageSet: string, seed: string, seedPnpm: string, nodeExtract: string, packedDsh: string, packedVendor: string, packedLandlock: string, downloads: string }} Target paths plus the shared immutable download cache.
|
||||
*/
|
||||
export function desktopTargetBuildPaths(target) {
|
||||
if (!SUPPORTED_TARGETS.has(target)) {
|
||||
throw new Error(`desktop build paths: unsupported target ${String(target)}`)
|
||||
}
|
||||
const root = join(BUILD_ROOT, 'targets', target)
|
||||
const packed = join(root, 'packed')
|
||||
return {
|
||||
root,
|
||||
artifacts: join(root, 'artifacts'),
|
||||
runtime: join(root, 'runtime'),
|
||||
packageSet: join(root, 'package-set'),
|
||||
seed: join(root, 'seed'),
|
||||
seedPnpm: join(root, 'seed-pnpm'),
|
||||
nodeExtract: join(root, 'node-extract'),
|
||||
packedDsh: join(packed, 'dsh'),
|
||||
packedVendor: join(packed, 'vendor'),
|
||||
packedLandlock: join(packed, 'landlock'),
|
||||
downloads: join(BUILD_ROOT, 'downloads'),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the paths owned by the target selected in a packaging environment.
|
||||
* @param {NodeJS.ProcessEnv} env - Packaging environment.
|
||||
* @param {NodeJS.Platform} hostPlatform - Build-host platform used when no target override exists.
|
||||
* @param {string} hostArch - Build-host architecture used when no target override exists.
|
||||
* @returns {ReturnType<typeof desktopTargetBuildPaths>} Selected target paths.
|
||||
*/
|
||||
export function resolveDesktopTargetBuildPaths(
|
||||
env = process.env,
|
||||
hostPlatform = process.platform,
|
||||
hostArch = process.arch,
|
||||
) {
|
||||
return desktopTargetBuildPaths(resolveDesktopBuildTarget(env, hostPlatform, hostArch))
|
||||
}
|
||||
@@ -8,22 +8,21 @@ import { load } from 'js-yaml'
|
||||
import type { DesktopPackageTargetName } from './package-target.ts'
|
||||
import {
|
||||
desktopBuildRecordFilename,
|
||||
desktopUpdateMetadataFilename,
|
||||
resolveDesktopUploadConfig,
|
||||
} from './desktop-auto-update-environment.mjs'
|
||||
import { desktopTargetBuildPaths } from './desktop-build-paths.mjs'
|
||||
|
||||
const APP_ROOT = resolve(import.meta.dirname, '..')
|
||||
const REPOSITORY_ROOT = resolve(APP_ROOT, '..', '..')
|
||||
const ARTIFACTS_ROOT = join(APP_ROOT, '.desktop-build', 'artifacts')
|
||||
|
||||
const TARGETS = {
|
||||
'mac-arm64': { platform: 'darwin', arch: 'arm64', os: 'mac', metadata: 'latest-mac.yml' },
|
||||
'mac-x64': { platform: 'darwin', arch: 'x64', os: 'mac', metadata: 'latest-mac.yml' },
|
||||
'win-x64': { platform: 'win32', arch: 'x64', os: 'win', metadata: 'latest.yml' },
|
||||
'mac-arm64': { platform: 'darwin', arch: 'arm64', os: 'mac' },
|
||||
'mac-x64': { platform: 'darwin', arch: 'x64', os: 'mac' },
|
||||
'win-x64': { platform: 'win32', arch: 'x64', os: 'win' },
|
||||
} as const satisfies Record<DesktopPackageTargetName, {
|
||||
readonly platform: NodeJS.Platform
|
||||
readonly arch: string
|
||||
readonly os: string
|
||||
readonly metadata: string
|
||||
}>
|
||||
|
||||
/** One local file and its final object metadata. */
|
||||
@@ -181,7 +180,7 @@ export async function createDesktopUploadPlan(
|
||||
const environment = options.environment ?? process.env
|
||||
const repositoryRoot = options.repositoryRoot ?? REPOSITORY_ROOT
|
||||
const appRoot = options.appRoot ?? APP_ROOT
|
||||
const artifactsRoot = options.artifactsRoot ?? ARTIFACTS_ROOT
|
||||
const artifactsRoot = options.artifactsRoot ?? desktopTargetBuildPaths(targetName).artifacts
|
||||
const dshVersion = await manifestVersion(join(repositoryRoot, 'package.json'), 'dsh package')
|
||||
const desktopVersion = await manifestVersion(join(appRoot, 'package.json'), 'desktop package')
|
||||
if (dshVersion !== desktopVersion) {
|
||||
@@ -201,7 +200,8 @@ export async function createDesktopUploadPlan(
|
||||
throw new Error(`desktop upload: ${targetName} package completion record does not match dsh ${dshVersion} and ${update.environment} update destination`)
|
||||
}
|
||||
|
||||
const metadataPath = join(artifactsRoot, target.metadata)
|
||||
const metadataFilename = desktopUpdateMetadataFilename(dshVersion, target.platform)
|
||||
const metadataPath = join(artifactsRoot, metadataFilename)
|
||||
let metadataValue: unknown
|
||||
try {
|
||||
metadataValue = load(await readFile(metadataPath, 'utf8'))
|
||||
@@ -209,18 +209,18 @@ export async function createDesktopUploadPlan(
|
||||
catch (error) {
|
||||
throw new Error(`desktop upload: cannot read update metadata at ${metadataPath}: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
const metadata = object(metadataValue, target.metadata)
|
||||
const metadataVersion = stringField(metadata.version, `${target.metadata}.version`)
|
||||
const metadata = object(metadataValue, metadataFilename)
|
||||
const metadataVersion = stringField(metadata.version, `${metadataFilename}.version`)
|
||||
if (metadataVersion !== dshVersion) {
|
||||
throw new Error(`desktop upload: ${target.metadata} version ${metadataVersion} does not match current dsh version ${dshVersion}`)
|
||||
throw new Error(`desktop upload: ${metadataFilename} version ${metadataVersion} does not match current dsh version ${dshVersion}`)
|
||||
}
|
||||
if (!Array.isArray(metadata.files) || metadata.files.length !== 1) {
|
||||
throw new Error(`desktop upload: ${target.metadata}.files must contain exactly one target update file`)
|
||||
throw new Error(`desktop upload: ${metadataFilename}.files must contain exactly one target update file`)
|
||||
}
|
||||
|
||||
const base = `deepseek-harness-${dshVersion}-${target.os}-${target.arch}`
|
||||
const updaterExtension = target.platform === 'darwin' ? 'zip' : 'exe'
|
||||
const updaterInfo = updateFileInfo(metadata.files[0], `${target.metadata}.files[0]`, `${base}.${updaterExtension}`)
|
||||
const updaterInfo = updateFileInfo(metadata.files[0], `${metadataFilename}.files[0]`, `${base}.${updaterExtension}`)
|
||||
const updaterPath = await verifyChecksummedArtifact(artifactsRoot, updaterInfo)
|
||||
const artifacts: DesktopUploadArtifact[] = []
|
||||
|
||||
@@ -234,8 +234,8 @@ export async function createDesktopUploadPlan(
|
||||
)
|
||||
}
|
||||
else {
|
||||
const blockMapSize = object(metadata.files[0], `${target.metadata}.files[0]`).blockMapSize
|
||||
numberField(blockMapSize, `${target.metadata}.files[0].blockMapSize`)
|
||||
const blockMapSize = object(metadata.files[0], `${metadataFilename}.files[0]`).blockMapSize
|
||||
numberField(blockMapSize, `${metadataFilename}.files[0].blockMapSize`)
|
||||
artifacts.push(uploadArtifact(
|
||||
updaterPath,
|
||||
update.keyPrefix,
|
||||
|
||||
@@ -8,13 +8,10 @@ import {
|
||||
desktopBuildRecordFilename,
|
||||
resolveDesktopAutoUpdateConfig,
|
||||
} from './desktop-auto-update-environment.mjs'
|
||||
import { desktopTargetBuildPaths } from './desktop-build-paths.mjs'
|
||||
|
||||
const APP_ROOT = resolve(import.meta.dirname, '..')
|
||||
const REPOSITORY_ROOT = resolve(APP_ROOT, '..', '..')
|
||||
const DSH_PACK_ROOT = join(REPOSITORY_ROOT, 'dist', 'npm')
|
||||
const VENDOR_PACK_ROOT = join(REPOSITORY_ROOT, 'dist', 'npm-vendor')
|
||||
const LANDLOCK_PACK_ROOT = join(REPOSITORY_ROOT, 'dist', 'npm-landlock')
|
||||
const ARTIFACTS_ROOT = join(APP_ROOT, '.desktop-build', 'artifacts')
|
||||
const WINDOWS_SIGNING_ENV_PREFIX = 'DSH_DESKTOP_WINDOWS_'
|
||||
const WINDOWS_SIGNING_ENV_NAMES = [
|
||||
'DSH_DESKTOP_WINDOWS_CER_FILE',
|
||||
@@ -97,14 +94,18 @@ function packageVersion(path: string, label: string): string {
|
||||
return manifest.version
|
||||
}
|
||||
|
||||
function writeReleaseRecord(target: DesktopPackageTarget, environment: NodeJS.ProcessEnv): void {
|
||||
function writeReleaseRecord(
|
||||
target: DesktopPackageTarget,
|
||||
environment: NodeJS.ProcessEnv,
|
||||
artifactsRoot: string,
|
||||
): void {
|
||||
const desktopVersion = packageVersion(join(APP_ROOT, 'package.json'), 'desktop package')
|
||||
const dshVersion = packageVersion(join(REPOSITORY_ROOT, 'package.json'), 'dsh package')
|
||||
if (desktopVersion !== dshVersion) {
|
||||
throw new Error(`desktop package: desktop version ${desktopVersion} does not match dsh version ${dshVersion}`)
|
||||
}
|
||||
const update = resolveDesktopAutoUpdateConfig(environment, target.platform, target.arch)
|
||||
const recordPath = join(ARTIFACTS_ROOT, desktopBuildRecordFilename(target.name))
|
||||
const recordPath = join(artifactsRoot, desktopBuildRecordFilename(target.name))
|
||||
const temporaryPath = `${recordPath}.tmp`
|
||||
writeFileSync(temporaryPath, `${JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
@@ -237,7 +238,8 @@ function runPnpm(
|
||||
async function main(): Promise<void> {
|
||||
const invocation = parseDesktopPackageInvocation(process.argv.slice(2))
|
||||
const { target } = invocation
|
||||
const releaseRecordPath = join(ARTIFACTS_ROOT, desktopBuildRecordFilename(target.name))
|
||||
const buildPaths = desktopTargetBuildPaths(target.name)
|
||||
const releaseRecordPath = join(buildPaths.artifacts, desktopBuildRecordFilename(target.name))
|
||||
if (!invocation.prepareOnly) {
|
||||
rmSync(releaseRecordPath, { force: true })
|
||||
rmSync(`${releaseRecordPath}.tmp`, { force: true })
|
||||
@@ -253,24 +255,24 @@ async function main(): Promise<void> {
|
||||
if (process.env[name] !== undefined) electronBuilderEnv[name] = process.env[name]
|
||||
}
|
||||
await runPnpm(['run', 'build:official'], buildEnv, REPOSITORY_ROOT)
|
||||
await runPnpm(['run', 'release:pack', '--family', 'dsh', '--out', DSH_PACK_ROOT], buildEnv, REPOSITORY_ROOT)
|
||||
await runPnpm(['run', 'release:pack', '--family', 'vendor', '--out', VENDOR_PACK_ROOT], buildEnv, REPOSITORY_ROOT)
|
||||
rmSync(LANDLOCK_PACK_ROOT, { recursive: true, force: true })
|
||||
mkdirSync(LANDLOCK_PACK_ROOT, { recursive: true })
|
||||
await runPnpm(['run', 'release:pack', '--family', 'dsh', '--out', buildPaths.packedDsh], buildEnv, REPOSITORY_ROOT)
|
||||
await runPnpm(['run', 'release:pack', '--family', 'vendor', '--out', buildPaths.packedVendor], buildEnv, REPOSITORY_ROOT)
|
||||
rmSync(buildPaths.packedLandlock, { recursive: true, force: true })
|
||||
mkdirSync(buildPaths.packedLandlock, { recursive: true })
|
||||
await runPnpm(['--dir', 'native/landlock-run', 'run', 'build:ts'], buildEnv, REPOSITORY_ROOT)
|
||||
await runPnpm([
|
||||
'--dir',
|
||||
'native/landlock-run/packages/entry',
|
||||
'pack',
|
||||
'--pack-destination',
|
||||
LANDLOCK_PACK_ROOT,
|
||||
buildPaths.packedLandlock,
|
||||
], buildEnv, REPOSITORY_ROOT)
|
||||
await runPnpm(['run', 'prepare:runtime'], targetEnv)
|
||||
await runPnpm(['run', 'prepare:packages'], targetEnv)
|
||||
await runPnpm(['run', 'prepare:seed'], targetEnv)
|
||||
if (invocation.prepareOnly) return
|
||||
await runPnpm(desktopElectronBuilderArguments(target, invocation.directory), electronBuilderEnv)
|
||||
if (!invocation.directory) writeReleaseRecord(target, electronBuilderEnv)
|
||||
if (!invocation.directory) writeReleaseRecord(target, electronBuilderEnv, buildPaths.artifacts)
|
||||
}
|
||||
|
||||
if (process.argv[1] !== undefined && import.meta.filename === resolve(process.argv[1])) await main()
|
||||
|
||||
@@ -22,15 +22,17 @@ import {
|
||||
} from '../src/core-package-set.ts'
|
||||
import { capture } from '../../../scripts/release/process.ts'
|
||||
import { tarballFiles } from '../../../scripts/release/tarball.ts'
|
||||
import { resolveDesktopTargetBuildPaths } from './desktop-build-paths.mjs'
|
||||
|
||||
const DSH_PACKAGE = '@deepseek-ai/dsh'
|
||||
const APP_ROOT = resolve(import.meta.dirname, '..')
|
||||
const REPOSITORY_ROOT = resolve(APP_ROOT, '..', '..')
|
||||
const OUTPUT_ROOT = join(APP_ROOT, '.desktop-build', 'package-set')
|
||||
const BUILD_PATHS = resolveDesktopTargetBuildPaths()
|
||||
const OUTPUT_ROOT = BUILD_PATHS.packageSet
|
||||
const DEFAULT_INPUTS = [
|
||||
join(REPOSITORY_ROOT, 'dist', 'npm'),
|
||||
join(REPOSITORY_ROOT, 'dist', 'npm-vendor'),
|
||||
join(REPOSITORY_ROOT, 'dist', 'npm-landlock'),
|
||||
BUILD_PATHS.packedDsh,
|
||||
BUILD_PATHS.packedVendor,
|
||||
BUILD_PATHS.packedLandlock,
|
||||
]
|
||||
|
||||
const REQUIRED_DEPENDENCY_SECTIONS = ['dependencies', 'peerDependencies'] as const
|
||||
@@ -122,7 +124,7 @@ export function assertDesktopDshPackageFiles(files: readonly string[]): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prepare `.desktop-build/package-set` from release tarball directories. */
|
||||
/** Prepare the selected target's package set from its release tarball directories. */
|
||||
export function prepareDesktopPackageSet(inputs: readonly string[], output = OUTPUT_ROOT): void {
|
||||
const selected = selectDesktopPackageClosure(packedPackages(inputs))
|
||||
const dsh = selected.find(packed => packed.manifest.name === DSH_PACKAGE)
|
||||
|
||||
@@ -5,16 +5,16 @@ import { spawnSync } from 'node:child_process'
|
||||
import { cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { chmod, readFile } from 'node:fs/promises'
|
||||
import { createRequire } from 'node:module'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { pipeline } from 'node:stream/promises'
|
||||
import extractZip from 'extract-zip'
|
||||
import { extract } from 'tar'
|
||||
import { resolveDesktopTargetBuildPaths } from './desktop-build-paths.mjs'
|
||||
|
||||
const NODE_VERSION = '24.17.0'
|
||||
const APP_ROOT = resolve(import.meta.dirname, '..')
|
||||
const BUILD_ROOT = join(APP_ROOT, '.desktop-build')
|
||||
const RUNTIME_ROOT = join(BUILD_ROOT, 'runtime')
|
||||
const DOWNLOAD_ROOT = join(BUILD_ROOT, 'downloads')
|
||||
const BUILD_PATHS = resolveDesktopTargetBuildPaths()
|
||||
const RUNTIME_ROOT = BUILD_PATHS.runtime
|
||||
const DOWNLOAD_ROOT = BUILD_PATHS.downloads
|
||||
|
||||
type RuntimePlatform = 'darwin' | 'linux' | 'win'
|
||||
type RuntimeArch = 'arm64' | 'x64'
|
||||
@@ -52,7 +52,7 @@ async function prepareNode(platform: RuntimePlatform, arch: RuntimeArch): Promis
|
||||
const actual = createHash('sha256').update(await readFile(archive)).digest('hex')
|
||||
if (actual !== expected) throw new Error(`desktop runtime: checksum mismatch for ${archiveName}`)
|
||||
|
||||
const extraction = join(BUILD_ROOT, 'node-extract')
|
||||
const extraction = BUILD_PATHS.nodeExtract
|
||||
rmSync(extraction, { recursive: true, force: true })
|
||||
mkdirSync(extraction, { recursive: true })
|
||||
if (platform === 'win') await extractZip(archive, { dir: extraction })
|
||||
|
||||
@@ -28,15 +28,16 @@ import {
|
||||
signMacOSSeedStore,
|
||||
verifyMacOSSeedStore,
|
||||
} from './macos-seed-store.ts'
|
||||
import { resolveDesktopTargetBuildPaths } from './desktop-build-paths.mjs'
|
||||
|
||||
const APP_ROOT = resolve(import.meta.dirname, '..')
|
||||
const BUILD_ROOT = join(APP_ROOT, '.desktop-build')
|
||||
const SEED_OUTPUT_ROOT = join(BUILD_ROOT, 'seed')
|
||||
const BUILD_PATHS = resolveDesktopTargetBuildPaths()
|
||||
const SEED_OUTPUT_ROOT = BUILD_PATHS.seed
|
||||
const SEED_ROOT = mkdtempSync(join(tmpdir(), 'dsh-desktop-seed-'))
|
||||
const STORE_ROOT = join(SEED_ROOT, 'store')
|
||||
const RUNTIME_ROOT = join(BUILD_ROOT, 'runtime')
|
||||
const PNPM_BUILD_STATE = join(BUILD_ROOT, 'seed-pnpm')
|
||||
const PACKAGE_SET_ROOT = join(BUILD_ROOT, 'package-set')
|
||||
const RUNTIME_ROOT = BUILD_PATHS.runtime
|
||||
const PNPM_BUILD_STATE = BUILD_PATHS.seedPnpm
|
||||
const PACKAGE_SET_ROOT = BUILD_PATHS.packageSet
|
||||
const NODE = join(RUNTIME_ROOT, 'node', process.platform === 'win32' ? 'node.exe' : 'node')
|
||||
const PNPM = join(RUNTIME_ROOT, 'pnpm', 'bin', 'pnpm.mjs')
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
desktopBuildRecordFilename,
|
||||
desktopUpdateMetadataFilename,
|
||||
resolveDesktopAutoUpdateConfig,
|
||||
resolveDesktopAutoUpdateEnvironment,
|
||||
resolveDesktopAutoUpdateTarget,
|
||||
@@ -77,4 +78,12 @@ describe('desktop auto-update environment', () => {
|
||||
expect(() => resolveDesktopAutoUpdateTarget('linux', 'x64')).toThrow(/unsupported target/u)
|
||||
expect(() => desktopBuildRecordFilename('linux-x64' as 'mac-arm64')).toThrow(/unsupported target/u)
|
||||
})
|
||||
|
||||
it('matches electron-builder channel metadata names to the Desktop version', () => {
|
||||
expect(desktopUpdateMetadataFilename('1.2.3', 'darwin')).toBe('latest-mac.yml')
|
||||
expect(desktopUpdateMetadataFilename('1.2.3-alpha.4', 'darwin')).toBe('alpha-mac.yml')
|
||||
expect(desktopUpdateMetadataFilename('1.2.3-beta.2', 'win32')).toBe('beta.yml')
|
||||
expect(() => desktopUpdateMetadataFilename('not-semver', 'darwin')).toThrow(/invalid Desktop version/u)
|
||||
expect(() => desktopUpdateMetadataFilename('1.2.3', 'linux')).toThrow(/unsupported metadata platform/u)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { join, sep } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
desktopTargetBuildPaths,
|
||||
resolveDesktopBuildTarget,
|
||||
} from '../scripts/desktop-build-paths.mjs'
|
||||
|
||||
describe('desktop build paths', () => {
|
||||
it('isolates every mutable build directory by complete target', () => {
|
||||
const arm64 = desktopTargetBuildPaths('mac-arm64')
|
||||
const x64 = desktopTargetBuildPaths('mac-x64')
|
||||
const windows = desktopTargetBuildPaths('win-x64')
|
||||
const mutableKeys = [
|
||||
'root',
|
||||
'artifacts',
|
||||
'runtime',
|
||||
'packageSet',
|
||||
'seed',
|
||||
'seedPnpm',
|
||||
'nodeExtract',
|
||||
'packedDsh',
|
||||
'packedVendor',
|
||||
'packedLandlock',
|
||||
] as const
|
||||
|
||||
for (const key of mutableKeys) {
|
||||
expect(new Set([arm64[key], x64[key], windows[key]]).size).toBe(3)
|
||||
}
|
||||
expect(arm64.artifacts).toContain(join('targets', 'mac-arm64', 'artifacts'))
|
||||
expect(x64.seed).toContain(join('targets', 'mac-x64', 'seed'))
|
||||
expect(windows.runtime).toContain(join('targets', 'win-x64', 'runtime'))
|
||||
})
|
||||
|
||||
it('shares only the immutable upstream download cache', () => {
|
||||
const arm64 = desktopTargetBuildPaths('mac-arm64')
|
||||
const x64 = desktopTargetBuildPaths('mac-x64')
|
||||
expect(arm64.downloads).toBe(x64.downloads)
|
||||
expect(arm64.downloads).not.toContain(`${sep}targets${sep}`)
|
||||
})
|
||||
|
||||
it('resolves environment overrides and rejects unsupported targets', () => {
|
||||
expect(resolveDesktopBuildTarget({
|
||||
DSH_DESKTOP_TARGET_PLATFORM: 'darwin',
|
||||
DSH_DESKTOP_TARGET_ARCH: 'x64',
|
||||
}, 'darwin', 'arm64')).toBe('mac-x64')
|
||||
expect(resolveDesktopBuildTarget({}, 'win32', 'x64')).toBe('win-x64')
|
||||
expect(() => resolveDesktopBuildTarget({}, 'linux', 'x64')).toThrow(/unsupported target/u)
|
||||
expect(() => desktopTargetBuildPaths('linux-x64' as 'mac-x64')).toThrow(/unsupported target/u)
|
||||
})
|
||||
})
|
||||
@@ -4,6 +4,7 @@ import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { createDesktopUploadPlan } from '../scripts/desktop-upload-plan.ts'
|
||||
import { desktopUpdateMetadataFilename } from '../scripts/desktop-auto-update-environment.mjs'
|
||||
import type { DesktopPackageTargetName } from '../scripts/package-target.ts'
|
||||
|
||||
const temporaryDirectories: string[] = []
|
||||
@@ -54,7 +55,7 @@ async function fixture(
|
||||
await writeFile(join(artifactsRoot, `${base}.zip`), zip)
|
||||
await writeFile(join(artifactsRoot, `${base}.zip.blockmap`), 'blockmap')
|
||||
await writeFile(join(artifactsRoot, `${base}.dmg`), 'notarized DMG fixture')
|
||||
await writeFile(join(artifactsRoot, 'latest-mac.yml'), `${JSON.stringify({
|
||||
await writeFile(join(artifactsRoot, desktopUpdateMetadataFilename(version, 'darwin')), `${JSON.stringify({
|
||||
version,
|
||||
files: [{ url: `${base}.zip`, size: Buffer.byteLength(zip), sha512: digest(zip) }],
|
||||
})}\n`)
|
||||
@@ -62,7 +63,7 @@ async function fixture(
|
||||
else {
|
||||
const executable = 'signed NSIS executable fixture'
|
||||
await writeFile(join(artifactsRoot, `${base}.exe`), executable)
|
||||
await writeFile(join(artifactsRoot, 'latest.yml'), `${JSON.stringify({
|
||||
await writeFile(join(artifactsRoot, desktopUpdateMetadataFilename(version, 'win32')), `${JSON.stringify({
|
||||
version,
|
||||
files: [{
|
||||
url: `${base}.exe`,
|
||||
@@ -118,6 +119,17 @@ describe('desktop upload plan', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('uploads the prerelease channel metadata emitted by electron-builder', async () => {
|
||||
const paths = await fixture('mac-arm64', '1.2.3-alpha.4')
|
||||
const plan = await createDesktopUploadPlan('mac-arm64', paths)
|
||||
expect(plan.artifacts.map(artifact => artifact.filename)).toEqual([
|
||||
'deepseek-harness-1.2.3-alpha.4-mac-arm64.dmg',
|
||||
'deepseek-harness-1.2.3-alpha.4-mac-arm64.zip',
|
||||
'deepseek-harness-1.2.3-alpha.4-mac-arm64.zip.blockmap',
|
||||
'alpha-mac.yml',
|
||||
])
|
||||
})
|
||||
|
||||
it('validates the Windows installer with its embedded blockmap and production destination', async () => {
|
||||
const paths = await fixture('win-x64', '2.0.0', 'production')
|
||||
const plan = await createDesktopUploadPlan('win-x64', paths)
|
||||
|
||||
@@ -33,6 +33,12 @@ describe('desktop macOS release signature', () => {
|
||||
it('loads release identifiers from the environment and requires code signing', async () => {
|
||||
const { createElectronBuilderConfig } = await import('../electron-builder.config.mjs')
|
||||
const config = createElectronBuilderConfig(RELEASE_ENVIRONMENT, 'darwin', 'arm64')
|
||||
expect(config.directories.output).toContain('/.desktop-build/targets/mac-arm64/artifacts')
|
||||
expect(config.extraResources).toHaveLength(2)
|
||||
expect(config.extraResources[0]?.to).toBe('runtime')
|
||||
expect(config.extraResources[1]?.to).toBe('seed')
|
||||
expect(config.extraResources[0]?.from).toContain('/.desktop-build/targets/mac-arm64/runtime')
|
||||
expect(config.extraResources[1]?.from).toContain('/.desktop-build/targets/mac-arm64/seed')
|
||||
expect(config).toMatchObject({
|
||||
appId: RELEASE_ENVIRONMENT.DSH_DESKTOP_APP_ID,
|
||||
mac: {
|
||||
|
||||
Reference in New Issue
Block a user