1+ import json
12import pathlib
23import re
34import os
67from github import Github , Auth
78
89root = pathlib .Path (__file__ ).parent .resolve ()
10+ PROJECTS_CONFIG = root / "projects.json"
911
1012TOKEN = os .environ .get ("GH_TOKEN" , "" )
1113ORG_NAME = "variableway"
@@ -22,6 +24,78 @@ def replace_chunk(content, marker, chunk, inline=False):
2224 return r .sub (chunk , content )
2325
2426
27+ def load_tracked_projects ():
28+ """Load tracked projects from projects.json."""
29+ if not PROJECTS_CONFIG .exists ():
30+ print (f"Warning: { PROJECTS_CONFIG } not found, using empty project list" )
31+ return []
32+ data = json .loads (PROJECTS_CONFIG .read_text (encoding = "utf-8" ))
33+ projects = data .get ("projects" , data if isinstance (data , list ) else [])
34+ tracked = []
35+ for item in projects :
36+ if isinstance (item , str ):
37+ repo = item
38+ display_name = item .split ("/" )[- 1 ]
39+ else :
40+ repo = item ["repo" ]
41+ display_name = item .get ("display_name" , repo .split ("/" )[- 1 ])
42+ tracked .append ({"repo" : repo , "display_name" : display_name })
43+ return tracked
44+
45+
46+ def fetch_latest_release (repo ):
47+ """Return the latest non-prerelease release for a repo, or None."""
48+ try :
49+ for release in repo .get_releases ():
50+ if release .prerelease or (release .tag_name or "" ).lower () == "nightly" :
51+ continue
52+ title = (release .title or "" ).strip () or release .tag_name or "Release"
53+ return {
54+ "tag" : release .tag_name or title ,
55+ "title" : title ,
56+ "published_at" : release .published_at .strftime ("%Y-%m-%d" ),
57+ "url" : release .html_url ,
58+ }
59+ except Exception as e :
60+ print (f"Error fetching releases for { repo .full_name } : { e } " )
61+ return None
62+
63+
64+ def fetch_tracked_projects (oauth_token , tracked ):
65+ """Fetch GitHub metadata and latest release for configured projects."""
66+ if not tracked :
67+ return []
68+
69+ try :
70+ auth = Auth .Token (oauth_token ) if oauth_token else None
71+ g = Github (auth = auth )
72+ repos = []
73+ for item in tracked :
74+ repo_name = item ["repo" ]
75+ try :
76+ repo = g .get_repo (repo_name )
77+ release = fetch_latest_release (repo )
78+ repos .append (
79+ {
80+ "name" : item ["display_name" ],
81+ "url" : repo .html_url ,
82+ "description" : (repo .description or "" ).replace ("|" , "/" ),
83+ "stars" : repo .stargazers_count ,
84+ "language" : repo .language or "-" ,
85+ "forks" : repo .forks_count ,
86+ "latest_release" : release ["tag" ] if release else "-" ,
87+ "latest_release_url" : release ["url" ] if release else repo .html_url ,
88+ "release_published_at" : release ["published_at" ] if release else "" ,
89+ }
90+ )
91+ except Exception as e :
92+ print (f"Error fetching repo { repo_name } : { e } " )
93+ return repos
94+ except Exception as e :
95+ print (f"Error fetching tracked projects: { e } " )
96+ return []
97+
98+
2599def fetch_org_repos (oauth_token ):
26100 """Fetch all public non-fork repos for the organization."""
27101 try :
@@ -48,58 +122,23 @@ def fetch_org_repos(oauth_token):
48122 return []
49123
50124
51- def fetch_org_releases (oauth_token ):
52- """Fetch latest releases across all org repos."""
53- try :
54- auth = Auth .Token (oauth_token ) if oauth_token else None
55- g = Github (auth = auth )
56- org = g .get_organization (ORG_NAME )
57- releases = []
58- for repo in org .get_repos (type = "public" ):
59- if repo .fork :
60- continue
61- try :
62- repo_releases = list (repo .get_releases ())
63- if not repo_releases :
64- continue
65- for release in repo_releases [:10 ]:
66- if release .prerelease or (release .tag_name or "" ).lower () == "nightly" :
67- continue
68- title = (release .title or "" ).strip () or release .tag_name or "Release"
69- releases .append (
70- {
71- "repo" : repo .name ,
72- "repo_url" : repo .html_url ,
73- "title" : title ,
74- "published_at" : release .published_at .strftime ("%Y-%m-%d" ),
75- "url" : release .html_url ,
76- }
77- )
78- except Exception as e :
79- print (f"Error fetching releases for { repo .name } : { e } " )
80- releases .sort (key = lambda r : r ["published_at" ], reverse = True )
81- # Keep only latest release per repo
82- seen = set ()
83- unique = []
84- for r in releases :
85- if r ["repo" ] not in seen :
86- seen .add (r ["repo" ])
87- unique .append (r )
88- return unique
89- except Exception as e :
90- print (f"Error fetching org releases: { e } " )
91- return []
92-
93-
94125def build_project_table (repos ):
95- """Generate markdown table from repo list."""
126+ """Generate markdown table from tracked repo list."""
96127 lines = [
97- "| Name | Description | Stars | Language |" ,
98- "|------|-------------|-------|----------|" ,
128+ "| Name | Description | Latest Release | Stars | Language |" ,
129+ "|------|-------------|----------------|------- |----------|" ,
99130 ]
100131 for r in repos :
132+ release_cell = (
133+ f"[{ r ['latest_release' ]} ]({ r ['latest_release_url' ]} )"
134+ if r .get ("latest_release" ) and r ["latest_release" ] != "-"
135+ else "-"
136+ )
101137 lines .append (
102- "| [{name}]({url}) | {description} | ⭐ {stars} | {language} |" .format (** r )
138+ "| [{name}]({url}) | {description} | {release} | ⭐ {stars} | {language} |" .format (
139+ release = release_cell ,
140+ ** r ,
141+ )
103142 )
104143 return "\n " .join (lines )
105144
@@ -111,36 +150,56 @@ def build_org_stats(repos):
111150 avg_stars = round (total_stars / total_repos , 1 ) if total_repos else 0
112151 now = datetime .now (timezone .utc ).strftime ("%Y-%m-%d %H:%M:%S" )
113152 return (
114- "**Statistics**: {repos} repositories, {stars} total stars, {avg} average\n \n "
153+ "**Statistics**: {repos} featured repositories, {stars} total stars, {avg} average\n \n "
115154 "*Last updated: {now}*"
116155 ).format (repos = total_repos , stars = total_stars , avg = avg_stars , now = now )
117156
118157
119- def build_releases_md (releases ):
120- """Generate releases markdown."""
158+ def build_releases_md (repos ):
159+ """Generate releases markdown from tracked projects."""
160+ releases = []
161+ for repo in repos :
162+ if not repo .get ("latest_release" ) or repo ["latest_release" ] == "-" :
163+ continue
164+ releases .append (
165+ {
166+ "repo" : repo ["name" ],
167+ "title" : repo ["latest_release" ],
168+ "url" : repo ["latest_release_url" ],
169+ "published_at" : repo .get ("release_published_at" , "" ),
170+ }
171+ )
172+ releases .sort (key = lambda r : r ["published_at" ], reverse = True )
121173 if not releases :
122174 return "• No releases yet"
123175 lines = []
124- for r in releases [: 6 ] :
176+ for r in releases :
125177 lines .append ("• [{repo} - {title}]({url}) - {published_at}" .format (** r ))
126178 return "<br>" .join (lines )
127179
128180
129- def update_readme (path , repos , releases ):
181+ def update_readme (path , repos ):
130182 """Update a single README file with dynamic content."""
131- contents = path .open (). read ( )
183+ contents = path .read_text ( encoding = "utf-8" )
132184
133185 rewritten = replace_chunk (contents , "project_list" , build_project_table (repos ))
134186 rewritten = replace_chunk (rewritten , "org_stats" , build_org_stats (repos ))
135- rewritten = replace_chunk (rewritten , "recent_releases" , build_releases_md (releases ))
187+ rewritten = replace_chunk (rewritten , "recent_releases" , build_releases_md (repos ))
136188
137- path .open ( "w" ). write ( rewritten )
189+ path .write_text ( rewritten , encoding = "utf-8" )
138190 print (f"Updated { path .relative_to (root )} " )
139191
140192
141193if __name__ == "__main__" :
142- repos = fetch_org_repos (TOKEN )
143- releases = fetch_org_releases (TOKEN )
144-
145- for readme_path in [root / "profile" / "README.md" , root / "README.md" ]:
146- update_readme (readme_path , repos , releases )
194+ tracked = load_tracked_projects ()
195+ repos = fetch_tracked_projects (TOKEN , tracked )
196+
197+ if not repos :
198+ print ("No tracked project data fetched, falling back to org repos without release info" )
199+ repos = fetch_org_repos (TOKEN )
200+ for repo in repos :
201+ repo ["latest_release" ] = "-"
202+ repo ["latest_release_url" ] = repo ["url" ]
203+ repo ["release_published_at" ] = ""
204+
205+ update_readme (root / "profile" / "README.md" , repos )
0 commit comments