From fd98059cfeb0d09eee2d59fa2d6b4105784ef1fa Mon Sep 17 00:00:00 2001 From: Stijnus Date: Thu, 23 Jan 2025 16:02:50 +0100 Subject: [PATCH] connection github enhancements --- .../settings/connections/ConnectionsTab.tsx | 170 ++++++++++++++++-- app/components/settings/debug/DebugTab.tsx | 164 ++++++++++++++++- app/routes/api.system.git-info.ts | 26 +++ package.json | 1 + pages/api/system/git-info.ts | 30 ++++ pnpm-lock.yaml | 57 +++++- 6 files changed, 422 insertions(+), 26 deletions(-) create mode 100644 app/routes/api.system.git-info.ts create mode 100644 pages/api/system/git-info.ts diff --git a/app/components/settings/connections/ConnectionsTab.tsx b/app/components/settings/connections/ConnectionsTab.tsx index f3506f92..2c7c6cbb 100644 --- a/app/components/settings/connections/ConnectionsTab.tsx +++ b/app/components/settings/connections/ConnectionsTab.tsx @@ -8,11 +8,34 @@ interface GitHubUserResponse { login: string; avatar_url: string; html_url: string; + name: string; + bio: string; + public_repos: number; + followers: number; + following: number; +} + +interface GitHubRepoInfo { + name: string; + full_name: string; + html_url: string; + description: string; + stargazers_count: number; + forks_count: number; + default_branch: string; + updated_at: string; +} + +interface GitHubStats { + repos: GitHubRepoInfo[]; + totalStars: number; + totalForks: number; } interface GitHubConnection { user: GitHubUserResponse | null; token: string; + stats?: GitHubStats; } export default function ConnectionsTab() { @@ -22,18 +45,59 @@ export default function ConnectionsTab() { }); const [isLoading, setIsLoading] = useState(true); const [isConnecting, setIsConnecting] = useState(false); + const [isFetchingStats, setIsFetchingStats] = useState(false); // Load saved connection on mount useEffect(() => { const savedConnection = localStorage.getItem('github_connection'); if (savedConnection) { - setConnection(JSON.parse(savedConnection)); + const parsed = JSON.parse(savedConnection); + setConnection(parsed); + if (parsed.user && parsed.token) { + fetchGitHubStats(parsed.token); + } } setIsLoading(false); }, []); + const fetchGitHubStats = async (token: string) => { + try { + setIsFetchingStats(true); + + // Fetch repositories + const reposResponse = await fetch('https://api.github.com/user/repos?sort=updated&per_page=10', { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (!reposResponse.ok) throw new Error('Failed to fetch repositories'); + + const repos = await reposResponse.json() as GitHubRepoInfo[]; + + // Calculate total stats + const totalStars = repos.reduce((acc, repo) => acc + repo.stargazers_count, 0); + const totalForks = repos.reduce((acc, repo) => acc + repo.forks_count, 0); + + setConnection(prev => ({ + ...prev, + stats: { + repos, + totalStars, + totalForks, + }, + })); + + } catch (error) { + logStore.logError('Failed to fetch GitHub stats', { error }); + toast.error('Failed to fetch GitHub statistics'); + } finally { + setIsFetchingStats(false); + } + }; + const fetchGithubUser = async (token: string) => { try { setIsConnecting(true); @@ -44,16 +108,18 @@ export default function ConnectionsTab() { }, }); - if (!response.ok) { - throw new Error('Invalid token or unauthorized'); - } + if (!response.ok) throw new Error('Invalid token or unauthorized'); - const data = (await response.json()) as GitHubUserResponse; + const data = await response.json() as GitHubUserResponse; const newConnection = { user: data, token }; // Save connection localStorage.setItem('github_connection', JSON.stringify(newConnection)); setConnection(newConnection); + + // Fetch additional stats + await fetchGitHubStats(token); + toast.success('Successfully connected to GitHub'); } catch (error) { logStore.logError('Failed to authenticate with GitHub', { error }); @@ -75,16 +141,7 @@ export default function ConnectionsTab() { toast.success('Disconnected from GitHub'); }; - if (isLoading) { - return ( -
-
-
- Loading... -
-
- ); - } + if (isLoading) return ; return (
@@ -200,9 +257,92 @@ export default function ConnectionsTab() { )}
+ + {connection.user && connection.stats && ( +
+
+ {connection.user.login} +
+

+ {connection.user.name || connection.user.login} +

+ {connection.user.bio && ( +

{connection.user.bio}

+ )} +
+ +
+ {connection.user.followers} followers + + +
+ {connection.stats.totalStars} stars + + +
+ {connection.stats.totalForks} forks + +
+
+
+ +

+ Recent Repositories +

+
); } + +function LoadingSpinner() { + return ( +
+
+
+ Loading... +
+
+ ); +} diff --git a/app/components/settings/debug/DebugTab.tsx b/app/components/settings/debug/DebugTab.tsx index 9238fb85..9ee8827a 100644 --- a/app/components/settings/debug/DebugTab.tsx +++ b/app/components/settings/debug/DebugTab.tsx @@ -84,6 +84,7 @@ interface SystemInfo { } interface WebAppInfo { + // Local WebApp Info name: string; version: string; description: string; @@ -91,6 +92,33 @@ interface WebAppInfo { nodeVersion: string; dependencies: { [key: string]: string }; devDependencies: { [key: string]: string }; + // Build Info + buildTime?: string; + buildNumber?: string; + environment?: string; + // Git Info + gitInfo?: { + branch: string; + commit: string; + commitTime: string; + author: string; + remoteUrl: string; + }; + // GitHub Repository Info + repoInfo?: { + name: string; + fullName: string; + description: string; + stars: number; + forks: number; + openIssues: number; + defaultBranch: string; + lastUpdate: string; + owner: { + login: string; + avatarUrl: string; + }; + }; } export default function DebugTab() { @@ -298,14 +326,54 @@ export default function DebugTab() { try { setLoading((prev) => ({ ...prev, webAppInfo: true })); - const response = await fetch('/api/system/app-info'); - - if (!response.ok) { + // Fetch local app info + const appInfoResponse = await fetch('/api/system/app-info'); + if (!appInfoResponse.ok) { throw new Error('Failed to fetch webapp info'); } + const appData = await appInfoResponse.json(); - const data = await response.json(); - setWebAppInfo(data as WebAppInfo); + // Fetch git info + const gitInfoResponse = await fetch('/api/system/git-info'); + let gitInfo = null; + if (gitInfoResponse.ok) { + gitInfo = await gitInfoResponse.json(); + } + + // Fetch GitHub repository info + const repoInfoResponse = await fetch('https://api.github.com/repos/stackblitz-labs/bolt.diy'); + let repoInfo = null; + if (repoInfoResponse.ok) { + const repoData = await repoInfoResponse.json(); + repoInfo = { + name: repoData.name, + fullName: repoData.full_name, + description: repoData.description, + stars: repoData.stargazers_count, + forks: repoData.forks_count, + openIssues: repoData.open_issues_count, + defaultBranch: repoData.default_branch, + lastUpdate: repoData.updated_at, + owner: { + login: repoData.owner.login, + avatarUrl: repoData.owner.avatar_url, + }, + }; + } + + // Get build info from environment variables or config + const buildInfo = { + buildTime: process.env.NEXT_PUBLIC_BUILD_TIME || new Date().toISOString(), + buildNumber: process.env.NEXT_PUBLIC_BUILD_NUMBER || 'development', + environment: process.env.NEXT_PUBLIC_ENV || 'development', + }; + + setWebAppInfo({ + ...appData, + ...buildInfo, + gitInfo, + repoInfo, + }); } catch (error) { console.error('Failed to fetch webapp info:', error); toast.error('Failed to fetch webapp information'); @@ -895,6 +963,27 @@ export default function DebugTab() { Node Version: {webAppInfo.nodeVersion}
+ {webAppInfo.buildTime && ( +
+
+ Build Time: + {webAppInfo.buildTime} +
+ )} + {webAppInfo.buildNumber && ( +
+
+ Build Number: + {webAppInfo.buildNumber} +
+ )} + {webAppInfo.environment && ( +
+
+ Environment: + {webAppInfo.environment} +
+ )}
@@ -912,6 +1001,71 @@ export default function DebugTab() { ))}
+ {webAppInfo.gitInfo && ( +
+
+
+ Git Info: +
+
+
+ Branch: {webAppInfo.gitInfo.branch} +
+
+ Commit: {webAppInfo.gitInfo.commit} +
+
+ Commit Time: {webAppInfo.gitInfo.commitTime} +
+
+ Author: {webAppInfo.gitInfo.author} +
+
+ Remote URL: {webAppInfo.gitInfo.remoteUrl} +
+
+
+ )} + {webAppInfo.repoInfo && ( +
+
+
+ GitHub Repository: +
+
+
+ Name: {webAppInfo.repoInfo.name} +
+
+ Full Name: {webAppInfo.repoInfo.fullName} +
+
+ Description: {webAppInfo.repoInfo.description} +
+
+ Stars: {webAppInfo.repoInfo.stars} +
+
+ Forks: {webAppInfo.repoInfo.forks} +
+
+ Open Issues: {webAppInfo.repoInfo.openIssues} +
+
+ Default Branch: {webAppInfo.repoInfo.defaultBranch} +
+
+ Last Update: {webAppInfo.repoInfo.lastUpdate} +
+
+ Owner: {webAppInfo.repoInfo.owner.login} +
+
+ Avatar URL: {webAppInfo.repoInfo.owner.avatarUrl} +
+
+
+ )}
) : ( diff --git a/app/routes/api.system.git-info.ts b/app/routes/api.system.git-info.ts new file mode 100644 index 00000000..e30c2626 --- /dev/null +++ b/app/routes/api.system.git-info.ts @@ -0,0 +1,26 @@ +import { json } from '@remix-run/node'; +import type { LoaderFunctionArgs } from '@remix-run/node'; +import { execSync } from 'child_process'; + +export async function loader({ request }: LoaderFunctionArgs) { + try { + const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); + const commit = execSync('git rev-parse --short HEAD').toString().trim(); + const lastCommitMessage = execSync('git log -1 --pretty=%B').toString().trim(); + + return json({ + branch, + commit, + lastCommitMessage, + timestamp: new Date().toISOString(), + }); + } catch (error) { + return json( + { + error: 'Failed to fetch git information', + details: error instanceof Error ? error.message : 'Unknown error', + }, + { status: 500 }, + ); + } +} diff --git a/package.json b/package.json index 8a2d24e2..6ea47a5d 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "@radix-ui/react-tooltip": "^1.1.4", "@remix-run/cloudflare": "^2.15.0", "@remix-run/cloudflare-pages": "^2.15.0", + "@remix-run/node": "^2.15.2", "@remix-run/react": "^2.15.0", "@uiw/codemirror-theme-vscode": "^4.23.6", "@unocss/reset": "^0.61.9", diff --git a/pages/api/system/git-info.ts b/pages/api/system/git-info.ts new file mode 100644 index 00000000..02619108 --- /dev/null +++ b/pages/api/system/git-info.ts @@ -0,0 +1,30 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { execSync } from 'child_process'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'GET') { + return res.status(405).json({ message: 'Method not allowed' }); + } + + try { + // Get git information using git commands + const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); + const commit = execSync('git rev-parse HEAD').toString().trim(); + const commitTime = execSync('git log -1 --format=%cd').toString().trim(); + const author = execSync('git log -1 --format=%an').toString().trim(); + const remoteUrl = execSync('git config --get remote.origin.url').toString().trim(); + + const gitInfo = { + branch, + commit, + commitTime, + author, + remoteUrl, + }; + + res.status(200).json(gitInfo); + } catch (error) { + console.error('Failed to get git information:', error); + res.status(500).json({ message: 'Failed to get git information' }); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3b6c4bf..b1714f4d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,6 +122,9 @@ importers: '@remix-run/cloudflare-pages': specifier: ^2.15.0 version: 2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.7.2) + '@remix-run/node': + specifier: ^2.15.2 + version: 2.15.2(typescript@5.7.2) '@remix-run/react': specifier: ^2.15.0 version: 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) @@ -241,10 +244,10 @@ importers: version: 4.0.0 remix-island: specifier: ^0.2.0 - version: 0.2.0(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@remix-run/server-runtime@2.15.0(typescript@5.7.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.2.0(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@remix-run/server-runtime@2.15.2(typescript@5.7.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) remix-utils: specifier: ^7.7.0 - version: 7.7.0(@remix-run/cloudflare@2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.7.2))(@remix-run/node@2.15.0(typescript@5.7.2))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@remix-run/router@1.21.0)(react@18.3.1)(zod@3.23.8) + version: 7.7.0(@remix-run/cloudflare@2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.7.2))(@remix-run/node@2.15.2(typescript@5.7.2))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@remix-run/router@1.21.0)(react@18.3.1)(zod@3.23.8) shiki: specifier: ^1.24.0 version: 1.24.0 @@ -2294,6 +2297,15 @@ packages: typescript: optional: true + '@remix-run/node@2.15.2': + resolution: {integrity: sha512-NS/h5uxje7DYCNgcKqKAiUhf0r2HVnoYUBWLyIIMmCUP1ddWurBP6xTPcWzGhEvV/EvguniYi1wJZ5+X8sonWw==} + engines: {node: '>=18.0.0'} + peerDependencies: + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + '@remix-run/react@2.15.0': resolution: {integrity: sha512-puqDbi9N/WfaUhzDnw2pACXtCB7ukrtFJ9ILwpEuhlaTBpjefifJ89igokW+tt1ePphIFMivAm/YspcbZdCQsA==} engines: {node: '>=18.0.0'} @@ -2318,6 +2330,15 @@ packages: typescript: optional: true + '@remix-run/server-runtime@2.15.2': + resolution: {integrity: sha512-OqiPcvEnnU88B8b1LIWHHkQ3Tz2GDAmQ1RihFNQsbrFKpDsQLkw0lJlnfgKA/uHd0CEEacpfV7C9qqJT3V6Z2g==} + engines: {node: '>=18.0.0'} + peerDependencies: + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + '@remix-run/web-blob@3.1.0': resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==} @@ -8640,6 +8661,18 @@ snapshots: optionalDependencies: typescript: 5.7.2 + '@remix-run/node@2.15.2(typescript@5.7.2)': + dependencies: + '@remix-run/server-runtime': 2.15.2(typescript@5.7.2) + '@remix-run/web-fetch': 4.4.2 + '@web3-storage/multipart-parser': 1.0.0 + cookie-signature: 1.2.2 + source-map-support: 0.5.21 + stream-slice: 0.1.2 + undici: 6.21.0 + optionalDependencies: + typescript: 5.7.2 + '@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': dependencies: '@remix-run/router': 1.21.0 @@ -8666,6 +8699,18 @@ snapshots: optionalDependencies: typescript: 5.7.2 + '@remix-run/server-runtime@2.15.2(typescript@5.7.2)': + dependencies: + '@remix-run/router': 1.21.0 + '@types/cookie': 0.6.0 + '@web3-storage/multipart-parser': 1.0.0 + cookie: 0.6.0 + set-cookie-parser: 2.7.1 + source-map: 0.7.4 + turbo-stream: 2.4.0 + optionalDependencies: + typescript: 5.7.2 + '@remix-run/web-blob@3.1.0': dependencies: '@remix-run/web-stream': 1.1.0 @@ -12680,19 +12725,19 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 - remix-island@0.2.0(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@remix-run/server-runtime@2.15.0(typescript@5.7.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + remix-island@0.2.0(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@remix-run/server-runtime@2.15.2(typescript@5.7.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@remix-run/react': 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) - '@remix-run/server-runtime': 2.15.0(typescript@5.7.2) + '@remix-run/server-runtime': 2.15.2(typescript@5.7.2) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - remix-utils@7.7.0(@remix-run/cloudflare@2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.7.2))(@remix-run/node@2.15.0(typescript@5.7.2))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@remix-run/router@1.21.0)(react@18.3.1)(zod@3.23.8): + remix-utils@7.7.0(@remix-run/cloudflare@2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.7.2))(@remix-run/node@2.15.2(typescript@5.7.2))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@remix-run/router@1.21.0)(react@18.3.1)(zod@3.23.8): dependencies: type-fest: 4.30.0 optionalDependencies: '@remix-run/cloudflare': 2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.7.2) - '@remix-run/node': 2.15.0(typescript@5.7.2) + '@remix-run/node': 2.15.2(typescript@5.7.2) '@remix-run/react': 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@remix-run/router': 1.21.0 react: 18.3.1