-
Notifications
You must be signed in to change notification settings - Fork 40
feat: generate rich profile READMEs with badges, repos, and contributions #3962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -618,6 +618,159 @@ EOF | |||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # --- Map language name to shields.io badge --- | ||||||||||||||||||||||||||||||||||||||||
| _lang_badge() { | ||||||||||||||||||||||||||||||||||||||||
| local lang="$1" | ||||||||||||||||||||||||||||||||||||||||
| case "$lang" in | ||||||||||||||||||||||||||||||||||||||||
| Shell) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| TypeScript) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| JavaScript) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Python) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Ruby) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Go) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Rust) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Java) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| PHP) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| C) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| "C++") echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| "C#") echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Swift) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Kotlin) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Dart) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| HTML) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| CSS) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Lua) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Elixir) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Scala) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Haskell) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Vue) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| Svelte) echo '' ;; | ||||||||||||||||||||||||||||||||||||||||
| *) echo "" ;; | ||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # --- Generate rich profile README from GitHub data --- | ||||||||||||||||||||||||||||||||||||||||
| _generate_rich_readme() { | ||||||||||||||||||||||||||||||||||||||||
| local gh_user="$1" | ||||||||||||||||||||||||||||||||||||||||
| local readme_path="$2" | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Fetch user profile | ||||||||||||||||||||||||||||||||||||||||
| local user_json | ||||||||||||||||||||||||||||||||||||||||
| user_json=$(gh api "users/${gh_user}" 2>/dev/null) || user_json="{}" | ||||||||||||||||||||||||||||||||||||||||
| local display_name bio blog twitter | ||||||||||||||||||||||||||||||||||||||||
| display_name=$(echo "$user_json" | jq -r '.name // empty' 2>/dev/null) | ||||||||||||||||||||||||||||||||||||||||
| display_name="${display_name:-$gh_user}" | ||||||||||||||||||||||||||||||||||||||||
| bio=$(echo "$user_json" | jq -r '.bio // empty' 2>/dev/null) | ||||||||||||||||||||||||||||||||||||||||
| blog=$(echo "$user_json" | jq -r 'select(.blog != null and .blog != "") | .blog' 2>/dev/null) | ||||||||||||||||||||||||||||||||||||||||
| twitter=$(echo "$user_json" | jq -r 'select(.twitter_username != null and .twitter_username != "") | .twitter_username' 2>/dev/null) | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+662
to
+666
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section has a couple of areas for improvement:
Here is a suggested refactoring that addresses both points.
Suggested change
References
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Fetch repos and detect languages | ||||||||||||||||||||||||||||||||||||||||
| local repos_json | ||||||||||||||||||||||||||||||||||||||||
| repos_json=$(gh api "users/${gh_user}/repos?per_page=100&sort=updated" 2>/dev/null) || repos_json="[]" | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Unique languages from all repos (sorted) | ||||||||||||||||||||||||||||||||||||||||
| local languages | ||||||||||||||||||||||||||||||||||||||||
| languages=$(echo "$repos_json" | jq -r '[.[].language | select(. != null)] | unique | .[]' 2>/dev/null) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Build badge line | ||||||||||||||||||||||||||||||||||||||||
| local badges="" | ||||||||||||||||||||||||||||||||||||||||
| while IFS= read -r lang; do | ||||||||||||||||||||||||||||||||||||||||
| [[ -z "$lang" ]] && continue | ||||||||||||||||||||||||||||||||||||||||
| local badge | ||||||||||||||||||||||||||||||||||||||||
| badge=$(_lang_badge "$lang") | ||||||||||||||||||||||||||||||||||||||||
| badges="${badges}${badge}"$'\n' | ||||||||||||||||||||||||||||||||||||||||
| done <<<"$languages" | ||||||||||||||||||||||||||||||||||||||||
| # Always add common tooling badges | ||||||||||||||||||||||||||||||||||||||||
| badges="${badges}"''$'\n' | ||||||||||||||||||||||||||||||||||||||||
| badges="${badges}"''$'\n' | ||||||||||||||||||||||||||||||||||||||||
| badges="${badges}"''$'\n' | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Build own repos section (non-fork, non-profile, with description) | ||||||||||||||||||||||||||||||||||||||||
| local own_repos="" | ||||||||||||||||||||||||||||||||||||||||
| while IFS= read -r row; do | ||||||||||||||||||||||||||||||||||||||||
| [[ -z "$row" ]] && continue | ||||||||||||||||||||||||||||||||||||||||
| local rname rdesc rurl | ||||||||||||||||||||||||||||||||||||||||
| rname=$(echo "$row" | jq -r '.name') | ||||||||||||||||||||||||||||||||||||||||
| rdesc=$(echo "$row" | jq -r '.description // "No description"') | ||||||||||||||||||||||||||||||||||||||||
| rurl=$(echo "$row" | jq -r '.html_url') | ||||||||||||||||||||||||||||||||||||||||
| own_repos="${own_repos}- **[${rname}](${rurl})** -- ${rdesc}"$'\n' | ||||||||||||||||||||||||||||||||||||||||
| done < <(echo "$repos_json" | jq -c ".[] | select(.fork == false and .name != \"${gh_user}\")") | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Build contributions section (forks with description) | ||||||||||||||||||||||||||||||||||||||||
| local contrib_repos="" | ||||||||||||||||||||||||||||||||||||||||
| while IFS= read -r row; do | ||||||||||||||||||||||||||||||||||||||||
| [[ -z "$row" ]] && continue | ||||||||||||||||||||||||||||||||||||||||
| local rname rdesc rurl | ||||||||||||||||||||||||||||||||||||||||
| rname=$(echo "$row" | jq -r '.name') | ||||||||||||||||||||||||||||||||||||||||
| rdesc=$(echo "$row" | jq -r '.description // "No description"') | ||||||||||||||||||||||||||||||||||||||||
| rurl=$(echo "$row" | jq -r '.html_url') | ||||||||||||||||||||||||||||||||||||||||
| # Try to get parent repo URL | ||||||||||||||||||||||||||||||||||||||||
| local parent_url | ||||||||||||||||||||||||||||||||||||||||
| parent_url=$(gh api "repos/${gh_user}/${rname}" --jq '.parent.html_url // empty' 2>/dev/null) | ||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$parent_url" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| contrib_repos="${contrib_repos}- **[${rname}](${parent_url})** -- ${rdesc}"$'\n' | ||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||
| contrib_repos="${contrib_repos}- **[${rname}](${rurl})** -- ${rdesc}"$'\n' | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+697
to
+714
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section embeds user-controlled repository names (
Suggested change
References
|
||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| done < <(echo "$repos_json" | jq -c '.[] | select(.fork == true)') | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+701
to
+716
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This loop is inefficient and will perform poorly for users with many forked repositories. It suffers from two issues:
You can resolve both issues by using a single
Suggested change
References
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Build connect section | ||||||||||||||||||||||||||||||||||||||||
| local connect="" | ||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$blog" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| connect="${connect}[](${blog})"$'\n' | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$twitter" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| connect="${connect}[](https://twitter.com/${twitter})"$'\n' | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+721
to
+724
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| connect="${connect}[](https://github.com/${gh_user})"$'\n' | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Compose the README | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| echo "# ${display_name}" | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$bio" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| echo "**${bio}**" | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+730
to
+733
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| # Badges | ||||||||||||||||||||||||||||||||||||||||
| printf '%s' "$badges" | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| echo "> Shipping with AI agents around the clock -- human hours for thinking, machine hours for doing." | ||||||||||||||||||||||||||||||||||||||||
| echo "> Stats auto-updated by [aidevops](https://aidevops.sh)." | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| echo "<!-- STATS-START -->" | ||||||||||||||||||||||||||||||||||||||||
| echo "<!-- Stats will be populated on first update -->" | ||||||||||||||||||||||||||||||||||||||||
| echo "<!-- STATS-END -->" | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| # Own repos | ||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$own_repos" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| echo "## Projects" | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| printf '%s' "$own_repos" | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| # Contributions | ||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$contrib_repos" ]]; then | ||||||||||||||||||||||||||||||||||||||||
| echo "## Contributions" | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| printf '%s' "$contrib_repos" | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| # Connect | ||||||||||||||||||||||||||||||||||||||||
| echo "## Connect" | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| printf '%s' "$connect" | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| echo "---" | ||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||
| echo "<!-- UPDATED-START -->" | ||||||||||||||||||||||||||||||||||||||||
| echo "<!-- UPDATED-END -->" | ||||||||||||||||||||||||||||||||||||||||
| } >"$readme_path" | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return 0 | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # --- Initialize profile README repo --- | ||||||||||||||||||||||||||||||||||||||||
| # Creates the username/username GitHub repo if it doesn't exist, clones it, | ||||||||||||||||||||||||||||||||||||||||
| # seeds a starter README with stat markers, and registers it in repos.json. | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -683,25 +836,8 @@ cmd_init() { | |||||||||||||||||||||||||||||||||||||||
| # Seed README.md if it doesn't have stat markers | ||||||||||||||||||||||||||||||||||||||||
| local readme_path="${repo_dir}/README.md" | ||||||||||||||||||||||||||||||||||||||||
| if [[ ! -f "$readme_path" ]] || ! grep -q '<!-- STATS-START -->' "$readme_path"; then | ||||||||||||||||||||||||||||||||||||||||
| echo "Creating starter README with stat markers" | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Get display name from git config or GitHub | ||||||||||||||||||||||||||||||||||||||||
| local display_name | ||||||||||||||||||||||||||||||||||||||||
| display_name=$(git config --global user.name 2>/dev/null || echo "$gh_user") | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| cat >"$readme_path" <<README | ||||||||||||||||||||||||||||||||||||||||
| # ${display_name} | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| > Shipping with AI agents around the clock -- human hours for thinking, machine hours for doing. | ||||||||||||||||||||||||||||||||||||||||
| > Stats auto-updated by [aidevops](https://aidevops.sh). | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| <!-- STATS-START --> | ||||||||||||||||||||||||||||||||||||||||
| <!-- Stats will be populated on first update --> | ||||||||||||||||||||||||||||||||||||||||
| <!-- STATS-END --> | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| <!-- UPDATED-START --> | ||||||||||||||||||||||||||||||||||||||||
| <!-- UPDATED-END --> | ||||||||||||||||||||||||||||||||||||||||
| README | ||||||||||||||||||||||||||||||||||||||||
| echo "Creating rich profile README..." | ||||||||||||||||||||||||||||||||||||||||
| _generate_rich_readme "$gh_user" "$readme_path" | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| git -C "$repo_dir" add README.md | ||||||||||||||||||||||||||||||||||||||||
| git -C "$repo_dir" commit -m "feat: initialize profile README with aidevops stat markers" --no-verify 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
langvariable is embedded directly into a Markdown image tag without sanitization. If a repository has a maliciously crafted language name (e.g., containing](...)), it could lead to Markdown injection in the generated README.