11import { defineEventHandler , H3Event } from "h3" ;
22import { useOctokitInstallation } from "../utils/octokit" ;
3- import AdmZip from "adm-zip " ;
3+ import { unzipSync , strFromU8 } from "fflate " ;
44
55function extractCountsBlock ( content : string ) : any | null {
66 const lines = content . split ( "\n" ) ;
@@ -30,7 +30,6 @@ function extractCountsBlock(content: string): any | null {
3030 try {
3131 return JSON . parse ( block ) ;
3232 } catch ( e ) {
33- console . error ( "Failed to parse Counts block as JSON:" , block , e ) ;
3433 return null ;
3534 }
3635}
@@ -40,86 +39,110 @@ let lastFetch = 0;
4039const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000 ; // 1 week in ms
4140
4241export default defineEventHandler ( async ( event : H3Event ) => {
43- if ( cachedData && Date . now ( ) - lastFetch < CACHE_DURATION ) {
44- return cachedData ;
45- }
46- const owner = "stackblitz-labs" ;
47- const repo = "pkg.pr.new" ;
48- const workflowId = "stats.yml" ;
42+ try {
43+ if ( cachedData && Date . now ( ) - lastFetch < CACHE_DURATION ) {
44+ return cachedData ;
45+ }
46+ const owner = "stackblitz-labs" ;
47+ const repo = "pkg.pr.new" ;
48+ const workflowId = "stats.yml" ;
4949
50- const octokit = await useOctokitInstallation ( event , owner , repo ) ;
50+ const octokit = await useOctokitInstallation ( event , owner , repo ) ;
5151
52- const runs = await octokit . paginate ( octokit . rest . actions . listWorkflowRuns , {
53- owner,
54- repo,
55- workflow_id : workflowId ,
56- per_page : 100 ,
57- status : "success" ,
58- } ) ;
52+ const runs = await octokit . paginate ( octokit . rest . actions . listWorkflowRuns , {
53+ owner,
54+ repo,
55+ workflow_id : workflowId ,
56+ per_page : 100 ,
57+ status : "success" ,
58+ } ) ;
5959
60- // Only process the latest 100 runs for performance
61- const latestRuns = runs . slice ( 0 , 100 ) ;
60+ // Only process the latest 100 runs for performance
61+ const latestRuns = runs . slice ( 0 , 100 ) ;
6262
63- // 4. For each run, download logs, unzip, and extract Counts
64- const results = [ ] ;
65- for ( const run of latestRuns ) {
66- let stats = null ;
67- try {
68- const logsResponse = await octokit . rest . actions . downloadWorkflowRunLogs ( {
69- owner,
70- repo,
71- run_id : run . id ,
72- } ) ;
73- if ( logsResponse . status === 302 ) {
74- }
75- if ( ! logsResponse . data ) {
76- } else {
77- let buffer ;
78- if ( Buffer . isBuffer ( logsResponse . data ) ) {
79- buffer = logsResponse . data ;
80- } else if ( logsResponse . data instanceof ArrayBuffer ) {
81- buffer = Buffer . from ( new Uint8Array ( logsResponse . data ) ) ;
63+ // 4. For each run, download logs, unzip, and extract Counts
64+ const results = [ ] ;
65+ for ( const run of latestRuns ) {
66+ let stats = null ;
67+ try {
68+ const logsResponse = await octokit . rest . actions . downloadWorkflowRunLogs (
69+ {
70+ owner,
71+ repo,
72+ run_id : run . id ,
73+ } ,
74+ ) ;
75+ if ( ! logsResponse . data ) {
8276 } else {
83- throw new Error ( "logsResponse.data is not a Buffer or ArrayBuffer" ) ;
84- }
85- const zip = new AdmZip ( buffer ) ;
86- const entries = zip . getEntries ( ) ;
87- if ( entries . length === 0 ) {
88- }
89- let found = false ;
90- for ( const entry of entries ) {
91- const content = entry . getData ( ) . toString ( "utf8" ) ;
92- const extracted = extractCountsBlock ( content ) ;
93- if ( extracted ) {
94- stats = extracted ;
95- found = true ;
96- break ;
77+ let zipData ;
78+ if ( logsResponse . data instanceof ArrayBuffer ) {
79+ zipData = new Uint8Array ( logsResponse . data ) ;
80+ } else if ( logsResponse . data instanceof Uint8Array ) {
81+ zipData = logsResponse . data ;
82+ } else if (
83+ typeof Buffer !== "undefined" &&
84+ Buffer . isBuffer ( logsResponse . data )
85+ ) {
86+ zipData = new Uint8Array (
87+ logsResponse . data . buffer ,
88+ logsResponse . data . byteOffset ,
89+ logsResponse . data . byteLength ,
90+ ) ;
91+ } else {
92+ throw new Error ( "logsResponse.data is not a supported binary type" ) ;
93+ }
94+ let files ;
95+ try {
96+ files = unzipSync ( zipData ) ;
97+ } catch ( e ) {
98+ continue ;
99+ }
100+ let found = false ;
101+ for ( const [ , fileData ] of Object . entries ( files ) ) {
102+ let content ;
103+ try {
104+ content = strFromU8 ( fileData ) ;
105+ } catch ( e ) {
106+ continue ;
107+ }
108+ if ( typeof content !== "string" || ! content ) {
109+ continue ;
110+ }
111+ let extracted ;
112+ try {
113+ extracted = extractCountsBlock ( content ) ;
114+ } catch ( e ) {
115+ continue ;
116+ }
117+ if ( extracted ) {
118+ stats = extracted ;
119+ found = true ;
120+ break ;
121+ }
97122 }
98123 }
99- if ( ! found ) {
124+ } catch ( e ) {
125+ if (
126+ e instanceof Error &&
127+ e . message &&
128+ e . message . includes ( "Server Error" )
129+ ) {
130+ // skip
100131 }
101132 }
102- } catch ( e ) {
103- if (
104- e instanceof Error &&
105- e . message &&
106- e . message . includes ( "Server Error" )
107- ) {
108- // skip
109- } else {
110- // console.error(e)
133+ if ( stats ) {
134+ results . push ( {
135+ run_number : run . run_number ,
136+ created_at : run . created_at ,
137+ stats,
138+ } ) ;
111139 }
112140 }
113- if ( stats ) {
114- results . push ( {
115- run_number : run . run_number ,
116- created_at : run . created_at ,
117- stats,
118- } ) ;
119- }
141+ // 5. Return JSON
142+ cachedData = { runs : results } ;
143+ lastFetch = Date . now ( ) ;
144+ return cachedData ;
145+ } catch ( err ) {
146+ return { error : true , message : String ( err ) , stack : ( err as any ) ?. stack } ;
120147 }
121- // 5. Return JSON
122- cachedData = { runs : results } ;
123- lastFetch = Date . now ( ) ;
124- return cachedData ;
125148} ) ;
0 commit comments