const fs = require('fs'); const path = require('path'); const { createLogger } = require('../utils/logger'); const log = createLogger('cache:projects'); function tryLoadProjectsHeatmapCache(pageId, config) { if (!pageId) return null; const cacheDirFromEnv = process.env.PROJECTS_CACHE_DIR ? String(process.env.PROJECTS_CACHE_DIR) : ''; const cacheDirFromConfig = config && config.site && config.site.github && config.site.github.cacheDir ? String(config.site.github.cacheDir) : ''; const cacheDir = cacheDirFromEnv || cacheDirFromConfig || 'dev'; const cacheBaseDir = path.isAbsolute(cacheDir) ? cacheDir : path.join(process.cwd(), cacheDir); const cachePath = path.join(cacheBaseDir, `${pageId}.heatmap-cache.json`); if (!fs.existsSync(cachePath)) return null; try { const raw = fs.readFileSync(cachePath, 'utf8'); const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== 'object') return null; const username = parsed.username ? String(parsed.username).trim() : ''; const html = parsed.html ? String(parsed.html) : ''; if (!username || !html) return null; return { username, html, meta: { pageId: parsed.pageId || pageId, generatedAt: parsed.generatedAt || '', sourceUrl: parsed.sourceUrl || '', }, }; } catch (e) { log.warn('heatmap 缓存读取失败,将降级为运行时加载', { path: cachePath }); return null; } } function tryLoadProjectsRepoCache(pageId, config) { if (!pageId) return null; const cacheDirFromEnv = process.env.PROJECTS_CACHE_DIR ? String(process.env.PROJECTS_CACHE_DIR) : ''; const cacheDirFromConfig = config && config.site && config.site.github && config.site.github.cacheDir ? String(config.site.github.cacheDir) : ''; const cacheDir = cacheDirFromEnv || cacheDirFromConfig || 'dev'; const cacheBaseDir = path.isAbsolute(cacheDir) ? cacheDir : path.join(process.cwd(), cacheDir); const cachePath = path.join(cacheBaseDir, `${pageId}.repo-cache.json`); if (!fs.existsSync(cachePath)) return null; try { const raw = fs.readFileSync(cachePath, 'utf8'); const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== 'object') return null; const repos = Array.isArray(parsed.repos) ? parsed.repos : []; const map = new Map(); repos.forEach((r) => { const url = r && r.url ? String(r.url) : ''; if (!url) return; map.set(url, { language: r && r.language ? String(r.language) : '', languageColor: r && r.languageColor ? String(r.languageColor) : '', stars: Number.isFinite(r && r.stars) ? r.stars : null, forks: Number.isFinite(r && r.forks) ? r.forks : null, }); }); return { map, meta: { pageId: parsed.pageId || pageId, generatedAt: parsed.generatedAt || '', }, }; } catch (e) { log.warn('projects 缓存读取失败,将仅展示标题与描述', { path: cachePath }); return null; } } function normalizeGithubRepoUrl(url) { if (!url) return ''; try { const u = new URL(String(url)); if (u.hostname.toLowerCase() !== 'github.com') return ''; const parts = u.pathname.split('/').filter(Boolean); if (parts.length < 2) return ''; const owner = parts[0]; const repo = parts[1].replace(/\\.git$/i, ''); if (!owner || !repo) return ''; return `https://github.com/${owner}/${repo}`; } catch { return ''; } } function applyRepoMetaToCategories(categories, repoMetaMap) { if (!Array.isArray(categories) || !(repoMetaMap instanceof Map)) return; const walk = (node) => { if (!node || typeof node !== 'object') return; if (Array.isArray(node.subcategories)) node.subcategories.forEach(walk); if (Array.isArray(node.groups)) node.groups.forEach(walk); if (Array.isArray(node.subgroups)) node.subgroups.forEach(walk); if (Array.isArray(node.sites)) { node.sites.forEach((site) => { if (!site || typeof site !== 'object' || !site.url) return; const canonical = normalizeGithubRepoUrl(site.url); if (!canonical) return; const meta = repoMetaMap.get(canonical); if (!meta) return; site.language = meta.language || ''; site.languageColor = meta.languageColor || ''; site.stars = meta.stars; site.forks = meta.forks; }); } }; categories.forEach(walk); } function normalizeGithubHeatmapColor(input) { const raw = String(input || '') .trim() .replace(/^#/, ''); const color = raw.toLowerCase(); if (/^[0-9a-f]{6}$/.test(color)) return color; if (/^[0-9a-f]{3}$/.test(color)) return color; return '339af0'; } function getGithubUsernameFromConfig(config) { const username = config && config.site && config.site.github && config.site.github.username ? String(config.site.github.username).trim() : ''; return username; } function buildProjectsMeta(config) { const username = getGithubUsernameFromConfig(config); if (!username) return null; const color = normalizeGithubHeatmapColor( config && config.site && config.site.github && config.site.github.heatmapColor ? config.site.github.heatmapColor : '339af0' ); return { heatmap: { username, profileUrl: `https://github.com/${username}`, imageUrl: `https://ghchart.rshah.org/${color}/${username}`, }, }; } module.exports = { tryLoadProjectsRepoCache, tryLoadProjectsHeatmapCache, applyRepoMetaToCategories, buildProjectsMeta, };