Skip to content

Commit b8a580b

Browse files
committed
Add stable releases
1 parent 61a325e commit b8a580b

File tree

7 files changed

+205
-62
lines changed

7 files changed

+205
-62
lines changed

package-lock.json

+9-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@
1919
"react": "^18",
2020
"react-device-detect": "^2.2.3",
2121
"react-dom": "^18",
22-
"recharts": "^2.12.7"
22+
"recharts": "^2.12.7",
23+
"semver": "^7.6.3"
2324
},
2425
"devDependencies": {
2526
"@types/d3": "^7.4.3",
2627
"@types/jsdom": "^21.1.7",
2728
"@types/node": "^22",
2829
"@types/react": "^18",
2930
"@types/react-dom": "^18",
31+
"@types/semver": "^7.5.8",
3032
"@typescript-eslint/eslint-plugin": "^8.0.0",
3133
"@typescript-eslint/parser": "^8.0.0",
3234
"d3": "^7.9.0",

src/app/downloads/config.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,21 @@ import { SummaryStatistics } from "@/app/compatibility/avm2/report_utils";
1414

1515
export const repository = { owner: "ruffle-rs", repo: "ruffle" };
1616

17+
export const maxMinor = 3;
18+
export const maxMajor = 1;
1719
export const maxNightlies = 5;
1820

1921
export const githubReleasesUrl = `https://github.com/${repository.owner}/${repository.repo}/releases`;
22+
export const githubStableReleasesUrl = `${githubReleasesUrl}?q=prerelease:false`;
23+
export const githubNightlyReleasesUrl = `${githubReleasesUrl}?q=prerelease:true`;
2024

2125
export interface GithubRelease {
2226
id: number;
2327
name: string;
2428
prerelease: boolean;
2529
downloads: ReleaseDownloads;
2630
url: string;
31+
tag: string;
2732
avm2_report_asset_id?: number;
2833
}
2934

src/app/downloads/github.tsx

+123-32
Original file line numberDiff line numberDiff line change
@@ -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";
1214
import { Octokit } from "octokit";
1315
import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods";
1416
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+
};
1526

1627
function 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+
70166
export 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
}
76171
export 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

102195
export 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,

src/app/downloads/page.tsx

+12-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import {
1818
githubReleasesUrl,
1919
maxNightlies,
2020
} from "@/app/downloads/config";
21-
import { getLatestReleases } from "@/app/downloads/github";
21+
import {
22+
getLatestNightlyReleases,
23+
getLatestStableReleases,
24+
} from "@/app/downloads/github";
2225

2326
function WebDownload({ latest }: { latest: GithubRelease | null }) {
2427
return (
@@ -94,19 +97,21 @@ function DesktopDownload({ latest }: { latest: GithubRelease | null }) {
9497
}
9598

9699
export default async function Page() {
97-
const releases = await getLatestReleases();
98-
const latest = releases.length > 0 ? releases[0] : null;
99-
const nightlies = releases
100+
const stableReleases = await getLatestStableReleases();
101+
const nightlies = (await getLatestNightlyReleases())
100102
.filter((release) => release.prerelease)
101103
.slice(0, maxNightlies);
104+
const latestStable =
105+
stableReleases.length > 0 ? stableReleases[0] : nightlies[0];
102106
return (
103107
<Container size="xl" className={classes.container}>
104108
<Stack gap="xl">
105109
<ExtensionList />
106-
<WebDownload latest={latest} />
107-
<DesktopDownload latest={latest} />
110+
<WebDownload latest={latestStable} />
111+
<DesktopDownload latest={latestStable} />
108112

109-
<ReleaseList releases={nightlies} />
113+
<ReleaseList releases={stableReleases} nightly={false} />
114+
<ReleaseList releases={nightlies} nightly={true} />
110115
</Stack>
111116
</Container>
112117
);

0 commit comments

Comments
 (0)