33 * Update the org profile README stats block using GitHub GraphQL.
44 *
55 * Env:
6- * ORG - required, your org login ("kir-rescomp")
7- * GH_TOKEN - a token; PAT with read:org + repo if you want private repos included ,
8- * otherwise the default GITHUB_TOKEN works for public.
6+ * ORG - org login (default: "kir-rescomp")
7+ * GH_TOKEN - PAT ( read:org + repo) to include private repos,
8+ * else falls back to GITHUB_TOKEN ( public only)
99 */
1010const fs = require ( "fs" ) ;
1111const path = require ( "path" ) ;
@@ -18,14 +18,15 @@ if (!GH_TOKEN) {
1818 process . exit ( 1 ) ;
1919}
2020
21- // Tweak filters:
21+ // Filters (tweak as you like)
2222const INCLUDE_FORKS = false ;
2323const INCLUDE_ARCHIVED = false ;
24- // null => both public & private; or "PUBLIC" / "PRIVATE"
24+ // null => both; "PUBLIC" or "PRIVATE" to restrict
2525const PRIVACY = null ;
2626
2727const README_PATH = path . join ( process . cwd ( ) , "profile" , "README.md" ) ;
2828
29+ // --- GraphQL helper ---------------------------------------------------------
2930const gql = ( query , variables ) =>
3031 new Promise ( ( resolve , reject ) => {
3132 const data = JSON . stringify ( { query, variables } ) ;
@@ -47,7 +48,14 @@ const gql = (query, variables) =>
4748 res . on ( "end" , ( ) => {
4849 try {
4950 const json = JSON . parse ( body ) ;
50- if ( json . errors ) return reject ( json . errors ) ;
51+ if ( ! String ( res . statusCode ) . startsWith ( "2" ) ) {
52+ return reject (
53+ new Error (
54+ `HTTP ${ res . statusCode } : ${ JSON . stringify ( json . errors || json ) } `
55+ )
56+ ) ;
57+ }
58+ if ( json . errors ) return reject ( new Error ( JSON . stringify ( json . errors ) ) ) ;
5159 resolve ( json . data ) ;
5260 } catch ( e ) {
5361 reject ( e ) ;
@@ -60,12 +68,20 @@ const gql = (query, variables) =>
6068 req . end ( ) ;
6169 } ) ;
6270
71+ // --- Query + pagination ------------------------------------------------------
6372const REPO_PAGE_SIZE = 50 ;
6473
6574const REPOS_QUERY = `
6675query($org:String!, $after:String, $isFork:Boolean, $privacy:RepositoryPrivacy, $pageSize:Int!) {
6776 organization(login: $org) {
68- repositories(first: $pageSize, after: $after, isFork: $isFork, privacy: $privacy, ownerAffiliations: OWNER, orderBy:{field:NAME, direction:ASC}) {
77+ repositories(
78+ first: $pageSize,
79+ after: $after,
80+ isFork: $isFork,
81+ privacy: $privacy,
82+ ownerAffiliations: OWNER,
83+ orderBy:{field:NAME, direction:ASC}
84+ ) {
6985 pageInfo { hasNextPage endCursor }
7086 nodes {
7187 name
@@ -97,6 +113,7 @@ query($org:String!, $after:String, $isFork:Boolean, $privacy:RepositoryPrivacy,
97113async function fetchAllRepos ( ) {
98114 let after = null ;
99115 const all = [ ] ;
116+ // eslint-disable-next-line no-constant-condition
100117 while ( true ) {
101118 const data = await gql ( REPOS_QUERY , {
102119 org : ORG ,
@@ -116,21 +133,24 @@ async function fetchAllRepos() {
116133 return all ;
117134}
118135
119- function formatNumber ( n ) {
120- return new Intl . NumberFormat ( "en-GB" ) . format ( n ) ;
121- }
136+ // --- Rendering helpers -------------------------------------------------------
137+ const fmt = ( n ) => new Intl . NumberFormat ( "en-GB" ) . format ( n ) ;
122138
123139function shields ( label , value , color , opts = { } ) {
124140 const { logo, link } = opts ;
125- const src = `https://img.shields.io/badge/${ encodeURIComponent ( label ) } -${ encodeURIComponent ( String ( value ) ) } -${ color } ?style=for-the-badge${ logo ? `&logo=${ encodeURIComponent ( logo ) } ` : "" } ` ;
141+ const src = `https://img.shields.io/badge/${ encodeURIComponent ( label ) } -${ encodeURIComponent (
142+ String ( value )
143+ ) } -${ color } ?style=for-the-badge${ logo ? `&logo=${ encodeURIComponent ( logo ) } ` : "" } `;
126144 const img = `<img alt="${ label } - ${ value } " src="${ src } " />` ;
127145 return link ? `<a href="${ link } ">${ img } </a>` : img ;
128146}
129147
130148function renderBadgesHTML ( stats ) {
131- const fmt = ( n ) => new Intl . NumberFormat ( "en-GB" ) . format ( n ) ;
132149 return [
133- shields ( "Repos" , fmt ( stats . repoCount ) , "0a84ff" , { logo : "github" , link : `https://github.com/${ ORG } ?tab=repositories` } ) ,
150+ shields ( "Repos" , fmt ( stats . repoCount ) , "0a84ff" , {
151+ logo : "github" ,
152+ link : `https://github.com/${ ORG } ?tab=repositories` ,
153+ } ) ,
134154 shields ( "Commits" , fmt ( stats . totalCommits ) , "10b981" ) ,
135155 shields ( "Issues (open)" , fmt ( stats . openIssues ) , "f59e0b" ) ,
136156 shields ( "PRs (open)" , fmt ( stats . openPRs ) , "8b5cf6" ) ,
@@ -152,20 +172,10 @@ function renderMarkdown(stats, topRepos) {
152172 forks,
153173 } = stats ;
154174
155- const fmt = ( n ) => new Intl . NumberFormat ( "en-GB" ) . format ( n ) ;
156-
157- const badges = [
158- renderBadge ( "Repos" , fmt ( repoCount ) , "0a84ff" , "github" , `https://github.com/${ ORG } ?tab=repositories` ) ,
159- renderBadge ( "Commits" , fmt ( totalCommits ) , "10b981" ) ,
160- renderBadge ( "Issues (open)" , fmt ( openIssues ) , "f59e0b" ) ,
161- renderBadge ( "PRs (open)" , fmt ( openPRs ) , "8b5cf6" ) ,
162- renderBadge ( "Stars" , fmt ( stars ) , "14b8a6" , "github" ) ,
163- renderBadge ( "Forks" , fmt ( forks ) , "06b6d4" , "github" ) ,
164- ] . join ( " " ) ;
165-
166175 const lines = [ ] ;
167176 lines . push ( `### 📊 Organisation Stats for **${ ORG } **` ) ;
168177 lines . push ( "" ) ;
178+ // HTML badges (centered). Markdown isn’t parsed inside HTML, so we use <img>.
169179 lines . push ( `<p align="center">${ renderBadgesHTML ( stats ) } </p>` ) ;
170180 lines . push ( "" ) ;
171181 lines . push ( `<table>` ) ;
@@ -176,9 +186,15 @@ function renderMarkdown(stats, topRepos) {
176186 lines . push ( `</thead>` ) ;
177187 lines . push ( `<tbody>` ) ;
178188 lines . push ( `<tr><td>📦 Repositories</td><td align="right"><code>${ fmt ( repoCount ) } </code></td></tr>` ) ;
179- lines . push ( `<tr><td>🧭 Commits (default branches)</td><td align="right"><code>${ fmt ( totalCommits ) } </code></td></tr>` ) ;
189+ lines . push (
190+ `<tr><td>🧭 Commits (default branches)</td><td align="right"><code>${ fmt (
191+ totalCommits
192+ ) } </code></td></tr>`
193+ ) ;
180194 lines . push ( `<tr><td>🐞 Issues — Open</td><td align="right"><code>${ fmt ( openIssues ) } </code></td></tr>` ) ;
181- lines . push ( `<tr><td>✅ Issues — Closed</td><td align="right"><code>${ fmt ( closedIssues ) } </code></td></tr>` ) ;
195+ lines . push (
196+ `<tr><td>✅ Issues — Closed</td><td align="right"><code>${ fmt ( closedIssues ) } </code></td></tr>`
197+ ) ;
182198 lines . push ( `<tr><td>🔁 PRs — Open</td><td align="right"><code>${ fmt ( openPRs ) } </code></td></tr>` ) ;
183199 lines . push ( `<tr><td>🧹 PRs — Closed</td><td align="right"><code>${ fmt ( closedPRs ) } </code></td></tr>` ) ;
184200 lines . push ( `<tr><td>🎉 PRs — Merged</td><td align="right"><code>${ fmt ( mergedPRs ) } </code></td></tr>` ) ;
@@ -187,7 +203,9 @@ function renderMarkdown(stats, topRepos) {
187203 lines . push ( `</tbody>` ) ;
188204 lines . push ( `</table>` ) ;
189205 lines . push ( "" ) ;
190- lines . push ( `<sub>Updated: ${ new Date ( ) . toISOString ( ) . replace ( "T" , " " ) . replace ( "Z" , " UTC" ) } </sub>` ) ;
206+ lines . push (
207+ `<sub>Updated: ${ new Date ( ) . toISOString ( ) . replace ( "T" , " " ) . replace ( "Z" , " UTC" ) } </sub>`
208+ ) ;
191209 lines . push ( "" ) ;
192210
193211 if ( topRepos . length ) {
@@ -198,7 +216,9 @@ function renderMarkdown(stats, topRepos) {
198216 lines . push ( `|---|---:|---:|---:|---:|---:|` ) ;
199217 for ( const r of topRepos ) {
200218 lines . push (
201- `| [${ r . name } ](https://github.com/${ ORG } /${ r . name } ) | ${ fmt ( r . commits ) } | ${ fmt ( r . issuesOpen ) } | ${ fmt ( r . prsOpen ) } | ${ fmt ( r . stars ) } | ${ fmt ( r . forks ) } |`
219+ `| [${ r . name } ](https://github.com/${ ORG } /${ r . name } ) | ${ fmt ( r . commits ) } | ${ fmt (
220+ r . issuesOpen
221+ ) } | ${ fmt ( r . prsOpen ) } | ${ fmt ( r . stars ) } | ${ fmt ( r . forks ) } |`
202222 ) ;
203223 }
204224 lines . push ( `</details>` ) ;
@@ -208,7 +228,6 @@ function renderMarkdown(stats, topRepos) {
208228 return lines . join ( "\n" ) ;
209229}
210230
211-
212231function replaceStatsSection ( readme , newBlock ) {
213232 const start = "<!-- ORG-STATS:START -->" ;
214233 const end = "<!-- ORG-STATS:END -->" ;
@@ -218,15 +237,21 @@ function replaceStatsSection(readme, newBlock) {
218237 return readme . replace ( pattern , replacement ) ;
219238}
220239
240+ // --- Main --------------------------------------------------------------------
221241( async ( ) => {
222242 try {
223243 const repos = await fetchAllRepos ( ) ;
224244
225245 const aggregated = {
226- repoCount : 0 , totalCommits : 0 ,
227- openIssues : 0 , closedIssues : 0 ,
228- openPRs : 0 , closedPRs : 0 , mergedPRs : 0 ,
229- stars : 0 , forks : 0 ,
246+ repoCount : 0 ,
247+ totalCommits : 0 ,
248+ openIssues : 0 ,
249+ closedIssues : 0 ,
250+ openPRs : 0 ,
251+ closedPRs : 0 ,
252+ mergedPRs : 0 ,
253+ stars : 0 ,
254+ forks : 0 ,
230255 } ;
231256
232257 const perRepo = [ ] ;
@@ -251,7 +276,14 @@ function replaceStatsSection(readme, newBlock) {
251276 aggregated . stars += stars ;
252277 aggregated . forks += forks ;
253278
254- perRepo . push ( { name : r . name , commits, issuesOpen, prsOpen, stars, forks } ) ;
279+ perRepo . push ( {
280+ name : r . name ,
281+ commits,
282+ issuesOpen,
283+ prsOpen,
284+ stars,
285+ forks,
286+ } ) ;
255287 }
256288
257289 perRepo . sort ( ( a , b ) => b . commits - a . commits ) ;
0 commit comments