Ver Fonte

fix(updater): 禁用 macOS/Linux 自动更新并补充安装说明

macOS/Linux 不再触发 updater 检查,设置页改为提示手动下载。
README 补充 codesign 与 xattr 解除隔离的操作步骤。

Co-authored-by: Cursor <cursoragent@cursor.com>
darknessomi há 2 meses atrás
pai
commit
2e51536ba7

+ 14 - 0
README.md

@@ -347,6 +347,20 @@ AI 生成的章节默认为草稿状态。草稿支持预览、编辑、重新
 
 前往 [GitHub Releases](https://github.com/Mochocyang/QMAI/releases) 下载最新版本安装包。
 
+**macOS 用户须知**
+
+Release 包未做 Apple 公证,首次打开可能提示「已损坏」或无法运行。将 `.app` 拖入「应用程序」后,在终端执行:
+
+```bash
+codesign --force --deep --sign - /Applications/QMaiWrite.app
+xattr -dr com.apple.quarantine /Applications/QMaiWrite.app
+```
+
+- 第一行:用 ad-hoc 签名让系统允许本地运行
+- 第二行:移除下载时附加的隔离标记
+
+macOS 暂不支持应用内自动更新,新版本请手动从 Releases 下载安装。
+
 ### 基本使用流程
 
 1. **新建小说项目**:指定项目名称、题材、目标字数

+ 49 - 23
src/components/settings/sections/changelog-section.tsx

@@ -2,6 +2,11 @@ import { useState, useRef } from "react"
 import { useTranslation } from "react-i18next"
 import { RefreshCw, Download, CheckCircle, AlertCircle } from "lucide-react"
 import { allChangelog } from "@/lib/changelog"
+import {
+  APP_AUTO_UPDATE_RELEASES_URL,
+  APP_AUTO_UPDATE_UNSUPPORTED_MESSAGE,
+  isAppAutoUpdateSupported,
+} from "@/lib/app-update-support"
 import { isTauri } from "@/lib/platform"
 import { formatUpdateErrorMessage } from "@/lib/update-error-message"
 
@@ -19,6 +24,7 @@ export function ChangelogSection() {
   const [errorMessage, setErrorMessage] = useState("")
   const [downloadProgress, setDownloadProgress] = useState(0)
   const updateHandleRef = useRef<unknown>(null)
+  const autoUpdateSupported = isAppAutoUpdateSupported()
 
   async function handleCheckUpdate() {
     if (!isTauri()) {
@@ -26,6 +32,11 @@ export function ChangelogSection() {
       setErrorMessage("仅桌面版支持自动更新检测")
       return
     }
+    if (!autoUpdateSupported) {
+      setUpdateStatus("error")
+      setErrorMessage(APP_AUTO_UPDATE_UNSUPPORTED_MESSAGE)
+      return
+    }
     setUpdateStatus("checking")
     setErrorMessage("")
     setDownloadProgress(0)
@@ -104,31 +115,46 @@ export function ChangelogSection() {
 
       {/* 检查更新区域 */}
       <div className="rounded-lg border border-border p-4">
-        <div className="flex items-center gap-3">
-          <button
-            type="button"
-            onClick={() => void handleCheckUpdate()}
-            disabled={updateStatus === "checking" || updateStatus === "downloading"}
-            className="inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
-          >
-            <RefreshCw className={`h-4 w-4 ${updateStatus === "checking" ? "animate-spin" : ""}`} />
-            {updateStatus === "checking" ? "正在检查..." : "检查更新"}
-          </button>
+        {autoUpdateSupported ? (
+          <div className="flex items-center gap-3">
+            <button
+              type="button"
+              onClick={() => void handleCheckUpdate()}
+              disabled={updateStatus === "checking" || updateStatus === "downloading"}
+              className="inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
+            >
+              <RefreshCw className={`h-4 w-4 ${updateStatus === "checking" ? "animate-spin" : ""}`} />
+              {updateStatus === "checking" ? "正在检查..." : "检查更新"}
+            </button>
 
-          {updateStatus === "up-to-date" ? (
-            <span className="inline-flex items-center gap-1.5 text-sm text-emerald-600 dark:text-emerald-400">
-              <CheckCircle className="h-4 w-4" />
-              当前已是最新版本
-            </span>
-          ) : null}
+            {updateStatus === "up-to-date" ? (
+              <span className="inline-flex items-center gap-1.5 text-sm text-emerald-600 dark:text-emerald-400">
+                <CheckCircle className="h-4 w-4" />
+                当前已是最新版本
+              </span>
+            ) : null}
 
-          {updateStatus === "error" ? (
-            <span className="inline-flex items-center gap-1.5 text-sm text-red-600 dark:text-red-400">
-              <AlertCircle className="h-4 w-4" />
-              {errorMessage || "检查更新失败"}
-            </span>
-          ) : null}
-        </div>
+            {updateStatus === "error" ? (
+              <span className="inline-flex items-center gap-1.5 text-sm text-red-600 dark:text-red-400">
+                <AlertCircle className="h-4 w-4" />
+                {errorMessage || "检查更新失败"}
+              </span>
+            ) : null}
+          </div>
+        ) : (
+          <p className="text-sm leading-relaxed text-muted-foreground">
+            {APP_AUTO_UPDATE_UNSUPPORTED_MESSAGE}
+            {" "}
+            <a
+              href={APP_AUTO_UPDATE_RELEASES_URL}
+              target="_blank"
+              rel="noreferrer"
+              className="font-medium text-primary hover:underline"
+            >
+              GitHub Releases
+            </a>
+          </p>
+        )}
 
         {updateStatus === "available" ? (
           <div className="mt-3 rounded-md border border-amber-200 bg-amber-50 p-3 dark:border-amber-900/60 dark:bg-amber-950/40">

+ 20 - 0
src/lib/app-update-support.ts

@@ -0,0 +1,20 @@
+import { isTauri } from "@/lib/platform"
+
+export const APP_AUTO_UPDATE_RELEASES_URL = "https://github.com/Mochocyang/QMAI/releases"
+
+export const APP_AUTO_UPDATE_UNSUPPORTED_MESSAGE =
+  "当前平台暂不支持应用内自动更新,请前往 GitHub Releases 手动下载安装包。"
+
+export function getDesktopPlatform(): "windows" | "macos" | "linux" | "unknown" {
+  if (!isTauri()) return "unknown"
+
+  const ua = navigator.userAgent
+  if (/Windows/i.test(ua)) return "windows"
+  if (/Mac/i.test(ua)) return "macos"
+  if (/Linux/i.test(ua)) return "linux"
+  return "unknown"
+}
+
+export function isAppAutoUpdateSupported(): boolean {
+  return getDesktopPlatform() === "windows"
+}

+ 2 - 1
src/lib/app-updater.ts

@@ -1,3 +1,4 @@
+import { isAppAutoUpdateSupported } from "@/lib/app-update-support"
 import { isTauri } from "@/lib/platform"
 import type { Update } from "@tauri-apps/plugin-updater"
 
@@ -89,7 +90,7 @@ export async function runAppUpdateFlow(bindings: UpdaterBindings) {
 }
 
 export async function checkForAppUpdate() {
-  if (!isTauri() || updateCheckStarted) return
+  if (!isTauri() || !isAppAutoUpdateSupported() || updateCheckStarted) return
 
   updateCheckStarted = true
   try {

+ 6 - 0
src/lib/update-error-message.ts

@@ -1,6 +1,12 @@
+import { APP_AUTO_UPDATE_UNSUPPORTED_MESSAGE } from "@/lib/app-update-support"
+
 export function formatUpdateErrorMessage(error: unknown): string {
   const raw = error instanceof Error ? error.message : String(error)
 
+  if (raw.includes("fallback platforms") && raw.includes("darwin-aarch64")) {
+    return APP_AUTO_UPDATE_UNSUPPORTED_MESSAGE
+  }
+
   if (raw.includes("Could not fetch a valid release JSON from the remote")) {
     return "检查更新失败:没有从更新服务器拿到有效的版本信息。请先确认网络可以访问 GitHub,若未启用代理请关闭系统或软件代理后重试。"
   }