Repository navigation
Update GitHub portfolio stats #41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Update GitHub portfolio stats | |
| on: | |
| schedule: | |
| - cron: "17 5 * * *" | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| update-stats: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout portfolio | |
| uses: actions/checkout@v4 | |
| - name: Generate GitHub stats | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| mkdir -p data | |
| node <<'NODE' | |
| const fs = require('fs'); | |
| const token = process.env.GH_TOKEN; | |
| const username = 'SCodeGit'; | |
| async function graphql(query, variables = {}) { | |
| const response = await fetch('https://api.github.com/graphql', { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${token}`, | |
| 'Content-Type': 'application/json', | |
| 'User-Agent': 'SCode-Portfolio-GitHub-Stats' | |
| }, | |
| body: JSON.stringify({ query, variables }) | |
| }); | |
| const json = await response.json(); | |
| if (!response.ok || json.errors) { | |
| throw new Error(JSON.stringify(json.errors || json)); | |
| } | |
| return json.data; | |
| } | |
| function isoDate(date) { | |
| return date.toISOString().replace('.000Z', 'Z'); | |
| } | |
| const now = new Date(); | |
| const from = new Date(now); | |
| from.setUTCFullYear(from.getUTCFullYear() - 1); | |
| const query = ` | |
| query($login: String!, $from: DateTime!, $to: DateTime!) { | |
| user(login: $login) { | |
| login | |
| name | |
| avatarUrl | |
| htmlUrl | |
| followers { totalCount } | |
| following { totalCount } | |
| repositories( | |
| first: 100 | |
| ownerAffiliations: OWNER | |
| privacy: PUBLIC | |
| ) { | |
| totalCount | |
| nodes { | |
| name | |
| url | |
| stargazerCount | |
| forkCount | |
| isFork | |
| } | |
| } | |
| contributionsCollection(from: $from, to: $to) { | |
| contributionCalendar { | |
| totalContributions | |
| weeks { | |
| contributionDays { | |
| date | |
| contributionCount | |
| } | |
| } | |
| } | |
| totalCommitContributions | |
| totalIssueContributions | |
| totalPullRequestContributions | |
| totalPullRequestReviewContributions | |
| totalRepositoryContributions | |
| totalRepositoriesWithContributedCommits | |
| } | |
| } | |
| } | |
| `; | |
| const data = await graphql(query, { | |
| login: username, | |
| from: isoDate(from), | |
| to: isoDate(now) | |
| }); | |
| const user = data.user; | |
| const collection = user.contributionsCollection; | |
| const calendar = collection.contributionCalendar; | |
| const days = calendar.weeks.flatMap(w => w.contributionDays); | |
| function currentStreak(days) { | |
| const map = new Map(days.map(d => [d.date, d.contributionCount])); | |
| let cursor = new Date(Date.UTC( | |
| now.getUTCFullYear(), | |
| now.getUTCMonth(), | |
| now.getUTCDate() | |
| )); | |
| const key = d => d.toISOString().slice(0, 10); | |
| if (!map.get(key(cursor))) { | |
| cursor.setUTCDate(cursor.getUTCDate() - 1); | |
| } | |
| let streak = 0; | |
| while (map.get(key(cursor)) > 0) { | |
| streak++; | |
| cursor.setUTCDate(cursor.getUTCDate() - 1); | |
| } | |
| return streak; | |
| } | |
| const repos = user.repositories.nodes || []; | |
| const totalStars = repos.reduce((sum, repo) => sum + repo.stargazerCount, 0); | |
| const output = { | |
| username, | |
| generatedAt: new Date().toISOString(), | |
| profile: { | |
| name: user.name, | |
| avatarUrl: user.avatarUrl, | |
| htmlUrl: user.htmlUrl, | |
| followers: user.followers.totalCount, | |
| following: user.following.totalCount, | |
| publicRepos: user.repositories.totalCount, | |
| totalStars | |
| }, | |
| contributions: { | |
| total: calendar.totalContributions, | |
| commits: collection.totalCommitContributions, | |
| issues: collection.totalIssueContributions, | |
| pullRequests: collection.totalPullRequestContributions, | |
| reviews: collection.totalPullRequestReviewContributions, | |
| repositoriesCreated: collection.totalRepositoryContributions, | |
| repositoriesContributedTo: collection.totalRepositoriesWithContributedCommits, | |
| currentStreak: currentStreak(days), | |
| weeks: calendar.weeks | |
| } | |
| }; | |
| fs.writeFileSync('data/github-stats.json', JSON.stringify(output, null, 2) + '\n'); | |
| console.log(`Generated ${output.contributions.total} contributions for ${username}`); | |
| NODE | |
| - name: Commit updated stats | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add data/github-stats.json | |
| if git diff --cached --quiet; then | |
| echo "No stats changes." | |
| else | |
| git commit -m "chore: update github stats" | |
| git push | |
| fi |