Skip to content

feat: 基础更新实现#27

Merged
imsyy merged 6 commits into
devfrom
feat/auto-update
Jun 5, 2026
Merged

feat: 基础更新实现#27
imsyy merged 6 commits into
devfrom
feat/auto-update

Conversation

@imsyy
Copy link
Copy Markdown
Member

@imsyy imsyy commented Jun 5, 2026

No description provided.

Copilot AI review requested due to automatic review settings June 5, 2026 15:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 thread src/stores/update.ts
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");
Copilot AI review requested due to automatic review settings June 5, 2026 15:47
@imsyy imsyy merged commit f73dedc into dev Jun 5, 2026
7 checks passed
@imsyy imsyy deleted the feat/auto-update branch June 5, 2026 15:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 thread electron/preload/index.ts
Comment on lines +399 to +402
openDownloadPage: () => ipcRenderer.invoke("update:openDownloadPage"),
// 订阅更新事件
onEvent: (callback: (event: UpdateEvent) => void) => subscribe("update:event", callback),
},
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.

2 participants