From 98a55b83af4d2c8249166a9aa7bdd85199f79518 Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Fri, 6 Feb 2026 01:46:39 -0800 Subject: [PATCH] feat: add contributors list and update settings page - Introduced a new contributors.json file containing a list of contributors with their GitHub usernames. - Updated AboutSettings component to display the list of contributors dynamically. - Modified tsconfig.json to enable JSON module resolution for better imports. - Enhanced Cargo.toml to include JSON feature for reqwest. - Added a new Rust module to fetch and update the contributors list from GitHub. --- .../src/Settings/pages/AboutSettings.tsx | 28 +- packages/interface/src/contributors.json | 450 ++++++++++++++++++ packages/interface/tsconfig.json | 1 + xtask/Cargo.toml | 2 +- xtask/src/contributors.rs | 156 ++++++ xtask/src/main.rs | 6 + 6 files changed, 641 insertions(+), 2 deletions(-) create mode 100644 packages/interface/src/contributors.json create mode 100644 xtask/src/contributors.rs diff --git a/packages/interface/src/Settings/pages/AboutSettings.tsx b/packages/interface/src/Settings/pages/AboutSettings.tsx index ad813cf6b..5536bfdb9 100644 --- a/packages/interface/src/Settings/pages/AboutSettings.tsx +++ b/packages/interface/src/Settings/pages/AboutSettings.tsx @@ -3,6 +3,7 @@ import { Ball } from "@sd/assets/images"; import Orb from "../../components/Orb"; import { TopBarButton } from "@sd/ui"; import { GlobeHemisphereWest, GithubLogo, DiscordLogo } from "@phosphor-icons/react"; +import contributors from "../../contributors.json"; export function AboutSettings() { @@ -96,11 +97,36 @@ export function AboutSettings() { + {/* Contributors */} + +

+ {contributors.map((c, i) => ( + + {i > 0 && " · "} + + {c.name} + + + ))} +

+
+ {/* License */} , +} + +#[derive(Serialize)] +struct Contributor { + name: String, + github: String, +} + +/// Try to get a GitHub token from the environment or `gh` CLI +fn get_github_token() -> Option { + if let Ok(token) = std::env::var("GITHUB_TOKEN") { + return Some(token); + } + + std::process::Command::new("gh") + .args(["auth", "token"]) + .output() + .ok() + .and_then(|o| { + if o.status.success() { + String::from_utf8(o.stdout).ok().map(|s| s.trim().to_string()) + } else { + None + } + }) +} + +fn github_get( + client: &reqwest::blocking::Client, + url: &str, + token: Option<&str>, +) -> reqwest::blocking::RequestBuilder { + let mut req = client.get(url); + if let Some(token) = token { + req = req.bearer_auth(token); + } + req +} + +pub fn update(project_root: &Path) -> Result<()> { + println!("Fetching contributors from GitHub..."); + + let token = get_github_token(); + if token.is_some() { + println!(" using authenticated requests"); + } else { + println!(" no token found, using unauthenticated requests (may hit rate limits)"); + println!(" tip: install `gh` CLI and run `gh auth login` for higher limits"); + } + + let client = reqwest::blocking::Client::builder() + .user_agent("spacedrive-xtask") + .build() + .context("Failed to build HTTP client")?; + + // Paginate through all contributors + let mut all_contributors = Vec::new(); + let mut page = 1u32; + + loop { + let url = format!( + "https://api.github.com/repos/{}/contributors?per_page=100&page={}", + REPO, page + ); + + let resp: Vec = github_get(&client, &url, token.as_deref()) + .send() + .context("Failed to fetch contributors")? + .json() + .context("Failed to parse contributors response")?; + + if resp.is_empty() { + break; + } + + all_contributors.extend(resp); + page += 1; + } + + // Filter out bots and excluded accounts + let humans: Vec<_> = all_contributors + .iter() + .filter(|c| c.account_type == "User" && !EXCLUDED_LOGINS.contains(&c.login.as_str())) + .collect(); + + println!("Found {} contributors, resolving names...", humans.len()); + + let mut contributors = Vec::new(); + + for (i, contributor) in humans.iter().enumerate() { + let name = match resolve_name(&client, &contributor.login, token.as_deref()) { + Ok(Some(n)) => n, + _ => contributor.login.clone(), + }; + + contributors.push(Contributor { + name, + github: contributor.login.clone(), + }); + + // Progress indicator every 25 users + if (i + 1) % 25 == 0 { + println!(" resolved {}/{}", i + 1, humans.len()); + } + } + + println!(" resolved {}/{}", contributors.len(), humans.len()); + + let output_path = project_root.join(OUTPUT_PATH); + let json = serde_json::to_string_pretty(&contributors) + .context("Failed to serialize contributors")?; + + std::fs::write(&output_path, format!("{}\n", json)) + .context("Failed to write contributors.json")?; + + println!( + "Wrote {} contributors to {}", + contributors.len(), + OUTPUT_PATH + ); + + Ok(()) +} + +fn resolve_name( + client: &reqwest::blocking::Client, + login: &str, + token: Option<&str>, +) -> Result> { + let url = format!("https://api.github.com/users/{}", login); + let user: GitHubUser = github_get(client, &url, token) + .send() + .context("Failed to fetch user")? + .json() + .context("Failed to parse user response")?; + + Ok(user.name.filter(|n: &String| !n.is_empty())) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 3f3beb9c3..77a66d823 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -25,6 +25,7 @@ //! - No external tools required (except cargo/rustup) mod config; +mod contributors; mod native_deps; mod system; mod test_core; @@ -67,6 +68,7 @@ fn main() -> Result<()> { eprintln!(" build-ios Build sd-ios-core XCFramework for iOS devices and simulator"); eprintln!(" build-mobile Build sd-mobile-core for React Native iOS/Android"); eprintln!(" test-core Run all core integration tests with progress tracking"); + eprintln!(" update-contributors Fetch contributors from GitHub and update contributors.json"); eprintln!(); eprintln!("Examples:"); eprintln!(" cargo xtask setup # First time setup"); @@ -88,6 +90,10 @@ fn main() -> Result<()> { .unwrap_or(false); test_core_command(verbose)?; } + "update-contributors" => { + let project_root = find_workspace_root()?; + contributors::update(&project_root)?; + } _ => { eprintln!("Unknown command: {}", args[1]); eprintln!("Run 'cargo xtask' for usage information.");