mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-11-09 20:07:43 +08:00
Merge pull request #7844 from deepseek-harness/fix/desktop-channel-version-policy
fix(desktop): 允许人工选择任意版本进入 stable 渠道
This commit is contained in:
+2
-2
@@ -21,8 +21,8 @@
|
||||
en: 29def175ab33b7d6
|
||||
zh: 87e55ec28c20c7c1
|
||||
/agent-note-package-and-update-the-electron-desktop-application/updates-and-recovery:
|
||||
en: 4cb7014acfe71762
|
||||
zh: 2178e0337cba0324
|
||||
en: 61a02870beb953ec
|
||||
zh: 205ecce330959e9a
|
||||
/agent-note-package-and-update-the-electron-desktop-application/security-and-release-policy:
|
||||
en: 5faaa53b58121311
|
||||
zh: e1c3fc8d21a27d1a
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ Electron update uses one `electron-updater` release stream and signed `electron-
|
||||
|
||||
The [immediate-window reference](../../../../apps/desktop/README.md) owns the local loading page, direct Host startup, and recovery in the main window. Profile reconciliation follows the [bundled-runtime decision](2026-09-08-desktop-bundled-runtime-and-external-plugins.md).
|
||||
|
||||
`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 package operation 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, channel-selected 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. The selected channel determines the metadata name: `stable.yml` or `nightly.yml`, with `-mac` before the extension for macOS; version suffixes do not select the channel. 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 package operation remain separate operations.
|
||||
|
||||
Test releases require `DOWNLOAD_TEST_RELEASE_ID` from the platform dotenv file. Their feeds and binaries share `dsh-desk/<release-id>/`; production retains its fixed directories. The ID is part of the packaged update URL, so upload rejects a different ID. Internal distribution can supply a new installer after rotation; installed clients retain their existing feed. Random directories reduce guessing but provide no authorization for link holders. The [release configuration](../../../../apps/desktop/publishing/README.md#upload-updates) defines generation and reuse.
|
||||
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ Electron 更新只使用一个 `electron-updater` 发布流和签名 `electron-b
|
||||
|
||||
[立即显示窗口参考](../../../../apps/desktop/README.zh.md)负责本地加载页、直接启动 Host 和主窗口恢复。profile 协调遵循[内置运行时决策](2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md)。
|
||||
|
||||
`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 包操作仍是两个独立操作。
|
||||
`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 生成的频道元数据,并且不会删除历史对象。所选渠道决定元数据名称:`stable.yml` 或 `nightly.yml`,macOS 在扩展名前加 `-mac`;版本后缀不决定渠道。NSIS 把 blockmap 嵌入已签名的可执行文件,macOS ZIP 则使用独立 blockmap;两者都让 electron-updater 在平台支持时只下载变化的数据块,而应用替换与本地 pnpm 包操作仍是两个独立操作。
|
||||
|
||||
测试发布必须从平台 dotenv 文件提供 `DOWNLOAD_TEST_RELEASE_ID`。清单和安装包共用 `dsh-desk/<release-id>/`,生产环境保留固定目录。ID 是打包更新 URL 的一部分,因此上传会拒绝不同的 ID。轮换后可通过内部分发提供新安装包,已安装客户端保留原清单地址。随机目录降低被猜中的概率,但不对持有链接者进行鉴权。[发布配置](../../../../apps/desktop/publishing/README.zh.md#upload-updates)定义生成与复用方式。
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
en: 403bd786a8411349
|
||||
zh: c44584e8a7e5b74a
|
||||
/agent-note-deferred-desktop-update-extensions/proposal/stable-subscription-and-channel-switching:
|
||||
en: f39b1ed0d941d372
|
||||
zh: 05b9a006c9ec0d3c
|
||||
en: 33663c160d5e280b
|
||||
zh: 96689283c31ed820
|
||||
/agent-note-deferred-desktop-update-extensions/proposal/automatic-installation-setting:
|
||||
en: 758d49f0a7dc6707
|
||||
zh: ddb0bfe616eb93bc
|
||||
|
||||
@@ -29,16 +29,7 @@ Do not use `0.1.3-dsk.1` to repair stable `0.1.3`: SemVer orders it below the st
|
||||
|
||||
### Stable subscription and channel switching
|
||||
|
||||
| User option | Feed | Release contents |
|
||||
|---|---|---|
|
||||
| Stable | `latest.yml` / `latest-mac.yml` | Stable only |
|
||||
| Nightly | `nightly.yml` / `nightly-mac.yml` | Alpha, rc, and stable |
|
||||
|
||||
The [initial publication rule](2026-09-08-desktop-update-policy-and-installation.md) adds the stable feed when shared stable releases exist, without switching fixed-Nightly clients. A user-facing stable subscription and selector remain deferred. If enabled, persist the selected channel; never infer it from the installed version. Preserve `allowDowngrade = false` when assigning the updater channel. Moving from Nightly to stable keeps a higher installed Nightly until a higher stable version exists, without downgrading Desktop or bundled dsh.
|
||||
|
||||
Channel switching may immediately check; download behavior must follow the approved setting. Ignore old-channel responses and reconfirm downloaded artifacts against the new channel before installation. A channel change does not clear mandatory blocking without a valid policy response for the new conditions. Display a persistent Nightly explanation under the selector, with candidate Chinese copy “提前体验新功能,版本可能不稳定。”; hide it after selecting stable, while separately explaining any wait for a higher stable version.
|
||||
|
||||
Design compatibility only for Desktop packages actually distributed. If a future migration has real clients reading `alpha.yml` or `rc.yml`, publish a higher bridge version to the old feed and Nightly until the old entry can be retired. No such bridge is required initially.
|
||||
This part of the proposal is superseded by [Desktop channel settings](../../../../apps/desktop/README.md#local-desktop-settings) and the [packaging channel policy](../../../../apps/desktop/packaging/README.md). The implemented selector persists explicit user choices; publishers select versions independently of channel names, and channel changes do not permit downgrades.
|
||||
|
||||
### Automatic-installation setting
|
||||
|
||||
|
||||
@@ -29,16 +29,7 @@ Status: proposed
|
||||
|
||||
### 稳定版订阅与通道切换
|
||||
|
||||
| 用户选项 | Feed | 发布内容 |
|
||||
|---|---|---|
|
||||
| 稳定版 | `latest.yml` / `latest-mac.yml` | 仅稳定版本 |
|
||||
| Nightly | `nightly.yml` / `nightly-mac.yml` | alpha、rc 和稳定版本 |
|
||||
|
||||
[一期发布规则](2026-09-08-desktop-update-policy-and-installation.zh.md)在共用稳定版本出现时增加稳定 feed,不切换固定 Nightly 客户端。面向用户的稳定版订阅与选择器仍延后。启用后持久化所选通道,不从已安装版本反推。设置 updater 通道时保持 `allowDowngrade = false`。从 Nightly 切到稳定版时,较高的已安装 Nightly 持续运行,直到出现更高稳定版;不得降级 Desktop 或内置 dsh。
|
||||
|
||||
切换通道可以立即检查,下载行为必须遵循获准设置。忽略旧通道响应,安装前按新通道重新确认已下载产物。通道改变不能在缺少新条件有效策略响应时解除强制阻断。选择器下方常驻 Nightly 说明,候选中文为“提前体验新功能,版本可能不稳定。”;选择稳定版后隐藏,必要时另行说明需等待更高稳定版。
|
||||
|
||||
仅为实际分发过的 Desktop 安装包设计兼容。后续迁移若存在读取 `alpha.yml` 或 `rc.yml` 的真实客户端,应向旧 feed 与 Nightly 发布更高桥接版本,直到可淘汰旧入口。一期不需要这类桥接。
|
||||
本提案的这一部分已由[桌面渠道设置](../../../../apps/desktop/README.zh.md#local-desktop-settings)和[打包渠道策略](../../../../apps/desktop/packaging/README.zh.md)取代。已实现的选择器持久化用户的显式选择;发布者独立于渠道名称选择版本,切换渠道不允许降级。
|
||||
|
||||
### 自动安装设置
|
||||
|
||||
|
||||
@@ -15,15 +15,15 @@ on:
|
||||
default: test
|
||||
required: true
|
||||
update_channel:
|
||||
description: '升级通道:nightly 或 stable(须为稳定版本);默认通道由此打包输入确定'
|
||||
description: '升级通道:人工选择 nightly 或 stable,与版本后缀无关;默认通道由此打包输入确定'
|
||||
type: choice
|
||||
options: [nightly, stable]
|
||||
default: nightly
|
||||
required: true
|
||||
version_mode:
|
||||
description: '版本模式:default 为 test 自动编号、production 跟随产品;product 保留原版本,auto 自动编号,manual 手填'
|
||||
description: '版本模式:default 按环境自动选择(test 取 build-number,production 取 follow-dsh);follow-dsh 跟随 dsh 包版本号;build-number 自动追加构建编号;manual 手填'
|
||||
type: choice
|
||||
options: [default, product, auto, manual]
|
||||
options: [default, follow-dsh, build-number, manual]
|
||||
default: default
|
||||
required: true
|
||||
build_version:
|
||||
@@ -75,7 +75,8 @@ jobs:
|
||||
PACKAGE_MACOS_ZIP_COMPRESSION_LEVEL: ${{ inputs.compression_level }}
|
||||
PACKAGE_SKIP_NOTARIZATION: ${{ inputs.skip_notarization }}
|
||||
PACKAGE_SOURCE_REF: ${{ inputs.source_ref }}
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode }}
|
||||
# TODO: Remove this legacy source_ref mode mapping on 2027-01-04 with the parser aliases.
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode == 'follow-dsh' && 'product' || inputs.version_mode == 'build-number' && 'auto' || inputs.version_mode }}
|
||||
DSH_DESKTOP_UPDATE_CHANNEL: ${{ inputs.update_channel }}
|
||||
DSH_TELEMETRY_DISABLED: '1'
|
||||
DSH_PACKAGING_PRESET: ${{ inputs.preset }}
|
||||
|
||||
@@ -15,15 +15,15 @@ on:
|
||||
default: test
|
||||
required: true
|
||||
update_channel:
|
||||
description: '升级通道:nightly 或 stable(须为稳定版本);默认通道由此打包输入确定'
|
||||
description: '升级通道:人工选择 nightly 或 stable,与版本后缀无关;默认通道由此打包输入确定'
|
||||
type: choice
|
||||
options: [nightly, stable]
|
||||
default: nightly
|
||||
required: true
|
||||
version_mode:
|
||||
description: '版本模式:default 为 test 自动编号、production 跟随产品;product 保留原版本,auto 自动编号,manual 手填'
|
||||
description: '版本模式:default 按环境自动选择(test 取 build-number,production 取 follow-dsh);follow-dsh 跟随 dsh 包版本号;build-number 自动追加构建编号;manual 手填'
|
||||
type: choice
|
||||
options: [default, product, auto, manual]
|
||||
options: [default, follow-dsh, build-number, manual]
|
||||
default: default
|
||||
required: true
|
||||
build_version:
|
||||
@@ -75,7 +75,8 @@ jobs:
|
||||
PACKAGE_MACOS_ZIP_COMPRESSION_LEVEL: ${{ inputs.compression_level }}
|
||||
PACKAGE_SKIP_NOTARIZATION: ${{ inputs.skip_notarization }}
|
||||
PACKAGE_SOURCE_REF: ${{ inputs.source_ref }}
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode }}
|
||||
# TODO: Remove this legacy source_ref mode mapping on 2027-01-04 with the parser aliases.
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode == 'follow-dsh' && 'product' || inputs.version_mode == 'build-number' && 'auto' || inputs.version_mode }}
|
||||
DSH_DESKTOP_UPDATE_CHANNEL: ${{ inputs.update_channel }}
|
||||
DSH_TELEMETRY_DISABLED: '1'
|
||||
DSH_PACKAGING_PRESET: ${{ inputs.preset }}
|
||||
|
||||
@@ -15,15 +15,15 @@ on:
|
||||
default: test
|
||||
required: true
|
||||
update_channel:
|
||||
description: '升级通道:nightly 或 stable(须为稳定版本);默认通道由此打包输入确定'
|
||||
description: '升级通道:人工选择 nightly 或 stable,与版本后缀无关;默认通道由此打包输入确定'
|
||||
type: choice
|
||||
options: [nightly, stable]
|
||||
default: nightly
|
||||
required: true
|
||||
version_mode:
|
||||
description: '版本模式:default 为 test 自动编号、production 跟随产品;product 保留原版本,auto 自动编号,manual 手填'
|
||||
description: '版本模式:default 按环境自动选择(test 取 build-number,production 取 follow-dsh);follow-dsh 跟随 dsh 包版本号;build-number 自动追加构建编号;manual 手填'
|
||||
type: choice
|
||||
options: [default, product, auto, manual]
|
||||
options: [default, follow-dsh, build-number, manual]
|
||||
default: default
|
||||
required: true
|
||||
build_version:
|
||||
@@ -77,7 +77,8 @@ jobs:
|
||||
shell: pwsh
|
||||
env:
|
||||
PACKAGE_SOURCE_REF: ${{ inputs.source_ref }}
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode }}
|
||||
# TODO: Remove this legacy source_ref mode mapping on 2027-01-04 with the parser aliases.
|
||||
PACKAGE_VERSION_MODE: ${{ inputs.version_mode == 'follow-dsh' && 'product' || inputs.version_mode == 'build-number' && 'auto' || inputs.version_mode }}
|
||||
DSH_DESKTOP_UPDATE_CHANNEL: ${{ inputs.update_channel }}
|
||||
DSH_TELEMETRY_DISABLED: '1'
|
||||
DSH_PACKAGING_PRESET: ${{ inputs.preset }}
|
||||
|
||||
@@ -60,8 +60,8 @@
|
||||
en: 67bf0a18faa7aa76
|
||||
zh: 782e14ce62bb939f
|
||||
/deepseek-harness-desktop/updates/local-desktop-settings:
|
||||
en: 01461be5b81e8609
|
||||
zh: 58e818df821a3aa2
|
||||
en: dbe57394894b7368
|
||||
zh: a3bb788c2ce631a4
|
||||
/deepseek-harness-desktop/updates/mandatory-update-policy:
|
||||
en: 182a9c6705213e13
|
||||
zh: 681762f83490f4c6
|
||||
|
||||
@@ -304,7 +304,7 @@ On first launch, including after an upgrade, Desktop creates this document if it
|
||||
|
||||
Startup loading never overwrites existing documents; an omitted `updates` object or field resolves to `false`. Set the field to `true` to permit test authentication dialogs, then fully quit and restart the application. Set it back to `false` and restart to suppress them again. Creation, read, or validation failures use the default `false` without blocking startup. Desktop attempts to warn through the Electron console with the file path; unavailable logging does not block startup. Correct the file and restart to apply an explicit setting. Unknown fields are preserved. Installation updates retain this user-data file; Windows uninstallation removes it under the [uninstall rules](#windows-uninstall-rules).
|
||||
|
||||
In Desktop **Settings → General → Update channel**, **Stable** selects `stable` and **Development** selects `nightly`. The main process saves `updates.channel` in this same file and applies the choice immediately through IPC; no Host settings service is involved. An omitted channel uses the build-injected package default. External edits and deletion are read only on the next launch; no file watcher runs. The authentication-popup setting remains startup-only. UI writes retain unrelated fields from the latest valid document; external editors must not write concurrently with a UI save.
|
||||
In Desktop **Settings → General → Update channel**, **Stable** selects `stable` and **Development** selects `nightly`. Publishers choose versions for each channel independently of version suffixes; Stable can also deliver alpha or RC versions. The main process saves `updates.channel` in this same file and applies the choice immediately through IPC; no Host settings service is involved. An omitted channel uses the build-injected package default. External edits and deletion are read only on the next launch; no file watcher runs. The authentication-popup setting remains startup-only. UI writes retain unrelated fields from the latest valid document; external editors must not write concurrently with a UI save.
|
||||
|
||||
During a UI switch, the selector shows the pending choice with a loading indicator. Failure restores the actual runtime selection and shows a short toast; ordinary save failures keep the current channel and update target accessible. Invalid startup settings or failure to retire the previous update suspends update operations; without a mandatory policy block, the application remains usable. Startup settings errors show a non-blocking ten-second toast with a details action. The update indicator retains the suspension reason independently of download progress; open it for recovery guidance and a button to reveal the settings file. Fix the file, then choose **Reload settings** in the error details or restart; this explicit reload applies only update settings and never rewrites the file. The reload button shows a busy indicator for at least half a second and rejects repeated clicks. Invalid files keep updates paused. Selecting a valid channel in Settings can also recover an invalid channel; malformed JSON must be repaired first. A known mandatory-update block remains enforced; its configuration-error action opens the same file-location and reload dialog. Successful channel changes trigger a fresh mandatory-policy check, waiting for an older channel’s in-flight check if necessary. Successful switching retires pending checks, downloads and installation eligibility before checking the new feed, including when both feeds advertise the same version. Switching never downgrades or installs automatically. Installation preparation rejects UI switches; once `quitAndInstall()` hands off, file edits cannot cancel that installation.
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ Electron 主进程在启动时读取一次 `app.getPath('userData')/desktop/sett
|
||||
|
||||
启动读取不会覆盖已有文件;缺少 `updates` 对象或该字段时均按 `false` 处理。将字段设为 `true` 后完全退出并重启应用,即可允许测试鉴权弹窗。改回 `false` 并重启即可再次禁止。创建、读取或校验失败时使用默认值 `false`,不阻止启动。Desktop 尝试在 Electron 控制台记录包含文件路径的警告;日志不可用也不阻止启动。修正文件并重启后才会应用显式设置。未知字段会保留。安装更新保留此用户数据文件;Windows 卸载按[卸载规则](#windows-uninstall-rules)删除它。
|
||||
|
||||
在桌面版**设置 → 通用设置 → 更新渠道**中,**正式版**选择 `stable`,**开发版**选择 `nightly`。主进程在同一文件中保存 `updates.channel`,并通过 IPC 立即应用选择,不依赖 Host 设置服务。省略 channel 时使用打包注入的包内默认值。外部编辑和删除仅在下次启动读取,不运行文件监听器。鉴权弹窗设置仍须重启生效。UI 写入保留最新有效文件中的其他字段;外部编辑器不能与 UI 保存同时写入。
|
||||
在桌面版**设置 → 通用设置 → 更新渠道**中,**正式版**选择 `stable`,**开发版**选择 `nightly`。发布者独立于版本后缀为各渠道选择版本;正式版渠道也可以提供 alpha 或 RC 版本。主进程在同一文件中保存 `updates.channel`,并通过 IPC 立即应用选择,不依赖 Host 设置服务。省略 channel 时使用打包注入的包内默认值。外部编辑和删除仅在下次启动读取,不运行文件监听器。鉴权弹窗设置仍须重启生效。UI 写入保留最新有效文件中的其他字段;外部编辑器不能与 UI 保存同时写入。
|
||||
|
||||
UI 切换期间,选择器显示待应用选项和加载指示;失败时恢复实际运行渠道,并显示简短 toast。普通保存失败不会阻止使用当前渠道及旧更新目标。启动配置无效或无法撤销旧更新安装资格会暂停更新操作;没有强更策略阻塞时,主应用仍可使用。启动配置错误会显示持续十秒、可查看详情的非阻塞提示。更新入口独立于下载进度保留暂停原因;打开后可查看恢复指引、配置路径并在系统文件管理器中定位文件。修正文件后,可在错误详情中选择**重新读取配置**或重启;显式重读只应用更新配置,不改写文件。重读按钮至少显示半秒加载指示,期间不接受重复点击;文件仍无效时继续暂停更新。在设置中选择有效渠道也可恢复无效渠道;JSON 损坏须先修复文件。已知强更阻塞继续生效;强更页面的配置异常入口可打开同一文件定位和重读对话框。渠道切换成功后立即重新检查强更策略;如旧渠道的检查仍在进行,则等待其完成后检查新渠道。切换成功时先收尾检查、下载并撤销安装资格,再检查新渠道;两个渠道版本号相同也遵循该规则。切换不降级,也不自动安装。安装准备期间拒绝 UI 切换;调用 `quitAndInstall()` 交接后,编辑文件不能取消本次安装。
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@
|
||||
en: 0b35c7471c09b27a
|
||||
zh: cc4a382b99fc3210
|
||||
/desktop-packaging/release-versions:
|
||||
en: 8cc17b1e5ae3e422
|
||||
zh: 7de3e4f1e76be3e3
|
||||
en: bdee4adb7916e6cc
|
||||
zh: 2880c3594ebab7ab
|
||||
/desktop-packaging/configuration-and-local-builds:
|
||||
en: 5b4f0f80a117f202
|
||||
zh: ff05be196c181087
|
||||
/desktop-packaging/windows-actions-packaging:
|
||||
en: 52f0d14ee575ad1a
|
||||
zh: 94a7f76f4c15031e
|
||||
en: b39449c2f945cab9
|
||||
zh: a742ba9efa282be8
|
||||
/desktop-packaging/macos-self-hosted-packaging:
|
||||
en: 08619f0e26782636
|
||||
zh: 7c236cbe46be4a47
|
||||
|
||||
@@ -51,7 +51,7 @@ Record the current dsh version as the base. A production Desktop release uses th
|
||||
|
||||
Use the actual creation date in Asia/Shanghai. For each base and date, start the index at 1 and increment after checking retained release records and published objects; never reuse a published version. Test distribution does not publish the corresponding unsuffixed base.
|
||||
|
||||
Manual Desktop CI uses a separate unpublished build identifier: `auto` appends the UTC date, workflow run number and attempt (`.YYYYMMDD.run.attempt`, with `-test` for a stable base). For example, `0.2.1-alpha.1.20261008.42.2`. These numbers are not reserved release sequence numbers.
|
||||
Version mode `build-number` appends the UTC date, workflow run number and attempt (`.YYYYMMDD.run.attempt`, with `-test` for a stable base). For example, `0.2.1-alpha.1.20261008.42.2`. These numbers are not reserved release sequence numbers.
|
||||
|
||||
Pass the confirmed version to the packaging command as `--build-version`, which reaches the artifact names, the update feed, and the upload validation as one value. The manifests keep the product version, so a test build no longer rewrites the release family and leaves nothing to revert:
|
||||
|
||||
@@ -63,7 +63,7 @@ pnpm --dir apps/desktop run package:win:x64 --build-version 0.1.6-alpha.1.202609
|
||||
|
||||
A production release publishes the product version and takes no `--build-version`. Uploading one tags the packaged commit as `desktop-v<version>` once the artifacts are public; a build from a modified checkout is not tagged, and a tagging failure prints the command to run by hand rather than failing a completed upload. Test and local builds are deliberately left untagged, and every artifact records `dshBuildCommit` and `dshBuildDirty` in its manifest so a build handed over directly remains traceable.
|
||||
|
||||
The installer embeds `DSH_DESKTOP_UPDATE_CHANNEL` (`nightly` by default, or `stable`) independently of version and deployment. This value supplies the default channel; the saved selection in [Desktop settings](../README.md#local-desktop-settings) takes precedence, including after installing another package. Stable-channel packages reject prerelease versions. A stable product can produce a nightly `-test.<date>.<sequence>` build, but that version ranks below the stable base and cannot upgrade an already installed higher version. Keep automatic downgrade disabled; stable corrective releases require a product version bump. CI version selection and publication are documented in [CI publication](../publishing/ci/README.md#package-and-publish).
|
||||
The installer embeds `DSH_DESKTOP_UPDATE_CHANNEL` (`nightly` by default, or `stable`) independently of version and deployment. This value supplies the default channel; the saved selection in [Desktop settings](../README.md#local-desktop-settings) takes precedence, including after installing another package. Operators select the channel independently of version suffixes; both channels accept release and prerelease versions. A stable product can produce a `-test.<date>.<sequence>` build, but that version ranks below the stable base and cannot upgrade an already installed higher version. Keep automatic downgrade disabled; stable corrective releases require a product version bump. CI version selection and publication are documented in [CI publication](../publishing/ci/README.md#package-and-publish).
|
||||
|
||||
<a id="configuration-and-local-builds"></a>
|
||||
|
||||
@@ -109,9 +109,9 @@ Each target owns its packed package inputs, prepared runtime, package set, dsh t
|
||||
|
||||
For supervised runner startup and recovery, follow [Windows runner machine setup](../windows-signing/README.md#machine-setup).
|
||||
|
||||
The `Package Desktop · Windows · x64` [manual workflow](../../../.github/workflows/windows-package.yml) selects the dedicated Windows x64 runner label `dsh-win-package-trial` and the machine preset `test` or `production`. Choose `signed` (default) or `unsigned`; signed mode uses the existing EV token preflight, signature cache and failure interlock. It does not publish or upload a release. `build_version` defaults to `auto`, using the CI identifier in [Release versions](#release-versions); an explicit value may be the checkout's product version or an extended build version such as `0.2.1-alpha.1.20261009.1` for product `0.2.1-alpha.1`. The version prefix must match the selected checkout; validation errors show its current example. Manifests remain unchanged.
|
||||
The `Package Desktop · Windows · x64` [manual workflow](../../../.github/workflows/windows-package.yml) selects the dedicated Windows x64 runner label `dsh-win-package-trial` and the machine preset `test` or `production`. Choose `signed` (default) or `unsigned`; signed mode uses the existing EV token preflight, signature cache and failure interlock. It does not publish or upload a release. `version_mode` defaults to `default` (`follow-dsh` for production, `build-number` for test); `build-number` uses the CI identifier in [Release versions](#release-versions). `build_version` is required for `manual` and ignored by the other modes; its value may be the checkout's product version or an extended build version such as `0.2.1-alpha.1.20261009.1` for product `0.2.1-alpha.1`. The version prefix must match the selected checkout; validation errors show its current example. Manifests remain unchanged.
|
||||
|
||||
Select the workflow in Actions, choose the source branch, preset, signing mode and version, then click Run workflow. Before merging input changes, test the pushed branch with `gh workflow run windows-package.yml --ref <branch> -f preset=test -f signing=unsigned -f build_version=auto`; verify the web form after merging. There is no push or PR trigger. Logs and successful EXEs are retained for 14 days. Inspect the retained logs and installer before relying on packaging; installation is separate. Before checkout, Node removes the runner-owned source directory without following dependency junctions; the workflow validates its location and rejects linked ancestors. Never point it at a development checkout. Runs share one concurrency group without cancelling an active build; only one run remains pending, and a newer dispatch replaces it. Diagnostics upload requires a successful clean checkout. Machine settings stay outside the checkout and are never uploaded as artifacts.
|
||||
Select the workflow in Actions, choose the source branch, preset, signing mode and version, then click Run workflow. Before merging input changes, test the pushed branch with `gh workflow run windows-package.yml --ref <branch> -f source_ref=<branch> -f preset=test -f signing=unsigned -f version_mode=build-number`; verify the web form after merging. There is no push or PR trigger. Logs and successful EXEs are retained for 14 days. Inspect the retained logs and installer before relying on packaging; installation is separate. Before checkout, Node removes the runner-owned source directory without following dependency junctions; the workflow validates its location and rejects linked ancestors. Never point it at a development checkout. Runs share one concurrency group without cancelling an active build; only one run remains pending, and a newer dispatch replaces it. Diagnostics upload requires a successful clean checkout. Machine settings stay outside the checkout and are never uploaded as artifacts.
|
||||
|
||||
All Windows packaging modes, including test and unsigned, require the repository variable `DESKTOP_PUBLISH_RUNNERS`; configure the [original-machine route](../publishing/ci/README.md#machine-setup) before dispatch.
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ description: "在本地或 Windows/macOS Actions 中生成 Desktop 安装包
|
||||
|
||||
日期使用实际创建时的 Asia/Shanghai 日期。每个基础版本、每天的序号从 1 开始,检查保留的发布记录与已发布对象后递增;绝不复用已发布版本。test 分发不发布对应的无后缀基础版本。
|
||||
|
||||
Desktop 手动 CI 使用独立的未发布构建标识:`auto` 追加 UTC 日期、工作流运行编号和尝试次数(`.YYYYMMDD.run.attempt`,稳定基础版本使用 `-test`)。例如 `0.2.1-alpha.1.20261008.42.2`。这些编号不是预留的发布序号。
|
||||
版本模式 `build-number` 追加 UTC 日期、工作流运行编号和尝试次数(`.YYYYMMDD.run.attempt`,稳定基础版本使用 `-test`)。例如 `0.2.1-alpha.1.20261008.42.2`。这些编号不是预留的发布序号。
|
||||
|
||||
把确认后的版本通过 `--build-version` 传给打包命令,该值同时决定产物文件名、更新 feed 与上传校验。清单保留产品版本,因此 test 打包不再改写发布家族,也不留下需要还原的改动:
|
||||
|
||||
@@ -63,7 +63,7 @@ pnpm --dir apps/desktop run package:win:x64 --build-version 0.1.6-alpha.1.202609
|
||||
|
||||
production 发布使用产品版本本身,不传 `--build-version`。其上传成功后会把打包所用 commit 打成 `desktop-v<版本>` 标签;来自有改动工作区的构建不打标签,打标签失败也只打印手工命令,不会让已完成的上传变成失败。test 与本地构建有意不留标签,而所有产物的清单都记录 `dshBuildCommit` 与 `dshBuildDirty`,直接分发的构建同样可溯源。
|
||||
|
||||
安装包独立于版本和发布环境内置 `DSH_DESKTOP_UPDATE_CHANNEL`(默认 `nightly`,也可选择 `stable`)。该值提供默认渠道;[桌面设置](../README.zh.md#local-desktop-settings)中保存的选择优先,包括安装另一个包之后。stable 包拒绝预发布版本。稳定产品可生成 nightly 的 `-test.<date>.<sequence>` 构建,但该版本低于稳定基准,不能升级已安装的更高版本。保持自动降级关闭;稳定修复版需要提升产品版本。CI 版本选择与发布见 [CI 发布](../publishing/ci/README.zh.md#package-and-publish)。
|
||||
安装包独立于版本和发布环境内置 `DSH_DESKTOP_UPDATE_CHANNEL`(默认 `nightly`,也可选择 `stable`)。该值提供默认渠道;[桌面设置](../README.zh.md#local-desktop-settings)中保存的选择优先,包括安装另一个包之后。操作人员独立于版本后缀选择渠道;两个渠道均接受正式版本和预发布版本。稳定产品可生成 `-test.<date>.<sequence>` 构建,但该版本低于稳定基准,不能升级已安装的更高版本。保持自动降级关闭;稳定修复版需要提升产品版本。CI 版本选择与发布见 [CI 发布](../publishing/ci/README.zh.md#package-and-publish)。
|
||||
|
||||
<a id="configuration-and-local-builds"></a>
|
||||
|
||||
@@ -109,9 +109,9 @@ macOS 正式发布和本地未签名 DMG 都通过 [electron-builder 配置](../
|
||||
|
||||
runner 的受监督启动和恢复见 [Windows runner 机器准备](../windows-signing/README.zh.md#machine-setup)。
|
||||
|
||||
`Package Desktop · Windows · x64` [手动工作流](../../../.github/workflows/windows-package.yml) 选择专用 Windows x64 runner 标签 `dsh-win-package-trial`,并使用机器 preset `test` 或 `production`。选择 `signed`(默认)或 `unsigned`;签名模式复用现有 EV token 预检、签名缓存与失败 interlock,不发布或上传 release。`build_version` 默认为 `auto`,使用[发布版本](#release-versions)中的 CI 标识;显式值可以是源码产品版本,或扩展构建版本,例如产品 `0.2.1-alpha.1` 对应 `0.2.1-alpha.1.20261009.1`。版本前缀必须匹配所选源码;校验错误显示其当前示例。manifest 保持不变。
|
||||
`Package Desktop · Windows · x64` [手动工作流](../../../.github/workflows/windows-package.yml) 选择专用 Windows x64 runner 标签 `dsh-win-package-trial`,并使用机器 preset `test` 或 `production`。选择 `signed`(默认)或 `unsigned`;签名模式复用现有 EV token 预检、签名缓存与失败 interlock,不发布或上传 release。`version_mode` 默认为 `default`(production 使用 `follow-dsh`,test 使用 `build-number`);`build-number` 使用[发布版本](#release-versions)中的 CI 标识。`manual` 模式必须填写 `build_version`,其他模式忽略该字段;其值可以是源码产品版本,或扩展构建版本,例如产品 `0.2.1-alpha.1` 对应 `0.2.1-alpha.1.20261009.1`。版本前缀必须匹配所选源码;校验错误显示其当前示例。manifest 保持不变。
|
||||
|
||||
在 Actions 选择工作流、源码分支、preset、签名模式和版本,再点击 Run workflow。输入变更合并前,用 `gh workflow run windows-package.yml --ref <branch> -f preset=test -f signing=unsigned -f build_version=auto` 测试已推送分支;合并后验证网页表单。它不监听 push 或 PR。日志和成功生成的 EXE 保留 14 天。依赖打包流程前检查日志和安装包;安装是独立操作。检出前,Node 删除 runner 专用源码目录,不跟随依赖 junction;工作流校验目标位置并拒绝链接祖先目录。不得指向日常开发目录。运行共享一个并发组,不取消正在进行的构建;最多保留一个待运行任务,新触发会替换它。仅在成功完成干净检出后上传诊断。机器配置位于 checkout 之外,不作为产物上传。
|
||||
在 Actions 选择工作流、源码分支、preset、签名模式和版本,再点击 Run workflow。输入变更合并前,用 `gh workflow run windows-package.yml --ref <branch> -f source_ref=<branch> -f preset=test -f signing=unsigned -f version_mode=build-number` 测试已推送分支;合并后验证网页表单。它不监听 push 或 PR。日志和成功生成的 EXE 保留 14 天。依赖打包流程前检查日志和安装包;安装是独立操作。检出前,Node 删除 runner 专用源码目录,不跟随依赖 junction;工作流校验目标位置并拒绝链接祖先目录。不得指向日常开发目录。运行共享一个并发组,不取消正在进行的构建;最多保留一个待运行任务,新触发会替换它。仅在成功完成干净检出后上传诊断。机器配置位于 checkout 之外,不作为产物上传。
|
||||
|
||||
所有 Windows 打包模式(含 test 和 unsigned)都要求仓库变量 `DESKTOP_PUBLISH_RUNNERS`;触发前配置[原机器路由](../publishing/ci/README.zh.md#machine-setup)。
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
en: 0f43f2e193e95043
|
||||
zh: 1c1efb062c06f7b8
|
||||
/desktop-ci-publication/package-and-publish:
|
||||
en: e62e9d93a50e6489
|
||||
zh: 2491c37e40f4c496
|
||||
en: 0437ba83ef3d5921
|
||||
zh: 395c5efe332cff78
|
||||
/desktop-ci-publication/recovery-and-records:
|
||||
en: d6eb9cbac5b819a0
|
||||
zh: 0de1ec24055d1007
|
||||
|
||||
@@ -72,7 +72,7 @@ The publication form accepts an optional `staging_percentage` integer from 0 to
|
||||
|
||||
For publication to either test or production, choose Windows `signing=signed` or leave macOS **Skip notarization** unchecked. Unsigned and unnotarized packages are only for internal debugging; they cannot publish or provide automatic updates. Before installing dependencies, the hosted routing job rejects debugging artifacts identified by the selected run and attempt. An archive-only rerun retains the original build attempt in artifact names, so this early check can miss it; local manifest qualification still rejects debugging builds before publication. Missing artifact metadata does not establish eligibility: the original machine still validates its retained manifest and signatures. A debugging-package rejection requires a new signed/notarized package and its run ID / attempt; retrying publication cannot qualify the existing bytes.
|
||||
|
||||
Select the platform's `Package Desktop` workflow, preset, source, channel and version mode. `source_ref` accepts repository branch names (for example `feature/desktop-update`) or `dsh-v*` tags for test; production accepts only `master` or `dsh-v*` tags. Names matching `dsh-v` followed by a digit and letters, digits, dots or hyphens are treated as tags, even if a branch has that name. Tags must match the selected product version. We recommend dispatching the packaging workflow from `master` and enter the source separately; raw commit SHAs, PR refs and fork repositories are not supported. The selected source must contain the CI adapters, so older sources fail before dependency installation. Only select trusted branches: their build code runs on the signing machine. This is an operating convention, not enforced workflow-origin or credential isolation. Publication uses the recorded commit and requires it to remain in the selected branch history; normal branch advances are allowed, deletion or a diverging force-push rejects. Tag publication requires the tag to still resolve to the recorded commit. Dispatch the publisher from `master`; its hosted routing job rejects other refs before scheduling the original machine. The packaged source commit is recorded separately. `version_mode=default` selects product for production and auto for test; product preserves the selected checkout version, auto appends date/run/attempt, and manual requires `build_version`. A stable-channel package requires a stable version, so test/stable needs product or a stable manual version. The optional `test_release_id` is the test URL prefix: blank retains the machine default, successive upgrade tests on that platform need the same value, and defaults should be rotated periodically. It is not a GitHub run ID. See [packaging configuration](../../packaging/README.md#configuration-and-local-builds) for its validation and rotation limits.
|
||||
Select the platform's `Package Desktop` workflow, preset, source, channel and version mode. `source_ref` accepts repository branch names (for example `feature/desktop-update`) or `dsh-v*` tags for test; production accepts only `master` or `dsh-v*` tags. Names matching `dsh-v` followed by a digit and letters, digits, dots or hyphens are treated as tags, even if a branch has that name. Tags must match the selected product version. We recommend dispatching the packaging workflow from `master` and enter the source separately; raw commit SHAs, PR refs and fork repositories are not supported. Sources without the CI adapters fail before dependency installation. Only select trusted branches: their build code runs on the signing machine. This is an operating convention, not enforced workflow-origin or credential isolation. Publication uses the recorded commit and requires it to remain in the selected branch history; normal branch advances are allowed, deletion or a diverging force-push rejects. Tag publication requires the tag to still resolve to the recorded commit. Dispatch the publisher from `master`; its hosted routing job rejects other refs before scheduling the original machine. The packaged source commit is recorded separately. `version_mode=default` selects follow-dsh for production and build-number for test; follow-dsh preserves the selected checkout’s dsh version, build-number appends date/run/attempt in either environment, and manual requires `build_version`. Existing dispatch commands using `-f version_mode=auto` must use `-f version_mode=build-number`, and `-f version_mode=product` becomes `-f version_mode=follow-dsh`; the local CLI option `--build-version auto` is unchanged. Workflows temporarily pass legacy mode names to sources that contain the CI adapters but predate the mode rename; current parsers accept both names. Removal is planned for 2027-01-04, not automatic: remove workflow mappings and parser aliases together, then require selected sources to support the new mode names. The operator-selected channel accepts any supported build version, including alpha and RC versions; it does not change version precedence or permit client downgrades. The optional `test_release_id` is the test URL prefix: blank retains the machine default, successive upgrade tests on that platform need the same value, and defaults should be rotated periodically. It is not a GitHub run ID. See [packaging configuration](../../packaging/README.md#configuration-and-local-builds) for its validation and rotation limits.
|
||||
|
||||
Packaging only retains files. After success, open **Publish Desktop · COS → Run workflow** on `master`, enter `package_run_id` from the packaging run URL, and optionally enter `package_run_attempt`. Blank selects the latest attempt and its successful package job. An archival-only retry retains the original build attempt; a failed newer package job never falls back to an older success. An explicit attempt selects that build even when its separate GitHub archival failed. Windows must be signed; macOS must finish notarization and stapling. GitHub job evidence selects the original runner; its local manifest supplies the preset, version, source and channel before approval. No installer or request artifact is downloaded. Retained packages with the old `publishRequested=false` flag remain eligible; missing or changed local files stop publication.
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ Environment 审批限制此工作流,不限制同一机器账户运行的任
|
||||
|
||||
发布到 test 或 production 都必须在 Windows 选择 `signing=signed`,或在 macOS 不勾选**跳过公证**。无签名、无公证的包仅限内部调试,不能发布或用于自动更新。托管路由 job 会在安装依赖前,依据所选 run 和 attempt 对应的 artifact 名称拒绝已标明的调试包。仅重跑归档时,artifact 名称保留原构建 attempt,早期检查可能未识别该包;本地清单资格校验仍会在发布前拒绝调试包。缺少 artifact 元数据不代表允许发布:原机器仍会验证留存清单和签名。遇到调试包拒绝提示时,需重新完成签名或公证,再使用新包的 run ID / attempt 发布;重跑发布不能使现有产物获得发布资格。
|
||||
|
||||
选择对应平台的 `Package Desktop` 工作流、preset、源码、通道和版本模式。test 的 `source_ref` 接受仓库内分支名(如 `feature/desktop-update`)或 `dsh-v*` tag;production 仅接受 `master` 或 `dsh-v*` tag。符合 `dsh-v` 后接数字、其余为字母、数字、点或连字符的名称按 tag 处理,即使存在同名分支。tag 必须与所选产品版本一致。建议从 `master` 启动打包工作流,再单独填写打包源码;不支持直接填写 commit SHA、PR ref 或 fork 仓库。源码必须包含 CI 适配脚本,旧源码会在安装依赖前失败。仅选择可信分支:其构建代码将在签名机器上执行。这是操作约定,不提供工作流来源强制限制或凭据隔离。发布使用记录的 commit,并要求该提交仍在所选分支历史中;正常追加提交不影响发布,删除分支或导致历史分叉的强推会拒绝。tag 发布要求 tag 仍指向记录的提交。从 `master` 发起发布;托管路由 job 会在派发到原机器前拒绝其他 ref。实际打包源码提交单独记录。`version_mode=default` 对 production 选择 product,对 test 选择 auto;product 保留所选源码版本,auto 追加日期/run/attempt,manual 要求填写 `build_version`。stable 包要求稳定版本,因此 test/stable 需选择 product 或稳定的手填版本。可选 `test_release_id` 是测试 URL 前缀:留空沿用机器默认值,同一平台连续升级测试需保持相同值,并应定期轮换默认值。它不是 GitHub run ID。格式校验和轮换限制见[打包配置](../../packaging/README.zh.md#configuration-and-local-builds)。
|
||||
选择对应平台的 `Package Desktop` 工作流、preset、源码、通道和版本模式。test 的 `source_ref` 接受仓库内分支名(如 `feature/desktop-update`)或 `dsh-v*` tag;production 仅接受 `master` 或 `dsh-v*` tag。符合 `dsh-v` 后接数字、其余为字母、数字、点或连字符的名称按 tag 处理,即使存在同名分支。tag 必须与所选产品版本一致。建议从 `master` 启动打包工作流,再单独填写打包源码;不支持直接填写 commit SHA、PR ref 或 fork 仓库。源码必须包含 CI 适配脚本,旧源码会在安装依赖前失败。仅选择可信分支:其构建代码将在签名机器上执行。这是操作约定,不提供工作流来源强制限制或凭据隔离。发布使用记录的 commit,并要求该提交仍在所选分支历史中;正常追加提交不影响发布,删除分支或导致历史分叉的强推会拒绝。tag 发布要求 tag 仍指向记录的提交。从 `master` 发起发布;托管路由 job 会在派发到原机器前拒绝其他 ref。实际打包源码提交单独记录。`version_mode=default` 对 production 选择 follow-dsh,对 test 选择 build-number;follow-dsh 跟随所选源码的 dsh 包版本号,build-number 在任一环境均追加日期/run/attempt,manual 要求填写 `build_version`。已有派发命令中的 `-f version_mode=auto` 须改为 `-f version_mode=build-number`,`-f version_mode=product` 改为 `-f version_mode=follow-dsh`;本地 CLI 的 `--build-version auto` 不变。工作流暂时传递旧模式名,以兼容已含 CI 适配脚本但早于模式改名的源码;当前解析器同时接受新旧名称。计划于 2027-01-04 同时删除 workflow 映射和解析器别名,并要求所选源码支持新模式名;该日期不会触发自动失效。操作人员选择的渠道接受任意受支持的构建版本,包括 alpha 和 RC 版本;渠道不改变版本优先级,也不允许客户端降级。可选 `test_release_id` 是测试 URL 前缀:留空沿用机器默认值,同一平台连续升级测试需保持相同值,并应定期轮换默认值。它不是 GitHub run ID。格式校验和轮换限制见[打包配置](../../packaging/README.zh.md#configuration-and-local-builds)。
|
||||
|
||||
打包只保留文件。成功后,在 `master` 上打开 **Publish Desktop · COS → Run workflow**,填写打包运行 URL 中的 `package_run_id`,并可填写 `package_run_attempt`。留空选择最新尝试及其成功的 package job;归档重跑继续使用原始构建 attempt,新的构建失败时不回退到较早成功构建。显式填写则选择对应构建,独立的 GitHub 归档失败不取消其资格。Windows 必须完成签名,macOS 必须完成公证和 stapling。GitHub job 证据确定原 runner,本机清单在审批前提供 preset、版本、源码和通道。不会下载安装包或请求 artifact。旧清单的 `publishRequested=false` 不影响发布资格;本机文件缺失或变化会停止发布。
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Resolve the version one build publishes, which is not always the version the
|
||||
* repository declares.
|
||||
*
|
||||
* Manual Desktop CI uses UTC date, workflow run number and attempt as
|
||||
* Desktop CI version mode `build-number` uses UTC date, workflow run number and attempt as
|
||||
* `.YYYYMMDD.run.attempt`; these identify unpublished builds, not release sequences.
|
||||
*
|
||||
* A production release publishes the version in the manifests, aligned with the
|
||||
|
||||
@@ -9,14 +9,16 @@ import { desktopBuildVersionPrefix, validateDesktopBuildVersion } from './deskto
|
||||
* @returns {string} Validated build version.
|
||||
*/
|
||||
export function desktopCiVersion(environment, productVersion, now = new Date()) {
|
||||
const mode = environment.PACKAGE_VERSION_MODE
|
||||
const selected = mode === 'default' ? (environment.DSH_PACKAGING_PRESET === 'production' ? 'product' : 'auto') : mode
|
||||
if (selected !== undefined && !['product', 'auto', 'manual'].includes(selected)) throw new Error('Invalid version mode')
|
||||
const requested = selected === 'product' ? productVersion : selected === 'auto' ? 'auto' : environment.PACKAGE_BUILD_VERSION?.trim()
|
||||
const input = environment.PACKAGE_VERSION_MODE
|
||||
// TODO: Remove legacy mode aliases on 2027-01-04 with the workflow source_ref mapping.
|
||||
const mode = input === 'product' ? 'follow-dsh' : input === 'auto' ? 'build-number' : input
|
||||
const selected = mode === 'default' ? (environment.DSH_PACKAGING_PRESET === 'production' ? 'follow-dsh' : 'build-number') : mode
|
||||
if (selected !== undefined && !['follow-dsh', 'build-number', 'manual'].includes(selected)) throw new Error('Invalid version mode')
|
||||
const requested = selected === 'follow-dsh' ? productVersion : selected === 'build-number' ? 'auto' : environment.PACKAGE_BUILD_VERSION?.trim()
|
||||
if (selected === 'manual' && (!requested || requested === 'auto')) throw new Error('Manual version mode requires an explicit version')
|
||||
const date = now.toISOString().slice(0, 10).replaceAll('-', '')
|
||||
const example = `${desktopBuildVersionPrefix(productVersion)}${date}.1`
|
||||
if (!requested) throw new Error(`Build version is required: use auto, ${productVersion}, or e.g. ${example}`)
|
||||
if (!requested) throw new Error(`Build version is required: select version_mode=build-number, or use ${productVersion}, or e.g. ${example}`)
|
||||
let candidate = requested
|
||||
if (requested === 'auto') {
|
||||
for (const name of ['GITHUB_RUN_NUMBER', 'GITHUB_RUN_ATTEMPT']) {
|
||||
@@ -24,11 +26,10 @@ export function desktopCiVersion(environment, productVersion, now = new Date())
|
||||
}
|
||||
candidate = `${desktopBuildVersionPrefix(productVersion)}${date}.${environment.GITHUB_RUN_NUMBER}.${environment.GITHUB_RUN_ATTEMPT}`
|
||||
}
|
||||
if (environment.DSH_DESKTOP_UPDATE_CHANNEL === 'stable' && candidate.includes('-')) throw new Error('Stable channel requires a stable version; select product or manual stable version')
|
||||
try {
|
||||
const resolved = validateDesktopBuildVersion(candidate, productVersion)
|
||||
return resolved
|
||||
} catch {
|
||||
throw new Error(`Invalid build version for this checkout: use auto, ${productVersion}, or e.g. ${example}`)
|
||||
throw new Error(`Invalid build version for this checkout: select version_mode=build-number, or use ${productVersion}, or e.g. ${example}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +223,6 @@ export async function createDesktopUploadPlan(
|
||||
throw new Error(`desktop upload: ${targetName} package completion record for ${buildVersion} does not match the ${update.environment} update destination`)
|
||||
}
|
||||
|
||||
if (update.channel === 'stable' && buildVersion.includes('-')) throw new Error('desktop upload: stable requires a stable version')
|
||||
const metadataFilename = desktopUpdateMetadataFilename(buildVersion, target.platform, update.channel)
|
||||
const metadataPath = join(artifactsRoot, metadataFilename)
|
||||
let metadataValue: unknown
|
||||
|
||||
@@ -105,7 +105,6 @@ export function createElectronBuilderConfig(
|
||||
// the artifact names, the update feed, and the installed app.getVersion() the updater compares against.
|
||||
const productVersion = JSON.parse(readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf8')).version
|
||||
const buildVersion = resolveDesktopBuildVersion(env, productVersion)
|
||||
if (update?.channel === 'stable' && buildVersion.includes('-')) throw new Error('desktop package: stable requires a stable version')
|
||||
const packaged = resolveDesktopBuildCommit(env)
|
||||
return {
|
||||
appId,
|
||||
|
||||
@@ -276,7 +276,6 @@ export class DesktopUpdateCoordinator {
|
||||
this.cancellation = result.cancellationToken
|
||||
const version = result.updateInfo.version
|
||||
if (valid(version) === null) throw new Error('desktop update: feed version is invalid')
|
||||
if (this.channel === 'stable' && version.includes('-')) throw new Error('desktop update: stable feed contains a prerelease')
|
||||
this.release = result.isUpdateAvailable && gt(version, this.currentVersion())
|
||||
? { kind: 'checked', version } : { kind: 'none' }
|
||||
return this.setState(this.candidate === undefined ? { phase: 'idle' } : { phase: 'available', version })
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { AppUpdater } from 'electron-updater'
|
||||
|
||||
/**
|
||||
* Select a feed without permitting downgrades or prereleases on stable.
|
||||
* Select the operator-managed feed, accepting prereleases while preventing downgrades.
|
||||
* @param updater - Updater whose prior operations have settled.
|
||||
* @param channel - Selected distribution channel.
|
||||
*/
|
||||
@@ -10,5 +10,5 @@ export function selectUpdateChannel(updater: AppUpdater, channel: 'nightly' | 's
|
||||
updater.channel = channel
|
||||
// The library channel setter enables downgrades.
|
||||
updater.allowDowngrade = false
|
||||
updater.allowPrerelease = channel === 'nightly'
|
||||
updater.allowPrerelease = true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { expect, it } from 'vitest'
|
||||
import { desktopCiVersion } from '../scripts/desktop-ci-version.mjs'
|
||||
import { desktopBuildVersionPrefix } from '../scripts/desktop-build-version.mjs'
|
||||
import { createElectronBuilderConfig } from '../scripts/electron-builder-config.mjs'
|
||||
|
||||
it.each(['nightly', 'stable'] as const)('resolves every CI version mode independently of %s', (channel) => {
|
||||
const now = new Date('2026-11-03T00:00:00Z')
|
||||
for (const version of ['1.2.3', '1.2.3-alpha.1', '1.2.3-rc.1']) {
|
||||
const env = { DSH_DESKTOP_UPDATE_CHANNEL: channel, GITHUB_RUN_NUMBER: '7', GITHUB_RUN_ATTEMPT: '2' }
|
||||
const generated = `${desktopBuildVersionPrefix(version)}20261103.7.2`
|
||||
expect(desktopCiVersion({ ...env, PACKAGE_VERSION_MODE: 'follow-dsh' }, version, now)).toBe(version)
|
||||
for (const preset of ['test', 'production']) {
|
||||
expect(desktopCiVersion({ ...env, DSH_PACKAGING_PRESET: preset, PACKAGE_VERSION_MODE: 'build-number' }, version, now)).toBe(generated)
|
||||
}
|
||||
expect(desktopCiVersion({ ...env, PACKAGE_VERSION_MODE: 'product' }, version, now)).toBe(version)
|
||||
expect(desktopCiVersion({ ...env, PACKAGE_VERSION_MODE: 'auto' }, version, now)).toBe(generated)
|
||||
expect(desktopCiVersion({ ...env, PACKAGE_VERSION_MODE: 'manual', PACKAGE_BUILD_VERSION: generated }, version, now)).toBe(generated)
|
||||
expect(desktopCiVersion({ ...env, PACKAGE_VERSION_MODE: 'default', DSH_PACKAGING_PRESET: 'production' }, version, now)).toBe(version)
|
||||
expect(desktopCiVersion({ ...env, PACKAGE_VERSION_MODE: 'default', DSH_PACKAGING_PRESET: 'test' }, version, now)).toBe(generated)
|
||||
expect(() => desktopCiVersion({ ...env, PACKAGE_VERSION_MODE: 'manual', PACKAGE_BUILD_VERSION: 'invalid' }, version, now)).toThrow('Invalid build version')
|
||||
}
|
||||
})
|
||||
|
||||
it.each(['arm64', 'x64'] as const)('embeds stable in a prerelease macOS %s package and provider', (arch) => {
|
||||
const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string }
|
||||
const version = `${desktopBuildVersionPrefix(manifest.version)}20261103.1`
|
||||
const config = createElectronBuilderConfig({
|
||||
DSH_DESKTOP_APP_ID: 'com.example.channel-test',
|
||||
DSH_DESKTOP_AUTO_UPDATE_ENV: 'production',
|
||||
DSH_DESKTOP_UPDATE_CHANNEL: 'stable',
|
||||
DSH_DESKTOP_BUILD_VERSION: version,
|
||||
DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN: 'https://policy.example.com',
|
||||
DSH_DESKTOP_MACOS_SIGNING_IDENTITY: 'Example Company (TEAMID1234)',
|
||||
DSH_DESKTOP_MACOS_TEAM_ID: 'TEAMID1234',
|
||||
APPLE_KEYCHAIN_PROFILE: 'channel-test',
|
||||
}, 'darwin', arch)
|
||||
expect(config.extraMetadata).toMatchObject({ version, dshDesktopUpdateChannel: 'stable' })
|
||||
expect(config.publish).toEqual([{ provider: 'generic', url: `https://download.deepseek.com/dsh-desk/feeds/mac-${arch}/`, channel: 'stable' }])
|
||||
})
|
||||
|
||||
it('accepts a stable prerelease in an unsigned Windows x64 configuration', () => {
|
||||
const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string }
|
||||
const version = `${desktopBuildVersionPrefix(manifest.version)}20261103.1`
|
||||
const config = createElectronBuilderConfig({
|
||||
DSH_DESKTOP_APP_ID: 'com.example.channel-test',
|
||||
DSH_DESKTOP_AUTO_UPDATE_ENV: 'production',
|
||||
DSH_DESKTOP_UPDATE_CHANNEL: 'stable',
|
||||
DSH_DESKTOP_BUILD_VERSION: version,
|
||||
DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN: 'https://policy.example.com',
|
||||
DSH_DESKTOP_UNSIGNED: '1',
|
||||
}, 'win32', 'x64')
|
||||
expect(config.extraMetadata).toMatchObject({ version })
|
||||
expect(config.publish).toBeNull()
|
||||
})
|
||||
@@ -81,6 +81,21 @@ it.each(['binary', 'same-version-feed', 'newer-feed'] as const)('stops on %s con
|
||||
expect(f.writes).toHaveLength(0)
|
||||
})
|
||||
|
||||
it.each(['1.0.0-rc.1', '1.0.0-test.20261104.1.1'])('rejects %s before writing when stable already serves its release version', async (version) => {
|
||||
const f = fixture('production')
|
||||
const plan: DesktopUploadPlan = {
|
||||
...f.plan,
|
||||
version,
|
||||
artifacts: f.plan.artifacts.map(artifact => artifact.channelMetadata
|
||||
? { ...artifact, filename: 'stable.yml', key: 'feed/stable.yml', contents: `version: ${version}\n` }
|
||||
: artifact),
|
||||
}
|
||||
f.objects.set('feed/stable.yml', observed('version: 1.0.0\n'))
|
||||
await expect(publishDesktopRelease(plan, f.operations)).rejects.toMatchObject({ code: 'superseded' })
|
||||
expect(f.writes).toEqual([])
|
||||
expect(f.tags()).toBe(0)
|
||||
})
|
||||
|
||||
it('rejects a files-only plan and a production build without clean commit evidence', async () => {
|
||||
const f = fixture('production')
|
||||
await expect(publishDesktopRelease({ ...f.plan, artifacts: f.plan.artifacts.slice(0, 2) }, f.operations)).rejects.toMatchObject({ code: 'invalid-plan' })
|
||||
|
||||
@@ -186,8 +186,25 @@ describe('desktop upload plan', () => {
|
||||
for (const plan of [nightly, stable]) {
|
||||
expect(plan.artifacts.find(file => file.channelMetadata)!.contents).toContain('stagingPercentage: 10')
|
||||
}
|
||||
const prerelease = await fixture('win-x64', '1.2.3-alpha.1', environment, 'stable')
|
||||
await expect(createDesktopUploadPlan('win-x64', prerelease)).rejects.toThrow(/stable requires a stable version/u)
|
||||
})
|
||||
|
||||
it.each(['mac-arm64', 'mac-x64', 'win-x64'] as const)('publishes manually selected prereleases on stable for %s', async (target) => {
|
||||
const summaries: { environment: string; version: string; keys: string[] }[] = []
|
||||
for (const environment of ['test', 'production'] as const) {
|
||||
for (const version of ['1.2.3-alpha.1', '1.2.3-rc.1']) {
|
||||
const plan = await createDesktopUploadPlan(target, await fixture(target, version, environment, 'stable'))
|
||||
expect(plan.version).toBe(version)
|
||||
const feed = plan.artifacts.find(file => file.channelMetadata)!
|
||||
expect(feed.filename).toBe(target === 'win-x64' ? 'stable.yml' : 'stable-mac.yml')
|
||||
const payload = plan.artifacts.find(file => file.filename.endsWith(target === 'win-x64' ? '.exe' : '.zip'))!
|
||||
expect(payload.key).toContain(`/bin/${target}/stable/`)
|
||||
expect(load(feed.contents!)).toMatchObject({ version, files: [{
|
||||
url: `${environment === 'test' ? TEST_ORIGIN : 'https://download.deepseek.com'}/${payload.key}`,
|
||||
}] })
|
||||
summaries.push({ environment, version, keys: plan.artifacts.map(file => file.key) })
|
||||
}
|
||||
}
|
||||
await expect(`${JSON.stringify(summaries, null, 2)}\n`).toMatchFileSnapshot(`./expected/stable-prerelease-${target}.json`)
|
||||
})
|
||||
|
||||
it('publishes fixed feeds referencing versioned binaries without overriding CDN cache policy', async () => {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"environment": "test",
|
||||
"version": "1.2.3-alpha.1",
|
||||
"keys": [
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-arm64/stable/deepseek-harness-1.2.3-alpha.1-mac-arm64.dmg",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-arm64/stable/deepseek-harness-1.2.3-alpha.1-mac-arm64.zip",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-arm64/stable/deepseek-harness-1.2.3-alpha.1-mac-arm64.zip.blockmap",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/feeds/mac-arm64/stable-mac.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"environment": "test",
|
||||
"version": "1.2.3-rc.1",
|
||||
"keys": [
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-arm64/stable/deepseek-harness-1.2.3-rc.1-mac-arm64.dmg",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-arm64/stable/deepseek-harness-1.2.3-rc.1-mac-arm64.zip",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-arm64/stable/deepseek-harness-1.2.3-rc.1-mac-arm64.zip.blockmap",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/feeds/mac-arm64/stable-mac.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"environment": "production",
|
||||
"version": "1.2.3-alpha.1",
|
||||
"keys": [
|
||||
"dsh-desk/bin/mac-arm64/stable/deepseek-harness-1.2.3-alpha.1-mac-arm64.dmg",
|
||||
"dsh-desk/bin/mac-arm64/stable/deepseek-harness-1.2.3-alpha.1-mac-arm64.zip",
|
||||
"dsh-desk/bin/mac-arm64/stable/deepseek-harness-1.2.3-alpha.1-mac-arm64.zip.blockmap",
|
||||
"dsh-desk/feeds/mac-arm64/stable-mac.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"environment": "production",
|
||||
"version": "1.2.3-rc.1",
|
||||
"keys": [
|
||||
"dsh-desk/bin/mac-arm64/stable/deepseek-harness-1.2.3-rc.1-mac-arm64.dmg",
|
||||
"dsh-desk/bin/mac-arm64/stable/deepseek-harness-1.2.3-rc.1-mac-arm64.zip",
|
||||
"dsh-desk/bin/mac-arm64/stable/deepseek-harness-1.2.3-rc.1-mac-arm64.zip.blockmap",
|
||||
"dsh-desk/feeds/mac-arm64/stable-mac.yml"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"environment": "test",
|
||||
"version": "1.2.3-alpha.1",
|
||||
"keys": [
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-x64/stable/deepseek-harness-1.2.3-alpha.1-mac-x64.dmg",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-x64/stable/deepseek-harness-1.2.3-alpha.1-mac-x64.zip",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-x64/stable/deepseek-harness-1.2.3-alpha.1-mac-x64.zip.blockmap",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/feeds/mac-x64/stable-mac.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"environment": "test",
|
||||
"version": "1.2.3-rc.1",
|
||||
"keys": [
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-x64/stable/deepseek-harness-1.2.3-rc.1-mac-x64.dmg",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-x64/stable/deepseek-harness-1.2.3-rc.1-mac-x64.zip",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/mac-x64/stable/deepseek-harness-1.2.3-rc.1-mac-x64.zip.blockmap",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/feeds/mac-x64/stable-mac.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"environment": "production",
|
||||
"version": "1.2.3-alpha.1",
|
||||
"keys": [
|
||||
"dsh-desk/bin/mac-x64/stable/deepseek-harness-1.2.3-alpha.1-mac-x64.dmg",
|
||||
"dsh-desk/bin/mac-x64/stable/deepseek-harness-1.2.3-alpha.1-mac-x64.zip",
|
||||
"dsh-desk/bin/mac-x64/stable/deepseek-harness-1.2.3-alpha.1-mac-x64.zip.blockmap",
|
||||
"dsh-desk/feeds/mac-x64/stable-mac.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"environment": "production",
|
||||
"version": "1.2.3-rc.1",
|
||||
"keys": [
|
||||
"dsh-desk/bin/mac-x64/stable/deepseek-harness-1.2.3-rc.1-mac-x64.dmg",
|
||||
"dsh-desk/bin/mac-x64/stable/deepseek-harness-1.2.3-rc.1-mac-x64.zip",
|
||||
"dsh-desk/bin/mac-x64/stable/deepseek-harness-1.2.3-rc.1-mac-x64.zip.blockmap",
|
||||
"dsh-desk/feeds/mac-x64/stable-mac.yml"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
[
|
||||
{
|
||||
"environment": "test",
|
||||
"version": "1.2.3-alpha.1",
|
||||
"keys": [
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/win-x64/stable/deepseek-harness-1.2.3-alpha.1-win-x64.exe",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/win-x64/stable/deepseek-harness-1.2.3-alpha.1-win-x64.exe.blockmap",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/feeds/win-x64/stable.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"environment": "test",
|
||||
"version": "1.2.3-rc.1",
|
||||
"keys": [
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/win-x64/stable/deepseek-harness-1.2.3-rc.1-win-x64.exe",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/bin/win-x64/stable/deepseek-harness-1.2.3-rc.1-win-x64.exe.blockmap",
|
||||
"dsh-desk/0123456789abcdef0123456789abcdef/feeds/win-x64/stable.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"environment": "production",
|
||||
"version": "1.2.3-alpha.1",
|
||||
"keys": [
|
||||
"dsh-desk/bin/win-x64/stable/deepseek-harness-1.2.3-alpha.1-win-x64.exe",
|
||||
"dsh-desk/bin/win-x64/stable/deepseek-harness-1.2.3-alpha.1-win-x64.exe.blockmap",
|
||||
"dsh-desk/feeds/win-x64/stable.yml"
|
||||
]
|
||||
},
|
||||
{
|
||||
"environment": "production",
|
||||
"version": "1.2.3-rc.1",
|
||||
"keys": [
|
||||
"dsh-desk/bin/win-x64/stable/deepseek-harness-1.2.3-rc.1-win-x64.exe",
|
||||
"dsh-desk/bin/win-x64/stable/deepseek-harness-1.2.3-rc.1-win-x64.exe.blockmap",
|
||||
"dsh-desk/feeds/win-x64/stable.yml"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -14,7 +14,7 @@ describe('manual Windows CI version', () => {
|
||||
})
|
||||
it.each(['', 'bad', '0.3.0'])('reports checkout-specific guidance for %s', (version) => {
|
||||
expect(() => windowsCiVersion({ PACKAGE_BUILD_VERSION: version }, '0.2.1-alpha.1', now))
|
||||
.toThrow(/auto, 0\.2\.1-alpha\.1, or e\.g\. 0\.2\.1-alpha\.1\.20261009\.1/u)
|
||||
.toThrow(/version_mode=build-number, or use 0\.2\.1-alpha\.1, or e\.g\. 0\.2\.1-alpha\.1\.20261009\.1/u)
|
||||
})
|
||||
it.each([{ GITHUB_RUN_NUMBER: '0' }, { GITHUB_RUN_ATTEMPT: '1\nINJECTED=value' }])('rejects invalid run identity %j', (override) => {
|
||||
expect(() => windowsCiVersion({ ...inputs, ...override }, '0.2.1', now)).toThrow(/positive integer/u)
|
||||
@@ -25,7 +25,7 @@ describe('manual Windows CI version', () => {
|
||||
it('resolves the selected version policy independently of the update channel', () => {
|
||||
expect(windowsCiVersion({ ...inputs, PACKAGE_VERSION_MODE: 'default', DSH_PACKAGING_PRESET: 'production' }, '1.2.3', now)).toBe('1.2.3')
|
||||
expect(windowsCiVersion({ ...inputs, PACKAGE_VERSION_MODE: 'default', DSH_PACKAGING_PRESET: 'test' }, '1.2.3', now)).toBe('1.2.3-test.20261009.42.2')
|
||||
expect(windowsCiVersion({ PACKAGE_VERSION_MODE: 'product' }, '1.2.3-alpha.1', now)).toBe('1.2.3-alpha.1')
|
||||
expect(windowsCiVersion({ PACKAGE_VERSION_MODE: 'follow-dsh' }, '1.2.3-alpha.1', now)).toBe('1.2.3-alpha.1')
|
||||
expect(() => windowsCiVersion({ PACKAGE_VERSION_MODE: 'manual', PACKAGE_BUILD_VERSION: 'auto' }, '1.2.3', now)).toThrow(/explicit version/u)
|
||||
expect(() => windowsCiVersion({ PACKAGE_VERSION_MODE: 'invalid' }, '1.2.3', now)).toThrow(/version mode/u)
|
||||
})
|
||||
|
||||
@@ -244,18 +244,28 @@ it.each([new DesktopUpdatePreparationError('stop-failed', 'private diagnostic'),
|
||||
})
|
||||
|
||||
|
||||
it('keeps a stable-version nightly installation on nightly and rejects prereleases on stable', async () => {
|
||||
const nightly = fixture('nightly', '1.0.0')
|
||||
expect(nightly.updater.channel).toBe('nightly')
|
||||
expect(nightly.updater.allowPrerelease).toBe(true)
|
||||
expect((await nightly.coordinator.check()).phase).toBe('available')
|
||||
expect(nightly.updater.channel).toBe('nightly')
|
||||
const stable = fixture('stable', '1.0.0')
|
||||
expect(stable.updater.channel).toBe('stable')
|
||||
expect(stable.updater.allowPrerelease).toBe(false)
|
||||
expect(stable.updater.allowDowngrade).toBe(false)
|
||||
expect((await stable.coordinator.check()).phase).toBe('error')
|
||||
expect(stable.downloadUpdate).not.toHaveBeenCalled()
|
||||
it.each(['nightly', 'stable'] as const)('accepts newer prereleases on the selected %s channel', async (channel) => {
|
||||
const f = fixture(channel, '1.0.0')
|
||||
expect(f.updater.channel).toBe(channel)
|
||||
expect(f.updater.allowPrerelease).toBe(true)
|
||||
expect(f.updater.allowDowngrade).toBe(false)
|
||||
for (const version of ['1.1.0-alpha.1', '1.1.0-rc.2', '1.1.0']) {
|
||||
f.checkForUpdates.mockResolvedValueOnce({ isUpdateAvailable: true, updateInfo: { version } })
|
||||
expect(await f.coordinator.check()).toMatchObject({ phase: 'available', version })
|
||||
expect(f.updater.channel).toBe(channel)
|
||||
}
|
||||
})
|
||||
|
||||
it.each(['nightly', 'stable'] as const)('rejects older, equal and malformed versions on %s', async (channel) => {
|
||||
for (const version of ['0.9.0', '1.0.0-rc.1', '1.0.0', 'invalid']) {
|
||||
const f = fixture(channel, '1.0.0')
|
||||
f.checkForUpdates.mockResolvedValueOnce({ isUpdateAvailable: true, updateInfo: { version } })
|
||||
expect((await f.coordinator.check()).phase).toBe(version === 'invalid' ? 'error' : 'idle')
|
||||
await expect(f.coordinator.download(version)).rejects.toThrow(/no checked update/u)
|
||||
await expect(f.coordinator.install(version)).rejects.toThrow(/confirmed target is not ready/u)
|
||||
expect(f.downloadUpdate).not.toHaveBeenCalled()
|
||||
expect(f.quitAndInstall).not.toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -60,11 +60,19 @@ async function main() {
|
||||
await target.checkForUpdates()
|
||||
assert.equal(requests.at(-1), `/stable${process.platform === 'darwin' ? '-mac' : ''}.yml`)
|
||||
assert.equal(target.allowDowngrade, false)
|
||||
assert.equal(target.allowPrerelease, false)
|
||||
version = '0.9.0'
|
||||
await discardDesktopUpdate(target)
|
||||
await target.checkForUpdates()
|
||||
assert.equal(target.updateInfoAndProvider, null)
|
||||
assert.equal(target.allowPrerelease, true)
|
||||
for (const candidate of ['2.0.0-alpha.1', '2.0.0-rc.1', '2.0.0']) {
|
||||
version = candidate
|
||||
await discardDesktopUpdate(target)
|
||||
assert.equal((await target.checkForUpdates()).isUpdateAvailable, true)
|
||||
assert.equal(target.updateInfoAndProvider.info.version, candidate)
|
||||
}
|
||||
for (const candidate of ['0.9.0-rc.1', '1.0.0-rc.1']) {
|
||||
version = candidate
|
||||
await discardDesktopUpdate(target)
|
||||
assert.equal((await target.checkForUpdates()).isUpdateAvailable, false)
|
||||
assert.equal(target.updateInfoAndProvider, null)
|
||||
}
|
||||
version = '2.0.0'
|
||||
percentage = 0
|
||||
await discardDesktopUpdate(target)
|
||||
@@ -98,7 +106,7 @@ async function main() {
|
||||
// A later process can encounter files that a previous process could not remove.
|
||||
await writeFile(file, oldPayload)
|
||||
await writeFile(join(pending, 'update-info.json'), cachedInfo)
|
||||
version = '2.1.0'
|
||||
version = '2.1.0-rc.1'
|
||||
payload = Buffer.from('different release after obsolete cache survived')
|
||||
sha512 = createHash('sha512').update(payload).digest('base64')
|
||||
const recovered = new Target(undefined, adapter)
|
||||
@@ -115,7 +123,7 @@ async function main() {
|
||||
await recovered.checkForUpdates()
|
||||
await recovered.downloadUpdate()
|
||||
assert.deepEqual(await readFile(recovered.downloadedUpdateHelper.file), payload)
|
||||
assert.equal(recovered.downloadedUpdateHelper.versionInfo.version, '2.1.0')
|
||||
assert.equal(recovered.downloadedUpdateHelper.versionInfo.version, '2.1.0-rc.1')
|
||||
console.log('PASS: obsolete cache rejected by a new updater instance; current payload verified')
|
||||
console.log('PASS: real provider channel switching, rollout eligibility, real downloads, native platform cache and proxy retirement')
|
||||
} finally {
|
||||
|
||||
@@ -33,6 +33,8 @@ describe.each(['arm64', 'x64'])('macOS %s packaging', (arch) => {
|
||||
|
||||
it('confines signed Mac packaging to manual dispatch and a dedicated Apple Silicon runner', () => {
|
||||
expect(Object.keys(workflow.on)).toEqual(['workflow_dispatch'])
|
||||
expect(workflow.jobs.package!.env.PACKAGE_VERSION_MODE).toBe("${{ inputs.version_mode == 'follow-dsh' && 'product' || inputs.version_mode == 'build-number' && 'auto' || inputs.version_mode }}")
|
||||
expect(workflow.on.workflow_dispatch).toMatchObject({ inputs: { version_mode: { default: 'default', options: ['default', 'follow-dsh', 'build-number', 'manual'] } } })
|
||||
expect(workflow.jobs.package).not.toHaveProperty('name')
|
||||
expect(workflow.on.workflow_dispatch).toMatchObject({ inputs: { test_release_id: { type: 'string', default: '', required: false } } })
|
||||
expect(workflow.on.workflow_dispatch).not.toHaveProperty('inputs.publish')
|
||||
|
||||
@@ -62,6 +62,7 @@ it.skipIf(process.platform !== 'win32')('resets only the allocated checkout with
|
||||
|
||||
it('routes manual packaging to the dedicated runner with explicit preset and signing selection', () => {
|
||||
expect(Object.keys(workflow.on)).toEqual(['workflow_dispatch'])
|
||||
expect(workflow.on.workflow_dispatch).toMatchObject({ inputs: { version_mode: { default: 'default', options: ['default', 'follow-dsh', 'build-number', 'manual'] } } })
|
||||
expect(workflow.jobs.package).not.toHaveProperty('name')
|
||||
expect(job['runs-on']).toBe('dsh-win-package-trial')
|
||||
expect(JSON.stringify(job.env)).not.toContain('runner.')
|
||||
@@ -79,7 +80,7 @@ it('routes manual packaging to the dedicated runner with explicit preset and sig
|
||||
expect(workflow.on.workflow_dispatch!.inputs.version_mode!.default).toBe('default')
|
||||
expect(workflow.on.workflow_dispatch!.inputs.source_ref!.default).toBe('master')
|
||||
expect(workflow.on.workflow_dispatch!.inputs.update_channel!.default).toBe('nightly')
|
||||
expect(job.env.PACKAGE_VERSION_MODE).toBe('${{ inputs.version_mode }}')
|
||||
expect(job.env.PACKAGE_VERSION_MODE).toBe("${{ inputs.version_mode == 'follow-dsh' && 'product' || inputs.version_mode == 'build-number' && 'auto' || inputs.version_mode }}")
|
||||
expect(job.env.PACKAGE_SOURCE_REF).toBe('${{ inputs.source_ref }}')
|
||||
expect(job.env.DSH_DESKTOP_UPDATE_CHANNEL).toBe('${{ inputs.update_channel }}')
|
||||
expect(job.env.DSH_PACKAGING_PRESET).toBe('${{ inputs.preset }}')
|
||||
|
||||
Reference in New Issue
Block a user