mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 21:22:43 +00:00
feat(cli): add release name handling for topic releases
This commit is contained in:
1 parent
6f9e4ecb3b
commit
8e8522a773
1 file changed
+30
-11
+30
-11
@@ -111,15 +111,16 @@ finished.sort((a, b) => a.finishedAt.localeCompare(b.finishedAt) || a.topic.numb
|
|||||||
|
|
||||||
// ── existing releases ────────────────────────────────────────────
|
// ── existing releases ────────────────────────────────────────────
|
||||||
|
|
||||||
/** Topic -> its release. The body is kept in sync with render() on every run. */
|
/** Topic -> its release. Name and body are kept in sync with the render on every run. */
|
||||||
const released = new Map<number, { id: number; tag: string; body: string }>();
|
const released = new Map<number, { id: number; tag: string; name: string; body: string }>();
|
||||||
for await (const raw of gh.list(`/repos/${gh.repo}/releases`)) {
|
for await (const raw of gh.list(`/repos/${gh.repo}/releases`)) {
|
||||||
if (!raw || typeof raw !== "object") continue;
|
if (!raw || typeof raw !== "object") continue;
|
||||||
if (!("id" in raw) || typeof raw.id !== "number") continue;
|
if (!("id" in raw) || typeof raw.id !== "number") continue;
|
||||||
if (!("tag_name" in raw) || typeof raw.tag_name !== "string") continue;
|
if (!("tag_name" in raw) || typeof raw.tag_name !== "string") continue;
|
||||||
if (!("body" in raw) || typeof raw.body !== "string") continue;
|
if (!("body" in raw) || typeof raw.body !== "string") continue;
|
||||||
|
const name = "name" in raw && typeof raw.name === "string" ? raw.name : "";
|
||||||
const m = raw.body.match(/<!-- release-topics: #(\d+) -->/);
|
const m = raw.body.match(/<!-- release-topics: #(\d+) -->/);
|
||||||
if (m) released.set(Number(m[1]), { id: raw.id, tag: raw.tag_name, body: raw.body });
|
if (m) released.set(Number(m[1]), { id: raw.id, tag: raw.tag_name, name, body: raw.body });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── timever ──────────────────────────────────────────────────────
|
// ── timever ──────────────────────────────────────────────────────
|
||||||
@@ -158,6 +159,17 @@ const ET_DAY = new Intl.DateTimeFormat("en-US", {
|
|||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
});
|
});
|
||||||
|
/**
|
||||||
|
* The release name: the ET day the topic finished (`September 4, 2026`). The
|
||||||
|
* docs changelog titles each entry with it, so the timeline reads by day; the
|
||||||
|
* topic itself leads the body's summary sentence.
|
||||||
|
*/
|
||||||
|
const ET_NAME = new Intl.DateTimeFormat("en-US", {
|
||||||
|
timeZone: "America/New_York",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
});
|
||||||
|
|
||||||
/** Topic body sections worth carrying into the changelog (skipped while still `TODO`). */
|
/** Topic body sections worth carrying into the changelog (skipped while still `TODO`). */
|
||||||
const NOTES = ["Trigger", "Invariant", "Trap"] as const;
|
const NOTES = ["Trigger", "Invariant", "Trap"] as const;
|
||||||
@@ -202,10 +214,11 @@ function render(f: Finished, tag: string, sha: string, files: Map<number, string
|
|||||||
|
|
||||||
// Blume's changelog derives each page's meta description from the opening
|
// Blume's changelog derives each page's meta description from the opening
|
||||||
// prose, stripping markdown and line breaks without adding a space — so lead
|
// prose, stripping markdown and line breaks without adding a space — so lead
|
||||||
// with one self-contained sentence and keep the fields on one `·` line.
|
// with one self-contained sentence and keep the fields on one `·` line. The
|
||||||
|
// release name is only the date, so the sentence names the topic.
|
||||||
const when = ET_WHEN.format(new Date(f.finishedAt));
|
const when = ET_WHEN.format(new Date(f.finishedAt));
|
||||||
const summary =
|
const summary =
|
||||||
`Finished ${when} ET: all ${f.core.length} core problems closed, ` +
|
`**${topic.title}** finished ${when} ET: all ${f.core.length} core problems closed, ` +
|
||||||
`${onDay.length} ${onDay.length === 1 ? "problem" : "problems"} closed that day.`;
|
`${onDay.length} ${onDay.length === 1 ? "problem" : "problems"} closed that day.`;
|
||||||
const fields: string[] = [];
|
const fields: string[] = [];
|
||||||
const scheduled = bodyField(topic.body, "Scheduled");
|
const scheduled = bodyField(topic.body, "Scheduled");
|
||||||
@@ -243,8 +256,13 @@ for (const f of finished) {
|
|||||||
const { topic } = f;
|
const { topic } = f;
|
||||||
const finishedEt = etDate(new Date(f.finishedAt));
|
const finishedEt = etDate(new Date(f.finishedAt));
|
||||||
const existing = released.get(topic.number);
|
const existing = released.get(topic.number);
|
||||||
// An existing release keeps its tag and commit; only its body is reconciled.
|
// An existing release keeps its tag and commit; only its name and body are
|
||||||
|
// reconciled. Its tag may postdate this clone (released by another run), so
|
||||||
|
// fetch it when missing — read-only, so a dry run does it too.
|
||||||
const tag = existing?.tag ?? tags.get(topic.number)!;
|
const tag = existing?.tag ?? tags.get(topic.number)!;
|
||||||
|
if (existing && Bun.spawnSync(["git", "rev-parse", "-q", "--verify", `refs/tags/${tag}`], { cwd: ROOT }).exitCode !== 0) {
|
||||||
|
git("fetch", "-q", "origin", `refs/tags/${tag}:refs/tags/${tag}`);
|
||||||
|
}
|
||||||
const sha = existing
|
const sha = existing
|
||||||
? git("rev-parse", `${tag}^{commit}`)
|
? git("rev-parse", `${tag}^{commit}`)
|
||||||
: git("rev-list", "-1", "--first-parent", `--before=${f.finishedAt}`, "HEAD");
|
: git("rev-list", "-1", "--first-parent", `--before=${f.finishedAt}`, "HEAD");
|
||||||
@@ -263,17 +281,18 @@ for (const f of finished) {
|
|||||||
files.set(Number(lc), [...(files.get(Number(lc)) ?? []), path]);
|
files.set(Number(lc), [...(files.get(Number(lc)) ?? []), path]);
|
||||||
}
|
}
|
||||||
const body = render(f, tag, sha, files);
|
const body = render(f, tag, sha, files);
|
||||||
|
const name = ET_NAME.format(new Date(f.finishedAt));
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
if (existing.body === body) {
|
if (existing.body === body && existing.name === name) {
|
||||||
report.push([`#${topic.number}`, topic.title, finishedEt, tag, "released"]);
|
report.push([`#${topic.number}`, topic.title, finishedEt, tag, "released"]);
|
||||||
} else if (DRY) {
|
} else if (DRY) {
|
||||||
previews.push(`## ${tag} — ${topic.title}\n\n${body}`);
|
previews.push(`## ${name} (${tag})\n\n${body}`);
|
||||||
report.push([`#${topic.number}`, topic.title, finishedEt, tag, "would update"]);
|
report.push([`#${topic.number}`, topic.title, finishedEt, tag, "would update"]);
|
||||||
} else {
|
} else {
|
||||||
await gh.api(`/repos/${gh.repo}/releases/${existing.id}`, {
|
await gh.api(`/repos/${gh.repo}/releases/${existing.id}`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: JSON.stringify({ body }),
|
body: JSON.stringify({ name, body }),
|
||||||
});
|
});
|
||||||
report.push([`#${topic.number}`, topic.title, finishedEt, tag, "updated"]);
|
report.push([`#${topic.number}`, topic.title, finishedEt, tag, "updated"]);
|
||||||
updated++;
|
updated++;
|
||||||
@@ -282,7 +301,7 @@ for (const f of finished) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (DRY) {
|
if (DRY) {
|
||||||
previews.push(`## ${tag} — ${topic.title}\n\n${body}`);
|
previews.push(`## ${name} (${tag})\n\n${body}`);
|
||||||
report.push([`#${topic.number}`, topic.title, finishedEt, tag, "would release"]);
|
report.push([`#${topic.number}`, topic.title, finishedEt, tag, "would release"]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -303,7 +322,7 @@ for (const f of finished) {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
tag_name: tag,
|
tag_name: tag,
|
||||||
name: topic.title,
|
name,
|
||||||
body,
|
body,
|
||||||
make_latest: topic.number === latest ? "true" : "false",
|
make_latest: topic.number === latest ? "true" : "false",
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in new issue
Block a user