version-fetcher.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. // electron/modules/version-fetcher.ts - 版本获取模块
  2. // 通过各平台包管理器查询可用版本
  3. import * as os from 'os'
  4. import { execa } from 'execa'
  5. import type { SoftwareType, VersionItem, VersionResult, Platform, GitMirrorType, NodejsMirrorType } from './types'
  6. import {
  7. MIN_SUPPORTED_NODE_VERSION,
  8. MAX_MAJOR_VERSIONS,
  9. VERSION_CACHE_TTL,
  10. ERROR_MESSAGES,
  11. BREW_PACKAGES,
  12. GIT_MIRRORS,
  13. NODEJS_MIRRORS,
  14. VSCODE_API
  15. } from './constants'
  16. import { setCache, getCache, clearCache } from './utils'
  17. import { updateAptSourceForQuery } from './source-updater'
  18. import {
  19. getGitMirrorFromConfig,
  20. saveGitMirrorConfig,
  21. getNodejsMirrorFromConfig,
  22. saveNodejsMirrorConfig
  23. } from './config'
  24. /**
  25. * 版本比较函数(降序)
  26. */
  27. function compareVersions(a: string, b: string): number {
  28. const aParts = a.split('.').map(Number)
  29. const bParts = b.split('.').map(Number)
  30. for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
  31. const diff = (bParts[i] || 0) - (aParts[i] || 0)
  32. if (diff !== 0) return diff
  33. }
  34. return 0
  35. }
  36. /**
  37. * 添加版本到分组 Map
  38. */
  39. function addToVersionMap(
  40. map: Map<string, VersionItem[]>,
  41. fullVersion: string,
  42. label: string,
  43. options?: { extra?: Partial<VersionItem>; groupKey?: string }
  44. ): void {
  45. const parts = fullVersion.split('.')
  46. const key = options?.groupKey ?? `${parts[0]}.${parts[1]}`
  47. const list = map.get(key) ?? []
  48. // 检查是否已存在相同版本,避免重复
  49. if (!list.some((item) => item.value === fullVersion)) {
  50. list.push({ value: fullVersion, label, ...options?.extra })
  51. }
  52. map.set(key, list)
  53. }
  54. /**
  55. * 通用版本列表构建器
  56. */
  57. function buildVersionList(
  58. versionsByMajor: Map<string, VersionItem[]>,
  59. options: {
  60. maxMajors?: number
  61. specialVersions?: VersionItem[]
  62. } = {}
  63. ): VersionItem[] {
  64. const { maxMajors = MAX_MAJOR_VERSIONS, specialVersions = [] } = options
  65. const versions: VersionItem[] = []
  66. // 按版本号降序排列
  67. const sortedMajors = Array.from(versionsByMajor.keys())
  68. .sort(compareVersions)
  69. .slice(0, maxMajors)
  70. sortedMajors.forEach((major, index) => {
  71. const isFirst = index === 0
  72. const label = isFirst ? `── v${major}.x (最新) ──` : `── v${major}.x ──`
  73. versions.push({ value: '', label, disabled: true, separator: true })
  74. const majorVersions = versionsByMajor.get(major) ?? []
  75. const sorted = majorVersions.sort((a, b) => compareVersions(a.value, b.value))
  76. versions.push(...sorted)
  77. })
  78. // 添加特殊版本
  79. if (specialVersions.length > 0) {
  80. versions.push({ value: '', label: '── 其他版本 ──', disabled: true, separator: true })
  81. versions.push(...specialVersions)
  82. }
  83. return versions
  84. }
  85. /**
  86. * 获取当前平台
  87. */
  88. function getPlatform(): Platform {
  89. return os.platform() as Platform
  90. }
  91. // ==================== Node.js 镜像配置 ====================
  92. /**
  93. * 设置 Node.js 镜像(会持久化保存)
  94. */
  95. export function setNodejsMirror(mirror: NodejsMirrorType): void {
  96. saveNodejsMirrorConfig(mirror)
  97. // 清除 Node.js 版本缓存,以便重新获取
  98. clearCache('versions_nodejs')
  99. }
  100. /**
  101. * 获取当前 Node.js 镜像配置
  102. */
  103. export function getNodejsMirrorConfig(): { mirror: NodejsMirrorType } {
  104. return { mirror: getNodejsMirrorFromConfig() }
  105. }
  106. /**
  107. * 获取当前 Node.js 镜像类型(内部使用)
  108. */
  109. function getCurrentNodejsMirror(): NodejsMirrorType {
  110. return getNodejsMirrorFromConfig()
  111. }
  112. /**
  113. * 获取 Node.js 下载 URL
  114. */
  115. export function getNodejsDownloadUrl(version: string): string {
  116. const arch = os.arch() === 'x64' ? 'x64' : os.arch() === 'arm64' ? 'arm64' : 'x86'
  117. const mirror = NODEJS_MIRRORS[getCurrentNodejsMirror()]
  118. return mirror.getDownloadUrl(version, arch)
  119. }
  120. // ==================== Node.js API 版本数据类型 ====================
  121. interface NodejsVersionInfo {
  122. version: string
  123. date: string
  124. lts: boolean | string
  125. security: boolean
  126. }
  127. // ==================== macOS brew 版本查询 ====================
  128. /**
  129. * 使用 brew 查询软件信息
  130. * brew 不支持安装历史版本,只返回当前可安装的版本
  131. */
  132. async function getBrewVersion(formula: string): Promise<string | null> {
  133. try {
  134. const result = await execa('brew', ['info', '--json=v2', formula], {
  135. timeout: 30000
  136. })
  137. const data = JSON.parse(result.stdout)
  138. // 处理 formula 和 cask 两种情况
  139. if (data.formulae && data.formulae.length > 0) {
  140. return data.formulae[0].versions?.stable || null
  141. }
  142. if (data.casks && data.casks.length > 0) {
  143. return data.casks[0].version || null
  144. }
  145. return null
  146. } catch (error) {
  147. console.error(`brew 查询 ${formula} 版本失败:`, error)
  148. return null
  149. }
  150. }
  151. /**
  152. * 获取 brew 可用的 Node.js 版本
  153. * brew 支持 node (最新), node@20, node@18 等
  154. */
  155. async function getBrewNodeVersions(): Promise<Map<string, VersionItem[]>> {
  156. const versionsByMajor = new Map<string, VersionItem[]>()
  157. // 查询各个 formula 的版本
  158. const formulas = [
  159. { name: BREW_PACKAGES.nodejs.default, majorHint: null },
  160. { name: BREW_PACKAGES.nodejs['20'], majorHint: '20' },
  161. { name: BREW_PACKAGES.nodejs['18'], majorHint: '18' }
  162. ]
  163. const results = await Promise.allSettled(
  164. formulas.map(async (f) => {
  165. const version = await getBrewVersion(f.name)
  166. return { formula: f.name, majorHint: f.majorHint, version }
  167. })
  168. )
  169. for (const result of results) {
  170. if (result.status === 'fulfilled' && result.value.version) {
  171. const { formula, version } = result.value
  172. const major = version.split('.')[0]
  173. const majorNum = parseInt(major)
  174. if (majorNum >= MIN_SUPPORTED_NODE_VERSION) {
  175. const label = formula === 'node'
  176. ? `Node.js ${version} (最新)`
  177. : `Node.js ${version}`
  178. addToVersionMap(versionsByMajor, version, label, { groupKey: major })
  179. }
  180. }
  181. }
  182. return versionsByMajor
  183. }
  184. // ==================== Linux apt 版本查询 ====================
  185. /**
  186. * 使用 apt 查询软件可用版本
  187. * apt 通常只有仓库中的一个版本
  188. */
  189. async function getAptVersion(packageName: string): Promise<string | null> {
  190. try {
  191. const result = await execa('apt-cache', ['policy', packageName], {
  192. timeout: 30000
  193. })
  194. // 解析输出,查找候选版本
  195. const match = result.stdout.match(/Candidate:\s*(\S+)/)
  196. if (match) {
  197. // 提取版本号(去除 epoch 和 debian 修订号)
  198. const fullVersion = match[1]
  199. const versionMatch = fullVersion.match(/(\d+\.\d+\.\d+)/)
  200. return versionMatch ? versionMatch[1] : fullVersion
  201. }
  202. return null
  203. } catch (error) {
  204. console.error(`apt 查询 ${packageName} 版本失败:`, error)
  205. return null
  206. }
  207. }
  208. // ==================== Node.js 版本获取 ====================
  209. /**
  210. * 从 Node.js API 获取版本列表
  211. */
  212. async function getNodeVersionsFromAPI(): Promise<NodejsVersionInfo[]> {
  213. const mirror = NODEJS_MIRRORS[getCurrentNodejsMirror()]
  214. try {
  215. const response = await fetch(mirror.versionsUrl, {
  216. headers: { 'User-Agent': 'ApqInstaller' },
  217. signal: AbortSignal.timeout(15000)
  218. })
  219. if (!response.ok) {
  220. throw new Error(`API 请求失败: ${response.status}`)
  221. }
  222. return await response.json() as NodejsVersionInfo[]
  223. } catch (error) {
  224. console.error(`从 ${mirror.name} 获取 Node.js 版本失败:`, error)
  225. return []
  226. }
  227. }
  228. async function getNodeVersionsWindows(): Promise<VersionResult> {
  229. const versionsByMajor = new Map<string, VersionItem[]>()
  230. const versions = await getNodeVersionsFromAPI()
  231. if (versions.length === 0) {
  232. throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
  233. }
  234. // 用于记录每个主版本的最新 LTS 版本
  235. const ltsVersions = new Map<string, string>()
  236. for (const info of versions) {
  237. // 版本格式: v22.11.0 -> 22.11.0
  238. const version = info.version.replace(/^v/, '')
  239. const major = version.split('.')[0]
  240. const majorNum = parseInt(major)
  241. if (majorNum >= MIN_SUPPORTED_NODE_VERSION) {
  242. const isLts = info.lts !== false
  243. const ltsName = typeof info.lts === 'string' ? info.lts : null
  244. // 记录每个主版本的第一个 LTS 版本(最新的)
  245. if (isLts && !ltsVersions.has(major)) {
  246. ltsVersions.set(major, version)
  247. }
  248. let label = `Node.js ${version}`
  249. if (ltsName) {
  250. label += ` (${ltsName})`
  251. }
  252. addToVersionMap(versionsByMajor, version, label, {
  253. groupKey: major,
  254. extra: { lts: isLts }
  255. })
  256. }
  257. }
  258. if (versionsByMajor.size === 0) {
  259. throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
  260. }
  261. const mirrorName = NODEJS_MIRRORS[getCurrentNodejsMirror()].name
  262. return {
  263. versions: buildVersionList(versionsByMajor),
  264. warning: `下载源: ${mirrorName}`
  265. }
  266. }
  267. async function getNodeVersionsMac(): Promise<VersionResult> {
  268. const versionsByMajor = await getBrewNodeVersions()
  269. if (versionsByMajor.size === 0) {
  270. throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
  271. }
  272. return {
  273. versions: buildVersionList(versionsByMajor),
  274. warning: 'brew 仅支持安装当前可用版本,不支持选择历史版本'
  275. }
  276. }
  277. async function getNodeVersionsLinux(): Promise<VersionResult> {
  278. const version = await getAptVersion('nodejs')
  279. if (!version) {
  280. throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
  281. }
  282. const major = version.split('.')[0]
  283. const versionsByMajor = new Map<string, VersionItem[]>()
  284. addToVersionMap(versionsByMajor, version, `Node.js ${version}`, { groupKey: major })
  285. return {
  286. versions: buildVersionList(versionsByMajor),
  287. warning: 'apt 仅支持安装仓库中的版本,如需其他版本请使用 nvm'
  288. }
  289. }
  290. async function getNodeVersions(): Promise<VersionResult> {
  291. const cacheKey = 'versions_nodejs'
  292. const cached = getCache<VersionResult>(cacheKey)
  293. if (cached) return cached
  294. const platform = getPlatform()
  295. let result: VersionResult
  296. switch (platform) {
  297. case 'win32':
  298. result = await getNodeVersionsWindows()
  299. break
  300. case 'darwin':
  301. result = await getNodeVersionsMac()
  302. break
  303. case 'linux':
  304. result = await getNodeVersionsLinux()
  305. break
  306. default:
  307. throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
  308. }
  309. setCache(cacheKey, result, VERSION_CACHE_TTL)
  310. return result
  311. }
  312. // ==================== VS Code 版本获取 ====================
  313. /**
  314. * 从 VS Code API 获取版本列表
  315. */
  316. async function getVSCodeVersionsFromAPI(): Promise<string[]> {
  317. try {
  318. const response = await fetch(VSCODE_API.versionsUrl, {
  319. headers: { 'User-Agent': 'ApqInstaller' },
  320. signal: AbortSignal.timeout(15000)
  321. })
  322. if (!response.ok) {
  323. throw new Error(`API 请求失败: ${response.status}`)
  324. }
  325. return await response.json() as string[]
  326. } catch (error) {
  327. console.error('从 VS Code API 获取版本失败:', error)
  328. return []
  329. }
  330. }
  331. /**
  332. * 获取 VS Code 下载 URL
  333. */
  334. export function getVSCodeDownloadUrl(version: string): string {
  335. const arch = os.arch() === 'x64' ? 'x64' : os.arch() === 'arm64' ? 'arm64' : 'x86'
  336. return VSCODE_API.getDownloadUrl(version, arch, 'user')
  337. }
  338. async function getVSCodeVersionsWindows(): Promise<VersionResult> {
  339. const versionsByMajor = new Map<string, VersionItem[]>()
  340. const versions = await getVSCodeVersionsFromAPI()
  341. if (versions.length === 0) {
  342. throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
  343. }
  344. for (const version of versions) {
  345. if (/^\d+\.\d+\.\d+$/.test(version)) {
  346. addToVersionMap(versionsByMajor, version, `VS Code ${version}`)
  347. }
  348. }
  349. if (versionsByMajor.size === 0) {
  350. throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
  351. }
  352. return {
  353. versions: buildVersionList(versionsByMajor, {
  354. specialVersions: [{ value: 'insiders', label: 'Insiders (预览版)' }]
  355. }),
  356. warning: null
  357. }
  358. }
  359. async function getVSCodeVersionsMac(): Promise<VersionResult> {
  360. const version = await getBrewVersion(BREW_PACKAGES.vscode.stable)
  361. if (!version) {
  362. throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
  363. }
  364. const versionsByMajor = new Map<string, VersionItem[]>()
  365. addToVersionMap(versionsByMajor, version, `VS Code ${version}`)
  366. return {
  367. versions: buildVersionList(versionsByMajor, {
  368. specialVersions: [{ value: 'insiders', label: 'Insiders (预览版)' }]
  369. }),
  370. warning: 'brew 仅支持安装当前最新版本'
  371. }
  372. }
  373. async function getVSCodeVersionsLinux(): Promise<VersionResult> {
  374. // Linux 使用 snap 安装 VS Code,snap 不支持版本选择
  375. // 返回一个占位版本
  376. const versions: VersionItem[] = [
  377. { value: 'stable', label: 'VS Code (最新稳定版)' },
  378. { value: 'insiders', label: 'Insiders (预览版)' }
  379. ]
  380. return {
  381. versions,
  382. warning: 'snap 仅支持安装最新版本'
  383. }
  384. }
  385. async function getVSCodeVersions(): Promise<VersionResult> {
  386. const cacheKey = 'versions_vscode'
  387. const cached = getCache<VersionResult>(cacheKey)
  388. if (cached) return cached
  389. const platform = getPlatform()
  390. let result: VersionResult
  391. switch (platform) {
  392. case 'win32':
  393. result = await getVSCodeVersionsWindows()
  394. break
  395. case 'darwin':
  396. result = await getVSCodeVersionsMac()
  397. break
  398. case 'linux':
  399. result = await getVSCodeVersionsLinux()
  400. break
  401. default:
  402. throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
  403. }
  404. setCache(cacheKey, result, VERSION_CACHE_TTL)
  405. return result
  406. }
  407. // ==================== Git 版本获取 ====================
  408. /**
  409. * 设置 Git 镜像(会持久化保存)
  410. */
  411. export function setGitMirror(mirror: GitMirrorType): void {
  412. saveGitMirrorConfig(mirror)
  413. // 清除版本缓存,以便重新获取
  414. clearCache()
  415. }
  416. /**
  417. * 获取当前 Git 镜像配置
  418. */
  419. export function getGitMirrorConfig(): { mirror: GitMirrorType } {
  420. return { mirror: getGitMirrorFromConfig() }
  421. }
  422. /**
  423. * 获取当前 Git 镜像类型(内部使用)
  424. */
  425. function getCurrentGitMirror(): GitMirrorType {
  426. return getGitMirrorFromConfig()
  427. }
  428. /**
  429. * 获取 Git 下载 URL
  430. */
  431. export function getGitDownloadUrl(version: string): string {
  432. const arch = os.arch() === 'x64' ? '64' : '32'
  433. const mirror = GIT_MIRRORS[getCurrentGitMirror()]
  434. return mirror.getDownloadUrl(version, arch)
  435. }
  436. // 备用版本列表(当无法从任何源获取时使用)
  437. const FALLBACK_GIT_VERSIONS = [
  438. '2.47.1', '2.47.0',
  439. '2.46.2', '2.46.1', '2.46.0',
  440. '2.45.2', '2.45.1', '2.45.0',
  441. '2.44.0',
  442. '2.43.0',
  443. '2.42.0'
  444. ]
  445. /**
  446. * 从 GitHub API 获取版本列表
  447. */
  448. async function getGitVersionsFromGitHub(): Promise<string[]> {
  449. try {
  450. const response = await fetch('https://api.github.com/repos/git-for-windows/git/releases', {
  451. headers: {
  452. 'Accept': 'application/vnd.github.v3+json',
  453. 'User-Agent': 'ApqInstaller'
  454. },
  455. signal: AbortSignal.timeout(10000)
  456. })
  457. if (!response.ok) {
  458. throw new Error(`GitHub API 请求失败: ${response.status}`)
  459. }
  460. const releases = await response.json() as Array<{ tag_name: string; prerelease: boolean }>
  461. const versions: string[] = []
  462. for (const release of releases) {
  463. if (release.prerelease) continue
  464. const match = release.tag_name.match(/^v?(\d+\.\d+\.\d+)/)
  465. if (match && !versions.includes(match[1])) {
  466. versions.push(match[1])
  467. }
  468. }
  469. return versions
  470. } catch (error) {
  471. console.error('从 GitHub 获取 Git 版本失败:', error)
  472. return []
  473. }
  474. }
  475. /**
  476. * 从华为云镜像获取版本列表
  477. * 通过解析目录页面获取可用版本
  478. */
  479. async function getGitVersionsFromHuaweicloud(): Promise<string[]> {
  480. try {
  481. const response = await fetch('https://mirrors.huaweicloud.com/git-for-windows/', {
  482. headers: { 'User-Agent': 'ApqInstaller' },
  483. signal: AbortSignal.timeout(10000)
  484. })
  485. if (!response.ok) {
  486. throw new Error(`华为云镜像请求失败: ${response.status}`)
  487. }
  488. const html = await response.text()
  489. const versions: string[] = []
  490. // 解析 HTML 页面,查找版本目录链接
  491. // 格式: <a href="v2.47.1.windows.1/">v2.47.1.windows.1/</a>
  492. const regex = /href="v(\d+\.\d+\.\d+)\.windows\.\d+\/"/g
  493. let match
  494. while ((match = regex.exec(html)) !== null) {
  495. const version = match[1]
  496. if (!versions.includes(version)) {
  497. versions.push(version)
  498. }
  499. }
  500. // 按版本号降序排序
  501. versions.sort((a, b) => {
  502. const partsA = a.split('.').map(Number)
  503. const partsB = b.split('.').map(Number)
  504. for (let i = 0; i < 3; i++) {
  505. if (partsA[i] !== partsB[i]) {
  506. return partsB[i] - partsA[i]
  507. }
  508. }
  509. return 0
  510. })
  511. return versions
  512. } catch (error) {
  513. console.error('从华为云镜像获取 Git 版本失败:', error)
  514. return []
  515. }
  516. }
  517. async function getGitVersionsWindows(): Promise<VersionResult> {
  518. const versionsByMajor = new Map<string, VersionItem[]>()
  519. // 根据当前镜像源获取版本列表
  520. let versions: string[] = []
  521. const currentMirror = getCurrentGitMirror()
  522. if (currentMirror === 'github') {
  523. versions = await getGitVersionsFromGitHub()
  524. } else {
  525. // 华为云镜像
  526. versions = await getGitVersionsFromHuaweicloud()
  527. }
  528. // 如果获取失败,使用备用版本列表
  529. if (versions.length === 0) {
  530. console.log('使用备用 Git 版本列表')
  531. versions = FALLBACK_GIT_VERSIONS
  532. }
  533. for (const version of versions) {
  534. if (/^\d+\.\d+\.\d+/.test(version)) {
  535. addToVersionMap(versionsByMajor, version, `Git ${version}`)
  536. }
  537. }
  538. if (versionsByMajor.size === 0) {
  539. throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
  540. }
  541. const mirrorName = GIT_MIRRORS[getCurrentGitMirror()].name
  542. return {
  543. versions: buildVersionList(versionsByMajor, {
  544. specialVersions: [
  545. { value: 'mingit', label: 'MinGit (精简版)' },
  546. { value: 'lfs', label: 'Git LFS (大文件支持)' }
  547. ]
  548. }),
  549. warning: `下载源: ${mirrorName}`
  550. }
  551. }
  552. async function getGitVersionsMac(): Promise<VersionResult> {
  553. const version = await getBrewVersion(BREW_PACKAGES.git.stable)
  554. if (!version) {
  555. throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
  556. }
  557. const versionsByMajor = new Map<string, VersionItem[]>()
  558. addToVersionMap(versionsByMajor, version, `Git ${version}`)
  559. return {
  560. versions: buildVersionList(versionsByMajor, {
  561. specialVersions: [{ value: 'lfs', label: 'Git LFS (大文件支持)' }]
  562. }),
  563. warning: 'brew 仅支持安装当前最新版本'
  564. }
  565. }
  566. async function getGitVersionsLinux(): Promise<VersionResult> {
  567. const version = await getAptVersion('git')
  568. if (!version) {
  569. throw new Error(ERROR_MESSAGES.VERSION_FETCH_ERROR)
  570. }
  571. const versionsByMajor = new Map<string, VersionItem[]>()
  572. addToVersionMap(versionsByMajor, version, `Git ${version}`)
  573. return {
  574. versions: buildVersionList(versionsByMajor, {
  575. specialVersions: [{ value: 'lfs', label: 'Git LFS (大文件支持)' }]
  576. }),
  577. warning: 'apt 仅支持安装仓库中的版本'
  578. }
  579. }
  580. async function getGitVersions(): Promise<VersionResult> {
  581. const cacheKey = 'versions_git'
  582. const cached = getCache<VersionResult>(cacheKey)
  583. if (cached) return cached
  584. const platform = getPlatform()
  585. let result: VersionResult
  586. switch (platform) {
  587. case 'win32':
  588. result = await getGitVersionsWindows()
  589. break
  590. case 'darwin':
  591. result = await getGitVersionsMac()
  592. break
  593. case 'linux':
  594. result = await getGitVersionsLinux()
  595. break
  596. default:
  597. throw new Error(`${ERROR_MESSAGES.UNSUPPORTED_PLATFORM}: ${platform}`)
  598. }
  599. setCache(cacheKey, result, VERSION_CACHE_TTL)
  600. return result
  601. }
  602. // ==================== 统一导出 ====================
  603. export async function getVersions(software: SoftwareType): Promise<VersionResult> {
  604. // Linux 下获取版本前先更新 apt 源(带缓存,1天内只更新一次)
  605. const platform = getPlatform()
  606. if (platform === 'linux') {
  607. await updateAptSourceForQuery()
  608. }
  609. // Windows 使用 API 获取版本,不需要更新源
  610. // macOS 的 brew 不需要手动更新源,brew info 会自动获取最新信息
  611. switch (software) {
  612. case 'nodejs':
  613. return await getNodeVersions()
  614. case 'vscode':
  615. return await getVSCodeVersions()
  616. case 'git':
  617. return await getGitVersions()
  618. default:
  619. return { versions: [], warning: ERROR_MESSAGES.UNKNOWN_SOFTWARE }
  620. }
  621. }
  622. export { clearCache }