aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Mou <dev@mou.fo>2026-02-21 19:01:55 +1100
committerJoe Mou <dev@mou.fo>2026-02-21 19:02:05 +1100
commit1707f6555a193aeb40021b8b874686ca665a4fb2 (patch)
tree5ef8b3631e354bfe199d9ef759e818e9867805ba
parentf4ee61bfd9b9a637a8da9b419d2aae2d36455e2f (diff)
Add latest release details
By Gemini
-rw-r--r--src/scraper.test.ts2
-rw-r--r--src/scraper.ts24
-rw-r--r--views/repo.eta11
3 files changed, 36 insertions, 1 deletions
diff --git a/src/scraper.test.ts b/src/scraper.test.ts
index 0b91b6a..5bd609a 100644
--- a/src/scraper.test.ts
+++ b/src/scraper.test.ts
@@ -30,6 +30,8 @@ describe("GitHub scraper", () => {
assert.strictEqual(data.info.watchers?.length, 2);
assert.strictEqual(data.info.forks?.length, 3);
assert.strictEqual(data.info.numReleases, 36);
+ assert.strictEqual(data.info.latestReleaseTag, "v4.0.5");
+ assert.ok(data.info.latestReleaseDate?.startsWith("2024-03-18"));
assert.ok(data.items.length > 0);
assert.ok(Object.keys(data.overviewHtml ?? {}).includes("README.md"));
diff --git a/src/scraper.ts b/src/scraper.ts
index d36df5b..0ed77b5 100644
--- a/src/scraper.ts
+++ b/src/scraper.ts
@@ -157,6 +157,8 @@ export interface RepoInfo {
watchers: string | null;
forks: string | null;
numReleases?: number;
+ latestReleaseTag?: string;
+ latestReleaseDate?: string;
}
export interface GitHubRepo extends GitHubTree {
@@ -282,9 +284,29 @@ function parseRepoInfo(html: string): RepoInfo {
document,
) as unknown as Element;
let numReleases: number | undefined;
+ let latestReleaseTag: string | undefined;
+ let latestReleaseDate: string | undefined;
+
if (releaseElem) {
const releaseText = getAttributeValue(releaseElem, "title") || textContent(releaseElem);
numReleases = parseInt(releaseText.replace(/,/g, ""), 10);
+
+ // Find the latest release tag and date if available
+ const cell = (releaseElem.parent as Element)?.parent?.parent as Element; // span -> a -> h2 -> cell
+ if (cell) {
+ const latestLink = cssSelect.selectOne("a[href*='/releases/tag/']", cell) as Element;
+ if (latestLink) {
+ const tagElem = cssSelect.selectOne(".css-truncate-target", latestLink);
+ if (tagElem) {
+ latestReleaseTag = textContent(tagElem).trim();
+ }
+
+ const timeElem = cssSelect.selectOne("relative-time", latestLink);
+ if (timeElem) {
+ latestReleaseDate = getAttributeValue(timeElem, "datetime") || undefined;
+ }
+ }
+ }
}
return {
@@ -294,6 +316,8 @@ function parseRepoInfo(html: string): RepoInfo {
watchers,
forks,
numReleases,
+ latestReleaseTag,
+ latestReleaseDate,
};
}
diff --git a/views/repo.eta b/views/repo.eta
index a6fcfb3..6204b69 100644
--- a/views/repo.eta
+++ b/views/repo.eta
@@ -18,7 +18,16 @@
<span class="detail"><img src="/static/icons/git-fork.svg" alt="forks" class="icon"> <%= it.info.forks %></span>
<% } %>
<% if (it.info.numReleases) { %>
- <div class="detail"><a href="/<%= it.repo.owner %>/<%= it.repo.name %>/releases"><%= it.info.numReleases %> releases</a></div>
+ <div class="detail">
+ <a href="/<%= it.repo.owner %>/<%= it.repo.name %>/releases"><%= it.info.numReleases %> releases</a>
+ <% if (it.info.latestReleaseTag) { %>
+ (<a href="/<%= it.repo.owner %>/<%= it.repo.name %>/releases/tag/<%= it.info.latestReleaseTag %>"><%= it.info.latestReleaseTag %></a>
+ <% if (it.info.latestReleaseDate) { %>
+ <time class="timestamp" title="<%= it.info.latestReleaseDate %>"><%= it.info.latestReleaseDate.split("T")[0] %></time>
+ <% } %>
+ )
+ <% } %>
+ </div>
<% } %>
</div>
<% } %>