@@ -5,13 +5,24 @@ import {
5
5
DownloadKey ,
6
6
FilenamePatterns ,
7
7
GithubRelease ,
8
+ maxMajor ,
9
+ maxMinor ,
8
10
maxNightlies ,
9
11
ReleaseDownloads ,
10
12
repository ,
11
13
} from "@/app/downloads/config" ;
12
14
import { Octokit } from "octokit" ;
13
15
import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods" ;
14
16
import { 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
+ } ;
15
26
16
27
function createGithubAuth ( ) {
17
28
if ( process . env . GITHUB_TOKEN ) {
@@ -28,37 +39,65 @@ function throwBuildError() {
28
39
throw new Error ( "Build failed" ) ;
29
40
}
30
41
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 [ ] > {
33
87
try {
34
88
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 ,
37
92
...repository ,
38
93
} ) ;
39
94
const result = [ ] ;
40
- let avm2_report_asset_id : number | undefined = undefined ;
41
95
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 ;
52
99
}
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 ) ) ;
62
101
}
63
102
return result ;
64
103
} catch ( error ) {
@@ -67,25 +106,79 @@ export async function getLatestReleases(): Promise<GithubRelease[]> {
67
106
}
68
107
}
69
108
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
+
70
166
export async function getWeeklyContributions ( ) : Promise <
71
167
RestEndpointMethodTypes [ "repos" ] [ "getCommitActivityStats" ] [ "response" ]
72
168
> {
73
- const octokit = new Octokit ( { authStrategy : createGithubAuth } ) ;
74
169
return octokit . rest . repos . getCommitActivityStats ( repository ) ;
75
170
}
76
171
export async function fetchReport ( ) : Promise < AVM2Report | undefined > {
77
- const releases = await getLatestReleases ( ) ;
172
+ const releases = await getLatestNightlyReleases ( ) ;
78
173
const latest = releases . find (
79
174
( release ) => release . avm2_report_asset_id !== undefined ,
80
175
) ;
81
176
if ( ! latest ?. avm2_report_asset_id ) {
82
177
throwBuildError ( ) ;
83
178
return ;
84
179
}
85
- const octokit = new Octokit ( { authStrategy : createGithubAuth } ) ;
86
180
const asset = await octokit . rest . repos . getReleaseAsset ( {
87
- owner : repository . owner ,
88
- repo : repository . repo ,
181
+ ...repository ,
89
182
asset_id : latest . avm2_report_asset_id ,
90
183
headers : {
91
184
accept : "application/octet-stream" ,
@@ -100,10 +193,8 @@ export async function fetchReport(): Promise<AVM2Report | undefined> {
100
193
}
101
194
102
195
export async function getAVM1Progress ( ) : Promise < number > {
103
- const octokit = new Octokit ( { authStrategy : createGithubAuth } ) ;
104
196
const issues = await octokit . rest . issues . listForRepo ( {
105
- owner : repository . owner ,
106
- repo : repository . repo ,
197
+ ...repository ,
107
198
labels : "avm1-tracking" ,
108
199
state : "all" ,
109
200
per_page : 65 ,
0 commit comments