connection github enhancements
Browse files- app/components/settings/connections/ConnectionsTab.tsx +155 -15
- app/components/settings/debug/DebugTab.tsx +159 -5
- app/routes/api.system.git-info.ts +26 -0
- package.json +1 -0
- pages/api/system/git-info.ts +30 -0
- pnpm-lock.yaml +51 -6
app/components/settings/connections/ConnectionsTab.tsx
CHANGED
@@ -8,11 +8,34 @@ interface GitHubUserResponse {
|
|
8 |
login: string;
|
9 |
avatar_url: string;
|
10 |
html_url: string;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
}
|
12 |
|
13 |
interface GitHubConnection {
|
14 |
user: GitHubUserResponse | null;
|
15 |
token: string;
|
|
|
16 |
}
|
17 |
|
18 |
export default function ConnectionsTab() {
|
@@ -22,18 +45,59 @@ export default function ConnectionsTab() {
|
|
22 |
});
|
23 |
const [isLoading, setIsLoading] = useState(true);
|
24 |
const [isConnecting, setIsConnecting] = useState(false);
|
|
|
25 |
|
26 |
// Load saved connection on mount
|
27 |
useEffect(() => {
|
28 |
const savedConnection = localStorage.getItem('github_connection');
|
29 |
|
30 |
if (savedConnection) {
|
31 |
-
|
|
|
|
|
|
|
|
|
32 |
}
|
33 |
|
34 |
setIsLoading(false);
|
35 |
}, []);
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
const fetchGithubUser = async (token: string) => {
|
38 |
try {
|
39 |
setIsConnecting(true);
|
@@ -44,16 +108,18 @@ export default function ConnectionsTab() {
|
|
44 |
},
|
45 |
});
|
46 |
|
47 |
-
if (!response.ok)
|
48 |
-
throw new Error('Invalid token or unauthorized');
|
49 |
-
}
|
50 |
|
51 |
-
const data =
|
52 |
const newConnection = { user: data, token };
|
53 |
|
54 |
// Save connection
|
55 |
localStorage.setItem('github_connection', JSON.stringify(newConnection));
|
56 |
setConnection(newConnection);
|
|
|
|
|
|
|
|
|
57 |
toast.success('Successfully connected to GitHub');
|
58 |
} catch (error) {
|
59 |
logStore.logError('Failed to authenticate with GitHub', { error });
|
@@ -75,16 +141,7 @@ export default function ConnectionsTab() {
|
|
75 |
toast.success('Disconnected from GitHub');
|
76 |
};
|
77 |
|
78 |
-
if (isLoading)
|
79 |
-
return (
|
80 |
-
<div className="flex items-center justify-center p-4">
|
81 |
-
<div className="flex items-center gap-2">
|
82 |
-
<div className="i-ph:spinner-gap-bold animate-spin w-4 h-4" />
|
83 |
-
<span className="text-bolt-elements-textSecondary">Loading...</span>
|
84 |
-
</div>
|
85 |
-
</div>
|
86 |
-
);
|
87 |
-
}
|
88 |
|
89 |
return (
|
90 |
<div className="space-y-4">
|
@@ -200,9 +257,92 @@ export default function ConnectionsTab() {
|
|
200 |
</span>
|
201 |
)}
|
202 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
</div>
|
204 |
</motion.div>
|
205 |
</div>
|
206 |
</div>
|
207 |
);
|
208 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
login: string;
|
9 |
avatar_url: string;
|
10 |
html_url: string;
|
11 |
+
name: string;
|
12 |
+
bio: string;
|
13 |
+
public_repos: number;
|
14 |
+
followers: number;
|
15 |
+
following: number;
|
16 |
+
}
|
17 |
+
|
18 |
+
interface GitHubRepoInfo {
|
19 |
+
name: string;
|
20 |
+
full_name: string;
|
21 |
+
html_url: string;
|
22 |
+
description: string;
|
23 |
+
stargazers_count: number;
|
24 |
+
forks_count: number;
|
25 |
+
default_branch: string;
|
26 |
+
updated_at: string;
|
27 |
+
}
|
28 |
+
|
29 |
+
interface GitHubStats {
|
30 |
+
repos: GitHubRepoInfo[];
|
31 |
+
totalStars: number;
|
32 |
+
totalForks: number;
|
33 |
}
|
34 |
|
35 |
interface GitHubConnection {
|
36 |
user: GitHubUserResponse | null;
|
37 |
token: string;
|
38 |
+
stats?: GitHubStats;
|
39 |
}
|
40 |
|
41 |
export default function ConnectionsTab() {
|
|
|
45 |
});
|
46 |
const [isLoading, setIsLoading] = useState(true);
|
47 |
const [isConnecting, setIsConnecting] = useState(false);
|
48 |
+
const [isFetchingStats, setIsFetchingStats] = useState(false);
|
49 |
|
50 |
// Load saved connection on mount
|
51 |
useEffect(() => {
|
52 |
const savedConnection = localStorage.getItem('github_connection');
|
53 |
|
54 |
if (savedConnection) {
|
55 |
+
const parsed = JSON.parse(savedConnection);
|
56 |
+
setConnection(parsed);
|
57 |
+
if (parsed.user && parsed.token) {
|
58 |
+
fetchGitHubStats(parsed.token);
|
59 |
+
}
|
60 |
}
|
61 |
|
62 |
setIsLoading(false);
|
63 |
}, []);
|
64 |
|
65 |
+
const fetchGitHubStats = async (token: string) => {
|
66 |
+
try {
|
67 |
+
setIsFetchingStats(true);
|
68 |
+
|
69 |
+
// Fetch repositories
|
70 |
+
const reposResponse = await fetch('https://api.github.com/user/repos?sort=updated&per_page=10', {
|
71 |
+
headers: {
|
72 |
+
Authorization: `Bearer ${token}`,
|
73 |
+
},
|
74 |
+
});
|
75 |
+
|
76 |
+
if (!reposResponse.ok) throw new Error('Failed to fetch repositories');
|
77 |
+
|
78 |
+
const repos = await reposResponse.json() as GitHubRepoInfo[];
|
79 |
+
|
80 |
+
// Calculate total stats
|
81 |
+
const totalStars = repos.reduce((acc, repo) => acc + repo.stargazers_count, 0);
|
82 |
+
const totalForks = repos.reduce((acc, repo) => acc + repo.forks_count, 0);
|
83 |
+
|
84 |
+
setConnection(prev => ({
|
85 |
+
...prev,
|
86 |
+
stats: {
|
87 |
+
repos,
|
88 |
+
totalStars,
|
89 |
+
totalForks,
|
90 |
+
},
|
91 |
+
}));
|
92 |
+
|
93 |
+
} catch (error) {
|
94 |
+
logStore.logError('Failed to fetch GitHub stats', { error });
|
95 |
+
toast.error('Failed to fetch GitHub statistics');
|
96 |
+
} finally {
|
97 |
+
setIsFetchingStats(false);
|
98 |
+
}
|
99 |
+
};
|
100 |
+
|
101 |
const fetchGithubUser = async (token: string) => {
|
102 |
try {
|
103 |
setIsConnecting(true);
|
|
|
108 |
},
|
109 |
});
|
110 |
|
111 |
+
if (!response.ok) throw new Error('Invalid token or unauthorized');
|
|
|
|
|
112 |
|
113 |
+
const data = await response.json() as GitHubUserResponse;
|
114 |
const newConnection = { user: data, token };
|
115 |
|
116 |
// Save connection
|
117 |
localStorage.setItem('github_connection', JSON.stringify(newConnection));
|
118 |
setConnection(newConnection);
|
119 |
+
|
120 |
+
// Fetch additional stats
|
121 |
+
await fetchGitHubStats(token);
|
122 |
+
|
123 |
toast.success('Successfully connected to GitHub');
|
124 |
} catch (error) {
|
125 |
logStore.logError('Failed to authenticate with GitHub', { error });
|
|
|
141 |
toast.success('Disconnected from GitHub');
|
142 |
};
|
143 |
|
144 |
+
if (isLoading) return <LoadingSpinner />;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
|
146 |
return (
|
147 |
<div className="space-y-4">
|
|
|
257 |
</span>
|
258 |
)}
|
259 |
</div>
|
260 |
+
|
261 |
+
{connection.user && connection.stats && (
|
262 |
+
<div className="mt-6 border-t border-[#E5E5E5] dark:border-[#1A1A1A] pt-6">
|
263 |
+
<div className="flex items-center gap-4 mb-6">
|
264 |
+
<img
|
265 |
+
src={connection.user.avatar_url}
|
266 |
+
alt={connection.user.login}
|
267 |
+
className="w-16 h-16 rounded-full"
|
268 |
+
/>
|
269 |
+
<div>
|
270 |
+
<h3 className="text-lg font-medium text-bolt-elements-textPrimary">
|
271 |
+
{connection.user.name || connection.user.login}
|
272 |
+
</h3>
|
273 |
+
{connection.user.bio && (
|
274 |
+
<p className="text-sm text-bolt-elements-textSecondary">{connection.user.bio}</p>
|
275 |
+
)}
|
276 |
+
<div className="flex gap-4 mt-2 text-sm text-bolt-elements-textSecondary">
|
277 |
+
<span className="flex items-center gap-1">
|
278 |
+
<div className="i-ph:users w-4 h-4" />
|
279 |
+
{connection.user.followers} followers
|
280 |
+
</span>
|
281 |
+
<span className="flex items-center gap-1">
|
282 |
+
<div className="i-ph:star w-4 h-4" />
|
283 |
+
{connection.stats.totalStars} stars
|
284 |
+
</span>
|
285 |
+
<span className="flex items-center gap-1">
|
286 |
+
<div className="i-ph:git-fork w-4 h-4" />
|
287 |
+
{connection.stats.totalForks} forks
|
288 |
+
</span>
|
289 |
+
</div>
|
290 |
+
</div>
|
291 |
+
</div>
|
292 |
+
|
293 |
+
<h4 className="text-sm font-medium text-bolt-elements-textPrimary mb-3">
|
294 |
+
Recent Repositories
|
295 |
+
</h4>
|
296 |
+
<div className="space-y-3">
|
297 |
+
{connection.stats.repos.map((repo) => (
|
298 |
+
<a
|
299 |
+
key={repo.full_name}
|
300 |
+
href={repo.html_url}
|
301 |
+
target="_blank"
|
302 |
+
rel="noopener noreferrer"
|
303 |
+
className="block p-3 rounded-lg bg-[#F8F8F8] dark:bg-[#1A1A1A] hover:bg-[#F0F0F0] dark:hover:bg-[#252525] transition-colors"
|
304 |
+
>
|
305 |
+
<div className="flex items-center justify-between">
|
306 |
+
<div>
|
307 |
+
<h5 className="text-sm font-medium text-bolt-elements-textPrimary">
|
308 |
+
{repo.name}
|
309 |
+
</h5>
|
310 |
+
{repo.description && (
|
311 |
+
<p className="text-xs text-bolt-elements-textSecondary mt-1">
|
312 |
+
{repo.description}
|
313 |
+
</p>
|
314 |
+
)}
|
315 |
+
</div>
|
316 |
+
<div className="flex items-center gap-3 text-xs text-bolt-elements-textSecondary">
|
317 |
+
<span className="flex items-center gap-1">
|
318 |
+
<div className="i-ph:star w-3 h-3" />
|
319 |
+
{repo.stargazers_count}
|
320 |
+
</span>
|
321 |
+
<span className="flex items-center gap-1">
|
322 |
+
<div className="i-ph:git-fork w-3 h-3" />
|
323 |
+
{repo.forks_count}
|
324 |
+
</span>
|
325 |
+
</div>
|
326 |
+
</div>
|
327 |
+
</a>
|
328 |
+
))}
|
329 |
+
</div>
|
330 |
+
</div>
|
331 |
+
)}
|
332 |
</div>
|
333 |
</motion.div>
|
334 |
</div>
|
335 |
</div>
|
336 |
);
|
337 |
}
|
338 |
+
|
339 |
+
function LoadingSpinner() {
|
340 |
+
return (
|
341 |
+
<div className="flex items-center justify-center p-4">
|
342 |
+
<div className="flex items-center gap-2">
|
343 |
+
<div className="i-ph:spinner-gap-bold animate-spin w-4 h-4" />
|
344 |
+
<span className="text-bolt-elements-textSecondary">Loading...</span>
|
345 |
+
</div>
|
346 |
+
</div>
|
347 |
+
);
|
348 |
+
}
|
app/components/settings/debug/DebugTab.tsx
CHANGED
@@ -84,6 +84,7 @@ interface SystemInfo {
|
|
84 |
}
|
85 |
|
86 |
interface WebAppInfo {
|
|
|
87 |
name: string;
|
88 |
version: string;
|
89 |
description: string;
|
@@ -91,6 +92,33 @@ interface WebAppInfo {
|
|
91 |
nodeVersion: string;
|
92 |
dependencies: { [key: string]: string };
|
93 |
devDependencies: { [key: string]: string };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
}
|
95 |
|
96 |
export default function DebugTab() {
|
@@ -298,14 +326,54 @@ export default function DebugTab() {
|
|
298 |
try {
|
299 |
setLoading((prev) => ({ ...prev, webAppInfo: true }));
|
300 |
|
301 |
-
|
302 |
-
|
303 |
-
if (!
|
304 |
throw new Error('Failed to fetch webapp info');
|
305 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
306 |
|
307 |
-
|
308 |
-
|
|
|
|
|
|
|
|
|
309 |
} catch (error) {
|
310 |
console.error('Failed to fetch webapp info:', error);
|
311 |
toast.error('Failed to fetch webapp information');
|
@@ -895,6 +963,27 @@ export default function DebugTab() {
|
|
895 |
<span className="text-bolt-elements-textSecondary">Node Version: </span>
|
896 |
<span className="text-bolt-elements-textPrimary">{webAppInfo.nodeVersion}</span>
|
897 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
898 |
</div>
|
899 |
<div className="space-y-2">
|
900 |
<div className="text-sm">
|
@@ -912,6 +1001,71 @@ export default function DebugTab() {
|
|
912 |
))}
|
913 |
</div>
|
914 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
915 |
</div>
|
916 |
</div>
|
917 |
) : (
|
|
|
84 |
}
|
85 |
|
86 |
interface WebAppInfo {
|
87 |
+
// Local WebApp Info
|
88 |
name: string;
|
89 |
version: string;
|
90 |
description: string;
|
|
|
92 |
nodeVersion: string;
|
93 |
dependencies: { [key: string]: string };
|
94 |
devDependencies: { [key: string]: string };
|
95 |
+
// Build Info
|
96 |
+
buildTime?: string;
|
97 |
+
buildNumber?: string;
|
98 |
+
environment?: string;
|
99 |
+
// Git Info
|
100 |
+
gitInfo?: {
|
101 |
+
branch: string;
|
102 |
+
commit: string;
|
103 |
+
commitTime: string;
|
104 |
+
author: string;
|
105 |
+
remoteUrl: string;
|
106 |
+
};
|
107 |
+
// GitHub Repository Info
|
108 |
+
repoInfo?: {
|
109 |
+
name: string;
|
110 |
+
fullName: string;
|
111 |
+
description: string;
|
112 |
+
stars: number;
|
113 |
+
forks: number;
|
114 |
+
openIssues: number;
|
115 |
+
defaultBranch: string;
|
116 |
+
lastUpdate: string;
|
117 |
+
owner: {
|
118 |
+
login: string;
|
119 |
+
avatarUrl: string;
|
120 |
+
};
|
121 |
+
};
|
122 |
}
|
123 |
|
124 |
export default function DebugTab() {
|
|
|
326 |
try {
|
327 |
setLoading((prev) => ({ ...prev, webAppInfo: true }));
|
328 |
|
329 |
+
// Fetch local app info
|
330 |
+
const appInfoResponse = await fetch('/api/system/app-info');
|
331 |
+
if (!appInfoResponse.ok) {
|
332 |
throw new Error('Failed to fetch webapp info');
|
333 |
}
|
334 |
+
const appData = await appInfoResponse.json();
|
335 |
+
|
336 |
+
// Fetch git info
|
337 |
+
const gitInfoResponse = await fetch('/api/system/git-info');
|
338 |
+
let gitInfo = null;
|
339 |
+
if (gitInfoResponse.ok) {
|
340 |
+
gitInfo = await gitInfoResponse.json();
|
341 |
+
}
|
342 |
+
|
343 |
+
// Fetch GitHub repository info
|
344 |
+
const repoInfoResponse = await fetch('https://api.github.com/repos/stackblitz-labs/bolt.diy');
|
345 |
+
let repoInfo = null;
|
346 |
+
if (repoInfoResponse.ok) {
|
347 |
+
const repoData = await repoInfoResponse.json();
|
348 |
+
repoInfo = {
|
349 |
+
name: repoData.name,
|
350 |
+
fullName: repoData.full_name,
|
351 |
+
description: repoData.description,
|
352 |
+
stars: repoData.stargazers_count,
|
353 |
+
forks: repoData.forks_count,
|
354 |
+
openIssues: repoData.open_issues_count,
|
355 |
+
defaultBranch: repoData.default_branch,
|
356 |
+
lastUpdate: repoData.updated_at,
|
357 |
+
owner: {
|
358 |
+
login: repoData.owner.login,
|
359 |
+
avatarUrl: repoData.owner.avatar_url,
|
360 |
+
},
|
361 |
+
};
|
362 |
+
}
|
363 |
+
|
364 |
+
// Get build info from environment variables or config
|
365 |
+
const buildInfo = {
|
366 |
+
buildTime: process.env.NEXT_PUBLIC_BUILD_TIME || new Date().toISOString(),
|
367 |
+
buildNumber: process.env.NEXT_PUBLIC_BUILD_NUMBER || 'development',
|
368 |
+
environment: process.env.NEXT_PUBLIC_ENV || 'development',
|
369 |
+
};
|
370 |
|
371 |
+
setWebAppInfo({
|
372 |
+
...appData,
|
373 |
+
...buildInfo,
|
374 |
+
gitInfo,
|
375 |
+
repoInfo,
|
376 |
+
});
|
377 |
} catch (error) {
|
378 |
console.error('Failed to fetch webapp info:', error);
|
379 |
toast.error('Failed to fetch webapp information');
|
|
|
963 |
<span className="text-bolt-elements-textSecondary">Node Version: </span>
|
964 |
<span className="text-bolt-elements-textPrimary">{webAppInfo.nodeVersion}</span>
|
965 |
</div>
|
966 |
+
{webAppInfo.buildTime && (
|
967 |
+
<div className="text-sm flex items-center gap-2">
|
968 |
+
<div className="i-ph:calendar text-bolt-elements-textSecondary w-4 h-4" />
|
969 |
+
<span className="text-bolt-elements-textSecondary">Build Time: </span>
|
970 |
+
<span className="text-bolt-elements-textPrimary">{webAppInfo.buildTime}</span>
|
971 |
+
</div>
|
972 |
+
)}
|
973 |
+
{webAppInfo.buildNumber && (
|
974 |
+
<div className="text-sm flex items-center gap-2">
|
975 |
+
<div className="i-ph:hash text-bolt-elements-textSecondary w-4 h-4" />
|
976 |
+
<span className="text-bolt-elements-textSecondary">Build Number: </span>
|
977 |
+
<span className="text-bolt-elements-textPrimary">{webAppInfo.buildNumber}</span>
|
978 |
+
</div>
|
979 |
+
)}
|
980 |
+
{webAppInfo.environment && (
|
981 |
+
<div className="text-sm flex items-center gap-2">
|
982 |
+
<div className="i-ph:cloud text-bolt-elements-textSecondary w-4 h-4" />
|
983 |
+
<span className="text-bolt-elements-textSecondary">Environment: </span>
|
984 |
+
<span className="text-bolt-elements-textPrimary">{webAppInfo.environment}</span>
|
985 |
+
</div>
|
986 |
+
)}
|
987 |
</div>
|
988 |
<div className="space-y-2">
|
989 |
<div className="text-sm">
|
|
|
1001 |
))}
|
1002 |
</div>
|
1003 |
</div>
|
1004 |
+
{webAppInfo.gitInfo && (
|
1005 |
+
<div className="text-sm">
|
1006 |
+
<div className="flex items-center gap-2 mb-2">
|
1007 |
+
<div className="i-ph:git-branch text-bolt-elements-textSecondary w-4 h-4" />
|
1008 |
+
<span className="text-bolt-elements-textSecondary">Git Info:</span>
|
1009 |
+
</div>
|
1010 |
+
<div className="pl-6 space-y-1">
|
1011 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1012 |
+
Branch: {webAppInfo.gitInfo.branch}
|
1013 |
+
</div>
|
1014 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1015 |
+
Commit: {webAppInfo.gitInfo.commit}
|
1016 |
+
</div>
|
1017 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1018 |
+
Commit Time: {webAppInfo.gitInfo.commitTime}
|
1019 |
+
</div>
|
1020 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1021 |
+
Author: {webAppInfo.gitInfo.author}
|
1022 |
+
</div>
|
1023 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1024 |
+
Remote URL: {webAppInfo.gitInfo.remoteUrl}
|
1025 |
+
</div>
|
1026 |
+
</div>
|
1027 |
+
</div>
|
1028 |
+
)}
|
1029 |
+
{webAppInfo.repoInfo && (
|
1030 |
+
<div className="text-sm">
|
1031 |
+
<div className="flex items-center gap-2 mb-2">
|
1032 |
+
<div className="i-ph:github text-bolt-elements-textSecondary w-4 h-4" />
|
1033 |
+
<span className="text-bolt-elements-textSecondary">GitHub Repository:</span>
|
1034 |
+
</div>
|
1035 |
+
<div className="pl-6 space-y-1">
|
1036 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1037 |
+
Name: {webAppInfo.repoInfo.name}
|
1038 |
+
</div>
|
1039 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1040 |
+
Full Name: {webAppInfo.repoInfo.fullName}
|
1041 |
+
</div>
|
1042 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1043 |
+
Description: {webAppInfo.repoInfo.description}
|
1044 |
+
</div>
|
1045 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1046 |
+
Stars: {webAppInfo.repoInfo.stars}
|
1047 |
+
</div>
|
1048 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1049 |
+
Forks: {webAppInfo.repoInfo.forks}
|
1050 |
+
</div>
|
1051 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1052 |
+
Open Issues: {webAppInfo.repoInfo.openIssues}
|
1053 |
+
</div>
|
1054 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1055 |
+
Default Branch: {webAppInfo.repoInfo.defaultBranch}
|
1056 |
+
</div>
|
1057 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1058 |
+
Last Update: {webAppInfo.repoInfo.lastUpdate}
|
1059 |
+
</div>
|
1060 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1061 |
+
Owner: {webAppInfo.repoInfo.owner.login}
|
1062 |
+
</div>
|
1063 |
+
<div className="text-xs text-bolt-elements-textPrimary">
|
1064 |
+
Avatar URL: {webAppInfo.repoInfo.owner.avatarUrl}
|
1065 |
+
</div>
|
1066 |
+
</div>
|
1067 |
+
</div>
|
1068 |
+
)}
|
1069 |
</div>
|
1070 |
</div>
|
1071 |
) : (
|
app/routes/api.system.git-info.ts
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { json } from '@remix-run/node';
|
2 |
+
import type { LoaderFunctionArgs } from '@remix-run/node';
|
3 |
+
import { execSync } from 'child_process';
|
4 |
+
|
5 |
+
export async function loader({ request }: LoaderFunctionArgs) {
|
6 |
+
try {
|
7 |
+
const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
|
8 |
+
const commit = execSync('git rev-parse --short HEAD').toString().trim();
|
9 |
+
const lastCommitMessage = execSync('git log -1 --pretty=%B').toString().trim();
|
10 |
+
|
11 |
+
return json({
|
12 |
+
branch,
|
13 |
+
commit,
|
14 |
+
lastCommitMessage,
|
15 |
+
timestamp: new Date().toISOString(),
|
16 |
+
});
|
17 |
+
} catch (error) {
|
18 |
+
return json(
|
19 |
+
{
|
20 |
+
error: 'Failed to fetch git information',
|
21 |
+
details: error instanceof Error ? error.message : 'Unknown error',
|
22 |
+
},
|
23 |
+
{ status: 500 },
|
24 |
+
);
|
25 |
+
}
|
26 |
+
}
|
package.json
CHANGED
@@ -67,6 +67,7 @@
|
|
67 |
"@radix-ui/react-tooltip": "^1.1.4",
|
68 |
"@remix-run/cloudflare": "^2.15.0",
|
69 |
"@remix-run/cloudflare-pages": "^2.15.0",
|
|
|
70 |
"@remix-run/react": "^2.15.0",
|
71 |
"@uiw/codemirror-theme-vscode": "^4.23.6",
|
72 |
"@unocss/reset": "^0.61.9",
|
|
|
67 |
"@radix-ui/react-tooltip": "^1.1.4",
|
68 |
"@remix-run/cloudflare": "^2.15.0",
|
69 |
"@remix-run/cloudflare-pages": "^2.15.0",
|
70 |
+
"@remix-run/node": "^2.15.2",
|
71 |
"@remix-run/react": "^2.15.0",
|
72 |
"@uiw/codemirror-theme-vscode": "^4.23.6",
|
73 |
"@unocss/reset": "^0.61.9",
|
pages/api/system/git-info.ts
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NextApiRequest, NextApiResponse } from 'next';
|
2 |
+
import { execSync } from 'child_process';
|
3 |
+
|
4 |
+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
5 |
+
if (req.method !== 'GET') {
|
6 |
+
return res.status(405).json({ message: 'Method not allowed' });
|
7 |
+
}
|
8 |
+
|
9 |
+
try {
|
10 |
+
// Get git information using git commands
|
11 |
+
const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
|
12 |
+
const commit = execSync('git rev-parse HEAD').toString().trim();
|
13 |
+
const commitTime = execSync('git log -1 --format=%cd').toString().trim();
|
14 |
+
const author = execSync('git log -1 --format=%an').toString().trim();
|
15 |
+
const remoteUrl = execSync('git config --get remote.origin.url').toString().trim();
|
16 |
+
|
17 |
+
const gitInfo = {
|
18 |
+
branch,
|
19 |
+
commit,
|
20 |
+
commitTime,
|
21 |
+
author,
|
22 |
+
remoteUrl,
|
23 |
+
};
|
24 |
+
|
25 |
+
res.status(200).json(gitInfo);
|
26 |
+
} catch (error) {
|
27 |
+
console.error('Failed to get git information:', error);
|
28 |
+
res.status(500).json({ message: 'Failed to get git information' });
|
29 |
+
}
|
30 |
+
}
|
pnpm-lock.yaml
CHANGED
@@ -122,6 +122,9 @@ importers:
|
|
122 |
'@remix-run/cloudflare-pages':
|
123 |
specifier: ^2.15.0
|
124 |
version: 2.15.0(@cloudflare/[email protected])([email protected])
|
|
|
|
|
|
|
125 |
'@remix-run/react':
|
126 |
specifier: ^2.15.0
|
127 |
version: 2.15.0([email protected]([email protected]))([email protected])([email protected])
|
@@ -241,10 +244,10 @@ importers:
|
|
241 |
version: 4.0.0
|
242 |
remix-island:
|
243 |
specifier: ^0.2.0
|
244 |
-
version: 0.2.0(@remix-run/[email protected]([email protected]([email protected]))([email protected])([email protected]))(@remix-run/[email protected].
|
245 |
remix-utils:
|
246 |
specifier: ^7.7.0
|
247 |
-
version: 7.7.0(@remix-run/[email protected](@cloudflare/[email protected])([email protected]))(@remix-run/[email protected].
|
248 |
shiki:
|
249 |
specifier: ^1.24.0
|
250 |
version: 1.24.0
|
@@ -2294,6 +2297,15 @@ packages:
|
|
2294 |
typescript:
|
2295 |
optional: true
|
2296 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2297 |
'@remix-run/[email protected]':
|
2298 |
resolution: {integrity: sha512-puqDbi9N/WfaUhzDnw2pACXtCB7ukrtFJ9ILwpEuhlaTBpjefifJ89igokW+tt1ePphIFMivAm/YspcbZdCQsA==}
|
2299 |
engines: {node: '>=18.0.0'}
|
@@ -2318,6 +2330,15 @@ packages:
|
|
2318 |
typescript:
|
2319 |
optional: true
|
2320 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2321 |
'@remix-run/[email protected]':
|
2322 |
resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==}
|
2323 |
|
@@ -8640,6 +8661,18 @@ snapshots:
|
|
8640 |
optionalDependencies:
|
8641 |
typescript: 5.7.2
|
8642 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8643 |
'@remix-run/[email protected]([email protected]([email protected]))([email protected])([email protected])':
|
8644 |
dependencies:
|
8645 |
'@remix-run/router': 1.21.0
|
@@ -8666,6 +8699,18 @@ snapshots:
|
|
8666 |
optionalDependencies:
|
8667 |
typescript: 5.7.2
|
8668 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8669 |
'@remix-run/[email protected]':
|
8670 |
dependencies:
|
8671 |
'@remix-run/web-stream': 1.1.0
|
@@ -12680,19 +12725,19 @@ snapshots:
|
|
12680 |
mdast-util-to-markdown: 2.1.2
|
12681 |
unified: 11.0.5
|
12682 |
|
12683 |
-
[email protected](@remix-run/[email protected]([email protected]([email protected]))([email protected])([email protected]))(@remix-run/[email protected].
|
12684 |
dependencies:
|
12685 |
'@remix-run/react': 2.15.0([email protected]([email protected]))([email protected])([email protected])
|
12686 |
-
'@remix-run/server-runtime': 2.15.
|
12687 |
react: 18.3.1
|
12688 |
react-dom: 18.3.1([email protected])
|
12689 |
|
12690 |
-
[email protected](@remix-run/[email protected](@cloudflare/[email protected])([email protected]))(@remix-run/[email protected].
|
12691 |
dependencies:
|
12692 |
type-fest: 4.30.0
|
12693 |
optionalDependencies:
|
12694 |
'@remix-run/cloudflare': 2.15.0(@cloudflare/[email protected])([email protected])
|
12695 |
-
'@remix-run/node': 2.15.
|
12696 |
'@remix-run/react': 2.15.0([email protected]([email protected]))([email protected])([email protected])
|
12697 |
'@remix-run/router': 1.21.0
|
12698 |
react: 18.3.1
|
|
|
122 |
'@remix-run/cloudflare-pages':
|
123 |
specifier: ^2.15.0
|
124 |
version: 2.15.0(@cloudflare/[email protected])([email protected])
|
125 |
+
'@remix-run/node':
|
126 |
+
specifier: ^2.15.2
|
127 |
+
version: 2.15.2([email protected])
|
128 |
'@remix-run/react':
|
129 |
specifier: ^2.15.0
|
130 |
version: 2.15.0([email protected]([email protected]))([email protected])([email protected])
|
|
|
244 |
version: 4.0.0
|
245 |
remix-island:
|
246 |
specifier: ^0.2.0
|
247 |
+
version: 0.2.0(@remix-run/[email protected]([email protected]([email protected]))([email protected])([email protected]))(@remix-run/[email protected].2([email protected]))([email protected]([email protected]))([email protected])
|
248 |
remix-utils:
|
249 |
specifier: ^7.7.0
|
250 |
+
version: 7.7.0(@remix-run/[email protected](@cloudflare/[email protected])([email protected]))(@remix-run/[email protected].2([email protected]))(@remix-run/[email protected]([email protected]([email protected]))([email protected])([email protected]))(@remix-run/[email protected])([email protected])([email protected])
|
251 |
shiki:
|
252 |
specifier: ^1.24.0
|
253 |
version: 1.24.0
|
|
|
2297 |
typescript:
|
2298 |
optional: true
|
2299 |
|
2300 |
+
'@remix-run/[email protected]':
|
2301 |
+
resolution: {integrity: sha512-NS/h5uxje7DYCNgcKqKAiUhf0r2HVnoYUBWLyIIMmCUP1ddWurBP6xTPcWzGhEvV/EvguniYi1wJZ5+X8sonWw==}
|
2302 |
+
engines: {node: '>=18.0.0'}
|
2303 |
+
peerDependencies:
|
2304 |
+
typescript: ^5.1.0
|
2305 |
+
peerDependenciesMeta:
|
2306 |
+
typescript:
|
2307 |
+
optional: true
|
2308 |
+
|
2309 |
'@remix-run/[email protected]':
|
2310 |
resolution: {integrity: sha512-puqDbi9N/WfaUhzDnw2pACXtCB7ukrtFJ9ILwpEuhlaTBpjefifJ89igokW+tt1ePphIFMivAm/YspcbZdCQsA==}
|
2311 |
engines: {node: '>=18.0.0'}
|
|
|
2330 |
typescript:
|
2331 |
optional: true
|
2332 |
|
2333 |
+
'@remix-run/[email protected]':
|
2334 |
+
resolution: {integrity: sha512-OqiPcvEnnU88B8b1LIWHHkQ3Tz2GDAmQ1RihFNQsbrFKpDsQLkw0lJlnfgKA/uHd0CEEacpfV7C9qqJT3V6Z2g==}
|
2335 |
+
engines: {node: '>=18.0.0'}
|
2336 |
+
peerDependencies:
|
2337 |
+
typescript: ^5.1.0
|
2338 |
+
peerDependenciesMeta:
|
2339 |
+
typescript:
|
2340 |
+
optional: true
|
2341 |
+
|
2342 |
'@remix-run/[email protected]':
|
2343 |
resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==}
|
2344 |
|
|
|
8661 |
optionalDependencies:
|
8662 |
typescript: 5.7.2
|
8663 |
|
8664 |
+
'@remix-run/[email protected]([email protected])':
|
8665 |
+
dependencies:
|
8666 |
+
'@remix-run/server-runtime': 2.15.2([email protected])
|
8667 |
+
'@remix-run/web-fetch': 4.4.2
|
8668 |
+
'@web3-storage/multipart-parser': 1.0.0
|
8669 |
+
cookie-signature: 1.2.2
|
8670 |
+
source-map-support: 0.5.21
|
8671 |
+
stream-slice: 0.1.2
|
8672 |
+
undici: 6.21.0
|
8673 |
+
optionalDependencies:
|
8674 |
+
typescript: 5.7.2
|
8675 |
+
|
8676 |
'@remix-run/[email protected]([email protected]([email protected]))([email protected])([email protected])':
|
8677 |
dependencies:
|
8678 |
'@remix-run/router': 1.21.0
|
|
|
8699 |
optionalDependencies:
|
8700 |
typescript: 5.7.2
|
8701 |
|
8702 |
+
'@remix-run/[email protected]([email protected])':
|
8703 |
+
dependencies:
|
8704 |
+
'@remix-run/router': 1.21.0
|
8705 |
+
'@types/cookie': 0.6.0
|
8706 |
+
'@web3-storage/multipart-parser': 1.0.0
|
8707 |
+
cookie: 0.6.0
|
8708 |
+
set-cookie-parser: 2.7.1
|
8709 |
+
source-map: 0.7.4
|
8710 |
+
turbo-stream: 2.4.0
|
8711 |
+
optionalDependencies:
|
8712 |
+
typescript: 5.7.2
|
8713 |
+
|
8714 |
'@remix-run/[email protected]':
|
8715 |
dependencies:
|
8716 |
'@remix-run/web-stream': 1.1.0
|
|
|
12725 |
mdast-util-to-markdown: 2.1.2
|
12726 |
unified: 11.0.5
|
12727 |
|
12728 |
+
[email protected](@remix-run/[email protected]([email protected]([email protected]))([email protected])([email protected]))(@remix-run/[email protected].2([email protected]))([email protected]([email protected]))([email protected]):
|
12729 |
dependencies:
|
12730 |
'@remix-run/react': 2.15.0([email protected]([email protected]))([email protected])([email protected])
|
12731 |
+
'@remix-run/server-runtime': 2.15.2([email protected])
|
12732 |
react: 18.3.1
|
12733 |
react-dom: 18.3.1([email protected])
|
12734 |
|
12735 |
+
[email protected](@remix-run/[email protected](@cloudflare/[email protected])([email protected]))(@remix-run/[email protected].2([email protected]))(@remix-run/[email protected]([email protected]([email protected]))([email protected])([email protected]))(@remix-run/[email protected])([email protected])([email protected]):
|
12736 |
dependencies:
|
12737 |
type-fest: 4.30.0
|
12738 |
optionalDependencies:
|
12739 |
'@remix-run/cloudflare': 2.15.0(@cloudflare/[email protected])([email protected])
|
12740 |
+
'@remix-run/node': 2.15.2([email protected])
|
12741 |
'@remix-run/react': 2.15.0([email protected]([email protected]))([email protected])([email protected])
|
12742 |
'@remix-run/router': 1.21.0
|
12743 |
react: 18.3.1
|