用户原话:「我们内置了那么多镜像源 甚至用户不能手动选择指定版本下载。」这条之前 `InstallRequest` 根本没有版本字段,四家全是「该 major 最新 GA」。 core: - `InstallRequest.version: Option<String>` + `with_version`;`None` 时仍走 `source::fetch` 的老路径(`latest_url` / `parse_latest` / `fetch_latest` 一个字节未动)。 - `source::fetch` 在唯一分派点按 `Option<&str>` 分两路:`latest` 与 `pinned`,后者绝不换家。 - Temurin(新 `api/version.rs`):先 `assets/feature_releases/{major}/ga` 枚举(`page_size` 上限 20),把用户给的公共版本号翻成 release 名,再打 `assets/release_name/eclipse/{name}` 取归档——不走 `latest`。实测 `/v3/assets/version/{v}` 在线上回 404,故不用它(见报告)。 - Oracle:`versions_for` 接进选择路径(`select_version`),`None` 仍是 `pick_version`。 - Microsoft:只认写死快照里那一个版本号,别的版本明确报错并说清「这家不能自选版本」。 - Zulu / Termux:上游只有 latest / 版本由系统包定 → 明确报错,不回落、不换家。 - `catalog.rs` 收策略:`matches_version`(唯一相等判据,不做前缀近似)、 `version_pin_error`(四家文案互不相同且各自说清原因 + 下一步)、`version_mismatch` (兜底闸:装到的版本必须真是用户要的那一版)。 - `plan()` 改成接 `&InstallRequest`,`--dry-run` 与真装走同一条选择路径。 cli: - `java install <major> [--version V] [--dry-run] [--vendor X]`;`--json` 增 `requested_version`。 - 全局 `--version` 只在子命令之前成立,否则 `java install 21 --version 21.0.13+9` 会被 当成「打印启动器版本」吞掉。 拆分(守 400 行硬线,均为纯搬移 + 逐字节核对): - `api.rs` → `api/version.rs`、`oracle.rs` → `oracle/listing.rs`(纯的那一半)、 `cli/parse.rs` → `cli/parse/java.rs`、`commands/java.rs` → `commands/java_install.rs`。 门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-280):core --lib / --test game / --test launch、 cli --all-targets、fmt --check、clippy -D warnings 全部 EXIT=0。
按独立复审 `reviewer-updategui-r1` 的 p1-1 / fix-then-merge 返工。 P1-1(挡门) - `crates/cli/src/cli/parse.rs` 406 → **382** 行:`fn parse_update` 搬到 `crates/cli/src/cli/parse/update.rs`(与 `parse/{accounts,agent,instances}.rs` 同形状)。 全仓 `crates/**/*.rs` 最大仍是 400(`design/tests/card_neutral.rs` 等三处,未越线)。 上一轮报告里「所有改动文件都在 400 行内」那句是错的,已在 r2 报告里更正。 P3-6 `Job::apply` 不查包形态 - 新增 `self_replaceable(format)`(只有 `PortableExe` 允许)+ `ensure_self_replaceable`, **挡在取包之前**;错误分类 `InvalidManifest`(发布侧挂错,换源/重试无用)。 比复审建议的 `needs_installer()` 更严:`portable-zip`/`appimage` 同样不能原地当 exe。 - 用例:`crates/core/src/update/tests/job_cases.rs`(msi/zip 被拒 + 目标零改动 + 暂存目录 没被建 + 便携 exe 整环成功 + Nothing 不碰目标 + 闸门的源码锚点)。 P3-7 `ULL_UPDATE_DIR` 生产可用 - `default_source` 改走 `local_dir_override()`:`if !cfg!(debug_assertions) { return None }`, 发布构建里整段被常量折叠掉(这条路径在 release 二进制里不存在)。判据与三档取舍写在 `job.rs` 的文档表里(为什么不用 CLI 参数、为什么不用运行期开关)。 P3-1「忽略这个版本」语义 - `UpdateView.dismissed: bool` → `skipped: Option<String>`(记**版本号**):忽略 0.2.3 之后 同一次运行里冒出 0.2.4 **照常提示**;手动检查清空(`begin_check(manual)`),静默检查不清。 文案保持「忽略这个版本」。presenter / app 侧视图模型 / 两处用例同步。 P3-3 CLI `--json` 失败信封缺结构化分类 - `CliError` 加 `detail`(`Option<Box<Value>>`,装箱避免 `result_large_err`)+ `with_detail`, `to_json` 输出 `error.detail`;`commands/update.rs` 的 failure 填 `{"kind": core 分类 id, "retryable": 可重试位}`。 - 上一轮报告里引的 `{"kind":"digest_mismatch"}` 形状确实不存在(那是 CLI 的 user/internal), r2 报告换成了实机 dump。 P3-4 update 命令没有解析测试 - `crates/cli/tests/parse_test.rs`:`update_subcommands`(check / apply / `--dest` 两种写法) + `update_usage_errors`(无子命令 / 未知子命令 / 多余位置参数 / 缺少取值 / 未知选项); `help_text_covers_every_command` 的 topic 列表补 `"update"`。 P3-5 `install.rs` 模块文档与实测矛盾 - 改成「可以改名、不能覆盖/删除」,写明 M159 在 Windows 真机验过运行中的 exe 自替换, 并提醒别再照旧文档去设计「退出自身 → 另一个进程搬运」的双进程方案。 顺手(复审 P3-2/P3-8 里低成本的两条) - 设置页补「下载中」态(`SettingsUpdateApplying` 不再是死 key,下载期间不画「下载并安装」); 渠道显示改读 `update.channel`(`stable` 走 key,其它原样);删掉从未使用的 `UpdateBannerRestartLater`(catalog + 金表同步,29 条 key 全部有引用)。 - `adopt` 只让**检查**刷新 `checked_at`(应用回报不再冒充「上次检查」); `UpdateView::default()` 的 `current_version` 取本进程版本号(mock 下不再空白)。 门禁(`CARGO_TARGET_DIR=C:/Temp/ull-target/wt-159`):fmt 绿、clippy -D warnings 绿、 `cargo test --workspace --all-targets --no-fail-fast -j 4` **exit 0**(51 个测试二进制全 ok, `core::addon::pack` 未被 skip)。该族用例复现了复审记的泄漏:一次全量在 `/tmp` 留 72 个 `ull-pack-*`(`/tmp` 149M → 8853M),跑完已 `rm -rf` 清回 150M。
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent), MCP server 接入外部 Agent 工具,GUI 开发中。