update-contributors.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #!/usr/bin/env node
  2. /**
  3. * This script fetches contributor data from GitHub and updates the README.md file
  4. * with a contributors section showing avatars and usernames.
  5. */
  6. const https = require("https")
  7. const fs = require("fs")
  8. const path = require("path")
  9. // GitHub API URL for fetching contributors
  10. const GITHUB_API_URL = "https://api.github.com/repos/RooVetGit/Roo-Code/contributors?per_page=100"
  11. const README_PATH = path.join(__dirname, "..", "README.md")
  12. // Sentinel markers for contributors section
  13. const START_MARKER = "<!-- START CONTRIBUTORS SECTION - AUTO-GENERATED, DO NOT EDIT MANUALLY -->"
  14. const END_MARKER = "<!-- END CONTRIBUTORS SECTION -->"
  15. // HTTP options for GitHub API request
  16. const options = {
  17. headers: {
  18. "User-Agent": "Roo-Code-Contributors-Script",
  19. },
  20. }
  21. // Add GitHub token for authentication if available
  22. if (process.env.GITHUB_TOKEN) {
  23. options.headers.Authorization = `token ${process.env.GITHUB_TOKEN}`
  24. console.log("Using GitHub token from environment variable")
  25. }
  26. /**
  27. * Fetches contributors data from GitHub API
  28. * @returns {Promise<Array>} Array of contributor objects
  29. */
  30. function fetchContributors() {
  31. return new Promise((resolve, reject) => {
  32. https
  33. .get(GITHUB_API_URL, options, (res) => {
  34. if (res.statusCode !== 200) {
  35. reject(new Error(`GitHub API request failed with status code: ${res.statusCode}`))
  36. return
  37. }
  38. let data = ""
  39. res.on("data", (chunk) => {
  40. data += chunk
  41. })
  42. res.on("end", () => {
  43. try {
  44. const contributors = JSON.parse(data)
  45. resolve(contributors)
  46. } catch (error) {
  47. reject(new Error(`Failed to parse GitHub API response: ${error.message}`))
  48. }
  49. })
  50. })
  51. .on("error", (error) => {
  52. reject(new Error(`GitHub API request failed: ${error.message}`))
  53. })
  54. })
  55. }
  56. /**
  57. * Reads the README.md file
  58. * @returns {Promise<string>} README content
  59. */
  60. function readReadme() {
  61. return new Promise((resolve, reject) => {
  62. fs.readFile(README_PATH, "utf8", (err, data) => {
  63. if (err) {
  64. reject(new Error(`Failed to read README.md: ${err.message}`))
  65. return
  66. }
  67. resolve(data)
  68. })
  69. })
  70. }
  71. /**
  72. * Creates HTML for the contributors section
  73. * @param {Array} contributors Array of contributor objects from GitHub API
  74. * @returns {string} HTML for contributors section
  75. */
  76. function formatContributorsSection(contributors) {
  77. // Filter out GitHub Actions bot
  78. const filteredContributors = contributors.filter((c) => !c.login.includes("[bot]") && !c.login.includes("R00-B0T"))
  79. // Start building with Markdown table format
  80. let markdown = `${START_MARKER}
  81. ## Contributors
  82. Thanks to all our contributors who have helped make Roo Code better!
  83. `
  84. // Number of columns in the table
  85. const COLUMNS = 6
  86. // Create contributor cell HTML
  87. const createCell = (contributor) => {
  88. return `<a href="${contributor.html_url}"><img src="${contributor.avatar_url}" width="100" height="100" alt="${contributor.login}"/><br /><sub><b>${contributor.login}</b></sub></a>`
  89. }
  90. if (filteredContributors.length > 0) {
  91. // Table header is the first row of contributors
  92. const headerCells = filteredContributors.slice(0, COLUMNS).map(createCell)
  93. // Fill any empty cells in header row
  94. while (headerCells.length < COLUMNS) {
  95. headerCells.push(" ")
  96. }
  97. // Add header row
  98. markdown += `|${headerCells.join("|")}|\n`
  99. // Add alignment row
  100. markdown += "|"
  101. for (let i = 0; i < COLUMNS; i++) {
  102. markdown += ":---:|"
  103. }
  104. markdown += "\n"
  105. // Add remaining contributor rows starting with the second batch
  106. for (let i = COLUMNS; i < filteredContributors.length; i += COLUMNS) {
  107. const rowContributors = filteredContributors.slice(i, i + COLUMNS)
  108. // Create cells for each contributor in this row
  109. const cells = rowContributors.map(createCell)
  110. // Fill any empty cells to maintain table structure
  111. while (cells.length < COLUMNS) {
  112. cells.push(" ")
  113. }
  114. // Add row to the table
  115. markdown += `|${cells.join("|")}|\n`
  116. }
  117. }
  118. markdown += `${END_MARKER}`
  119. return markdown
  120. }
  121. /**
  122. * Updates the README.md file with contributors section
  123. * @param {string} readmeContent Original README content
  124. * @param {string} contributorsSection HTML for contributors section
  125. * @returns {Promise<void>}
  126. */
  127. function updateReadme(readmeContent, contributorsSection) {
  128. // Find existing contributors section markers
  129. const startPos = readmeContent.indexOf(START_MARKER)
  130. const endPos = readmeContent.indexOf(END_MARKER)
  131. if (startPos === -1 || endPos === -1) {
  132. console.warn("Warning: Could not find contributors section markers in README.md")
  133. console.warn("Skipping update - please add markers to enable automatic updates.")
  134. return
  135. }
  136. // Replace existing section, trimming whitespace at section boundaries
  137. const beforeSection = readmeContent.substring(0, startPos).trimEnd()
  138. const afterSection = readmeContent.substring(endPos + END_MARKER.length).trimStart()
  139. // Ensure single newline separators between sections
  140. const updatedContent = beforeSection + "\n\n" + contributorsSection.trim() + "\n\n" + afterSection
  141. return writeReadme(updatedContent)
  142. }
  143. /**
  144. * Writes updated content to README.md
  145. * @param {string} content Updated README content
  146. * @returns {Promise<void>}
  147. */
  148. function writeReadme(content) {
  149. return new Promise((resolve, reject) => {
  150. fs.writeFile(README_PATH, content, "utf8", (err) => {
  151. if (err) {
  152. reject(new Error(`Failed to write updated README.md: ${err.message}`))
  153. return
  154. }
  155. resolve()
  156. })
  157. })
  158. }
  159. /**
  160. * Main function that orchestrates the update process
  161. */
  162. async function main() {
  163. try {
  164. const contributors = await fetchContributors()
  165. const readmeContent = await readReadme()
  166. const contributorsSection = formatContributorsSection(contributors)
  167. await updateReadme(readmeContent, contributorsSection)
  168. } catch (error) {
  169. console.error(`Error: ${error.message}`)
  170. process.exit(1)
  171. }
  172. }
  173. // Run the script
  174. main()