VIBN Frontend for Coolify deployment

This commit is contained in:
2026-02-15 19:25:52 -08:00
commit 40bf8428cd
398 changed files with 76513 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
import { NextRequest, NextResponse } from 'next/server';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
interface GitCommit {
hash: string;
date: string;
author: string;
message: string;
filesChanged: number;
insertions: number;
deletions: number;
}
interface GitStats {
totalCommits: number;
firstCommit: string | null;
lastCommit: string | null;
totalFilesChanged: number;
totalInsertions: number;
totalDeletions: number;
commits: GitCommit[];
topFiles: Array<{ filePath: string; changeCount: number }>;
commitsByDay: Record<string, number>;
authors: Array<{ name: string; commitCount: number }>;
}
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ projectId: string }> }
) {
try {
const { projectId } = await params;
// For now, we'll use the current workspace
// In the future, we can store git repo path in project metadata
const repoPath = '/Users/markhenderson/ai-proxy';
// Get all commits with detailed stats
const { stdout: commitsOutput } = await execAsync(
`cd "${repoPath}" && git log --all --pretty=format:"%H|%ai|%an|%s" --numstat`,
{ maxBuffer: 10 * 1024 * 1024 } // 10MB buffer for large repos
);
if (!commitsOutput.trim()) {
return NextResponse.json({
totalCommits: 0,
firstCommit: null,
lastCommit: null,
totalFilesChanged: 0,
totalInsertions: 0,
totalDeletions: 0,
commits: [],
topFiles: [],
commitsByDay: {},
authors: []
});
}
// Parse commit data
const commits: GitCommit[] = [];
const fileChangeCounts = new Map<string, number>();
const commitsByDay: Record<string, number> = {};
const authorCounts = new Map<string, number>();
let totalFilesChanged = 0;
let totalInsertions = 0;
let totalDeletions = 0;
const lines = commitsOutput.split('\n');
let currentCommit: Partial<GitCommit> | null = null;
for (const line of lines) {
if (line.includes('|')) {
// This is a commit header line
if (currentCommit) {
commits.push(currentCommit as GitCommit);
}
const [hash, date, author, message] = line.split('|');
currentCommit = {
hash: hash.substring(0, 8),
date,
author,
message,
filesChanged: 0,
insertions: 0,
deletions: 0
};
// Count commits by day
const day = date.split(' ')[0];
commitsByDay[day] = (commitsByDay[day] || 0) + 1;
// Count commits by author
authorCounts.set(author, (authorCounts.get(author) || 0) + 1);
} else if (line.trim() && currentCommit) {
// This is a file stat line (insertions, deletions, filename)
const parts = line.trim().split('\t');
if (parts.length === 3) {
const [insertStr, delStr, filepath] = parts;
const insertions = insertStr === '-' ? 0 : parseInt(insertStr, 10) || 0;
const deletions = delStr === '-' ? 0 : parseInt(delStr, 10) || 0;
currentCommit.filesChanged!++;
currentCommit.insertions! += insertions;
currentCommit.deletions! += deletions;
totalFilesChanged++;
totalInsertions += insertions;
totalDeletions += deletions;
fileChangeCounts.set(filepath, (fileChangeCounts.get(filepath) || 0) + 1);
}
}
}
// Push the last commit
if (currentCommit) {
commits.push(currentCommit as GitCommit);
}
// Sort commits by date (most recent first)
commits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
const firstCommit = commits.length > 0 ? commits[commits.length - 1].date : null;
const lastCommit = commits.length > 0 ? commits[0].date : null;
// Get top 20 most changed files
const topFiles = Array.from(fileChangeCounts.entries())
.sort(([, countA], [, countB]) => countB - countA)
.slice(0, 20)
.map(([filePath, changeCount]) => ({ filePath, changeCount }));
// Get author stats
const authors = Array.from(authorCounts.entries())
.sort(([, countA], [, countB]) => countB - countA)
.map(([name, commitCount]) => ({ name, commitCount }));
const stats: GitStats = {
totalCommits: commits.length,
firstCommit,
lastCommit,
totalFilesChanged,
totalInsertions,
totalDeletions,
commits: commits.slice(0, 50), // Return last 50 commits for display
topFiles,
commitsByDay,
authors
};
return NextResponse.json(stats);
} catch (error) {
console.error('Error loading Git history:', error);
return NextResponse.json(
{
error: 'Could not load Git history',
details: error instanceof Error ? error.message : String(error)
},
{ status: 500 }
);
}
}