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/workers-types@4.20241127.0)(typescript@5.7.2)
|
|
|
|
|
|
|
|
|
|
| 125 |
'@remix-run/react':
|
| 126 |
specifier: ^2.15.0
|
| 127 |
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:
|
|
| 241 |
version: 4.0.0
|
| 242 |
remix-island:
|
| 243 |
specifier: ^0.2.0
|
| 244 |
-
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.
|
| 245 |
remix-utils:
|
| 246 |
specifier: ^7.7.0
|
| 247 |
-
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.
|
| 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/react@2.15.0':
|
| 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/web-blob@3.1.0':
|
| 2322 |
resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==}
|
| 2323 |
|
|
@@ -8640,6 +8661,18 @@ snapshots:
|
|
| 8640 |
optionalDependencies:
|
| 8641 |
typescript: 5.7.2
|
| 8642 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8643 |
'@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
|
| 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/web-blob@3.1.0':
|
| 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 |
-
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.
|
| 12684 |
dependencies:
|
| 12685 |
'@remix-run/react': 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
|
| 12686 |
-
'@remix-run/server-runtime': 2.15.
|
| 12687 |
react: 18.3.1
|
| 12688 |
react-dom: 18.3.1(react@18.3.1)
|
| 12689 |
|
| 12690 |
-
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.
|
| 12691 |
dependencies:
|
| 12692 |
type-fest: 4.30.0
|
| 12693 |
optionalDependencies:
|
| 12694 |
'@remix-run/cloudflare': 2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.7.2)
|
| 12695 |
-
'@remix-run/node': 2.15.
|
| 12696 |
'@remix-run/react': 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
|
| 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/workers-types@4.20241127.0)(typescript@5.7.2)
|
| 125 |
+
'@remix-run/node':
|
| 126 |
+
specifier: ^2.15.2
|
| 127 |
+
version: 2.15.2(typescript@5.7.2)
|
| 128 |
'@remix-run/react':
|
| 129 |
specifier: ^2.15.0
|
| 130 |
version: 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
|
|
|
|
| 244 |
version: 4.0.0
|
| 245 |
remix-island:
|
| 246 |
specifier: ^0.2.0
|
| 247 |
+
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)
|
| 248 |
remix-utils:
|
| 249 |
specifier: ^7.7.0
|
| 250 |
+
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)
|
| 251 |
shiki:
|
| 252 |
specifier: ^1.24.0
|
| 253 |
version: 1.24.0
|
|
|
|
| 2297 |
typescript:
|
| 2298 |
optional: true
|
| 2299 |
|
| 2300 |
+
'@remix-run/node@2.15.2':
|
| 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/react@2.15.0':
|
| 2310 |
resolution: {integrity: sha512-puqDbi9N/WfaUhzDnw2pACXtCB7ukrtFJ9ILwpEuhlaTBpjefifJ89igokW+tt1ePphIFMivAm/YspcbZdCQsA==}
|
| 2311 |
engines: {node: '>=18.0.0'}
|
|
|
|
| 2330 |
typescript:
|
| 2331 |
optional: true
|
| 2332 |
|
| 2333 |
+
'@remix-run/server-runtime@2.15.2':
|
| 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/web-blob@3.1.0':
|
| 2343 |
resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==}
|
| 2344 |
|
|
|
|
| 8661 |
optionalDependencies:
|
| 8662 |
typescript: 5.7.2
|
| 8663 |
|
| 8664 |
+
'@remix-run/node@2.15.2(typescript@5.7.2)':
|
| 8665 |
+
dependencies:
|
| 8666 |
+
'@remix-run/server-runtime': 2.15.2(typescript@5.7.2)
|
| 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/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
|
| 8677 |
dependencies:
|
| 8678 |
'@remix-run/router': 1.21.0
|
|
|
|
| 8699 |
optionalDependencies:
|
| 8700 |
typescript: 5.7.2
|
| 8701 |
|
| 8702 |
+
'@remix-run/server-runtime@2.15.2(typescript@5.7.2)':
|
| 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/web-blob@3.1.0':
|
| 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 |
+
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):
|
| 12729 |
dependencies:
|
| 12730 |
'@remix-run/react': 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
|
| 12731 |
+
'@remix-run/server-runtime': 2.15.2(typescript@5.7.2)
|
| 12732 |
react: 18.3.1
|
| 12733 |
react-dom: 18.3.1(react@18.3.1)
|
| 12734 |
|
| 12735 |
+
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):
|
| 12736 |
dependencies:
|
| 12737 |
type-fest: 4.30.0
|
| 12738 |
optionalDependencies:
|
| 12739 |
'@remix-run/cloudflare': 2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.7.2)
|
| 12740 |
+
'@remix-run/node': 2.15.2(typescript@5.7.2)
|
| 12741 |
'@remix-run/react': 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
|
| 12742 |
'@remix-run/router': 1.21.0
|
| 12743 |
react: 18.3.1
|