11import type { Octokit } from "@octokit/rest" ;
22import { graphql } from "@octokit/graphql" ;
33import { join , basename } from "node:path" ;
4+ import { logDebug } from "../logger" ;
5+ import { log } from "node:console" ;
46
57export async function findPrByBranch (
68 octokit : Octokit ,
@@ -235,6 +237,8 @@ export async function getPrComments(
235237 const allComments = await fetchAllPrComments ( octokit , owner , repo , prNumber ) ;
236238 const humanComments = allComments . filter ( ( c ) => ! c . isBot ) ;
237239
240+ logDebug ( `[allComments] Fetched ${ allComments . length } total comments (${ humanComments . length } human) for PR #${ prNumber } , latest cutoff: ${ cutoffDate } , latest comment date: ${ humanComments . map ( c => c . createdAt ) . sort ( ) . reverse ( ) } ` ) ;
241+
238242 if ( cutoffDate ) {
239243 const cutoff = new Date ( cutoffDate ) ;
240244 const newComments = humanComments . filter ( ( c ) => new Date ( c . createdAt ) > cutoff ) ;
@@ -253,6 +257,7 @@ export async function checkForNewHumanComments(
253257 latestCommitDate : string ,
254258) : Promise < boolean > {
255259 const { new : newComments } = await getPrComments ( octokit , owner , repo , prNumber , latestCommitDate ) ;
260+ logDebug ( `[checkForNewHumanComments] Found ${ newComments . length } new human comment(s) since ${ latestCommitDate } ` ) ;
256261 return newComments . length > 0 ;
257262}
258263
@@ -315,27 +320,19 @@ export function extractScreenshotPaths(progressContent: string, workDir: string)
315320 return paths ;
316321}
317322
318- /**
319- * Get the relative path of a file within a git repo.
320- */
321- async function getRelativePath ( workDir : string , absolutePath : string ) : Promise < string > {
322- // If path is already relative (starts within workDir), extract relative portion
323- if ( absolutePath . startsWith ( workDir ) ) {
324- return absolutePath . slice ( workDir . length ) . replace ( / ^ \/ / , "" ) ;
325- }
326- // Otherwise try git to resolve
327- try {
328- const result = await Bun . $ `git -C ${ workDir } ls-files --full-name ${ absolutePath } ` . text ( ) ;
329- return result . trim ( ) ;
330- } catch {
331- return basename ( absolutePath ) ;
332- }
323+ function getImageMimeType ( filePath : string ) : string | null {
324+ const lower = filePath . toLowerCase ( ) ;
325+ if ( lower . endsWith ( ".png" ) ) return "image/png" ;
326+ if ( lower . endsWith ( ".jpg" ) || lower . endsWith ( ".jpeg" ) ) return "image/jpeg" ;
327+ if ( lower . endsWith ( ".gif" ) ) return "image/gif" ;
328+ if ( lower . endsWith ( ".webp" ) ) return "image/webp" ;
329+ if ( lower . endsWith ( ".svg" ) ) return "image/svg+xml" ;
330+ return null ;
333331}
334332
335333/**
336334 * Upload screenshots referenced in progress.txt to the PR as a comment.
337- * Screenshots that are committed to the branch are referenced via raw GitHub URLs.
338- * Screenshots not yet tracked are committed first, then referenced.
335+ * This does not commit files; it only posts screenshots to the PR conversation.
339336 */
340337export async function uploadProgressScreenshots (
341338 octokit : Octokit ,
@@ -344,44 +341,56 @@ export async function uploadProgressScreenshots(
344341 prNumber : number ,
345342 progressContent : string ,
346343 workDir : string ,
347- branch : string ,
344+ _branch : string ,
348345) : Promise < void > {
349346 const screenshotPaths = extractScreenshotPaths ( progressContent , workDir ) ;
350347 if ( screenshotPaths . length === 0 ) return ;
351348
352- const imageEntries : Array < { name : string ; url : string } > = [ ] ;
349+ const imageEntries : Array < { name : string ; dataUrl : string } > = [ ] ;
350+ const skippedEntries : string [ ] = [ ] ;
351+ const MAX_IMAGE_BYTES = 1_500_000 ;
353352
354353 for ( const filePath of screenshotPaths ) {
355354 const file = Bun . file ( filePath ) ;
356355 if ( ! ( await file . exists ( ) ) ) continue ;
357356
358- const relativePath = await getRelativePath ( workDir , filePath ) ;
359357 const name = basename ( filePath ) ;
358+ const mimeType = getImageMimeType ( filePath ) ;
359+ if ( ! mimeType ) {
360+ skippedEntries . push ( `${ name } (unsupported image type)` ) ;
361+ continue ;
362+ }
363+
364+ const size = file . size ;
365+ if ( typeof size === "number" && size > MAX_IMAGE_BYTES ) {
366+ skippedEntries . push ( `${ name } (too large: ${ ( size / 1024 / 1024 ) . toFixed ( 2 ) } MB)` ) ;
367+ continue ;
368+ }
360369
361- // Check if file is tracked in git
362370 try {
363- await Bun . $ `git -C ${ workDir } ls-files --error-unmatch ${ filePath } ` . quiet ( ) ;
371+ const arrayBuffer = await file . arrayBuffer ( ) ;
372+ const base64 = Buffer . from ( arrayBuffer ) . toString ( "base64" ) ;
373+ const dataUrl = `data:${ mimeType } ;base64,${ base64 } ` ;
374+ imageEntries . push ( { name, dataUrl } ) ;
364375 } catch {
365- // File not tracked - add and commit it
366- try {
367- await Bun . $ `git -C ${ workDir } add ${ filePath } ` . quiet ( ) ;
368- await Bun . $ `git -C ${ workDir } commit -m ${ "chore: add screenshot " + name } ` . quiet ( ) ;
369- } catch {
370- continue ; // Skip if we can't commit
371- }
376+ skippedEntries . push ( `${ name } (failed to read file)` ) ;
377+ continue ;
372378 }
373-
374- const rawUrl = `https://raw.githubusercontent.com/${ owner } /${ repo } /${ branch } /${ relativePath } ` ;
375- imageEntries . push ( { name, url : rawUrl } ) ;
376379 }
377380
378- if ( imageEntries . length === 0 ) return ;
381+ if ( imageEntries . length === 0 && skippedEntries . length === 0 ) return ;
379382
380383 const body = [
381384 `🤖 **eternity-loop bot:** Screenshots from this run:\n` ,
382385 ...imageEntries . map ( ( entry , i ) =>
383- `### Screenshot ${ i + 1 } \n\n`
386+ `### Screenshot ${ i + 1 } \n\n`
384387 ) ,
388+ ...( skippedEntries . length > 0
389+ ? [
390+ "### Skipped files" ,
391+ ...skippedEntries . map ( ( entry ) => `- ${ entry } ` ) ,
392+ ]
393+ : [ ] ) ,
385394 ] . join ( "\n" ) ;
386395
387396 await postPrComment ( octokit , owner , repo , prNumber , body ) ;
0 commit comments