Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions src/app/api/webhooks/github-pr/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ function parseBlueprintFiles(files: any[], prAuthor: string): {
/**
* Fetch blueprint content from GitHub
*/
async function fetchBlueprintContent(octokit: Octokit, owner: string, repo: string, ref: string, path: string): Promise<string | null> {
async function fetchBlueprintContent(octokit: Octokit, owner: string, repo: string, ref: string, path: string): Promise<{ content: string | null; error?: string }> {
try {
const response = await octokit.repos.getContent({
owner,
Expand All @@ -135,20 +135,23 @@ async function fetchBlueprintContent(octokit: Octokit, owner: string, repo: stri
});

if ('content' in response.data && response.data.type === 'file') {
const content = Buffer.from(response.data.content, 'base64').toString('utf-8');

// Check size limit
const sizeKB = Buffer.byteLength(content, 'utf-8') / 1024;
// Check size limit using the API-reported size. For files over 1MB the
// Contents API returns `encoding: "none"` with empty content, so the
// reported size is the only reliable measure.
const sizeKB = response.data.size / 1024;
if (sizeKB > MAX_BLUEPRINT_SIZE_KB) {
throw new Error(`Blueprint exceeds size limit (${sizeKB.toFixed(1)}KB > ${MAX_BLUEPRINT_SIZE_KB}KB)`);
return {
content: null,
error: `Blueprint is ${sizeKB.toFixed(1)}KB, which exceeds the ${MAX_BLUEPRINT_SIZE_KB}KB limit. Please split it into smaller files.`,
};
}

return content;
return { content: Buffer.from(response.data.content, 'base64').toString('utf-8') };
}
return null;
return { content: null, error: 'Path did not resolve to a file' };
} catch (error: any) {
console.error(`[GitHub Webhook] Failed to fetch content for ${path}:`, error.message);
return null;
return { content: null, error: 'Failed to fetch blueprint content' };
}
}

Expand Down Expand Up @@ -407,7 +410,7 @@ export async function POST(req: NextRequest) {

for (const file of valid) {
// Fetch content from PR head
const content = await fetchBlueprintContent(
const { content, error: fetchError } = await fetchBlueprintContent(
octokit,
pr.head.repo.owner.login,
pr.head.repo.name,
Expand All @@ -416,7 +419,7 @@ export async function POST(req: NextRequest) {
);

if (!content) {
validationErrors.push({ filename: file.filename, error: 'Failed to fetch blueprint content' });
validationErrors.push({ filename: file.filename, error: fetchError || 'Failed to fetch blueprint content' });
continue;
}

Expand Down
26 changes: 19 additions & 7 deletions src/app/api/webhooks/github-push/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const UPSTREAM_OWNER = 'weval-org';
const UPSTREAM_REPO = 'configs';
const MAIN_BRANCH = 'main';
const MAX_BLUEPRINT_SIZE_KB = 500; // 500KB max, matches the PR webhook limit

const s3Client = new S3Client({
region: process.env.APP_S3_REGION!,
Expand Down Expand Up @@ -101,7 +102,7 @@ async function fetchBlueprintContent(
repo: string,
ref: string,
path: string
): Promise<string | null> {
): Promise<{ content: string | null; error?: string }> {
try {
const response = await octokit.repos.getContent({
owner,
Expand All @@ -111,12 +112,23 @@ async function fetchBlueprintContent(
});

if ('content' in response.data && response.data.type === 'file') {
return Buffer.from(response.data.content, 'base64').toString('utf-8');
// Check size limit using the API-reported size. For files over 1MB the
// Contents API returns `encoding: "none"` with empty content, so the
// reported size is the only reliable measure.
const sizeKB = response.data.size / 1024;
if (sizeKB > MAX_BLUEPRINT_SIZE_KB) {
return {
content: null,
error: `Blueprint is ${sizeKB.toFixed(1)}KB, which exceeds the ${MAX_BLUEPRINT_SIZE_KB}KB limit`,
};
}

return { content: Buffer.from(response.data.content, 'base64').toString('utf-8') };
}
return null;
return { content: null, error: 'Path did not resolve to a file' };
} catch (error: any) {
console.error(`[GitHub Push Webhook] Failed to fetch ${path}:`, error.message);
return null;
return { content: null, error: 'Failed to fetch blueprint content' };
}
}

Expand Down Expand Up @@ -308,7 +320,7 @@ export async function POST(req: NextRequest) {
console.log(`[GitHub Push Webhook] Processing ${file.filename}...`);

// Fetch content
const content = await fetchBlueprintContent(
const { content, error: fetchError } = await fetchBlueprintContent(
octokit,
UPSTREAM_OWNER,
UPSTREAM_REPO,
Expand All @@ -317,8 +329,8 @@ export async function POST(req: NextRequest) {
);

if (!content) {
console.error(`[GitHub Push Webhook] Failed to fetch content for ${file.filename}`);
results.errors.push(`${file.filename}: Failed to fetch content`);
console.error(`[GitHub Push Webhook] Failed to fetch content for ${file.filename}: ${fetchError}`);
results.errors.push(`${file.filename}: ${fetchError || 'Failed to fetch content'}`);
continue;
}

Expand Down