diff --git a/UPDATE_FEATURE_GUIDE.md b/UPDATE_FEATURE_GUIDE.md new file mode 100644 index 0000000..efb9d2c --- /dev/null +++ b/UPDATE_FEATURE_GUIDE.md @@ -0,0 +1,148 @@ +# 检查更新功能实现指南 + +## 功能概述 + +已成功为 GitHub Stars Manager 添加了完整的检查更新功能,包括: + +1. **版本信息管理** - 使用XML格式存储版本信息 +2. **自动更新检查** - 应用启动时自动检查更新 +3. **手动更新检查** - 设置页面中的检查更新按钮 +4. **更新提示界面** - 美观的更新对话框 +5. **版本管理工具** - 自动化版本更新脚本 + +## 文件结构 + +``` +├── versions/ +│ ├── version-info.xml # 版本信息XML文件 +│ └── README.md # 版本管理说明 +├── src/ +│ ├── services/ +│ │ └── updateService.ts # 更新检查服务 +│ └── components/ +│ └── UpdateChecker.tsx # 更新检查组件 +├── scripts/ +│ └── update-version.js # 版本更新脚本 +└── test-update.html # 功能测试页面 +``` + +## 使用方法 + +### 1. 发布新版本 + +使用自动化脚本更新版本: + +```bash +npm run update-version 0.1.4 "新增功能A" "修复bug B" "优化性能C" +``` + +这个命令会自动: +- 更新 `package.json` 中的版本号 +- 在 `versions/version-info.xml` 中添加新版本记录 +- 更新 `src/services/updateService.ts` 中的当前版本号 + +### 2. 提交到仓库 + +```bash +git add . +git commit -m "chore: bump version to v0.1.4" +git push origin main +``` + +### 3. 创建GitHub Release + +1. 在GitHub仓库中创建新的Release +2. 标签名称:`v0.1.4` +3. 上传构建好的安装包(如 `github-stars-manager-0.1.4.dmg`) +4. 确保下载链接与XML中的URL一致 + +## 功能特性 + +### 自动检查更新 +- 应用启动3秒后自动检查更新 +- 静默检查,不影响用户体验 +- 发现新版本时在控制台记录日志 + +### 手动检查更新 +- 设置页面中的"检查更新"按钮 +- 实时显示检查状态 +- 显示详细的更新信息 + +### 更新提示界面 +- 美观的模态对话框 +- 显示版本号和发布日期 +- 详细的更新日志列表 +- 一键跳转到下载页面 + +### 版本比较算法 +- 支持语义化版本号(x.y.z) +- 智能比较版本大小 +- 处理不同长度的版本号 + +## XML文件格式 + +```xml + + + + 0.1.3 + 2025-01-04 + + 添加检查更新功能 + 优化用户界面 + 修复已知bug + + https://github.com/AmintaCCCP/GithubStarsManager/releases/download/v0.1.3/github-stars-manager-0.1.3.dmg + + +``` + +## 测试方法 + +### 本地测试 +1. 打开 `test-update.html` 文件 +2. 点击"检查更新"按钮 +3. 验证功能是否正常工作 + +### 应用内测试 +1. 启动应用,等待3秒观察控制台日志 +2. 进入设置页面,点击"检查更新" +3. 验证更新对话框是否正确显示 + +## 注意事项 + +1. **版本号格式**:必须使用 x.y.z 格式的语义化版本号 +2. **XML文件编码**:确保使用UTF-8编码 +3. **下载链接**:确保GitHub Release中的下载链接可用 +4. **网络请求**:更新检查需要网络连接 +5. **CORS问题**:本地测试时可能遇到跨域问题 + +## 错误处理 + +- 网络连接失败时显示友好错误信息 +- XML解析错误时提供详细错误描述 +- 版本比较异常时使用默认处理逻辑 + +## 多语言支持 + +更新功能已集成应用的多语言系统: +- 中文界面显示中文提示 +- 英文界面显示英文提示 +- 自动根据应用语言设置调整 + +## 未来扩展 + +可以考虑添加的功能: +1. 自动下载更新包 +2. 增量更新支持 +3. 更新进度显示 +4. 更新历史记录 +5. 跳过版本功能 + +## 技术实现 + +- **前端框架**:React + TypeScript +- **HTTP请求**:Fetch API +- **XML解析**:DOMParser +- **版本比较**:自定义算法 +- **UI组件**:Tailwind CSS + Lucide Icons \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1a63c48..575f264 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "github-stars-manager", - "version": "1.0.0", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "github-stars-manager", - "version": "1.0.0", + "version": "0.1.3", "dependencies": { "date-fns": "^3.3.1", "lucide-react": "^0.344.0", @@ -29,7 +29,8 @@ "tailwindcss": "^3.4.1", "typescript": "^5.5.3", "typescript-eslint": "^8.3.0", - "vite": "^5.4.2" + "vite": "^5.4.2", + "xml2js": "^0.6.2" } }, "node_modules/@alloc/quick-lru": { @@ -3449,6 +3450,13 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "license": "ISC" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -4071,6 +4079,30 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index b3e8f8d..8d027d9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github-stars-manager", "private": true, - "version": "0.1.2", + "version": "0.1.3", "type": "module", "scripts": { "dev": "vite", @@ -11,15 +11,16 @@ "build:desktop": "node scripts/build-desktop.js", "electron": "electron electron/main.js", "electron:dev": "NODE_ENV=development electron electron/main.js", - "dist": "electron-builder" + "dist": "electron-builder", + "update-version": "node scripts/update-version.cjs" }, "dependencies": { + "date-fns": "^3.3.1", "lucide-react": "^0.344.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.22.0", - "zustand": "^4.5.0", - "date-fns": "^3.3.1" + "zustand": "^4.5.0" }, "devDependencies": { "@eslint/js": "^9.9.1", @@ -35,6 +36,7 @@ "tailwindcss": "^3.4.1", "typescript": "^5.5.3", "typescript-eslint": "^8.3.0", - "vite": "^5.4.2" + "vite": "^5.4.2", + "xml2js": "^0.6.2" } -} \ No newline at end of file +} diff --git a/scripts/update-version.cjs b/scripts/update-version.cjs new file mode 100644 index 0000000..5bf2938 --- /dev/null +++ b/scripts/update-version.cjs @@ -0,0 +1,231 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +/** + * 更新版本信息的脚本 + * 使用方法: + * node scripts/update-version.cjs [version] [changelog...] [--url=downloadUrl] + * node scripts/update-version.cjs --list (列出所有版本) + * node scripts/update-version.cjs --current (显示当前版本) + * + * 例如: + * node scripts/update-version.cjs 0.1.3 "修复搜索bug" "添加新功能" + * node scripts/update-version.cjs 0.1.3 "修复bug" --url="https://github.com/AmintaCCCP/GithubStarsManager/releases/tag/v0.1.3-fix" + */ + +function updateVersionInfo() { + const args = process.argv.slice(2); + + // 处理特殊命令 + if (args.length === 1) { + if (args[0] === '--list') { + listVersions(); + return; + } + if (args[0] === '--current') { + showCurrentVersion(); + return; + } + if (args[0] === '--help' || args[0] === '-h') { + showHelp(); + return; + } + } + + if (args.length < 2) { + console.error('❌ 参数不足'); + showHelp(); + process.exit(1); + } + + const newVersion = args[0]; + + // 解析参数,查找自定义下载链接 + let customDownloadUrl = null; + const changelog = []; + + for (let i = 1; i < args.length; i++) { + const arg = args[i]; + if (arg.startsWith('--url=')) { + customDownloadUrl = arg.substring(6); + } else { + changelog.push(arg); + } + } + + // 验证版本号格式 + if (!/^\d+\.\d+\.\d+$/.test(newVersion)) { + console.error('❌ 版本号格式错误,应该是 x.y.z 格式'); + process.exit(1); + } + + // 验证至少有一条更新日志 + if (changelog.length === 0) { + console.error('❌ 至少需要提供一条更新日志'); + process.exit(1); + } + + try { + // 更新 package.json + updatePackageJson(newVersion); + + // 更新 version-info.xml + updateVersionXML(newVersion, changelog, customDownloadUrl); + + // 更新 UpdateService 中的版本号 + updateServiceVersion(newVersion); + + console.log(`✅ 版本已更新到 ${newVersion}`); + console.log('📝 更新内容:'); + changelog.forEach((item, index) => { + console.log(` ${index + 1}. ${item}`); + }); + if (customDownloadUrl) { + console.log(`🔗 自定义下载链接: ${customDownloadUrl}`); + } + console.log('\n🔄 请记得提交这些更改到 Git 仓库'); + + } catch (error) { + console.error('❌ 更新版本失败:', error.message); + process.exit(1); + } +} + +function updatePackageJson(version) { + const packagePath = path.join(__dirname, '../package.json'); + const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + + packageJson.version = version; + + fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + '\n'); + console.log(`📦 已更新 package.json 版本到 ${version}`); +} + +function updateVersionXML(version, changelog, customDownloadUrl) { + const xmlPath = path.join(__dirname, '../versions/version-info.xml'); + const currentDate = new Date().toISOString().split('T')[0]; + + let xmlContent; + try { + xmlContent = fs.readFileSync(xmlPath, 'utf8'); + } catch (error) { + // 如果文件不存在,创建新的XML文件 + xmlContent = '\n\n'; + } + + // 生成下载链接 + const downloadUrl = customDownloadUrl || + `https://github.com/AmintaCCCP/GithubStarsManager/releases/download/v${version}/github-stars-manager-${version}.dmg`; + + // 解析现有的XML + const versionEntry = ` + ${version} + ${currentDate} + +${changelog.map(item => ` ${escapeXml(item)}`).join('\n')} + + ${escapeXml(downloadUrl)} + `; + + // 在 前插入新版本 + const updatedXml = xmlContent.replace('', `${versionEntry}\n`); + + fs.writeFileSync(xmlPath, updatedXml); + console.log(`📄 已更新 version-info.xml`); +} + +function updateServiceVersion(version) { + const servicePath = path.join(__dirname, '../src/services/updateService.ts'); + let serviceContent = fs.readFileSync(servicePath, 'utf8'); + + // 更新版本号 + serviceContent = serviceContent.replace( + /return '\d+\.\d+\.\d+';/, + `return '${version}';` + ); + + fs.writeFileSync(servicePath, serviceContent); + console.log(`🔧 已更新 UpdateService 版本到 ${version}`); +} + +function escapeXml(text) { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function listVersions() { + const xmlPath = path.join(__dirname, '../versions/version-info.xml'); + + try { + const xmlContent = fs.readFileSync(xmlPath, 'utf8'); + const parser = require('xml2js'); + + parser.parseString(xmlContent, (err, result) => { + if (err) { + console.error('❌ XML解析失败:', err.message); + return; + } + + const versions = result.versions.version || []; + console.log('📋 版本历史:'); + console.log(''); + + versions.forEach((version, index) => { + console.log(`${index + 1}. v${version.number[0]} (${version.releaseDate[0]})`); + if (version.changelog && version.changelog[0].item) { + version.changelog[0].item.forEach(item => { + console.log(` • ${item}`); + }); + } + console.log(''); + }); + }); + } catch (error) { + console.error('❌ 读取版本信息失败:', error.message); + } +} + +function showCurrentVersion() { + try { + const packagePath = path.join(__dirname, '../package.json'); + const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + console.log(`📦 当前版本: v${packageJson.version}`); + } catch (error) { + console.error('❌ 读取当前版本失败:', error.message); + } +} + +function showHelp() { + console.log('📖 版本管理工具使用说明'); + console.log(''); + console.log('用法:'); + console.log(' node scripts/update-version.cjs [--url=downloadUrl]'); + console.log(' node scripts/update-version.cjs --list 列出所有版本'); + console.log(' node scripts/update-version.cjs --current 显示当前版本'); + console.log(' node scripts/update-version.cjs --help 显示帮助'); + console.log(''); + console.log('示例:'); + console.log(' node scripts/update-version.cjs 0.1.3 "修复搜索bug" "添加新功能"'); + console.log(' node scripts/update-version.cjs 0.1.4 "优化性能" --url="https://github.com/AmintaCCCP/GithubStarsManager/releases/tag/v0.1.4-fix"'); + console.log(' npm run update-version 0.1.5 "修复已知问题" "提升用户体验"'); + console.log(''); + console.log('参数说明:'); + console.log(' 版本号,格式为 x.y.z'); + console.log(' 更新日志,至少需要一条'); + console.log(' --url= 自定义下载链接(可选)'); + console.log(''); + console.log('注意:'); + console.log(' • 版本号必须遵循 x.y.z 格式'); + console.log(' • 更新日志至少需要一条'); + console.log(' • 如果不指定 --url,将使用默认的 GitHub Release 链接格式'); + console.log(' • 更新后记得提交到Git仓库'); +} + +// 运行脚本 +updateVersionInfo(); \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index ea748ad..c21c4af 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,8 @@ import { CategorySidebar } from './components/CategorySidebar'; import { ReleaseTimeline } from './components/ReleaseTimeline'; import { SettingsPanel } from './components/SettingsPanel'; import { useAppStore } from './store/useAppStore'; +import { useAutoUpdateCheck } from './components/UpdateChecker'; +import { UpdateNotificationBanner } from './components/UpdateNotificationBanner'; function App() { const { @@ -19,6 +21,9 @@ function App() { const [selectedCategory, setSelectedCategory] = useState('all'); + // 自动检查更新 + useAutoUpdateCheck(); + // Apply theme to document useEffect(() => { if (theme === 'dark') { @@ -64,6 +69,7 @@ function App() { return (
+
{renderCurrentView()} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 8bff148..9da4b81 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Star, Settings, Calendar, Search, Moon, Sun, LogOut, RefreshCw, Github } from 'lucide-react'; +import { Star, Settings, Calendar, Search, Moon, Sun, LogOut, RefreshCw } from 'lucide-react'; import { useAppStore } from '../store/useAppStore'; import { GitHubApiService } from '../services/githubApi'; @@ -159,17 +159,6 @@ export const Header: React.FC = () => { {/* User Actions */}
- {/* GitHub Repository Link */} - - - - {/* Sync Status */}
{t('上次同步:', 'Last sync:')} {formatLastSync(lastSync)} diff --git a/src/components/SettingsPanel.tsx b/src/components/SettingsPanel.tsx index 4a2aa71..9789a05 100644 --- a/src/components/SettingsPanel.tsx +++ b/src/components/SettingsPanel.tsx @@ -14,12 +14,18 @@ import { Upload, RefreshCw, Globe, - MessageSquare + MessageSquare, + Package, + ExternalLink, + Mail, + Github, + Twitter } from 'lucide-react'; import { AIConfig, WebDAVConfig } from '../types'; import { useAppStore } from '../store/useAppStore'; import { AIService } from '../services/aiService'; import { WebDAVService } from '../services/webdavService'; +import { UpdateChecker } from './UpdateChecker'; export const SettingsPanel: React.FC = () => { const { @@ -351,6 +357,28 @@ Focus on practicality and accurate categorization to help users quickly understa return (
+ {/* Update Check */} +
+
+ +

+ {t('检查更新', 'Check for Updates')} +

+
+ +
+
+

+ {t('当前版本: v0.1.3', 'Current Version: v0.1.3')} +

+

+ {t('检查是否有新版本可用', 'Check if a new version is available')} +

+
+ +
+
+ {/* Language Settings */}
@@ -390,6 +418,42 @@ Focus on practicality and accurate categorization to help users quickly understa
+ {/* Contact Information */} +
+
+ +

+ {t('联系方式', 'Contact Information')} +

+
+ +
+

+ {t('如果您在使用过程中遇到任何问题或有建议,欢迎通过以下方式联系我:', 'If you encounter any issues or have suggestions while using the app, feel free to contact me through:')} +

+ +
+ + + +
+
+
+ {/* AI Configuration */}
diff --git a/src/components/UpdateChecker.tsx b/src/components/UpdateChecker.tsx new file mode 100644 index 0000000..4ff8a6e --- /dev/null +++ b/src/components/UpdateChecker.tsx @@ -0,0 +1,194 @@ +import React, { useState } from 'react'; +import { Download, RefreshCw, ExternalLink, Calendar, Package } from 'lucide-react'; +import { UpdateService, VersionInfo } from '../services/updateService'; +import { useAppStore } from '../store/useAppStore'; + +interface UpdateCheckerProps { + onUpdateAvailable?: (version: VersionInfo) => void; +} + +export const UpdateChecker: React.FC = ({ onUpdateAvailable }) => { + const { language, setUpdateNotification } = useAppStore(); + const [isChecking, setIsChecking] = useState(false); + const [updateInfo, setUpdateInfo] = useState(null); + const [showUpdateDialog, setShowUpdateDialog] = useState(false); + const [error, setError] = useState(null); + + const t = (zh: string, en: string) => language === 'zh' ? zh : en; + + const checkForUpdates = async (silent = false) => { + setIsChecking(true); + setError(null); + + try { + const result = await UpdateService.checkForUpdates(); + + if (result.hasUpdate && result.latestVersion) { + setUpdateInfo(result.latestVersion); + setShowUpdateDialog(true); + onUpdateAvailable?.(result.latestVersion); + + // 设置全局更新通知 + setUpdateNotification({ + version: result.latestVersion.number, + releaseDate: result.latestVersion.releaseDate, + changelog: result.latestVersion.changelog, + downloadUrl: result.latestVersion.downloadUrl, + dismissed: false + }); + } else if (!silent) { + // 只在手动检查时显示"已是最新版本"的消息 + alert(t('当前已是最新版本!', 'You are already using the latest version!')); + } + } catch (error) { + const errorMessage = t('检查更新失败,请检查网络连接', 'Failed to check for updates. Please check your network connection.'); + setError(errorMessage); + if (!silent) { + alert(errorMessage); + } + console.error('Update check failed:', error); + } finally { + setIsChecking(false); + } + }; + + const handleDownload = () => { + if (updateInfo?.downloadUrl) { + UpdateService.openDownloadUrl(updateInfo.downloadUrl); + setShowUpdateDialog(false); + } + }; + + const formatDate = (dateString: string) => { + try { + return new Date(dateString).toLocaleDateString(language === 'zh' ? 'zh-CN' : 'en-US'); + } catch { + return dateString; + } + }; + + return ( + <> + {/* 检查更新按钮 */} + + + {/* 错误提示 */} + {error && ( +
+

{error}

+
+ )} + + {/* 更新对话框 */} + {showUpdateDialog && updateInfo && ( +
+
+
+ {/* 标题 */} +
+
+ +
+
+

+ {t('发现新版本', 'New Version Available')} +

+

+ v{updateInfo.number} +

+
+
+ + {/* 版本信息 */} +
+
+ + {t('发布日期:', 'Release Date:')} {formatDate(updateInfo.releaseDate)} +
+ + {/* 更新日志 */} +
+

+ {t('更新内容:', 'What\'s New:')} +

+
    + {updateInfo.changelog.map((item, index) => ( +
  • + + {item} +
  • + ))} +
+
+
+ + {/* 按钮 */} +
+ + +
+
+
+
+ )} + + ); +}; + +// 用于应用启动时自动检查更新的Hook +export const useAutoUpdateCheck = () => { + const { setUpdateNotification } = useAppStore(); + + React.useEffect(() => { + const checkUpdatesOnStartup = async () => { + try { + const result = await UpdateService.checkForUpdates(); + if (result.hasUpdate && result.latestVersion) { + console.log('New version available:', result.latestVersion.number); + + // 设置全局更新通知 + setUpdateNotification({ + version: result.latestVersion.number, + releaseDate: result.latestVersion.releaseDate, + changelog: result.latestVersion.changelog, + downloadUrl: result.latestVersion.downloadUrl, + dismissed: false + }); + } + } catch (error) { + console.error('Startup update check failed:', error); + } + }; + + // 延迟3秒后检查更新,避免影响应用启动速度 + const timer = setTimeout(checkUpdatesOnStartup, 3000); + return () => clearTimeout(timer); + }, [setUpdateNotification]); +}; \ No newline at end of file diff --git a/src/components/UpdateNotificationBanner.tsx b/src/components/UpdateNotificationBanner.tsx new file mode 100644 index 0000000..bd85569 --- /dev/null +++ b/src/components/UpdateNotificationBanner.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { X, Download, Calendar, Package } from 'lucide-react'; +import { useAppStore } from '../store/useAppStore'; +import { UpdateService } from '../services/updateService'; + +export const UpdateNotificationBanner: React.FC = () => { + const { updateNotification, dismissUpdateNotification, language } = useAppStore(); + + const t = (zh: string, en: string) => language === 'zh' ? zh : en; + + if (!updateNotification || updateNotification.dismissed) { + return null; + } + + const handleDownload = () => { + UpdateService.openDownloadUrl(updateNotification.downloadUrl); + dismissUpdateNotification(); + }; + + const formatDate = (dateString: string) => { + try { + return new Date(dateString).toLocaleDateString(language === 'zh' ? 'zh-CN' : 'en-US'); + } catch { + return dateString; + } + }; + + return ( +
+
+
+
+
+ +
+
+
+

+ {t('发现新版本', 'New Version Available')} v{updateNotification.version} +

+
+ + {formatDate(updateNotification.releaseDate)} +
+
+

+ {updateNotification.changelog.slice(0, 2).join(' • ')} + {updateNotification.changelog.length > 2 && '...'} +

+
+
+ +
+ + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/services/updateService.ts b/src/services/updateService.ts new file mode 100644 index 0000000..69179f7 --- /dev/null +++ b/src/services/updateService.ts @@ -0,0 +1,119 @@ +export interface VersionInfo { + number: string; + releaseDate: string; + changelog: string[]; + downloadUrl: string; +} + +export interface UpdateCheckResult { + hasUpdate: boolean; + currentVersion: string; + latestVersion?: VersionInfo; +} + +export class UpdateService { + private static readonly REPO_URL = 'https://raw.githubusercontent.com/AmintaCCCP/GithubStarsManager/main/versions/version-info.xml'; + + private static getCurrentVersion(): string { + // 在实际应用中,这个版本号应该在构建时注入 + // 这里暂时硬编码,你可以通过构建脚本或环境变量来动态设置 + return '0.1.3'; + } + + static async checkForUpdates(): Promise { + const currentVersion = this.getCurrentVersion(); + + try { + const response = await fetch(this.REPO_URL); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const xmlText = await response.text(); + const versions = this.parseVersionXML(xmlText); + + if (versions.length === 0) { + return { + hasUpdate: false, + currentVersion + }; + } + + // 获取最新版本(假设XML中版本按时间排序,最后一个是最新的) + const latestVersion = versions[versions.length - 1]; + const hasUpdate = this.compareVersions(currentVersion, latestVersion.number) < 0; + + return { + hasUpdate, + currentVersion, + latestVersion: hasUpdate ? latestVersion : undefined + }; + } catch (error) { + console.error('检查更新失败:', error); + throw error; + } + } + + private static parseVersionXML(xmlText: string): VersionInfo[] { + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(xmlText, 'text/xml'); + + // 检查解析错误 + const parseError = xmlDoc.querySelector('parsererror'); + if (parseError) { + throw new Error('XML解析失败'); + } + + const versions: VersionInfo[] = []; + const versionNodes = xmlDoc.querySelectorAll('version'); + + versionNodes.forEach(versionNode => { + const number = versionNode.querySelector('number')?.textContent?.trim(); + const releaseDate = versionNode.querySelector('releaseDate')?.textContent?.trim(); + const downloadUrl = versionNode.querySelector('downloadUrl')?.textContent?.trim(); + + if (!number || !releaseDate || !downloadUrl) { + return; // 跳过不完整的版本信息 + } + + const changelog: string[] = []; + const changelogItems = versionNode.querySelectorAll('changelog item'); + changelogItems.forEach(item => { + const text = item.textContent?.trim(); + if (text) { + changelog.push(text); + } + }); + + versions.push({ + number, + releaseDate, + changelog, + downloadUrl + }); + }); + + return versions; + } + + private static compareVersions(version1: string, version2: string): number { + const v1Parts = version1.split('.').map(Number); + const v2Parts = version2.split('.').map(Number); + + const maxLength = Math.max(v1Parts.length, v2Parts.length); + + for (let i = 0; i < maxLength; i++) { + const v1Part = v1Parts[i] || 0; + const v2Part = v2Parts[i] || 0; + + if (v1Part < v2Part) return -1; + if (v1Part > v2Part) return 1; + } + + return 0; + } + + static openDownloadUrl(url: string): void { + window.open(url, '_blank'); + } +} \ No newline at end of file diff --git a/src/store/useAppStore.ts b/src/store/useAppStore.ts index 3e21cdf..f1da0c7 100644 --- a/src/store/useAppStore.ts +++ b/src/store/useAppStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; -import { AppState, Repository, Release, AIConfig, WebDAVConfig, SearchFilters, GitHubUser, Category, AssetFilter } from '../types'; +import { AppState, Repository, Release, AIConfig, WebDAVConfig, SearchFilters, GitHubUser, Category, AssetFilter, UpdateNotification } from '../types'; interface AppActions { // Auth actions @@ -52,6 +52,10 @@ interface AppActions { setTheme: (theme: 'light' | 'dark') => void; setCurrentView: (view: 'repositories' | 'releases' | 'settings') => void; setLanguage: (language: 'zh' | 'en') => void; + + // Update actions + setUpdateNotification: (notification: UpdateNotification | null) => void; + dismissUpdateNotification: () => void; } const initialSearchFilters: SearchFilters = { @@ -177,6 +181,7 @@ export const useAppStore = create()( theme: 'light', currentView: 'repositories', language: 'zh', + updateNotification: null, // Auth actions setUser: (user) => { @@ -307,6 +312,10 @@ export const useAppStore = create()( setTheme: (theme) => set({ theme }), setCurrentView: (currentView) => set({ currentView }), setLanguage: (language) => set({ language }), + + // Update actions + setUpdateNotification: (notification) => set({ updateNotification: notification }), + dismissUpdateNotification: () => set({ updateNotification: null }), }), { name: 'github-stars-manager', diff --git a/src/types/index.ts b/src/types/index.ts index 14266dc..3f68439 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -152,4 +152,15 @@ export interface AppState { theme: 'light' | 'dark'; currentView: 'repositories' | 'releases' | 'settings'; language: 'zh' | 'en'; + + // Update + updateNotification: UpdateNotification | null; +} + +export interface UpdateNotification { + version: string; + releaseDate: string; + changelog: string[]; + downloadUrl: string; + dismissed: boolean; } \ No newline at end of file diff --git a/test-update.html b/test-update.html new file mode 100644 index 0000000..6dbe311 --- /dev/null +++ b/test-update.html @@ -0,0 +1,216 @@ + + + + + + 更新功能测试 + + + +

GitHub Stars Manager - 更新功能测试

+ +
+

当前版本: v0.1.3

+ +
+
+
+ + + + \ No newline at end of file diff --git a/versions/README.md b/versions/README.md new file mode 100644 index 0000000..fd8f99c --- /dev/null +++ b/versions/README.md @@ -0,0 +1,67 @@ +# 版本管理说明 + +## 目录结构 + +- `version-info.xml` - 存储所有版本信息的XML文件 +- `README.md` - 本说明文件 + +## 版本更新流程 + +### 1. 更新版本信息 + +使用脚本自动更新版本: + +```bash +npm run update-version 0.1.3 "修复搜索功能bug" "添加新的过滤选项" "优化界面响应速度" +``` + +这个命令会: +- 更新 `package.json` 中的版本号 +- 在 `version-info.xml` 中添加新版本记录 +- 更新 `src/services/updateService.ts` 中的当前版本号 + +### 2. 手动更新(不推荐) + +如果需要手动更新 `version-info.xml`,请按照以下格式: + +```xml + + + + 0.1.3 + 2025-01-03 + + 修复搜索功能bug + 添加新的过滤选项 + 优化界面响应速度 + + https://github.com/AmintaCCCP/GithubStarsManager/releases/download/v0.1.3/github-stars-manager-0.1.3.dmg + + +``` + +### 3. 发布流程 + +1. 使用 `npm run update-version` 更新版本信息 +2. 提交更改到 Git 仓库: + ```bash + git add . + git commit -m "chore: bump version to v0.1.3" + git push origin main + ``` +3. 在 GitHub 上创建对应的 Release,并上传构建好的安装包 +4. 确保下载链接与 XML 中的 `downloadUrl` 一致 + +## XML 文件格式说明 + +- `number`: 版本号,格式为 x.y.z +- `releaseDate`: 发布日期,格式为 YYYY-MM-DD +- `changelog`: 更新日志,每个 `` 代表一条更新内容 +- `downloadUrl`: 对应版本的下载链接 + +## 注意事项 + +1. 版本号必须遵循语义化版本规范(Semantic Versioning) +2. 每次发布新版本时,确保 GitHub Release 中的下载链接可用 +3. XML 文件会被应用程序通过网络请求读取,确保文件格式正确 +4. 建议在发布前先在本地测试更新检查功能 \ No newline at end of file