From 0fee719a1bd74d87c495261b97f368cf5f0ffdd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=E4=BA=91?= Date: Tue, 6 May 2025 23:10:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=9F=E6=88=90=E6=9C=80=E5=90=8E?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4=E5=AD=97=E6=AE=B5=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A2=9E=E9=87=8F=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 20 ++++- build/build.js | 163 +++++++++++++++++++++++++++++++++--- 2 files changed, 170 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b315bc1..acb50129 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,14 @@ on: - 'repo/**' - 'build/build.js' - '.github/workflows/**' + workflow_dispatch: + inputs: + force_update: + description: '强制执行全量更新' + required: false + type: boolean + default: false + jobs: build: # Skip this job if the repository is a fork @@ -33,7 +41,15 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - run: node ./build/build.js + - name: Build repo.json + run: | + if [ "${{ github.event.inputs.force_update }}" = "true" ]; then + echo "执行强制全量更新" + node ./build/build.js --force + else + echo "执行增量更新" + node ./build/build.js + fi - uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: update repo.json @@ -63,4 +79,4 @@ jobs: r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }} r2-bucket: ${{ secrets.R2_BUCKET }} source-dir: my_files/ - destination-dir: github_mirror/ + destination-dir: github_mirror/ \ No newline at end of file diff --git a/build/build.js b/build/build.js index 45e59982..b18a220f 100644 --- a/build/build.js +++ b/build/build.js @@ -3,9 +3,50 @@ const path = require('path'); const crypto = require('crypto'); const { execSync } = require('child_process'); +// 处理命令行参数 +const args = process.argv.slice(2); +const forceFullUpdate = args.includes('--force') || args.includes('-f'); + // 在文件开头添加全局变量 const pathingDirsWithoutIcon = new Set(); +// 检查是否存在现有的repo.json文件 +const repoJsonPath = path.resolve(__dirname, '..', 'repo.json'); +let existingRepoJson = null; +let modifiedFiles = []; + +// 尝试加载现有的repo.json文件 +try { + if (fs.existsSync(repoJsonPath) && !forceFullUpdate) { + existingRepoJson = JSON.parse(fs.readFileSync(repoJsonPath, 'utf8')); + console.log('找到现有的repo.json文件,将执行增量更新'); + + // 获取Git中修改的文件 + try { + // 获取当前分支名称 + const currentBranch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); + console.log(`当前分支: ${currentBranch}`); + + // 获取此次变更的文件列表 + const changedFiles = execSync(`git diff --name-only HEAD~1 HEAD`).toString().trim().split('\n'); + modifiedFiles = changedFiles.filter(file => file.startsWith('repo/')); + console.log(`检测到 ${modifiedFiles.length} 个修改的文件:`); + modifiedFiles.forEach(file => console.log(` - ${file}`)); + } catch (e) { + console.warn('无法获取Git修改文件列表,将执行全量更新', e); + modifiedFiles = []; + } + } else { + if (forceFullUpdate) { + console.log('检测到--force参数,将执行全量更新'); + } else { + console.log('未找到现有的repo.json文件,将执行全量更新'); + } + } +} catch (e) { + console.warn('读取现有repo.json文件出错,将执行全量更新', e); +} + function calculateSHA1(filePath) { const fileBuffer = fs.readFileSync(filePath); const hashSum = crypto.createHash('sha1'); @@ -33,6 +74,24 @@ function formatTime(timestamp) { return timestamp.replace(/[-: ]/g, '').split('+')[0]; } +// 格式化最后更新时间为标准的北京时间格式:YYYY-MM-DD HH:MM:SS +function formatLastUpdated(timestamp) { + if (!timestamp) return null; + + try { + // 解析Git时间戳格式 (如: "2023-01-01 12:00:00 +0800") + const dateMatch = timestamp.match(/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/); + if (dateMatch) { + const [_, year, month, day, hour, minute, second] = dateMatch; + return `${year}-${month}-${day} ${hour}:${minute}:${second}`; + } + return timestamp; + } catch (e) { + console.warn(`格式化时间戳出错 ${timestamp}:`, e); + return timestamp; + } +} + function convertNewlines(text) { return text.replace(/\\n/g, '\n'); } @@ -48,16 +107,21 @@ function extractInfoFromCombatFile(filePath) { .map(char => char.trim()) .filter(char => char.length > 0 && !char.match(/^[,.]$/)); // 过滤掉单个逗号或句号 + // 获取最后更新时间 + const gitTimestamp = getGitTimestamp(filePath); + const lastUpdated = formatLastUpdated(gitTimestamp); + // 优先使用文件中的版本号,其次使用提交时间,最后使用 SHA const version = versionMatch ? versionMatch[1].trim() : - (getGitTimestamp(filePath) ? formatTime(getGitTimestamp(filePath)) : + (gitTimestamp ? formatTime(gitTimestamp) : calculateSHA1(filePath).substring(0, 7)); return { author: authorMatch ? authorMatch[1].trim() : '', description: descriptionMatch ? convertNewlines(descriptionMatch[1].trim()) : '', tags: tags, - version: version + version: version, + lastUpdated: lastUpdated }; } @@ -69,19 +133,25 @@ function extractInfoFromJSFolder(folderPath) { manifestContent = manifestContent.replace(/,(\s*[}\]])/g, '$1'); const manifest = JSON.parse(manifestContent); const combinedDescription = `${manifest.name || ''}~|~${manifest.description || ''}`; + + // 获取最后更新时间 + const gitTimestamp = getGitTimestamp(manifestPath); + const lastUpdated = formatLastUpdated(gitTimestamp); + return { version: manifest.version || '', description: convertNewlines(combinedDescription), author: manifest.authors && manifest.authors.length > 0 ? manifest.authors[0].name : '', - tags: manifest.tags || [] + tags: manifest.tags || [], + lastUpdated: lastUpdated }; } catch (error) { console.error(`解析 ${manifestPath} 时出错:`, error); console.error('文件内容:', fs.readFileSync(manifestPath, 'utf8')); - return { version: '', description: '', author: '', tags: [] }; + return { version: '', description: '', author: '', tags: [], lastUpdated: null }; } } - return { version: '', description: '', author: '', tags: [] }; + return { version: '', description: '', author: '', tags: [], lastUpdated: null }; } function extractInfoFromPathingFile(filePath, parentFolders) { @@ -100,9 +170,13 @@ function extractInfoFromPathingFile(filePath, parentFolders) { const contentObj = JSON.parse(content); + // 获取最后更新时间 + const gitTimestamp = getGitTimestamp(filePath); + const lastUpdated = formatLastUpdated(gitTimestamp); + // 优先使用文件中的版本号,其次使用提交时间,最后使用 SHA const version = contentObj.info?.version || - (getGitTimestamp(filePath) ? formatTime(getGitTimestamp(filePath)) : + (gitTimestamp ? formatTime(gitTimestamp) : calculateSHA1(filePath).substring(0, 7)); // 从父文件夹获取默认标签 @@ -132,7 +206,8 @@ function extractInfoFromPathingFile(filePath, parentFolders) { author: contentObj.info.author || '', description: convertNewlines(contentObj.info.description || ''), version: version, - tags: tags + tags: tags, + lastUpdated: lastUpdated }; } @@ -143,6 +218,10 @@ function extractInfoFromTCGFile(filePath, parentFolder) { const versionMatch = content.match(/\/\/\s*版本:(.*)/); const characterMatches = content.match(/角色\d+\s?=([^|\r\n{]+)/g); + // 获取最后更新时间 + const gitTimestamp = getGitTimestamp(filePath); + const lastUpdated = formatLastUpdated(gitTimestamp); + let tags = characterMatches ? characterMatches.map(match => match.split('=')[1].trim()) .filter(tag => tag && !tag.startsWith('角色')) @@ -157,18 +236,80 @@ function extractInfoFromTCGFile(filePath, parentFolder) { // 优先使用文件中的版本号,其次使用提交时间,最后使用 SHA const version = versionMatch ? versionMatch[1].trim() : - (getGitTimestamp(filePath) ? formatTime(getGitTimestamp(filePath)) : + (gitTimestamp ? formatTime(gitTimestamp) : calculateSHA1(filePath).substring(0, 7)); return { author: authorMatch ? authorMatch[1].trim() : '', description: descriptionMatch ? convertNewlines(descriptionMatch[1].trim()) : '', tags: [...new Set(tags)], // 去重 - version: version + version: version, + lastUpdated: lastUpdated }; } +// 检查文件是否需要处理(增量更新模式下) +function shouldProcessFile(filePath) { + // 如果没有现有的repo.json或没有修改文件列表,则处理所有文件 + if (!existingRepoJson || modifiedFiles.length === 0) { + return true; + } + + // 将filePath转换为相对于仓库根目录的路径 + const relativeFilePath = path.relative(path.resolve(__dirname, '..'), filePath).replace(/\\/g, '/'); + + // 检查此文件或其所在目录是否在修改列表中 + return modifiedFiles.some(modifiedFile => { + return relativeFilePath === modifiedFile || + relativeFilePath.startsWith(path.dirname(modifiedFile) + '/') || + modifiedFile.startsWith(relativeFilePath + '/'); + }); +} + +// 在目录树中查找节点的辅助函数 +function findNodeInTree(tree, nodePath, currentPath = '') { + if (!tree) return null; + + if (tree.type === 'directory') { + const newPath = currentPath ? `${currentPath}/${tree.name}` : tree.name; + + if (newPath === nodePath) { + return tree; + } + + if (tree.children) { + for (const child of tree.children) { + const result = findNodeInTree(child, nodePath, newPath); + if (result) { + return result; + } + } + } + } + + return null; +} + function generateDirectoryTree(dir, currentDepth = 0, parentFolders = []) { + // 检查是否在增量更新模式下需要处理此目录 + const shouldProcess = shouldProcessFile(dir); + + // 如果在增量更新模式下不需要处理此目录,尝试从现有repo.json找到对应节点 + if (!shouldProcess && existingRepoJson && existingRepoJson.indexes) { + const category = parentFolders[0]; + const relativePath = parentFolders.join('/'); + + // 在现有repo.json中查找此目录节点 + const categoryTree = existingRepoJson.indexes.find(index => index.name === category); + if (categoryTree) { + const existingNode = findNodeInTree(categoryTree, relativePath); + if (existingNode) { + console.log(`使用现有数据: ${relativePath}`); + return existingNode; + } + } + } + const stats = fs.statSync(dir); const info = { name: path.basename(dir), @@ -206,7 +347,8 @@ function generateDirectoryTree(dir, currentDepth = 0, parentFolders = []) { .map(child => { const childPath = path.join(dir, child); return generateDirectoryTree(childPath, currentDepth + 1, [...parentFolders, info.name]); - }); + }) + .filter(child => child !== null); // 过滤掉null } } else { // 如果是 desktop.ini 或 icon.ico 文件,直接返回 null @@ -298,6 +440,5 @@ const repoJson = { "indexes": result }; -const repoJsonPath = path.resolve(__dirname, '..', 'repo.json'); fs.writeFileSync(repoJsonPath, JSON.stringify(repoJson, null, 2)); console.log('repo.json 文件已创建并保存在 repo 同级目录中。');