Skip to content

feat: 手动更新按钮#235

Open
Joe-Bao wants to merge 4 commits into
MistEO:mainfrom
Joe-Bao:feat/update
Open

feat: 手动更新按钮#235
Joe-Bao wants to merge 4 commits into
MistEO:mainfrom
Joe-Bao:feat/update

Conversation

@Joe-Bao

@Joe-Bao Joe-Bao commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

fix #233

Summary

本 PR 在现有更新 section 中新增“选择本地更新包”入口,用于手动导入本地更新压缩包并复用现有更新安装流程。

本地更新不会请求 Mirror API,也不会进行线上版本审核或 sha256 校验;只校验压缩包内的 interface.json 是否为合法 ProjectInterface V2,并确认其属于当前项目。

Changes

  • 在更新 section 中新增本地更新按钮,支持选择 .zip.tar.gz.tgz 更新包。
  • 新增本地更新导入 hook,导入成功后会:
    • 设置当前更新信息
    • 将下载状态置为已完成
    • 保存 pending update 信息
    • 弹出安装确认弹窗
  • Rust 侧新增 read_update_package_interface 命令,用于从压缩包中读取 interface.json
  • 本地包校验规则:
    • 压缩包内必须存在 interface.json
    • interface_version 必须为 2
    • 必须包含 versionnamemirrorchyan_rid
    • 包内 name 必须等于当前项目 name
    • 包内 mirrorchyan_rid 必须等于当前项目 mirrorchyan_rid
  • debug 版本仍保持禁用更新语义,本地更新按钮不可用。
  • 新增本地更新相关错误提示与多语言文案。

Notes

本 PR 不包含全局拖放导入入口。此前排查发现 MXU 在提权运行时,Windows 会阻止从非提权 Explorer 向提权进程拖入文件,因此即使前端或 Tauri 监听拖放事件,也可能只能看到禁止符号,无法稳定拿到真实文件路径。为保证功能可靠,本次仅保留按钮选择文件的入口。

Summary by Sourcery

添加对从手动选择的本地安装包安装更新的支持,并复用现有的更新安装流程。

New Features:

  • 添加本地更新安装包导入流程,在不访问远程更新服务的情况下,验证安装包元数据并为安装做好准备。
  • 暴露一个新的 Tauri 命令,用于从 .zip.tar.gz 更新归档中读取 interface.json,以进行本地更新验证。
  • 在 Tauri 环境下运行时,引入新的设置界面按钮,用于选择本地更新安装包,并配套启用规则与用户反馈。

Enhancements:

  • 在下载成功和本地安装包导入后持久化待处理更新信息,以简化后续安装步骤。
Original summary in English

Summary by Sourcery

Add support for installing updates from a manually selected local package and reuse the existing update installation flow.

New Features:

  • Add a local update package import flow that validates package metadata and prepares it for installation without contacting remote update services.
  • Expose a new Tauri command to read interface.json from .zip and .tar.gz update archives for local update validation.
  • Introduce a new settings UI button to select local update packages when running under Tauri, with appropriate enablement rules and user feedback.

Enhancements:

  • Persist pending update information after successful downloads and local package imports to streamline subsequent installation steps.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并留下了一些高层次的反馈:

  • 构建和保存待处理更新信息的逻辑,目前分别存在于 UpdateSection 中的网络下载路径和 useLocalUpdatePackageImport 中的本地包流程,两处出现了重复;建议抽取一个共享的辅助方法,以减少逻辑偏差并保持这两个流程的一致性。
  • 支持的更新包格式分别在 TypeScript(SUPPORTED_UPDATE_PACKAGE_EXTENSIONS)和 Rust(read_update_package_interface)中单独定义,因此建议添加说明或集中管理这部分配置,以避免两边配置不同步。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- The logic for building and saving pending update info is now duplicated between the network download path in `UpdateSection` and the local package flow in `useLocalUpdatePackageImport`; consider extracting a shared helper to reduce drift and keep the two flows consistent.
- Supported update package formats are defined separately in TypeScript (`SUPPORTED_UPDATE_PACKAGE_EXTENSIONS`) and Rust (`read_update_package_interface`), so adding a note or centralizing this configuration would help prevent the two from getting out of sync.

