diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 48b5722e..06e23f27 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -21,6 +21,8 @@ on: # Product/dependency changes are checked by central CI. PR packaging # validates changes to the release machinery and distribution inputs. - '.github/workflows/release-desktop.yml' + - 'scripts/check-download-docs.py' + - 'scripts/test_download_docs.py' - 'cmd/packagede2e/**' - 'cmd/producte2e/**' - 'cmd/releasegate/**' @@ -142,6 +144,19 @@ jobs: "phase=$phase" >> $env:GITHUB_OUTPUT "signing_prepare=$($signingPrepare.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT + - name: Check public download documentation before release work + shell: pwsh + env: + EVENT_NAME: ${{ github.event_name }} + RELEASE_VERSION: ${{ steps.release.outputs.version }} + run: | + $arguments = @('-B', 'scripts/check-download-docs.py') + if ($env:EVENT_NAME -ceq 'push') { $arguments += @('--version', $env:RELEASE_VERSION) } + python @arguments + if ($LASTEXITCODE -ne 0) { throw "Update both READMEs and platform download guides before publishing" } + python -B -m unittest discover -s scripts -p test_download_docs.py -v + if ($LASTEXITCODE -ne 0) { throw "Download documentation regression checks failed" } + - name: Require successful full central CI for the exact release revision if: ${{ github.event_name != 'pull_request' }} shell: pwsh @@ -1071,6 +1086,15 @@ jobs: if ($env:IS_PRERELEASE -ceq 'false') { $verify += '--require-release-ready' } python @verify if ($LASTEXITCODE -ne 0) { throw "Mac archive verification failed: $arch" } + $dmgDirectory = "build/macos-dmg/$arch" + gh run download $env:GITHUB_RUN_ID --repo $env:GITHUB_REPOSITORY ` + --name "TraverseBoard-macos-dmg-$arch-$env:RELEASE_REVISION" --dir $dmgDirectory + if ($LASTEXITCODE -ne 0) { throw "Exact Mac DMG download failed: $arch" } + $dmgName = "TraverseBoard-$env:RELEASE_VERSION-macos-$arch-$($env:RELEASE_REVISION.Substring(0,12))-preview.dmg" + $dmgPath = Join-Path $dmgDirectory $dmgName + $expectedChecksum = (Get-FileHash -Algorithm SHA256 -LiteralPath $dmgPath).Hash.ToLowerInvariant() + " " + $dmgName + $checksum = [System.IO.File]::ReadAllText("$dmgPath.sha256").TrimEnd([char[]]"`r`n") + if ($checksum -cne $expectedChecksum) { throw "Mac DMG checksum differs: $arch" } } - name: Download stable Store submission from this run @@ -1280,7 +1304,7 @@ jobs: $assets = @( "build/public/TraverseBoard.exe", "build/desktop/direct-exe-signing.json", - "build/desktop/SHA256SUMS", + "build/public/SHA256SUMS", "build/desktop/release-metadata.json", "build/desktop/windows-compatibility.json", "build/desktop/standard-code-packaged-e2e.json", @@ -1312,12 +1336,16 @@ jobs: $assets += Join-Path build/attestations $bundleName } $macRevision = '${{ needs.portable-zip.outputs.revision }}' + $publicChecksums = [System.IO.File]::ReadAllText('build/desktop/SHA256SUMS').TrimEnd([char[]]"`r`n") foreach ($arch in @('arm64', 'amd64')) { $stem = "TraverseBoard-$env:RELEASE_VERSION-macos-$arch-$($macRevision.Substring(0,12))-preview" foreach ($extension in @('.zip', '.manifest.json', '.sha256')) { $assets += "build/macos/$arch/$stem$extension" } + $assets += "build/macos-dmg/$arch/$stem.dmg", "build/macos-dmg/$arch/$stem.dmg.sha256" + $publicChecksums += "`n" + [System.IO.File]::ReadAllText("build/macos-dmg/$arch/$stem.dmg.sha256").TrimEnd([char[]]"`r`n") } + [System.IO.File]::WriteAllText('build/public/SHA256SUMS', $publicChecksums + "`n", [System.Text.UTF8Encoding]::new($false)) foreach ($asset in $assets) { if (-not (Test-Path -LiteralPath $asset -PathType Leaf)) { throw "Release asset is missing: $asset" @@ -1412,6 +1440,15 @@ jobs: $missing.Count -ne 0) { throw "Published release asset inventory differs from the exact public allowlist" } + # Validate documented links against the uploaded draft before making + # it public. Missing EXE/checksums/either DMG keeps the release private. + $releaseJSON = gh api "repos/$env:GITHUB_REPOSITORY/releases/tags/$env:RELEASE_VERSION" + if ($LASTEXITCODE -ne 0) { throw "Download entry point asset readback failed" } + [System.IO.File]::WriteAllText('build/public/download-release.json', ($releaseJSON | Out-String), [System.Text.UTF8Encoding]::new($false)) + python -B scripts/check-download-docs.py ` + --version $env:RELEASE_VERSION --revision $macRevision ` + --release-json build/public/download-release.json --allow-draft + if ($LASTEXITCODE -ne 0) { throw "Public download entry points do not match the release assets" } $publish = @( "release", "edit", $env:RELEASE_VERSION, "--repo", $env:GITHUB_REPOSITORY, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7489d9a9..bc9c4df4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,6 +88,19 @@ export CYBERAGENT_API_CONTROL_TOKEN="local-control-token" --- +## 发行下载入口维护 + +准备发布时,同步 `README.md`、`README.en.md`、`docs/windows-release.md`、`docs/macos-release.md` 中 `public-downloads` 标记内的版本、日期、下载与启动说明,再运行: + +```sh +python -B scripts/check-download-docs.py +python -B -m unittest discover -s scripts -p test_download_docs.py -v +``` + +每个入口必须使用同一个明确的 Release tag;`/releases/latest` 不包含预发布,不能作为预览版入口。Windows 链接指向 `TraverseBoard.exe`,Mac 按 `macos-arm64` / `macos-amd64` 选择 DMG。Release 页负责展示包含源码 revision 的完整文件名,避免在源码文档中预先引用该提交自身的 hash。 + +发布 tag 时,Desktop release 工作流会要求四份文档的版本等于待发布版本;上传 Draft 资产后,还会确认 EXE、校验和及两个架构的 DMG 均已上传、非空,且 DMG 文件名绑定本次版本与源码 revision。任何入口或资产不一致都会阻止公开发布。Actions 测试候选另列,保留其登录与过期说明。 + ## OpenAPI 契约与 DTO 代码生成 Universal-Code 以 **Go 结构体与路由定义作为 API 的唯一真相来源 (Single Source of Truth)**。 diff --git a/README.en.md b/README.en.md index a6681cdf..08955aee 100644 --- a/README.en.md +++ b/README.en.md @@ -42,12 +42,18 @@ ### Option 1: Download Pre-built Binaries -| OS | Instructions | + +Current public preview: [**v1.0.0-preview.20261020**](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v1.0.0-preview.20261020), published on 2026-10-20. + +| OS | Download and launch | | :--- | :--- | -| **Windows 10/11** | Get the current Actions test package and launch steps from the [Windows download guide](docs/windows-release.md). The executable is named `TraverseBoard.exe`; double-click it to launch. Edge WebView2 Runtime is required. The newest public Release remains the [older preview from 2026-09-05](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v0.1.0-pr195-preview). | -| **macOS (Apple Silicon / Intel)** | Get the Actions preview ZIP for your architecture from the [macOS download guide](docs/macos-release.md), unpack it, and open `TraverseBoard.app`. It is ad-hoc signed and unnotarized. Public Releases do not yet contain Mac packages. | +| **Windows x64** | Download [TraverseBoard.exe](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/download/v1.0.0-preview.20261020/TraverseBoard.exe) and double-click it. Edge WebView2 Runtime is required. See the [Windows download guide](docs/windows-release.md). | +| **macOS Apple Silicon / Intel** | In [Release Assets](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v1.0.0-preview.20261020), choose the `.dmg` containing `macos-arm64` (Apple Silicon) or `macos-amd64` (Intel), then drag `TraverseBoard.app` into Applications. See the [macOS download guide](docs/macos-release.md). | + +Public Release downloads require no GitHub login and are not subject to Actions artifact retention limits. Verify downloads against [SHA256SUMS](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/download/v1.0.0-preview.20261020/SHA256SUMS) on the same release. The Windows EXE is unsigned; Mac apps are ad-hoc signed and unnotarized, so the OS may block the first launch. This is a preview; see the release notes for verified scope and known issues. + -Use the [Desktop release run list](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml) as the ongoing candidate entry point. Actions downloads require a GitHub login and expire. Each platform guide records the candidate version, source revision, and expiry date. +For test candidates newer than the public preview, use the [Desktop release run list](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml) and check the version and source revision. Actions downloads require a GitHub login and expire. Packages retain their historical filenames; the product name shown in the application is Universal-Code. ### Option 2: Build from Source diff --git a/README.md b/README.md index 07dd6f75..24845926 100644 --- a/README.md +++ b/README.md @@ -42,13 +42,19 @@ ### 方式一:下载预编译客户端 + +当前公开预览版:[**v1.0.0-preview.20261020**](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v1.0.0-preview.20261020)(2026-10-20 发布)。 + | 平台 | 下载与运行说明 | | :--- | :--- | -| **Windows 10/11** | 当前 Actions 测试包及启动步骤见 [Windows 下载说明](docs/windows-release.md)。实际文件名为 `TraverseBoard.exe`,双击运行,需 Edge WebView2 Runtime。最新公开 Release 仍是 [2026-09-05 的旧预览](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v0.1.0-pr195-preview)。 | -| **macOS (Apple Silicon / Intel)** | 从 [macOS 下载说明](docs/macos-release.md) 获取对应架构的 Actions 预览 ZIP,解包后打开 `TraverseBoard.app`。当前仅有 ad-hoc 签名,未公证;公开 Release 尚无 Mac 包。 | +| **Windows x64** | 下载 [TraverseBoard.exe](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/download/v1.0.0-preview.20261020/TraverseBoard.exe),双击运行;需要 Edge WebView2 Runtime。详见 [Windows 下载说明](docs/windows-release.md)。 | +| **macOS Apple Silicon / Intel** | 在 [Release Assets](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v1.0.0-preview.20261020) 中选择名称含 `macos-arm64`(M 系列)或 `macos-amd64`(Intel)的 `.dmg`,将 `TraverseBoard.app` 拖入 Applications。详见 [macOS 下载说明](docs/macos-release.md)。 | + +公开 Release 下载无需登录 GitHub,不受 Actions artifact 保留期限限制;可用同页的 [SHA256SUMS](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/download/v1.0.0-preview.20261020/SHA256SUMS) 核对文件。Windows EXE 未签名,Mac 包仅 ad-hoc 签名、未经 Apple 公证,首次启动可能被系统阻止。这是预览版,验证范围与已知问题以 Release 说明为准。 + -持续更新的候选入口为 [Desktop release 打包记录](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml)。Actions 下载需要登录 GitHub,且有保留期限;对应版本、源码提交和到期时间见平台说明。 -测试包沿用历史文件名,窗口中的产品名为 Universal-Code。 +需要比公开预览更新的测试候选时,查看 [Desktop release 打包记录](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml),核对版本与源码提交。Actions 下载需要登录 GitHub,且会过期。 +文件保留历史名称,窗口中的产品名为 Universal-Code。 ### 方式二:从源码构建运行 diff --git a/docs/macos-release.md b/docs/macos-release.md index 5331a616..a98f1c48 100644 --- a/docs/macos-release.md +++ b/docs/macos-release.md @@ -1,39 +1,38 @@ # macOS 下载与发行边界 -Mac 交付包含 Apple Silicon(arm64)和 Intel(amd64)两份 ZIP。文件名绑定完整版本、 -架构与源码 revision 前 12 位;manifest 内保存完整 revision。包内应用名为 -`TraverseBoard.app`,原 bundle identifier `workbench.prayu.desktop` 和数据目录不变。 +[首页](../README.md) | [English home](../README.en.md) | [Windows 下载](windows-release.md) -## 获取候选包 +## 下载与启动 / Download and launch -持续更新入口:[Desktop release 双架构打包记录](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml)。也可从 [主干中央 CI](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/ci.yml?query=branch%3Amain) 选择已成功且含 `TraverseBoard-macos-preview-arm64` artifact 的运行,获取 arm64 预览;主干 CI 不提供 Intel 包。下载前核对运行的源码 revision 和包内 metadata。 + +当前公开预览版 / Current public preview: [**v1.0.0-preview.20261020**](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v1.0.0-preview.20261020),发布于 / published on **2026-10-20**,已提供两种架构的 DMG。 -For newer candidates, use the [Desktop release run list](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml) for both architectures, or a successful [main-branch CI run](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/ci.yml?query=branch%3Amain) containing `TraverseBoard-macos-preview-arm64` for arm64 only. Check the run revision against the package metadata. +| Mac 类型 / Architecture | 下载 / Download | +| --- | --- | +| Apple Silicon(M 系列 / arm64) | 在 [Release Assets](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v1.0.0-preview.20261020) 选择名称含 `macos-arm64` 的 `.dmg` | +| Intel(amd64) | 在 [Release Assets](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v1.0.0-preview.20261020) 选择名称含 `macos-amd64` 的 `.dmg` | -截至 2026-10-20,[公开 Releases](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases) 尚无 Mac 资产。 -[PR #411 的双架构打包检查](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/runs/39306404692) -已完成原生构建与归档复验。以下链接对应 `v1.0.0-pr.411`、源码合并检出 -`4158910bc9f478b3511459e0b5babfdc0d4def97`;完整中央 CI 状态见 [PR 检查](https://github.com/CWNU-Open-Source-Community/Universal-Code/pull/411/checks)。 -下载需要登录 GitHub;两份 artifact 均保留 30 天,**2026-11-18 19:02 UTC(两种架构)到期**。 +1. 按处理器架构下载 DMG,用 `shasum -a 256 /path/to/download.dmg` 与同一 Release 的 [SHA256SUMS](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/download/v1.0.0-preview.20261020/SHA256SUMS) 核对。 +2. 打开 DMG,将 **`TraverseBoard.app`** 拖到 **Applications**,复制完成后弹出映像,再从 Applications 打开应用。前端和后端均已内置。 +3. 包只有 **ad-hoc 签名,未经 Apple 公证**,首次打开可能被 Gatekeeper 阻止。最低构建目标为 macOS 12;该版本的原生启动检查在 macOS 15 完成,完整交互、真实模型调用和升级恢复仍需验收。具体证据与限制见 Release 说明。 -Public Releases do not yet contain Mac assets. The following `v1.0.0-pr.411` previews passed native build and archive verification in [Desktop release](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/runs/39306404692), from merge revision `4158910bc9f478b3511459e0b5babfdc0d4def97`. A GitHub login is required. Downloads expire on **2026-11-18 at 19:02 UTC (both architectures)**. +Choose the DMG matching your processor from Release Assets, verify it against the same release's `SHA256SUMS`, then drag `TraverseBoard.app` into Applications and eject the image. The UI and backend are included. These **ad-hoc signed, unnotarized previews** may be blocked by Gatekeeper. The minimum build target is macOS 12; native startup was checked on macOS 15. Full interaction, real-model workflows and upgrade recovery remain unverified. -| Mac 类型 / Architecture | 下载 / Download | -| --- | --- | -| Apple Silicon(M 系列 / arm64) | [预览包 / Preview](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/runs/39306404692/artifacts/12214676343) | -| Intel(amd64) | [预览包 / Preview](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/runs/39306404692/artifacts/12214631395) | +公开 Release 下载无需登录 GitHub,不受 Actions artifact 保留期限限制。 +Public Release downloads require no GitHub login and are not subject to Actions artifact retention limits. + + +## 更新的测试候选 / Newer test candidates -在对应候选的 **Desktop release** 检查页面下载两个 `TraverseBoard-macos--` -artifact。artifact 外层是 GitHub 的传输 ZIP;解开后取得产品 ZIP、manifest 和 checksum。 -到期后在 [Desktop release 工作流](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml)选择较新的成功运行,核对版本、revision 和架构。 -CI artifact 不是 GitHub Release。预发布 tag 的流程成功后,同一 publisher -才会把两架构文件发布到同一个 Release 的 Assets 中。 +[Desktop release 双架构打包记录](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml)提供两种架构的测试候选。选择成功运行并核对版本、源码 revision 和架构;下载需要 GitHub 登录且会过期。 +`TraverseBoard-macos-dmg--` artifact 内含 DMG 与 checksum; +`TraverseBoard-macos--` artifact 内含产品 ZIP、manifest 与 checksum。 +先解开 Actions 的传输 ZIP;若选择产品 ZIP,再解开内层 ZIP 后打开 `TraverseBoard.app`。 -解开产品 ZIP 后打开 `TraverseBoard.app`,无需编译前端或单独启动后端。包内保留历史名称的 -`Start-Prayu-Operator-Preview.command` 作为兼容入口,并提供中英文使用说明。 -当前包只有 ad-hoc 签名;macOS 可能阻止首次启动。没有公证和真机验收证据时,不称正式 Mac 发行。 +For newer candidates, use a successful Desktop release run for the matching architecture and source revision. Actions downloads require login and expire. The DMG artifact contains an image and checksum; the archive artifact contains a product ZIP, manifest and checksum. Unpack the outer Actions transport ZIP first. -Extract the Actions transport ZIP, then the product ZIP inside it, and open `TraverseBoard.app`. The UI and backend are included. The bundle is ad-hoc signed and unnotarized, so macOS may block its first launch. +应用名仍为 `TraverseBoard.app`,bundle identifier `workbench.prayu.desktop` 和数据目录不变。 +发行文件名绑定版本、架构与源码 revision 前 12 位;ZIP manifest 保存完整 revision。 ## 构建与核验 @@ -64,8 +63,11 @@ Mach-O CPU 三者一致。构建两次比较二进制,再 ad-hoc 签名。`CFB 产物位于 `build/macos-public`。ZIP 包含完整应用、指南、兼容入口、元数据、兼容报告、 SBOM、NOTICE 与 LICENSE。`ditto` 保留 POSIX 权限;新目录解压后再次核对全部文件、 可执行权限、Mach-O、部署目标、Go metadata 和 codesign。 -manifest 与 checksum 绑定完整 ZIP。Windows publisher 复验 ZIP 内的实际文件、架构、 -版本、revision 和全部哈希,再汇总两架构的精确资产白名单;只有 publisher 可写 Release。 +manifest 与 checksum 绑定完整 ZIP。DMG 来自该核验后的 ZIP,原生 Mac job 会挂载映像 +核对包内字节、权限与签名。Windows publisher 复验 ZIP 内的实际文件、架构、版本、 +revision 和全部哈希,并下载同次运行的 DMG、核对其独立 checksum,再汇总两架构的 +ZIP、DMG 与校验文件。公开 `SHA256SUMS` 同时包含 Windows EXE 和两份 DMG; +只有 publisher 可写 Release,公开前须通过四份下载文档与实际资产的一致性检查。 ## 正式版尚缺什么 @@ -81,10 +83,11 @@ Developer ID 签名、公证、staple 验证及绑定最终分发包的真机验 通过隔离 Keychain 持久化测试,并随上方新候选编译。实际 `.app` 的授权提示、取消、锁定恢复、 重启与升级体验仍需在 Mac 登录会话验收。 -Mac 的普通 Agent Browser 当前返回 `platform_unsupported`,原生 Computer Use 与 Local Sandbox -也有独立平台实现缺口。不得通过删除身份校验、明文保存凭据或默认提升权限开放这些能力。 +Mac 浏览器、原生 Computer Use 与 Local Sandbox 的可用能力须以当前构建的运行时能力报告 +为准。浏览器启动检查不等于完整用户工作流验收;不得通过删除身份校验、明文保存凭据 +或默认提升权限开放尚不可用的能力。 -Native macOS CI passed the isolated Keychain persistence test and built the implementation into the newer candidate. Actual application permission prompts, cancellation, locked-keychain recovery, restart, and upgrade still need acceptance in a Mac login session. Ordinary Agent Browser, native Computer Use, and Local Sandbox have separate Mac platform gaps. +Native macOS CI passed the isolated Keychain persistence test. Actual application permission prompts, cancellation, locked-keychain recovery, restart, and upgrade still need acceptance in a Mac login session. Check the current build's runtime capability report for browser, native Computer Use and Local Sandbox availability; browser startup checks do not establish complete workflow acceptance. 现有 Windows 签名、Store、精确 CI revision、Draft evidence 和 attestation 门保持。 Mac job 不预先向 Draft 添加附件,避免破坏 Windows 的精确证据清单。 diff --git a/docs/windows-release.md b/docs/windows-release.md index 03318285..de5b0677 100644 --- a/docs/windows-release.md +++ b/docs/windows-release.md @@ -4,29 +4,24 @@ ## 下载与启动 / Download and launch -截至 2026-10-20,最新公开 Release 为 [v0.1.0-pr195-preview](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v0.1.0-pr195-preview),发布于 2026-09-05、源码为 `8df0df9ad48a9d53c03fdb63ac78d5c7199219b0`。其中的 `TraverseBoard.exe` 是旧预览,不能用于验收当前主线功能。v1.0.0 进度见 [发布说明](releases/v1.0.0.md)。 + +当前公开预览版 / Current public preview: [**v1.0.0-preview.20261020**](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v1.0.0-preview.20261020),发布于 / published on **2026-10-20**。 -As of 2026-10-20, the newest public Release is the older [v0.1.0-pr195-preview](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/tag/v0.1.0-pr195-preview), published on 2026-09-05 from revision `8df0df9ad48a9d53c03fdb63ac78d5c7199219b0`. Its `TraverseBoard.exe` predates current main-branch features. +1. 下载 [Windows x64 — TraverseBoard.exe](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/download/v1.0.0-preview.20261020/TraverseBoard.exe),安装好 Microsoft Edge WebView2 Evergreen Runtime 后双击运行。界面和后端均已内置,无需编译或另开服务。 +2. 在 PowerShell 中运行 `Get-FileHash .\TraverseBoard.exe -Algorithm SHA256`,与同一 Release 的 [SHA256SUMS](https://github.com/CWNU-Open-Source-Community/Universal-Code/releases/download/v1.0.0-preview.20261020/SHA256SUMS) 比较。 +3. 此 EXE 未作 Authenticode 签名,可能出现 SmartScreen“未知发布者”提示。已公开的检查包括 Windows 11 启动和部分界面交互;Windows 10 与完整兼容性、真实模型工作流尚未完成验收,详见 Release 说明。 -### 当前测试候选 / Current test candidate +Download the Windows x64 EXE above and double-click it with Edge WebView2 Evergreen Runtime installed. The UI and backend are included. Compare `Get-FileHash` output with the same release's `SHA256SUMS`. This unsigned preview may trigger SmartScreen. Windows 11 startup and selected UI interactions were checked; Windows 10, full compatibility and real-model workflows still need acceptance. -长期入口为 [Desktop release 打包记录](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml),成功运行中的 Windows artifact 可用于测试。当前中央 CI 主干运行不上传 Windows 应用包。 +公开 Release 下载无需 GitHub 登录,也没有 Actions artifact 的到期时间。文件沿用历史名,窗口显示 Universal-Code。只读启动入口为 `TraverseBoard.exe --safe-view`。 +Public Release downloads require no GitHub login and have no Actions artifact expiry. Use `TraverseBoard.exe --safe-view` for the read-only view. + -The [Desktop release run list](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml) remains the ongoing Windows candidate entry point. Main-branch central CI does not currently upload a Windows application package. +### 更新的测试候选 / Newer test candidates -[PR #411 的 Desktop release 打包检查](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/runs/39306404692)已通过。以下候选版本为 `v1.0.0-pr.411`,源码绑定 PR 合并检出 `4158910bc9f478b3511459e0b5babfdc0d4def97`;它与 PR 分支头提交不同。完整中央 CI 状态见 [PR 检查](https://github.com/CWNU-Open-Source-Community/Universal-Code/pull/411/checks)。 +开发测试候选见 [Desktop release 打包记录](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml)。选择成功运行,核对版本、源码 revision 和 `TraverseBoard-windows-release-*` artifact。Actions 下载需要登录 GitHub,有保留期限;解开传输 ZIP 后运行根目录的 `TraverseBoard.exe`。中央 CI 主干运行不上传 Windows 应用包。 -The [PR #411 Desktop release packaging check](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/runs/39306404692) passed. The candidate is `v1.0.0-pr.411`, built from the PR merge checkout `4158910bc9f478b3511459e0b5babfdc0d4def97`. See the [PR checks](https://github.com/CWNU-Open-Source-Community/Universal-Code/pull/411/checks) for the full central CI status. - -1. 登录 GitHub,下载 [Windows 测试候选](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/runs/39306404692/artifacts/12215261616)(`TraverseBoard-windows-release-v1.0.0-pr.411`)。该 artifact 保留 14 天,**2026-11-02 19:13 UTC 到期**。 -2. 解开 GitHub Actions 的传输 ZIP,双击根目录的 **`TraverseBoard.exe`**。Windows 10/11 需要已安装的 Edge WebView2 Evergreen Runtime。 -3. 这是未签名测试候选,可能出现 SmartScreen 提示。窗口显示 Universal-Code,文件仍沿用历史名。测试说明见[本机测试指南](../packaging/windows/LOCAL-TEST-GUIDE.txt)。 - -Sign in to GitHub, download the [Windows test candidate](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/runs/39306404692/artifacts/12215261616), and extract the Actions transport ZIP. It expires on **2026-11-02 at 19:13 UTC**. Double-click the root `TraverseBoard.exe` on Windows 10/11 with Edge WebView2 Evergreen Runtime installed. This unsigned candidate may trigger SmartScreen; the application displays Universal-Code while retaining the historical filename. - -候选到期后,在 [Desktop release 工作流](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml)选择较新的成功运行,核对版本、源码 revision 和 `TraverseBoard-windows-release-*` artifact。Actions 测试包与公开 Release 的直发 EXE 分发方式不同。只读启动入口为 `TraverseBoard.exe --safe-view`。 - -If this artifact has expired, select a newer successful [Desktop release run](https://github.com/CWNU-Open-Source-Community/Universal-Code/actions/workflows/release-desktop.yml), check its version and source revision, and download its `TraverseBoard-windows-release-*` artifact. `TraverseBoard.exe --safe-view` starts the read-only view. +For newer test candidates, select a successful Desktop release run and check its version, revision and Windows artifact. Actions downloads require login and expire; extract the transport ZIP before running the EXE. These candidates are separate from the public Release above. ### 从源码构建与启动 / Build and launch from source diff --git a/packaging/macos/RELEASE-NOTES.md b/packaging/macos/RELEASE-NOTES.md index 213f56d4..167e9b31 100644 --- a/packaging/macos/RELEASE-NOTES.md +++ b/packaging/macos/RELEASE-NOTES.md @@ -10,10 +10,12 @@ Download the matching `TraverseBoard-…-macos-…-preview.zip` from Assets, ext `TraverseBoard.app`. Matching `.sha256` and `.manifest.json` sidecars bind the complete archive. The ZIP includes the launcher, local guide, build metadata, SBOM and licenses. -CI 候选同时提供独立的 `TraverseBoard-macos-dmg--…` artifact。 +本页 Assets 同时提供两种架构的 `.dmg` 和对应的 `.dmg.sha256`;CI 候选中对应 +独立的 `TraverseBoard-macos-dmg--…` artifact。 打开其中的 DMG,将 `TraverseBoard.app` 拖到 `Applications`;复制完成后弹出映像。 DMG 来自同一份核验后的 ZIP,带独立的 checksum。它是拖拽复制容器,不是公证或自动安装器。 -CI candidates also provide a separate architecture-specific DMG artifact. Open the image, +Release Assets also include a DMG and matching `.dmg.sha256` for each architecture; +CI candidates provide them in a separate architecture-specific artifact. Open the image, drag `TraverseBoard.app` to `Applications`, then eject it after copying. The DMG is built from the verified ZIP and has its own checksum; it does not add notarization or auto-install. diff --git a/scripts/check-download-docs.py b/scripts/check-download-docs.py new file mode 100644 index 00000000..2a609d97 --- /dev/null +++ b/scripts/check-download-docs.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Check the four public download entry points, optionally against Release assets. + +Offline: python scripts/check-download-docs.py +Before publishing: add --version TAG --revision SHA --release-json release.json +--allow-draft. release.json must be the GitHub REST Release response after upload. +""" + +import argparse +import json +from pathlib import Path +import re +import sys + +ROOT = Path(__file__).resolve().parents[1] +REPOSITORY = "https://github.com/CWNU-Open-Source-Community/Universal-Code" +DOCUMENTS = ("README.md", "README.en.md", "docs/windows-release.md", "docs/macos-release.md") +START, END = "", "" +VERSION = r"v[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?" + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def check(root, version=None, release=None, revision=None, allow_draft=False): + for name in DOCUMENTS: + text = (root / name).read_text(encoding="utf-8") + require(text.count(START) == text.count(END) == 1, + f"{name}: public download block is missing or duplicated") + block = text.split(START)[1].split(END)[0] + tags = re.findall(re.escape(REPOSITORY) + r"/releases/tag/([^\s)<>#]+)", block) + require(tags and all(re.fullmatch(VERSION, tag) for tag in tags), + f"{name}: explicit public Release tag link is missing or invalid") + if version is None: + version = tags[0] + require(set(tags) == {version}, f"{name}: download tag differs from {version}") + # Check every Release URL, including accidentally retained old links + # outside the marked download block. Actions candidates are separate. + urls = re.findall(r"https://github\.com/[^\s)<>]+/releases/[^\s)<>]+", text) + prefix = REPOSITORY + "/releases/" + require(all(url.startswith((prefix + "tag/" + version + "#", + prefix + "download/" + version + "/")) + or url == prefix + "tag/" + version for url in urls), + f"{name}: stale, unpinned or foreign Release URL") + for asset in ("SHA256SUMS",) + (() if name.endswith("macos-release.md") else ("TraverseBoard.exe",)): + require(f"{prefix}download/{version}/{asset})" in block, + f"{name}: {asset} download link is missing") + if not name.endswith("windows-release.md"): + require(all(marker in block for marker in ("macos-arm64", "macos-amd64", ".dmg")), + f"{name}: both Mac DMG architectures must be documented") + + if release is not None: + require(release.get("tag_name") == version, "Release tag differs from download documentation") + require(release.get("draft") is False or (allow_draft and release.get("draft") is True), + "Release is not public (use --allow-draft only before publishing)") + assets = release.get("assets", []) + names = [asset["name"] for asset in assets] + require(len(names) == len(set(names)), "Release contains duplicate asset names") + available = {asset["name"] for asset in assets + if asset.get("state") == "uploaded" and asset.get("size", 0) > 0} + require({"TraverseBoard.exe", "SHA256SUMS"} <= available, + "Release is missing the Windows EXE or SHA256SUMS") + if revision is not None: + require(re.fullmatch(r"[0-9a-f]{40}", revision), "Invalid release revision") + for arch in ("arm64", "amd64"): + short = re.escape(revision[:12]) if revision else r"[0-9a-f]{12}" + pattern = rf"TraverseBoard-{re.escape(version)}-macos-{arch}-{short}-preview\.dmg" + require(len([name for name in available if re.fullmatch(pattern, name)]) == 1, + f"Release is missing the exact {arch} DMG") + for name in DOCUMENTS: + text = (root / name).read_text(encoding="utf-8") + linked = re.findall(re.escape(prefix + "download/" + version + "/") + r"([^\s)<>#]+)", text) + require(set(linked) <= available, f"{name}: linked asset is absent from the Release") + return version + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=ROOT) + parser.add_argument("--version") + parser.add_argument("--revision") + parser.add_argument("--release-json", type=Path) + parser.add_argument("--allow-draft", action="store_true") + args = parser.parse_args() + try: + release = json.loads(args.release_json.read_text(encoding="utf-8-sig")) if args.release_json else None + version = check(args.root, args.version, release, args.revision, args.allow_draft) + except (OSError, ValueError, KeyError, TypeError) as exc: + print(f"download-docs: {exc}", file=sys.stderr) + return 1 + print(f"download-docs: all four entry points agree on {version}" + + ("; Release assets verified" if release is not None else "")) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/test_download_docs.py b/scripts/test_download_docs.py new file mode 100644 index 00000000..b942effd --- /dev/null +++ b/scripts/test_download_docs.py @@ -0,0 +1,84 @@ +"""Regression coverage for stale links and missing public download assets.""" + +import copy +import importlib.util +from pathlib import Path +import tempfile +import unittest + +SPEC = importlib.util.spec_from_file_location("download_docs", Path(__file__).with_name("check-download-docs.py")) +docs = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(docs) + + +class DownloadDocsTest(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + for name in docs.DOCUMENTS: + path = self.root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text((docs.ROOT / name).read_text(encoding="utf-8"), encoding="utf-8") + self.version = docs.check(self.root) + self.revision = "1234567890ab" + "c" * 28 + self.release = {"tag_name": self.version, "draft": False, "assets": [ + {"name": name, "state": "uploaded", "size": 1} for name in ( + "TraverseBoard.exe", "SHA256SUMS", + *(f"TraverseBoard-{self.version}-macos-{arch}-{self.revision[:12]}-preview.dmg" + for arch in ("arm64", "amd64")), + )]} + + def test_published_release_and_assets_match(self): + self.assertEqual(docs.check(self.root, release=self.release, revision=self.revision), self.version) + + def test_each_stale_translation_or_platform_guide_is_rejected(self): + for name in docs.DOCUMENTS: + with self.subTest(name=name): + path = self.root / name + original = path.read_text(encoding="utf-8") + path.write_text(original.replace(self.version, "v0.1.0-old"), encoding="utf-8") + with self.assertRaises(ValueError): + docs.check(self.root, version=self.version) + path.write_text(original, encoding="utf-8") + + def test_next_release_requires_documentation_update(self): + with self.assertRaisesRegex(ValueError, "tag differs"): + docs.check(self.root, version="v1.0.0-next") + + def test_missing_or_empty_asset_is_rejected(self): + for index in range(len(self.release["assets"])): + for field, value in (("size", 0), ("state", "new")): + with self.subTest(index=index, field=field): + release = copy.deepcopy(self.release) + release["assets"][index][field] = value + with self.assertRaises(ValueError): + docs.check(self.root, release=release) + + def test_draft_needs_explicit_prepublish_mode(self): + self.release["draft"] = True + with self.assertRaisesRegex(ValueError, "not public"): + docs.check(self.root, release=self.release) + docs.check(self.root, release=self.release, allow_draft=True) + + def test_wrong_release_revision_is_rejected(self): + with self.assertRaisesRegex(ValueError, "exact arm64 DMG"): + docs.check(self.root, release=self.release, revision="d" * 40) + + def test_old_link_outside_download_block_is_rejected(self): + path = self.root / "README.en.md" + with path.open("a", encoding="utf-8") as handle: + handle.write(f"\n[Old download]({docs.REPOSITORY}/releases/tag/v0.1.0-old)\n") + with self.assertRaisesRegex(ValueError, "stale"): + docs.check(self.root) + + def test_latest_alias_cannot_hide_prereleases(self): + path = self.root / "README.md" + text = path.read_text(encoding="utf-8") + path.write_text(text.replace("/tag/" + self.version, "/latest"), encoding="utf-8") + with self.assertRaises(ValueError): + docs.check(self.root) + + +if __name__ == "__main__": + unittest.main()