feat: 基础更新实现#27
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
本次 PR 主要补齐「基础更新」能力:主进程接入 electron-updater 并通过 IPC 将更新事件推送到渲染层,渲染层提供统一的更新弹窗与入口;同时引入统一的 app-data 数据根目录(含便携版 userData 重定向),并新增 Release 工作流用于多平台/多架构构建与发布资产整理。
Changes:
- 新增自动更新链路:主进程 updater service + IPC + 渲染层 UpdateStore / UpdateDialog + 设置页手动检查入口
- 重构数据落盘路径:配置/数据库/缓存/日志/插件统一迁移到
{userData}/app-data/*,并支持便携版 userData 重定向 - 新增 Release CI:tag 触发多平台构建,合并更新清单并创建 GitHub 草稿 Release;补充相关文档与样式复用(markdown-body)
Reviewed changes
Copilot reviewed 38 out of 39 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/styles/markdown.css | 新增全局 markdown-body 排版样式,供 v-html 渲染内容复用 |
| src/styles/global.css | 全局样式引入 markdown.css |
| src/stores/update.ts | 新增渲染层更新状态 store(订阅事件/触发检查/下载/安装) |
| src/settings/categories/general.ts | 设置页新增「更新」分组:自动检查开关 + 手动检查按钮 |
| src/layouts/MainLayout.vue | 主布局挂载全局 UpdateDialog |
| src/i18n/locales/zh-CN.json | 增加更新相关文案与设置项文案(中文) |
| src/i18n/locales/en-US.json | 增加更新相关文案与设置项文案(英文) |
| src/components/onboarding/StepAgreement.vue | 协议页 v-html 容器改用 markdown-body,全局样式替代 scoped deep 样式 |
| src/components/modals/UpdateDialog.vue | 新增更新弹窗:版本信息/日期/大小/Release Notes(Markdown→HTML)与操作按钮 |
| src/components/layout/NavHeader.vue | 顶栏新增“有更新”下载图标入口,打开更新弹窗 |
| shared/types/update.ts | 新增更新事件/元信息/对外 API 的共享类型定义 |
| shared/types/settings.ts | 新增 update 配置类型;更新部分路径注释(app-data) |
| shared/types/plugin.ts | 更新插件脚本相对路径注释(app-data) |
| shared/defaults/settings.ts | 增加 update.autoCheck 默认值 |
| package.json | productName 调整为 SPlayer-Next |
| index.html | 启动画面样式优化(will-change/translateZ) |
| electron/preload/index.ts | preload 暴露 update API(check/download/install/openDownloadPage/onEvent) |
| electron/preload/index.d.ts | Window.api 类型新增 update: UpdateApi |
| electron/main/utils/paths.ts | 新增统一路径定义(dataRoot/configDir/databaseDir/defaultCacheDir/logsDir/pluginsDir)+ 便携版 userData 重定向 |
| electron/main/utils/logger.ts | 日志目录迁移到 app-data/logs,并新增 updaterLog scope |
| electron/main/utils/config.ts | 缓存根目录改用 app-data/cache;新增 isPortable |
| electron/main/store/index.ts | settings.json 路径迁移到 app-data/config |
| electron/main/services/updater.ts | 新增 updater service:事件绑定、定时检查、下载、退出安装、打开 Releases |
| electron/main/services/lastfm/credentials.ts | lastfm.json 路径迁移到 app-data/config |
| electron/main/plugins/storage.ts | 插件 KV 存储路径迁移到 app-data/plugins/data |
| electron/main/plugins/registry.ts | 插件根目录迁移到 app-data/plugins(scripts/manifest/data) |
| electron/main/ipc/update.ts | 新增更新相关 IPC handlers |
| electron/main/ipc/streaming.ts | streaming.json 路径迁移到 app-data/config |
| electron/main/ipc/index.ts | 注册 update IPC |
| electron/main/ipc/cache.ts | cache reset 返回默认目录改为 app-data/cache,并清理注释调整 |
| electron/main/database/index.ts | 数据库路径迁移到 app-data/database/library.db |
| electron/main/core/index.ts | 启动流程调整:新增 initUpdater/disposeUpdater,并微调初始化顺序/注释 |
| electron-builder.config.ts | 打包目标调整(含 portable);publish 改为 GitHub;若干命名调整 |
| docs/plugins-usage.md | 文档更新:插件脚本/数据路径改为 app-data |
| docs/plugins-development.md | 文档更新:插件日志/数据路径改为 app-data |
| dev-app-update.yml | dev 更新源从 generic 改为 GitHub provider |
| CLAUDE.md | 文档更新:统一数据目录 app-data 结构说明与若干路径修正 |
| .github/workflows/release.yml | 新增 Release 工作流:多平台/多架构构建、整理资产、创建草稿 Release |
| .github/scripts/prepare-release-assets.cjs | 新增脚本:合并/去重构建产物与更新清单,准备 release-assets |
Comments suppressed due to low confidence (2)
electron/main/store/index.ts:23
- 配置文件路径从 {userData}/settings.json 迁到 {userData}/app-data/config/settings.json 后,init() 只会读取新路径;旧版本已有用户升级会直接丢失设置(读不到旧文件),并且会立刻把 defaults 写回新路径覆盖实际期望。建议在读取阶段增加对旧路径的回退读取(可选:读取成功后自动写入新路径完成迁移)。
/** 配置文件路径 */
const configPath = path.join(configDir, "settings.json");
/** 配置版本键名 */
const META_KEY = "__configVersion";
/** 读取配置文件 */
const readFile = (): Record<string, unknown> => {
try {
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
} catch {
return {};
}
electron/main/plugins/registry.ts:43
- 插件根目录从 {userData}/plugins 迁到 {userData}/app-data/plugins 后,当前实现只会扫描新目录;老用户已安装的脚本/manifest/data 会“全部消失”。同时 storage.ts 已经改为写入新 pluginsDir,若仅靠回退读取会造成读写目录不一致。建议在启动时检测旧目录存在且新目录不存在时,先把旧目录整体迁移(rename/copy)到新位置,再继续 ensureDirs/扫描。
const pluginsRoot = (): string => pluginsDir;
const scriptsDir = (): string => path.join(pluginsRoot(), "scripts");
const manifestFile = (): string => path.join(pluginsRoot(), "manifest.json");
interface StoredManifest {
version: 1;
plugins: Record<string, PluginManifest>;
}
const ensureDirs = (): void => {
const dirs = [pluginsRoot(), scriptsDir(), path.join(pluginsRoot(), "data")];
for (const d of dirs) if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
};
Comment on lines
+56
to
+60
| // 订阅主进程推送的更新事件 | ||
| window.api.update.onEvent(handleEvent); | ||
| // 触发启动检查 | ||
| void window.api.update.check(false); | ||
|
|
Comment on lines
+1
to
+15
| <script setup lang="ts"> | ||
| import { marked } from "marked"; | ||
| import { useUpdateStore } from "@/stores/update"; | ||
| import { APP_VERSION } from "@/utils/config"; | ||
| import { formatFileSize } from "@/utils/format"; | ||
|
|
||
| const { t } = useI18n(); | ||
| const update = useUpdateStore(); | ||
|
|
||
| /** release notes 渲染为 HTML */ | ||
| const notesHtml = computed(() => | ||
| update.meta?.releaseNotes | ||
| ? (marked.parse(update.meta.releaseNotes, { async: false }) as string) | ||
| : "", | ||
| ); |
Comment on lines
+20
to
+79
| /** 本次检查是否由用户手动触发 */ | ||
| let manualCheck = false; | ||
|
|
||
| let intervalTimer: ReturnType<typeof setInterval> | null = null; | ||
|
|
||
| const emit = (event: UpdateEvent): void => sendToMain("update:event", event); | ||
|
|
||
| /** | ||
| * 规范化更新日志格式 | ||
| * @param notes 更新日志,可能是字符串或数组 | ||
| * @returns 规范化后的更新日志字符串 | ||
| */ | ||
| const normalizeNotes = (notes: UpdateInfo["releaseNotes"]): string => { | ||
| if (!notes) return ""; | ||
| if (typeof notes === "string") return notes; | ||
| return notes | ||
| .map((item) => item.note ?? "") | ||
| .filter(Boolean) | ||
| .join("\n\n"); | ||
| }; | ||
|
|
||
| /** | ||
| * 将 electron-updater 的 UpdateInfo 转换为 UpdateMeta | ||
| * @param info 更新信息 | ||
| * @returns 更新元数据 | ||
| */ | ||
| const toMeta = (info: UpdateInfo): UpdateMeta => ({ | ||
| version: info.version, | ||
| releaseNotes: normalizeNotes(info.releaseNotes), | ||
| releaseDate: info.releaseDate, | ||
| size: Math.max(0, ...(info.files ?? []).map((file) => file.size ?? 0)), | ||
| }); | ||
|
|
||
| const bindEvents = (): void => { | ||
| autoUpdater.on("checking-for-update", () => emit({ type: "checking" })); | ||
| autoUpdater.on("update-available", (info) => | ||
| emit({ type: "available", meta: toMeta(info), manual: manualCheck, canInstall: canSelfInstall }), | ||
| ); | ||
| autoUpdater.on("update-not-available", () => emit({ type: "notAvailable", manual: manualCheck })); | ||
| autoUpdater.on("download-progress", (progress) => | ||
| emit({ type: "progress", percent: Math.round(progress.percent) }), | ||
| ); | ||
| autoUpdater.on("update-downloaded", (info) => emit({ type: "downloaded", meta: toMeta(info) })); | ||
| autoUpdater.on("error", (error) => { | ||
| updaterLog.error("更新出错", error); | ||
| emit({ type: "error", message: error?.message ?? String(error), manual: manualCheck }); | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * 执行更新检查 | ||
| * @param manual 是否由用户手动触发 | ||
| */ | ||
| const runCheck = (manual: boolean): void => { | ||
| manualCheck = manual; | ||
| autoUpdater.checkForUpdates().catch((error) => { | ||
| updaterLog.error("检查更新失败", error); | ||
| emit({ type: "error", message: error?.message ?? String(error), manual }); | ||
| }); | ||
| }; |
Comment on lines
12
to
16
|
|
||
| const STORAGE_FILE = path.join(app.getPath("userData"), "streaming.json"); | ||
| const STORAGE_FILE = path.join(configDir, "streaming.json"); | ||
|
|
||
| /** 持久化形态:密码加密、accessToken/userId 不持久化(每次会话重新登录) */ | ||
| interface PersistedServer extends Omit< |
Comment on lines
8
to
10
| /** 凭证文件 */ | ||
| const STORAGE_FILE = path.join(app.getPath("userData"), "lastfm.json"); | ||
| const STORAGE_FILE = path.join(configDir, "lastfm.json"); | ||
|
|
Comment on lines
8
to
26
| @@ -22,7 +21,7 @@ export const isDbOpen = (): boolean => db !== null; | |||
|
|
|||
| /** 初始化数据库:打开连接、启用 WAL、建表建索引、执行迁移 */ | |||
| export const initDatabase = (): void => { | |||
| fs.mkdirSync(dbDir, { recursive: true }); | |||
| fs.mkdirSync(databaseDir, { recursive: true }); | |||
| db = new Database(dbPath); | |||
| db.pragma("journal_mode = WAL"); | |||
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 40 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
electron/main/store/index.ts:24
- 配置文件从
{userData}/settings.json改到{userData}/app-data/config/settings.json后,当前实现不会尝试读取旧路径;用户从旧版本升级会被当作“全新安装”,导致所有设置丢失(随后还会把默认值写到新路径)。建议在读取阶段兼容旧路径并在首次启动时自动迁移到新路径。
/** 配置文件路径 */
const configPath = path.join(configDir, "settings.json");
/** 配置版本键名 */
const META_KEY = "__configVersion";
/** 读取配置文件 */
const readFile = (): Record<string, unknown> => {
try {
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
} catch {
return {};
}
};
Comment on lines
+60
to
+63
| key: "autoCheckUpdate", | ||
| type: "switch", | ||
| binding: { store: "settings", path: "system.update.autoCheck" }, | ||
| defaultValue: true, |
Comment on lines
+10
to
+15
| /** release notes 渲染为 HTML */ | ||
| const notesHtml = computed(() => | ||
| update.meta?.releaseNotes | ||
| ? (marked.parse(update.meta.releaseNotes, { async: false }) as string) | ||
| : "", | ||
| ); |
Comment on lines
+399
to
+402
| openDownloadPage: () => ipcRenderer.invoke("update:openDownloadPage"), | ||
| // 订阅更新事件 | ||
| onEvent: (callback: (event: UpdateEvent) => void) => subscribe("update:event", callback), | ||
| }, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.