Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
- name: Generate footer stars
run: yarn generate:footer-stars

- name: Generate homepage release
run: yarn generate:homepage-release

- name: Build
run: yarn build

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/pr_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,8 @@ jobs:
- name: Generate footer stars
run: yarn generate:footer-stars

- name: Generate homepage release
run: yarn generate:homepage-release

- name: Build website
run: yarn build --locale en
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"docusaurus": "docusaurus",
"generate:footer-stars": "node scripts/generate-footer-stars.js",
"generate:homepage-release": "node scripts/generate-homepage-release.js",
"start": "node scripts/start.js",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
Expand Down
46 changes: 12 additions & 34 deletions scripts/generate-footer-stars.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const fs = require("fs");
const path = require("path");
const {
fetchGitHubJson,
readExistingJson,
writeJson,
} = require("./github-data-utils");

const badges = require("../src/components/FooterMoreBadges/badges.json");

Expand All @@ -16,40 +20,14 @@ function getRepoKey(owner, repo) {
return `${owner}/${repo}`;
}

function readExistingStars() {
if (!fs.existsSync(outputPath)) {
return null;
}

try {
return JSON.parse(fs.readFileSync(outputPath, "utf8"));
} catch {
return null;
}
}

async function fetchStars() {
const headers = {
Accept: "application/vnd.github+json",
"User-Agent": "casbin-website-footer-stars",
};

if (process.env.GITHUB_TOKEN) {
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
}

const results = await Promise.all(
badges.map(async({owner, repo}) => {
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
headers,
});

if (!response.ok) {
const body = await response.text();
throw new Error(`Failed to fetch stars for ${owner}/${repo}: ${response.status} ${body}`);
}

const data = await response.json();
const data = await fetchGitHubJson(
`https://api.github.com/repos/${owner}/${repo}`,
"casbin-website-footer-stars",
`stars for ${owner}/${repo}`
);

if (typeof data.stargazers_count !== "number") {
throw new Error(`Missing stargazers_count for ${owner}/${repo}`);
Expand All @@ -63,7 +41,7 @@ async function fetchStars() {
}

async function main() {
const existingStars = readExistingStars();
const existingStars = readExistingJson(outputPath);

try {
const stars = await fetchStars();
Expand All @@ -72,7 +50,7 @@ async function main() {
stars,
};

fs.writeFileSync(outputPath, `${JSON.stringify(payload, null, 2)}\n`);
writeJson(outputPath, payload);
process.stdout.write(`Updated footer stars at ${outputPath}\n`);
} catch (error) {
if (existingStars?.stars) {
Expand Down
51 changes: 51 additions & 0 deletions scripts/generate-homepage-release.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const path = require("path");
const {
fetchGitHubJson,
readExistingJson,
writeJson,
} = require("./github-data-utils");

const outputPath = path.join(__dirname, "..", "src", "data", "latest-release.json");
const fallbackTag = "v3.4.1";
const fallbackUrl = `https://github.com/apache/casbin/releases/tag/${fallbackTag}`;

async function fetchLatestRelease() {
const data = await fetchGitHubJson(
"https://api.github.com/repos/casbin/casbin/releases/latest",
"casbin-website-homepage-release",
"latest homepage release"
);

return {
generatedAt: new Date().toISOString(),
tagName: data.tag_name || fallbackTag,
url: data.html_url || fallbackUrl,
};
}

async function main() {
const existingRelease = readExistingJson(outputPath);

try {
const payload = await fetchLatestRelease();
writeJson(outputPath, payload);
process.stdout.write(`Updated homepage release at ${outputPath}\n`);
} catch (error) {
if (existingRelease?.tagName && existingRelease?.url) {
process.stderr.write(`${error.message}\nUsing existing homepage release JSON.\n`);
return;
}

writeJson(outputPath, {
generatedAt: new Date().toISOString(),
tagName: fallbackTag,
url: fallbackUrl,
});
process.stderr.write(`${error.message}\nUsing fallback homepage release JSON.\n`);
}
}

main().catch((error) => {
process.stderr.write(`${error.stack || error}\n`);
process.exit(1);
});
45 changes: 45 additions & 0 deletions scripts/github-data-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const fs = require("fs");
const path = require("path");

function readExistingJson(filePath) {
if (!fs.existsSync(filePath)) {
return null;
}

try {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch {
return null;
}
}

function writeJson(filePath, payload) {
fs.mkdirSync(path.dirname(filePath), {recursive: true});
fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`);
}

function getGitHubHeaders(userAgent) {
return {
Accept: "application/vnd.github+json",
"User-Agent": userAgent,
};
}

async function fetchGitHubJson(url, userAgent, errorLabel) {
const response = await fetch(url, {
headers: getGitHubHeaders(userAgent),
});

if (!response.ok) {
const body = await response.text();
throw new Error(`Failed to fetch ${errorLabel}: ${response.status} ${body}`);
}

return response.json();
}

module.exports = {
fetchGitHubJson,
readExistingJson,
writeJson,
};
5 changes: 5 additions & 0 deletions src/data/latest-release.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"generatedAt": "2026-04-18T02:02:59.172Z",
"tagName": "v3.10.0",
"url": "https://github.com/apache/casbin/releases/tag/v3.10.0"
}
16 changes: 6 additions & 10 deletions src/pages/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useEffect, useRef, useState} from "react";
import React, {useEffect, useRef} from "react";
import clsx from "clsx";
import Layout from "@theme/Layout";
import Link from "@docusaurus/Link";
Expand All @@ -12,26 +12,22 @@ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import AnimatedText from "../components/AnimatedText";
import {Code, Zap} from "lucide-react";
import LogoCarousel from "@site/src/components/LogoCarousel";
import latestReleaseData from "@site/src/data/latest-release.json";

function HomepageHeader() {
const [latestVersion, setLatestVersion] = useState("v3.4.1");
const headerRef = useRef(null);
const haloRef = useRef(null);
const {siteConfig} = useDocusaurusContext();
const {customFields} = siteConfig;
const latestVersion = latestReleaseData.tagName || "v3.4.1";
const latestReleaseLink =
latestReleaseData.url || `https://github.com/apache/casbin/releases/tag/${latestVersion}`;

// Activate halo behavior for this header
useHeroCursorHalo(headerRef, haloRef);

useEffect(() => {
fetch("https://api.github.com/repos/casbin/casbin/releases/latest")
.then(res => res.json())
.then(data => setLatestVersion(data.tag_name || "v3.4.1"))
.catch(() => setLatestVersion("v3.4.1"));
}, []);

const pillText = customFields?.customMessage || `${latestVersion} Released`;
const link = customFields?.customLink || `https://github.com/casbin/casbin/releases/tag/${latestVersion}`;
const link = customFields?.customLink || latestReleaseLink;

return (
<header ref={headerRef} className={clsx("hero hero--primary", styles.heroBanner)}>
Expand Down
Loading