Skip to content

Commit

Permalink
Add stable releases
Browse files Browse the repository at this point in the history
  • Loading branch information
kjarosh committed Aug 5, 2024
1 parent 61a325e commit b8a580b
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 62 deletions.
12 changes: 9 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@
"react": "^18",
"react-device-detect": "^2.2.3",
"react-dom": "^18",
"recharts": "^2.12.7"
"recharts": "^2.12.7",
"semver": "^7.6.3"
},
"devDependencies": {
"@types/d3": "^7.4.3",
"@types/jsdom": "^21.1.7",
"@types/node": "^22",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/semver": "^7.5.8",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"d3": "^7.9.0",
Expand Down
5 changes: 5 additions & 0 deletions src/app/downloads/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ import { SummaryStatistics } from "@/app/compatibility/avm2/report_utils";

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

export const maxMinor = 3;
export const maxMajor = 1;
export const maxNightlies = 5;

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

export interface GithubRelease {
id: number;
name: string;
prerelease: boolean;
downloads: ReleaseDownloads;
url: string;
tag: string;
avm2_report_asset_id?: number;
}

Expand Down
155 changes: 123 additions & 32 deletions src/app/downloads/github.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,24 @@ import {
DownloadKey,
FilenamePatterns,
GithubRelease,
maxMajor,
maxMinor,
maxNightlies,
ReleaseDownloads,
repository,
} from "@/app/downloads/config";
import { Octokit } from "octokit";
import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods";
import { parse } from "node-html-parser";
import semver from "semver/preload";
import { components } from "@octokit/openapi-types";

const octokit = new Octokit({ authStrategy: createGithubAuth });

const requestCache = {
// Set cache to 30 min to prevent rate limiting during development
request: { next: { revalidate: 1800 } },
};

function createGithubAuth() {
if (process.env.GITHUB_TOKEN) {
Expand All @@ -28,37 +39,65 @@ function throwBuildError() {
throw new Error("Build failed");
}

export async function getLatestReleases(): Promise<GithubRelease[]> {
const octokit = new Octokit({ authStrategy: createGithubAuth });
function mapRelease(release: components["schemas"]["release"]): GithubRelease {
const downloads: ReleaseDownloads = {};
let avm2_report_asset_id: number | undefined = undefined;
for (const asset of release.assets) {
if (asset.name === "avm2_report.json") {
avm2_report_asset_id = asset.id;
}
for (const [key, pattern] of Object.entries(FilenamePatterns)) {
if (asset.name.indexOf(pattern) > -1) {
downloads[key as DownloadKey] = asset.browser_download_url;
}
}
}

return {
id: release.id,
name: release.name || release.tag_name,
prerelease: release.prerelease,
url: release.html_url,
tag: release.tag_name,
downloads,
avm2_report_asset_id,
};
}

export async function getLatestRelease(): Promise<GithubRelease> {
try {
const response = await octokit.rest.repos.getLatestRelease({
...requestCache,
...repository,
});
return mapRelease(response.data);
} catch {
// There's no stable release, get the latest nightly.
}

const releases = await octokit.rest.repos.listReleases({
per_page: 1,
...requestCache,
...repository,
});
return mapRelease(releases.data[0]);
}

export async function getLatestNightlyReleases(): Promise<GithubRelease[]> {
try {
const releases = await octokit.rest.repos.listReleases({
per_page: maxNightlies + 2, // more than we need to account for a possible draft release + possible full release
request: { next: { revalidate: 1800 } },
// We have to take into account possible stable releases here
per_page: maxNightlies + 4,
...requestCache,
...repository,
});
const result = [];
let avm2_report_asset_id: number | undefined = undefined;
for (const release of releases.data) {
const downloads: ReleaseDownloads = {};
for (const asset of release.assets) {
if (asset.name === "avm2_report.json") {
avm2_report_asset_id = asset.id;
}
for (const [key, pattern] of Object.entries(FilenamePatterns)) {
if (asset.name.indexOf(pattern) > -1) {
downloads[key as DownloadKey] = asset.browser_download_url;
}
}
if (!release.prerelease) {
// Filter out stable releases
continue;
}

result.push({
id: release.id,
name: release.name || release.tag_name,
prerelease: release.prerelease,
url: release.html_url,
downloads,
avm2_report_asset_id,
});
result.push(mapRelease(release));
}
return result;
} catch (error) {
Expand All @@ -67,25 +106,79 @@ export async function getLatestReleases(): Promise<GithubRelease[]> {
}
}

export async function getLatestStableReleases(): Promise<GithubRelease[]> {
let newestMajor = null;

// Map representing releases from the current major version:
// `major.minor` -> `major.minor.patch`
// We want to ignore older patches and show last X minor versions.
const currentMajorReleases = new Map();

// Map representing releases from older major versions.
// `major` -> `major.minor.patch`
// We ignore here minor and patch versions, and
// gather the newest release per each major.
const olderMajors = new Map();

for (
let page = 1;
currentMajorReleases.size < maxMinor || olderMajors.size < maxMajor - 1;
++page
) {
const request = await octokit.rest.repos.listReleases({
// 100 per page disables cache as the result is >2MB
per_page: 80,
page: page,
...requestCache,
...repository,
});
if (request.status != 200 || request.data.length == 0) {
break;
}
for (const data of request.data) {
if (data.prerelease) {
continue;
}
const release = mapRelease(data);
const version = release.tag.replace(/^v/, "");
const major = semver.major(version);
const majorMinor = `${major}.${semver.minor(version)}`;
if (!newestMajor) {
newestMajor = major;
}
if (major === newestMajor) {
if (!currentMajorReleases.has(majorMinor)) {
currentMajorReleases.set(majorMinor, release);
}
} else {
if (!olderMajors.has(major)) {
olderMajors.set(major, release);
}
}
}
}

return Array.from(currentMajorReleases.values())
.slice(0, maxMinor)
.concat(Array.from(olderMajors.values()).slice(0, maxMajor - 1));
}

export async function getWeeklyContributions(): Promise<
RestEndpointMethodTypes["repos"]["getCommitActivityStats"]["response"]
> {
const octokit = new Octokit({ authStrategy: createGithubAuth });
return octokit.rest.repos.getCommitActivityStats(repository);
}
export async function fetchReport(): Promise<AVM2Report | undefined> {
const releases = await getLatestReleases();
const releases = await getLatestNightlyReleases();
const latest = releases.find(
(release) => release.avm2_report_asset_id !== undefined,
);
if (!latest?.avm2_report_asset_id) {
throwBuildError();
return;
}
const octokit = new Octokit({ authStrategy: createGithubAuth });
const asset = await octokit.rest.repos.getReleaseAsset({
owner: repository.owner,
repo: repository.repo,
...repository,
asset_id: latest.avm2_report_asset_id,
headers: {
accept: "application/octet-stream",
Expand All @@ -100,10 +193,8 @@ export async function fetchReport(): Promise<AVM2Report | undefined> {
}

export async function getAVM1Progress(): Promise<number> {
const octokit = new Octokit({ authStrategy: createGithubAuth });
const issues = await octokit.rest.issues.listForRepo({
owner: repository.owner,
repo: repository.repo,
...repository,
labels: "avm1-tracking",
state: "all",
per_page: 65,
Expand Down
19 changes: 12 additions & 7 deletions src/app/downloads/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import {
githubReleasesUrl,
maxNightlies,
} from "@/app/downloads/config";
import { getLatestReleases } from "@/app/downloads/github";
import {
getLatestNightlyReleases,
getLatestStableReleases,
} from "@/app/downloads/github";

function WebDownload({ latest }: { latest: GithubRelease | null }) {
return (
Expand Down Expand Up @@ -94,19 +97,21 @@ function DesktopDownload({ latest }: { latest: GithubRelease | null }) {
}

export default async function Page() {
const releases = await getLatestReleases();
const latest = releases.length > 0 ? releases[0] : null;
const nightlies = releases
const stableReleases = await getLatestStableReleases();
const nightlies = (await getLatestNightlyReleases())
.filter((release) => release.prerelease)
.slice(0, maxNightlies);
const latestStable =
stableReleases.length > 0 ? stableReleases[0] : nightlies[0];
return (
<Container size="xl" className={classes.container}>
<Stack gap="xl">
<ExtensionList />
<WebDownload latest={latest} />
<DesktopDownload latest={latest} />
<WebDownload latest={latestStable} />
<DesktopDownload latest={latestStable} />

<ReleaseList releases={nightlies} />
<ReleaseList releases={stableReleases} nightly={false} />
<ReleaseList releases={nightlies} nightly={true} />
</Stack>
</Container>
);
Expand Down
Loading

0 comments on commit b8a580b

Please sign in to comment.