|
| 1 | +# GitHub Contributors Feature |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Add contributor avatars with profile links to documentation pages using git + one GitHub API call. |
| 6 | + |
| 7 | +## Approach (Hybrid) |
| 8 | + |
| 9 | +1. One-time call to GitHub's `/repos/{owner}/{repo}/contributors` endpoint → cache email→user mapping |
| 10 | +2. Use `git log` locally to get contributor emails per file |
| 11 | +3. Match emails to cached GitHub usernames |
| 12 | +4. Construct URLs: `https://github.com/{username}.png` (avatar), `https://github.com/{username}` (profile) |
| 13 | + |
| 14 | +## Core Model |
| 15 | + |
| 16 | +**New file: `src/CloudNimble.DotNetDocs.Core/Contributor.cs`** |
| 17 | + |
| 18 | +```csharp |
| 19 | +public class Contributor |
| 20 | +{ |
| 21 | + public string Name { get; set; } |
| 22 | + public string Email { get; set; } |
| 23 | + public string? Username { get; set; } |
| 24 | + public string? AvatarUrl { get; set; } |
| 25 | + public string? ProfileUrl { get; set; } |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | +**Modify: `src/CloudNimble.DotNetDocs.Core/DocEntity.cs`** |
| 30 | + |
| 31 | +```csharp |
| 32 | +public List<Contributor>? Contributors { get; set; } |
| 33 | +``` |
| 34 | + |
| 35 | +**Modify: `src/CloudNimble.DotNetDocs.Core/ProjectContext.cs`** |
| 36 | + |
| 37 | +```csharp |
| 38 | +public bool ContributorsEnabled { get; set; } = false; |
| 39 | +``` |
| 40 | + |
| 41 | +## Implementation |
| 42 | + |
| 43 | +### 1. GitHelper (in Core for reuse by future GitLab/AzureDevOps plugins) |
| 44 | + |
| 45 | +**New file: `src/CloudNimble.DotNetDocs.Core/GitHelper.cs`** |
| 46 | + |
| 47 | +```csharp |
| 48 | +public static class GitHelper |
| 49 | +{ |
| 50 | + // git log --format="%an|%ae" -- {filePath} | sort -u |
| 51 | + public static List<(string Name, string Email)> GetFileContributors(string filePath); |
| 52 | + |
| 53 | + // git remote get-url origin |
| 54 | + public static string? GetRemoteUrl(); |
| 55 | + |
| 56 | + // Parse "https://github.com/Owner/Repo.git" → (Provider, Owner, Repo) |
| 57 | + public static (string? Provider, string? Owner, string? Repo) ParseRemoteUrl(string url); |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +### 2. GitHubContributorEnricher (in GitHub plugin) |
| 62 | + |
| 63 | +**New file: `src/CloudNimble.DotNetDocs.Plugins.GitHub/GitHubContributorEnricher.cs`** |
| 64 | + |
| 65 | +Implements `IDocEnricher`: |
| 66 | + |
| 67 | +```csharp |
| 68 | +public class GitHubContributorEnricher : IDocEnricher |
| 69 | +{ |
| 70 | + private Dictionary<string, Contributor>? _contributorCache; // email → Contributor |
| 71 | +
|
| 72 | + public async Task EnrichAsync(DocEntity entity) |
| 73 | + { |
| 74 | + if (entity is not DocAssembly assembly) return; |
| 75 | + |
| 76 | + // 1. Build cache once: GET /repos/{owner}/{repo}/contributors |
| 77 | + await BuildContributorCacheAsync(); |
| 78 | + |
| 79 | + // 2. Walk entity graph recursively |
| 80 | + foreach (var ns in assembly.Namespaces) |
| 81 | + foreach (var type in ns.Types) |
| 82 | + EnrichType(type); |
| 83 | + } |
| 84 | + |
| 85 | + private void EnrichType(DocType type) |
| 86 | + { |
| 87 | + var contributors = new HashSet<Contributor>(); |
| 88 | + |
| 89 | + // Source code file |
| 90 | + var sourcePath = type.Symbol?.Locations.FirstOrDefault()?.SourceTree?.FilePath; |
| 91 | + if (sourcePath is not null) |
| 92 | + AddContributorsFromFile(sourcePath, contributors); |
| 93 | + |
| 94 | + // Conceptual files (Usage.md, Examples.md, etc.) |
| 95 | + // ... get paths from ProjectContext.GetFullConceptualPath() + type path |
| 96 | +
|
| 97 | + type.Contributors = contributors.ToList(); |
| 98 | + } |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +### 3. Non-API Docs (Guides, etc.) |
| 103 | + |
| 104 | +**TODO: Define rendering approach** |
| 105 | + |
| 106 | +For non-generated pages (guides, tutorials, etc.), contributors need to be collected on-the-fly and rendered. Options: |
| 107 | + |
| 108 | +- **Mintlify Component**: Create a `<Contributors />` React component |
| 109 | +- **Markdown Injection**: Renderer injects contributor HTML directly into .mdx files |
| 110 | + |
| 111 | +Example output: |
| 112 | + |
| 113 | +```html |
| 114 | +<div class="contributors"> |
| 115 | + <a href="https://github.com/username"><img src="https://github.com/username.png" alt="username" /></a> |
| 116 | + ... |
| 117 | +</div> |
| 118 | +``` |
| 119 | + |
| 120 | +## Files to Create/Modify |
| 121 | + |
| 122 | +| File | Action | |
| 123 | +|------|--------| |
| 124 | +| `src/CloudNimble.DotNetDocs.Core/Contributor.cs` | Create | |
| 125 | +| `src/CloudNimble.DotNetDocs.Core/GitHelper.cs` | Create | |
| 126 | +| `src/CloudNimble.DotNetDocs.Core/DocEntity.cs` | Add `Contributors` property | |
| 127 | +| `src/CloudNimble.DotNetDocs.Core/ProjectContext.cs` | Add `ContributorsEnabled` flag | |
| 128 | +| `src/CloudNimble.DotNetDocs.Plugins.GitHub/GitHubContributorEnricher.cs` | Create | |
| 129 | + |
| 130 | +## Open Items |
| 131 | + |
| 132 | +- [ ] Define Mintlify vs Markdown rendering approach |
| 133 | +- [ ] Determine where contributor section appears in rendered output (top, bottom, sidebar?) |
| 134 | +- [ ] Handle case where GitHub API is unavailable (graceful degradation to name/email only?) |
| 135 | +- [ ] Consider caching the contributor cache to disk to avoid API calls on every build |
| 136 | + |
| 137 | +## Verification |
| 138 | + |
| 139 | +1. `dotnet build src -c Debug` - ensure it compiles |
| 140 | +2. Run against a project with git history |
| 141 | +3. Debug/inspect that `DocType.Contributors` is populated |
| 142 | +4. Check rendered .mdx output includes contributor avatars |
0 commit comments