@@ -5,13 +5,24 @@ import {
55 DownloadKey ,
66 FilenamePatterns ,
77 GithubRelease ,
8+ maxMajor ,
9+ maxMinor ,
810 maxNightlies ,
911 ReleaseDownloads ,
1012 repository ,
1113} from "@/app/downloads/config" ;
1214import { Octokit } from "octokit" ;
1315import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods" ;
1416import { parse } from "node-html-parser" ;
17+ import semver from "semver/preload" ;
18+ import { components } from "@octokit/openapi-types" ;
19+
20+ const octokit = new Octokit ( { authStrategy : createGithubAuth } ) ;
21+
22+ const requestCache = {
23+ // Set cache to 30 min to prevent rate limiting during development
24+ request : { next : { revalidate : 1800 } } ,
25+ } ;
1526
1627function createGithubAuth ( ) {
1728 if ( process . env . GITHUB_TOKEN ) {
@@ -28,37 +39,65 @@ function throwBuildError() {
2839 throw new Error ( "Build failed" ) ;
2940}
3041
31- export async function getLatestReleases ( ) : Promise < GithubRelease [ ] > {
32- const octokit = new Octokit ( { authStrategy : createGithubAuth } ) ;
42+ function mapRelease ( release : components [ "schemas" ] [ "release" ] ) : GithubRelease {
43+ const downloads : ReleaseDownloads = { } ;
44+ let avm2_report_asset_id : number | undefined = undefined ;
45+ for ( const asset of release . assets ) {
46+ if ( asset . name === "avm2_report.json" ) {
47+ avm2_report_asset_id = asset . id ;
48+ }
49+ for ( const [ key , pattern ] of Object . entries ( FilenamePatterns ) ) {
50+ if ( asset . name . indexOf ( pattern ) > - 1 ) {
51+ downloads [ key as DownloadKey ] = asset . browser_download_url ;
52+ }
53+ }
54+ }
55+
56+ return {
57+ id : release . id ,
58+ name : release . name || release . tag_name ,
59+ prerelease : release . prerelease ,
60+ url : release . html_url ,
61+ tag : release . tag_name ,
62+ downloads,
63+ avm2_report_asset_id,
64+ } ;
65+ }
66+
67+ export async function getLatestRelease ( ) : Promise < GithubRelease > {
68+ try {
69+ const response = await octokit . rest . repos . getLatestRelease ( {
70+ ...requestCache ,
71+ ...repository ,
72+ } ) ;
73+ return mapRelease ( response . data ) ;
74+ } catch {
75+ // There's no stable release, get the latest nightly.
76+ }
77+
78+ const releases = await octokit . rest . repos . listReleases ( {
79+ per_page : 1 ,
80+ ...requestCache ,
81+ ...repository ,
82+ } ) ;
83+ return mapRelease ( releases . data [ 0 ] ) ;
84+ }
85+
86+ export async function getLatestNightlyReleases ( ) : Promise < GithubRelease [ ] > {
3387 try {
3488 const releases = await octokit . rest . repos . listReleases ( {
35- per_page : maxNightlies + 2 , // more than we need to account for a possible draft release + possible full release
36- request : { next : { revalidate : 1800 } } ,
89+ // We have to take into account possible stable releases here
90+ per_page : maxNightlies + 4 ,
91+ ...requestCache ,
3792 ...repository ,
3893 } ) ;
3994 const result = [ ] ;
40- let avm2_report_asset_id : number | undefined = undefined ;
4195 for ( const release of releases . data ) {
42- const downloads : ReleaseDownloads = { } ;
43- for ( const asset of release . assets ) {
44- if ( asset . name === "avm2_report.json" ) {
45- avm2_report_asset_id = asset . id ;
46- }
47- for ( const [ key , pattern ] of Object . entries ( FilenamePatterns ) ) {
48- if ( asset . name . indexOf ( pattern ) > - 1 ) {
49- downloads [ key as DownloadKey ] = asset . browser_download_url ;
50- }
51- }
96+ if ( ! release . prerelease ) {
97+ // Filter out stable releases
98+ continue ;
5299 }
53-
54- result . push ( {
55- id : release . id ,
56- name : release . name || release . tag_name ,
57- prerelease : release . prerelease ,
58- url : release . html_url ,
59- downloads,
60- avm2_report_asset_id,
61- } ) ;
100+ result . push ( mapRelease ( release ) ) ;
62101 }
63102 return result ;
64103 } catch ( error ) {
@@ -67,25 +106,79 @@ export async function getLatestReleases(): Promise<GithubRelease[]> {
67106 }
68107}
69108
109+ export async function getLatestStableReleases ( ) : Promise < GithubRelease [ ] > {
110+ let newestMajor = null ;
111+
112+ // Map representing releases from the current major version:
113+ // `major.minor` -> `major.minor.patch`
114+ // We want to ignore older patches and show last X minor versions.
115+ const currentMajorReleases = new Map ( ) ;
116+
117+ // Map representing releases from older major versions.
118+ // `major` -> `major.minor.patch`
119+ // We ignore here minor and patch versions, and
120+ // gather the newest release per each major.
121+ const olderMajors = new Map ( ) ;
122+
123+ for (
124+ let page = 1 ;
125+ currentMajorReleases . size < maxMinor || olderMajors . size < maxMajor - 1 ;
126+ ++ page
127+ ) {
128+ const request = await octokit . rest . repos . listReleases ( {
129+ // 100 per page disables cache as the result is >2MB
130+ per_page : 80 ,
131+ page : page ,
132+ ...requestCache ,
133+ ...repository ,
134+ } ) ;
135+ if ( request . status != 200 || request . data . length == 0 ) {
136+ break ;
137+ }
138+ for ( const data of request . data ) {
139+ if ( data . prerelease ) {
140+ continue ;
141+ }
142+ const release = mapRelease ( data ) ;
143+ const version = release . tag . replace ( / ^ v / , "" ) ;
144+ const major = semver . major ( version ) ;
145+ const majorMinor = `${ major } .${ semver . minor ( version ) } ` ;
146+ if ( ! newestMajor ) {
147+ newestMajor = major ;
148+ }
149+ if ( major === newestMajor ) {
150+ if ( ! currentMajorReleases . has ( majorMinor ) ) {
151+ currentMajorReleases . set ( majorMinor , release ) ;
152+ }
153+ } else {
154+ if ( ! olderMajors . has ( major ) ) {
155+ olderMajors . set ( major , release ) ;
156+ }
157+ }
158+ }
159+ }
160+
161+ return Array . from ( currentMajorReleases . values ( ) )
162+ . slice ( 0 , maxMinor )
163+ . concat ( Array . from ( olderMajors . values ( ) ) . slice ( 0 , maxMajor - 1 ) ) ;
164+ }
165+
70166export async function getWeeklyContributions ( ) : Promise <
71167 RestEndpointMethodTypes [ "repos" ] [ "getCommitActivityStats" ] [ "response" ]
72168> {
73- const octokit = new Octokit ( { authStrategy : createGithubAuth } ) ;
74169 return octokit . rest . repos . getCommitActivityStats ( repository ) ;
75170}
76171export async function fetchReport ( ) : Promise < AVM2Report | undefined > {
77- const releases = await getLatestReleases ( ) ;
172+ const releases = await getLatestNightlyReleases ( ) ;
78173 const latest = releases . find (
79174 ( release ) => release . avm2_report_asset_id !== undefined ,
80175 ) ;
81176 if ( ! latest ?. avm2_report_asset_id ) {
82177 throwBuildError ( ) ;
83178 return ;
84179 }
85- const octokit = new Octokit ( { authStrategy : createGithubAuth } ) ;
86180 const asset = await octokit . rest . repos . getReleaseAsset ( {
87- owner : repository . owner ,
88- repo : repository . repo ,
181+ ...repository ,
89182 asset_id : latest . avm2_report_asset_id ,
90183 headers : {
91184 accept : "application/octet-stream" ,
@@ -100,10 +193,8 @@ export async function fetchReport(): Promise<AVM2Report | undefined> {
100193}
101194
102195export async function getAVM1Progress ( ) : Promise < number > {
103- const octokit = new Octokit ( { authStrategy : createGithubAuth } ) ;
104196 const issues = await octokit . rest . issues . listForRepo ( {
105- owner : repository . owner ,
106- repo : repository . repo ,
197+ ...repository ,
107198 labels : "avm1-tracking" ,
108199 state : "all" ,
109200 per_page : 65 ,
0 commit comments