## Individual Comments

### Comment 1
<location path="src/services/updateService.ts" line_range="948-957" />
<code_context>
+  projectInterface: ProjectInterface | null;
+}
+
+async function readLocalPackageInterface(filePath: string): Promise<ProjectInterface> {
+  try {
+    const content = await invoke<string>('read_update_package_interface', { packagePath: filePath });
+    const pi = parseJsonc<ProjectInterface>(content, 'interface.json');
+    if (pi.interface_version !== 2 || !pi.version || !pi.name || !pi.mirrorchyan_rid) {
+      throw new Error('invalid interface.json');
+    }
+    return pi;
+  } catch (error) {
+    log.warn('读取本地更新包 interface.json 失败:', error);
+    throw new LocalUpdatePackageError('missingPackageInterface');
+  }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 不同的 interface.json 读取失败模式都被折叠为 `missingPackageInterface`,可能会误导用户和调用方。

当前在 `readLocalPackageInterface` 中,所有失败(文件缺失、解析错误、结构无效)都会暴露为 `LocalUpdatePackageError('missingPackageInterface')`,其消息会明确声称 `interface.json` 缺失。这会把“缺失”和“无效”的接口混为一谈,导致对用户的提示不准确,也使调用方无法区分并分别处理这些情况。

请引入一个单独的错误(例如 `invalidPackageInterface`)用于解析/校验失败,或者向上抛出足够的上下文信息,使调用方和 UI 能够区分“缺失”和“格式错误/不兼容”的接口,并做出相应的处理/日志记录。

建议实现:

```typescript
async function readLocalPackageInterface(filePath: string): Promise<ProjectInterface> {
  try {
    const content = await invoke<string>('read_update_package_interface', { packagePath: filePath });
    const pi = parseJsonc<ProjectInterface>(content, 'interface.json');

    if (pi.interface_version !== 2 || !pi.version || !pi.name || !pi.mirrorchyan_rid) {
      log.warn('本地更新包 interface.json 无效或不兼容:', pi);
      throw new LocalUpdatePackageError('invalidPackageInterface');
    }

    return pi;
  } catch (error) {
    // 解析/校验失败需要向上传递,以便调用方区分「缺失」与「无效」
    if (error instanceof LocalUpdatePackageError && (error as any).code === 'invalidPackageInterface') {
      throw error;
    }

    log.warn('读取本地更新包 interface.json 失败:', error);
    throw new LocalUpdatePackageError('missingPackageInterface');
  }
}

```

为了完整支持新的 `invalidPackageInterface` 失败模式,你还需要:
1. 扩展 `LocalUpdatePackageError`(在其定义处),使其接受/识别 `'invalidPackageInterface'` 作为合法的 code,并通过上面使用的 `code`(或同等属性)暴露出来。
2. 更新当前处理 `missingPackageInterface` 的 UI 文案或“错误到消息”的映射逻辑,使其也能处理 `invalidPackageInterface`,并给出合适的提示(例如:“interface.json 格式无效或不兼容”)。
3. 如果 `LocalUpdatePackageError` 不使用 `code` 字段,而是其他名称(例如 `reason``type`),请在 `readLocalPackageInterface` 中相应地调整 `(error as any).code` 的访问。
</issue_to_address>

Sourcery 对开源项目免费 —— 如果你喜欢我们的评审,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • The logic for building and saving pending update info is now duplicated between the network download path in UpdateSection and the local package flow in useLocalUpdatePackageImport; consider extracting a shared helper to reduce drift and keep the two flows consistent.
  • Supported update package formats are defined separately in TypeScript (SUPPORTED_UPDATE_PACKAGE_EXTENSIONS) and Rust (read_update_package_interface), so adding a note or centralizing this configuration would help prevent the two from getting out of sync.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The logic for building and saving pending update info is now duplicated between the network download path in `UpdateSection` and the local package flow in `useLocalUpdatePackageImport`; consider extracting a shared helper to reduce drift and keep the two flows consistent.
- Supported update package formats are defined separately in TypeScript (`SUPPORTED_UPDATE_PACKAGE_EXTENSIONS`) and Rust (`read_update_package_interface`), so adding a note or centralizing this configuration would help prevent the two from getting out of sync.

## Individual Comments

### Comment 1
<location path="src/services/updateService.ts" line_range="948-957" />
<code_context>
+  projectInterface: ProjectInterface | null;
+}
+
+async function readLocalPackageInterface(filePath: string): Promise<ProjectInterface> {
+  try {
+    const content = await invoke<string>('read_update_package_interface', { packagePath: filePath });
+    const pi = parseJsonc<ProjectInterface>(content, 'interface.json');
+    if (pi.interface_version !== 2 || !pi.version || !pi.name || !pi.mirrorchyan_rid) {
+      throw new Error('invalid interface.json');
+    }
+    return pi;
+  } catch (error) {
+    log.warn('读取本地更新包 interface.json 失败:', error);
+    throw new LocalUpdatePackageError('missingPackageInterface');
+  }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Different failure modes for reading interface.json are collapsed into `missingPackageInterface`, which can mislead users and callers.

Currently all failures in `readLocalPackageInterface` (missing file, parse error, invalid structure) are surfaced as `LocalUpdatePackageError('missingPackageInterface')`, whose message specifically claims `interface.json` is absent. This conflates missing and invalid interfaces, produces inaccurate user messaging, and prevents callers from handling these cases differently.

Please either introduce a distinct error (e.g. `invalidPackageInterface`) for parse/validation failures, or propagate enough context so callers and the UI can distinguish missing from malformed/incompatible interfaces and handle/log them appropriately.

Suggested implementation:

```typescript
async function readLocalPackageInterface(filePath: string): Promise<ProjectInterface> {
  try {
    const content = await invoke<string>('read_update_package_interface', { packagePath: filePath });
    const pi = parseJsonc<ProjectInterface>(content, 'interface.json');

    if (pi.interface_version !== 2 || !pi.version || !pi.name || !pi.mirrorchyan_rid) {
      log.warn('本地更新包 interface.json 无效或不兼容:', pi);
      throw new LocalUpdatePackageError('invalidPackageInterface');
    }

    return pi;
  } catch (error) {
    // 解析/校验失败需要向上传递,以便调用方区分「缺失」与「无效」
    if (error instanceof LocalUpdatePackageError && (error as any).code === 'invalidPackageInterface') {
      throw error;
    }

    log.warn('读取本地更新包 interface.json 失败:', error);
    throw new LocalUpdatePackageError('missingPackageInterface');
  }
}

```

To fully support the new `invalidPackageInterface` failure mode, you will also need to:
1. Extend `LocalUpdatePackageError` (wherever it is defined) so it accepts/recognises `'invalidPackageInterface'` as a valid code and exposes it via a `code` (or equivalent) property used above.
2. Update any UI strings or error-to-message mappings that currently handle `missingPackageInterface` so they also handle `invalidPackageInterface` with appropriate messaging (e.g. “interface.json 格式无效或不兼容”).
3. If `LocalUpdatePackageError` does not use a `code` field but another name (e.g. `reason`, `type`), adjust the `(error as any).code` access accordingly in `readLocalPackageInterface`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +948 to +957
async function readLocalPackageInterface(filePath: string): Promise<ProjectInterface> {
try {
const content = await invoke<string>('read_update_package_interface', { packagePath: filePath });
const pi = parseJsonc<ProjectInterface>(content, 'interface.json');
if (pi.interface_version !== 2 || !pi.version || !pi.name || !pi.mirrorchyan_rid) {
throw new Error('invalid interface.json');
}
return pi;
} catch (error) {
log.warn('读取本地更新包 interface.json 失败:', error);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): 不同的 interface.json 读取失败模式都被折叠为 missingPackageInterface,可能会误导用户和调用方。

当前在 readLocalPackageInterface 中,所有失败(文件缺失、解析错误、结构无效)都会暴露为 LocalUpdatePackageError('missingPackageInterface'),其消息会明确声称 interface.json 缺失。这会把“缺失”和“无效”的接口混为一谈,导致对用户的提示不准确,也使调用方无法区分并分别处理这些情况。

请引入一个单独的错误(例如 invalidPackageInterface)用于解析/校验失败,或者向上抛出足够的上下文信息,使调用方和 UI 能够区分“缺失”和“格式错误/不兼容”的接口,并做出相应的处理/日志记录。

建议实现:

async function readLocalPackageInterface(filePath: string): Promise<ProjectInterface> {
  try {
    const content = await invoke<string>('read_update_package_interface', { packagePath: filePath });
    const pi = parseJsonc<ProjectInterface>(content, 'interface.json');

    if (pi.interface_version !== 2 || !pi.version || !pi.name || !pi.mirrorchyan_rid) {
      log.warn('本地更新包 interface.json 无效或不兼容:', pi);
      throw new LocalUpdatePackageError('invalidPackageInterface');
    }

    return pi;
  } catch (error) {
    // 解析/校验失败需要向上传递,以便调用方区分「缺失」与「无效」
    if (error instanceof LocalUpdatePackageError && (error as any).code === 'invalidPackageInterface') {
      throw error;
    }

    log.warn('读取本地更新包 interface.json 失败:', error);
    throw new LocalUpdatePackageError('missingPackageInterface');
  }
}

为了完整支持新的 invalidPackageInterface 失败模式,你还需要:

  1. 扩展 LocalUpdatePackageError(在其定义处),使其接受/识别 'invalidPackageInterface' 作为合法的 code,并通过上面使用的 code(或同等属性)暴露出来。
  2. 更新当前处理 missingPackageInterface 的 UI 文案或“错误到消息”的映射逻辑,使其也能处理 invalidPackageInterface,并给出合适的提示(例如:“interface.json 格式无效或不兼容”)。
  3. 如果 LocalUpdatePackageError 不使用 code 字段,而是其他名称(例如 reasontype),请在 readLocalPackageInterface 中相应地调整 (error as any).code 的访问。
Original comment in English

suggestion (bug_risk): Different failure modes for reading interface.json are collapsed into missingPackageInterface, which can mislead users and callers.

Currently all failures in readLocalPackageInterface (missing file, parse error, invalid structure) are surfaced as LocalUpdatePackageError('missingPackageInterface'), whose message specifically claims interface.json is absent. This conflates missing and invalid interfaces, produces inaccurate user messaging, and prevents callers from handling these cases differently.

Please either introduce a distinct error (e.g. invalidPackageInterface) for parse/validation failures, or propagate enough context so callers and the UI can distinguish missing from malformed/incompatible interfaces and handle/log them appropriately.

Suggested implementation:

async function readLocalPackageInterface(filePath: string): Promise<ProjectInterface> {
  try {
    const content = await invoke<string>('read_update_package_interface', { packagePath: filePath });
    const pi = parseJsonc<ProjectInterface>(content, 'interface.json');

    if (pi.interface_version !== 2 || !pi.version || !pi.name || !pi.mirrorchyan_rid) {
      log.warn('本地更新包 interface.json 无效或不兼容:', pi);
      throw new LocalUpdatePackageError('invalidPackageInterface');
    }

    return pi;
  } catch (error) {
    // 解析/校验失败需要向上传递,以便调用方区分「缺失」与「无效」
    if (error instanceof LocalUpdatePackageError && (error as any).code === 'invalidPackageInterface') {
      throw error;
    }

    log.warn('读取本地更新包 interface.json 失败:', error);
    throw new LocalUpdatePackageError('missingPackageInterface');
  }
}

To fully support the new invalidPackageInterface failure mode, you will also need to:

  1. Extend LocalUpdatePackageError (wherever it is defined) so it accepts/recognises 'invalidPackageInterface' as a valid code and exposes it via a code (or equivalent) property used above.
  2. Update any UI strings or error-to-message mappings that currently handle missingPackageInterface so they also handle invalidPackageInterface with appropriate messaging (e.g. “interface.json 格式无效或不兼容”).
  3. If LocalUpdatePackageError does not use a code field but another name (e.g. reason, type), adjust the (error as any).code access accordingly in readLocalPackageInterface.

@ocsin1 ocsin1 linked an issue Jun 8, 2026 that may be closed by this pull request
@Joe-Bao

Joe-Bao commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

草,issue怎么还能挂别家repo的

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

建议:希望添加拖拽手动更新功能。 增量更新和手动更新问题

1 participant