Skip to content

Commit 4c69f4b

Browse files
committed
update scripts and docs
1 parent 12ac9b0 commit 4c69f4b

5 files changed

Lines changed: 209 additions & 60 deletions

File tree

.github/workflows/build.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Update Org Profile
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
schedule:
8+
- cron: "0 0 * * *"
9+
10+
permissions:
11+
contents: write
12+
13+
jobs:
14+
build:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- uses: actions/setup-python@v5
20+
with:
21+
python-version: "3.11"
22+
23+
- name: Install dependencies
24+
run: pip install -r requirements.txt
25+
26+
- name: Build profile README
27+
env:
28+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29+
run: python build_readme.py
30+
31+
- name: Commit and push if changed
32+
run: |
33+
git config user.name "github-actions[bot]"
34+
git config user.email "github-actions[bot]@users.noreply.github.com"
35+
git add profile/README.md
36+
git diff --staged --quiet && echo "No changes" && exit 0
37+
git commit -m "docs: update variableway project list [skip ci]"
38+
git push

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# variableway/.github
2+
3+
Organization profile repository for [variableway](https://github.com/variableway).
4+
5+
The org profile shown on GitHub is maintained in [`profile/README.md`](profile/README.md) and updated automatically by GitHub Actions.

build_readme.py

Lines changed: 119 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import pathlib
23
import re
34
import os
@@ -6,6 +7,7 @@
67
from github import Github, Auth
78

89
root = pathlib.Path(__file__).parent.resolve()
10+
PROJECTS_CONFIG = root / "projects.json"
911

1012
TOKEN = os.environ.get("GH_TOKEN", "")
1113
ORG_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+
2599
def 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-
94125
def 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

141193
if __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)

profile/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# variableway
2+
3+
This is Org for Projects created by Learning AI Agent.
4+
5+
- Spark-\*\*\* projects are personal daily work projects.
6+
- innate-\*\*\* projects are products created by Learning AI Agent.
7+
8+
## Stats
9+
10+
<!-- org_stats starts -->
11+
12+
**Statistics**: 2 featured repositories, 1 total stars, 0.5 average
13+
14+
*Last updated: 2026-07-06 09:00:00*
15+
16+
<!-- org_stats ends -->
17+
18+
## Featured Projects
19+
20+
<!-- project_list starts -->
21+
22+
| Name | Description | Latest Release | Stars | Language |
23+
|------|-------------|----------------|-------|----------|
24+
| [innate-aiswitcher](https://github.com/variableway/innate-aiswitcher) | simple ai switch | [v0.1.0](https://github.com/variableway/innate-aiswitcher/releases/tag/v0.1.0) | ⭐ 0 | Go |
25+
| [spark-cli](https://github.com/variableway/spark-cli) | Daily Cli command ideas fired by spark | [v0.1.0](https://github.com/variableway/spark-cli/releases/tag/v0.1.0) | ⭐ 1 | Go |
26+
27+
<!-- project_list ends -->
28+
29+
## Latest Releases
30+
31+
<!-- recent_releases starts -->
32+
33+
[innate-aiswitcher - v0.1.0](https://github.com/variableway/innate-aiswitcher/releases/tag/v0.1.0) - 2026-05-29<br>• [spark-cli - v0.1.0](https://github.com/variableway/spark-cli/releases/tag/v0.1.0) - 2026-05-29
34+
35+
<!-- recent_releases ends -->

projects.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"projects": [
3+
{
4+
"repo": "variableway/innate-aiswitcher",
5+
"display_name": "innate-aiswitcher"
6+
},
7+
{
8+
"repo": "variableway/spark-cli",
9+
"display_name": "spark-cli"
10+
}
11+
]
12+
}

0 commit comments

Comments
 (0)