mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Handle multiple API pages worth of contributors (#2088)
This commit is contained in:
parent
ec01dd27ef
commit
40a0766161
1 changed files with 177 additions and 93 deletions
|
|
@ -8,8 +8,13 @@
|
|||
|
||||
const https = require("https")
|
||||
const fs = require("fs")
|
||||
const { promisify } = require("util")
|
||||
const path = require("path")
|
||||
|
||||
// Promisify filesystem operations
|
||||
const readFileAsync = promisify(fs.readFile)
|
||||
const writeFileAsync = promisify(fs.writeFile)
|
||||
|
||||
// GitHub API URL for fetching contributors
|
||||
const GITHUB_API_URL = "https://api.github.com/repos/RooVetGit/Roo-Code/contributors?per_page=100"
|
||||
const README_PATH = path.join(__dirname, "..", "README.md")
|
||||
|
|
@ -33,52 +38,144 @@ if (process.env.GITHUB_TOKEN) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetches contributors data from GitHub API
|
||||
* @returns {Promise<Array>} Array of contributor objects
|
||||
* Parses the GitHub API Link header to extract pagination URLs
|
||||
* Based on RFC 5988 format for the Link header
|
||||
* @param {string} header The Link header from GitHub API response
|
||||
* @returns {Object} Object containing URLs for next, prev, first, last pages (if available)
|
||||
*/
|
||||
function fetchContributors() {
|
||||
function parseLinkHeader(header) {
|
||||
// Return empty object if no header is provided
|
||||
if (!header || header.trim() === "") return {}
|
||||
|
||||
// Initialize links object
|
||||
const links = {}
|
||||
|
||||
// Split the header into individual link entries
|
||||
// Example: <https://api.github.com/...?page=2>; rel="next", <https://api.github.com/...?page=5>; rel="last"
|
||||
const entries = header.split(/,\s*/)
|
||||
|
||||
// Process each link entry
|
||||
for (const entry of entries) {
|
||||
// Extract the URL (between < and >) and the parameters (after >)
|
||||
const segments = entry.split(";")
|
||||
if (segments.length < 2) continue
|
||||
|
||||
// Extract URL from the first segment, removing < and >
|
||||
const urlMatch = segments[0].match(/<(.+)>/)
|
||||
if (!urlMatch) continue
|
||||
const url = urlMatch[1]
|
||||
|
||||
// Find the rel="value" parameter
|
||||
let rel = null
|
||||
for (let i = 1; i < segments.length; i++) {
|
||||
const relMatch = segments[i].match(/\s*rel\s*=\s*"?([^"]+)"?/)
|
||||
if (relMatch) {
|
||||
rel = relMatch[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Only add to links if both URL and rel were found
|
||||
if (rel) {
|
||||
links[rel] = url
|
||||
}
|
||||
}
|
||||
|
||||
return links
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an HTTP GET request and returns the response
|
||||
* @param {string} url The URL to fetch
|
||||
* @param {Object} options Request options
|
||||
* @returns {Promise<Object>} Response object with status, headers and body
|
||||
*/
|
||||
function httpGet(url, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(GITHUB_API_URL, options, (res) => {
|
||||
if (res.statusCode !== 200) {
|
||||
reject(new Error(`GitHub API request failed with status code: ${res.statusCode}`))
|
||||
return
|
||||
}
|
||||
|
||||
.get(url, options, (res) => {
|
||||
let data = ""
|
||||
res.on("data", (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
|
||||
res.on("end", () => {
|
||||
try {
|
||||
const contributors = JSON.parse(data)
|
||||
resolve(contributors)
|
||||
} catch (error) {
|
||||
reject(new Error(`Failed to parse GitHub API response: ${error.message}`))
|
||||
}
|
||||
resolve({
|
||||
statusCode: res.statusCode,
|
||||
headers: res.headers,
|
||||
body: data,
|
||||
})
|
||||
})
|
||||
})
|
||||
.on("error", (error) => {
|
||||
reject(new Error(`GitHub API request failed: ${error.message}`))
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a single page of contributors from GitHub API
|
||||
* @param {string} url The API URL to fetch
|
||||
* @returns {Promise<Object>} Object containing contributors and pagination links
|
||||
*/
|
||||
async function fetchContributorsPage(url) {
|
||||
try {
|
||||
// Make the HTTP request
|
||||
const response = await httpGet(url, options)
|
||||
|
||||
// Check for successful response
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error(`GitHub API request failed with status code: ${response.statusCode}`)
|
||||
}
|
||||
|
||||
// Parse the Link header for pagination
|
||||
const linkHeader = response.headers.link
|
||||
const links = parseLinkHeader(linkHeader)
|
||||
|
||||
// Parse the JSON response
|
||||
const contributors = JSON.parse(response.body)
|
||||
|
||||
return { contributors, links }
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to fetch contributors page: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all contributors data from GitHub API (handling pagination)
|
||||
* @returns {Promise<Array>} Array of all contributor objects
|
||||
*/
|
||||
async function fetchContributors() {
|
||||
let allContributors = []
|
||||
let currentUrl = GITHUB_API_URL
|
||||
let pageCount = 1
|
||||
|
||||
// Loop through all pages of contributors
|
||||
while (currentUrl) {
|
||||
console.log(`Fetching contributors page ${pageCount}...`)
|
||||
const { contributors, links } = await fetchContributorsPage(currentUrl)
|
||||
|
||||
allContributors = allContributors.concat(contributors)
|
||||
|
||||
// Move to the next page if it exists
|
||||
currentUrl = links.next
|
||||
pageCount++
|
||||
}
|
||||
|
||||
console.log(`Fetched ${allContributors.length} contributors from ${pageCount - 1} pages`)
|
||||
return allContributors
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the README.md file
|
||||
* @returns {Promise<string>} README content
|
||||
*/
|
||||
function readReadme() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(README_PATH, "utf8", (err, data) => {
|
||||
if (err) {
|
||||
reject(new Error(`Failed to read README.md: ${err.message}`))
|
||||
return
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
async function readReadme() {
|
||||
try {
|
||||
return await readFileAsync(README_PATH, "utf8")
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to read README.md: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -147,7 +244,7 @@ function formatContributorsSection(contributors) {
|
|||
* @param {string} contributorsSection HTML for contributors section
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function updateReadme(readmeContent, contributorsSection) {
|
||||
async function updateReadme(readmeContent, contributorsSection) {
|
||||
// Find existing contributors section markers
|
||||
const startPos = readmeContent.indexOf(START_MARKER)
|
||||
const endPos = readmeContent.indexOf(END_MARKER)
|
||||
|
|
@ -164,7 +261,7 @@ function updateReadme(readmeContent, contributorsSection) {
|
|||
// Ensure single newline separators between sections
|
||||
const updatedContent = beforeSection + "\n\n" + contributorsSection.trim() + "\n\n" + afterSection
|
||||
|
||||
return writeReadme(updatedContent)
|
||||
await writeReadme(updatedContent)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -172,47 +269,41 @@ function updateReadme(readmeContent, contributorsSection) {
|
|||
* @param {string} content Updated README content
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function writeReadme(content) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(README_PATH, content, "utf8", (err) => {
|
||||
if (err) {
|
||||
reject(new Error(`Failed to write updated README.md: ${err.message}`))
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
async function writeReadme(content) {
|
||||
try {
|
||||
await writeFileAsync(README_PATH, content, "utf8")
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to write updated README.md: ${err.message}`)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Finds all localized README files in the locales directory
|
||||
* @returns {Promise<string[]>} Array of README file paths
|
||||
*/
|
||||
function findLocalizedReadmes() {
|
||||
return new Promise((resolve) => {
|
||||
const readmeFiles = []
|
||||
async function findLocalizedReadmes() {
|
||||
const readmeFiles = []
|
||||
|
||||
// Check if locales directory exists
|
||||
if (!fs.existsSync(LOCALES_DIR)) {
|
||||
// No localized READMEs found
|
||||
return resolve(readmeFiles)
|
||||
// Check if locales directory exists
|
||||
if (!fs.existsSync(LOCALES_DIR)) {
|
||||
// No localized READMEs found
|
||||
return readmeFiles
|
||||
}
|
||||
|
||||
// Get all language subdirectories
|
||||
const languageDirs = fs
|
||||
.readdirSync(LOCALES_DIR, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name)
|
||||
|
||||
// Add all localized READMEs to the list
|
||||
for (const langDir of languageDirs) {
|
||||
const readmePath = path.join(LOCALES_DIR, langDir, "README.md")
|
||||
if (fs.existsSync(readmePath)) {
|
||||
readmeFiles.push(readmePath)
|
||||
}
|
||||
}
|
||||
|
||||
// Get all language subdirectories
|
||||
const languageDirs = fs
|
||||
.readdirSync(LOCALES_DIR, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name)
|
||||
|
||||
// Add all localized READMEs to the list
|
||||
for (const langDir of languageDirs) {
|
||||
const readmePath = path.join(LOCALES_DIR, langDir, "README.md")
|
||||
if (fs.existsSync(readmePath)) {
|
||||
readmeFiles.push(readmePath)
|
||||
}
|
||||
}
|
||||
|
||||
resolve(readmeFiles)
|
||||
})
|
||||
return readmeFiles
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -221,40 +312,33 @@ function findLocalizedReadmes() {
|
|||
* @param {string} contributorsSection HTML for contributors section
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function updateLocalizedReadme(filePath, contributorsSection) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(filePath, "utf8", (err, readmeContent) => {
|
||||
if (err) {
|
||||
console.warn(`Warning: Could not read ${filePath}: ${err.message}`)
|
||||
return resolve()
|
||||
}
|
||||
async function updateLocalizedReadme(filePath, contributorsSection) {
|
||||
try {
|
||||
// Read the file content
|
||||
const readmeContent = await readFileAsync(filePath, "utf8")
|
||||
|
||||
// Find existing contributors section markers
|
||||
const startPos = readmeContent.indexOf(START_MARKER)
|
||||
const endPos = readmeContent.indexOf(END_MARKER)
|
||||
// Find existing contributors section markers
|
||||
const startPos = readmeContent.indexOf(START_MARKER)
|
||||
const endPos = readmeContent.indexOf(END_MARKER)
|
||||
|
||||
if (startPos === -1 || endPos === -1) {
|
||||
console.warn(`Warning: Could not find contributors section markers in ${filePath}`)
|
||||
console.warn(`Skipping update for ${filePath}`)
|
||||
return resolve()
|
||||
}
|
||||
if (startPos === -1 || endPos === -1) {
|
||||
console.warn(`Warning: Could not find contributors section markers in ${filePath}`)
|
||||
console.warn(`Skipping update for ${filePath}`)
|
||||
return
|
||||
}
|
||||
|
||||
// Replace existing section, trimming whitespace at section boundaries
|
||||
const beforeSection = readmeContent.substring(0, startPos).trimEnd()
|
||||
const afterSection = readmeContent.substring(endPos + END_MARKER.length).trimStart()
|
||||
// Ensure single newline separators between sections
|
||||
const updatedContent = beforeSection + "\n\n" + contributorsSection.trim() + "\n\n" + afterSection
|
||||
// Replace existing section, trimming whitespace at section boundaries
|
||||
const beforeSection = readmeContent.substring(0, startPos).trimEnd()
|
||||
const afterSection = readmeContent.substring(endPos + END_MARKER.length).trimStart()
|
||||
// Ensure single newline separators between sections
|
||||
const updatedContent = beforeSection + "\n\n" + contributorsSection.trim() + "\n\n" + afterSection
|
||||
|
||||
fs.writeFile(filePath, updatedContent, "utf8", (writeErr) => {
|
||||
if (writeErr) {
|
||||
console.warn(`Warning: Failed to update ${filePath}: ${writeErr.message}`)
|
||||
return resolve()
|
||||
}
|
||||
console.log(`Updated ${filePath}`)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
// Write the updated content
|
||||
await writeFileAsync(filePath, updatedContent, "utf8")
|
||||
console.log(`Updated ${filePath}`)
|
||||
} catch (err) {
|
||||
console.warn(`Warning: Could not update ${filePath}: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -262,9 +346,9 @@ function updateLocalizedReadme(filePath, contributorsSection) {
|
|||
*/
|
||||
async function main() {
|
||||
try {
|
||||
// Fetch contributors from GitHub
|
||||
// Fetch contributors from GitHub (now handles pagination)
|
||||
const contributors = await fetchContributors()
|
||||
console.log(`Fetched ${contributors.length} contributors from GitHub`)
|
||||
console.log(`Total contributors: ${contributors.length}`)
|
||||
|
||||
// Generate contributors section
|
||||
const contributorsSection = formatContributorsSection(contributors)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue