mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 13:12:44 +00:00
92 lines
4.1 KiB
TypeScript
92 lines
4.1 KiB
TypeScript
/**
|
|
* The `/changelog` page's data: every timever release apps/cli/release-topics.ts
|
|
* published — one per finished topic and per finished `Review <N>` issue —
|
|
* read from the GitHub API at build time (pages/changelog.astro awaits it).
|
|
*
|
|
* One page, no per-release subpages: each release becomes one timeline card,
|
|
* so the fields come from the release itself rather than a staged content
|
|
* entry. The date is the timever tag (`YYYYMMDD.HHMMSS.NNNNNNNNN`, UTC — the
|
|
* instant the last required problem closed), never `published_at`, which the
|
|
* API cannot backdate and which reads the backfill day for every old release.
|
|
* The problems are the rows of the body's `### Closed …` tables, the shape
|
|
* release-topics.ts's render() writes: `| #issue | LC n · Name | set | diff |
|
|
* closed | [py](…) · [docs](…) |`. Only `### Closed …` sections are read, so
|
|
* a release published before open problems were dropped from the body still
|
|
* shows only what finished. The row's own `[docs]` link is not used: it is
|
|
* frozen at release time (a problem solved before the docs existed has
|
|
* none), while the page links each problem by its LC number to its page now.
|
|
*
|
|
* Auth: GITHUB_TOKEN when set (deploy.yml passes it), else unauthenticated —
|
|
* one request per 100 releases fits the anonymous limit. A failed request
|
|
* fails the build: a changelog silently missing its releases is worse.
|
|
*/
|
|
|
|
const REPO = "prdlk/leetcode";
|
|
const MARKER = /<!-- release-topics: #(\d+) -->/u;
|
|
const TIMEVER = /^(\d{4})(\d{2})(\d{2})\.(\d{2})(\d{2})(\d{2})\.\d{9}$/u;
|
|
const KIND = /^(Topic|Review) \d+ — /u;
|
|
|
|
export interface ReleaseProblem {
|
|
issue: number;
|
|
/** The LeetCode number, the key of the docs `/<num>` short links. */
|
|
lc: number;
|
|
/** `LC 57 · Insert Interval`. */
|
|
title: string;
|
|
set: string;
|
|
/** `easy` / `medium` / `hard`, the problem's `diff:*` label. */
|
|
difficulty: string;
|
|
}
|
|
|
|
export interface Release {
|
|
/** Topic or Review issue the release records. */
|
|
issue: number;
|
|
name: string;
|
|
kind: "Topic" | "Review";
|
|
/** UTC instant the last required problem closed, from the timever tag. */
|
|
finishedAt: Date;
|
|
url: string;
|
|
problems: ReleaseProblem[];
|
|
}
|
|
|
|
/** Rows of every `### Closed …` table in a release body. */
|
|
function closedProblems(body: string): ReleaseProblem[] {
|
|
const problems: ReleaseProblem[] = [];
|
|
let closed = false;
|
|
for (const line of body.split("\n")) {
|
|
if (line.startsWith("### ")) closed = line.startsWith("### Closed");
|
|
if (!closed || !line.startsWith("| #")) continue;
|
|
const [issue, title, set, difficulty] = line.slice(1, -1).split("|").map((cell) => cell.trim());
|
|
const lc = title?.match(/^LC (\d+) · /u)?.[1];
|
|
if (!issue || !title || !lc || !set || !difficulty) continue;
|
|
problems.push({ issue: Number(issue.slice(1)), lc: Number(lc), title, set, difficulty });
|
|
}
|
|
return problems;
|
|
}
|
|
|
|
export async function readReleases(): Promise<Release[]> {
|
|
const headers: Record<string, string> = { accept: "application/vnd.github+json" };
|
|
if (process.env.GITHUB_TOKEN) headers.authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
const releases: Release[] = [];
|
|
for (let page = 1; ; page++) {
|
|
const res = await fetch(`https://api.github.com/repos/${REPO}/releases?per_page=100&page=${page}`, { headers });
|
|
if (!res.ok) throw new Error(`GET ${REPO} releases -> ${res.status} ${await res.text()}`);
|
|
const batch = (await res.json()) as { tag_name: string; name: string | null; body: string | null; html_url: string }[];
|
|
for (const r of batch) {
|
|
const issue = r.body?.match(MARKER)?.[1];
|
|
const t = r.tag_name.match(TIMEVER);
|
|
const kind = r.name?.match(KIND)?.[1];
|
|
if (!issue || !t || !kind || !r.name || !r.body) continue;
|
|
releases.push({
|
|
issue: Number(issue),
|
|
name: r.name,
|
|
kind: kind as Release["kind"],
|
|
finishedAt: new Date(`${t[1]}-${t[2]}-${t[3]}T${t[4]}:${t[5]}:${t[6]}Z`),
|
|
url: r.html_url,
|
|
problems: closedProblems(r.body),
|
|
});
|
|
}
|
|
if (batch.length < 100) break;
|
|
}
|
|
return releases.sort((a, b) => b.finishedAt.getTime() - a.finishedAt.getTime());
|
|
}
|