mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 21:22:43 +00:00
feat(cli): sync LeetCode topic tags, generate guide placeholders, and write video frontmatter instead of embeds
This commit is contained in:
1 parent
a9550769f0
commit
0473a3651f
2 files changed
+155
-44
No files matched your search
+22
-24
@@ -2,24 +2,23 @@
|
||||
/**
|
||||
* Find the NeetCode explanation video for every docs page that has none.
|
||||
*
|
||||
* A finished solution page ends with `## Explanation` holding one
|
||||
* `<YouTube id="…" title="…" />` — NeetCode's walkthrough of the problem.
|
||||
* `sync.ts` never writes that section (it owns only `## Solution`), so pages
|
||||
* accumulate without one. This script lists every numbered page lacking a
|
||||
* `<YouTube` embed and looks each up on NeetCode's two channels, main channel
|
||||
* first, accepting only a title that names the page's exact LC number
|
||||
* (`… - Leetcode 226 - …`) and is not a #short. No YouTube API key: the
|
||||
* channel search page embeds its results as `ytInitialData` JSON, which is
|
||||
* all we parse. Ranking is the channel's own — the first exact-number match
|
||||
* wins.
|
||||
* A finished solution page carries frontmatter `video: <YouTube id>` —
|
||||
* NeetCode's walkthrough of the problem — which the docs `PageHeader` slot
|
||||
* (`apps/docs/components/ProblemBadges.astro`) renders as the red
|
||||
* "Explanation" badge beside the topic badges. `sync.ts` never writes that key
|
||||
* (it owns `icon:`, `topics:` and `## Solution`), so pages accumulate without
|
||||
* one. This script lists every numbered page lacking `video:` and looks each
|
||||
* up on NeetCode's two channels, main channel first, accepting only a title
|
||||
* that names the page's exact LC number (`… - Leetcode 226 - …`) and is not a
|
||||
* #short. No YouTube API key: the channel search page embeds its results as
|
||||
* `ytInitialData` JSON, which is all we parse. Ranking is the channel's own —
|
||||
* the first exact-number match wins.
|
||||
*
|
||||
* bun apps/cli/videos.ts # report: page → video (or "no match")
|
||||
* bun apps/cli/videos.ts --write # also append `## Explanation` + embed
|
||||
* bun apps/cli/videos.ts --write # also add `video:` to the frontmatter
|
||||
*
|
||||
* --write appends at end of file (below `## Solution`, where sync.ts leaves
|
||||
* human sections alone) and adds the `## Explanation` heading only when the
|
||||
* page lacks it. A page with no match is left untouched, so re-runs are
|
||||
* no-ops once every page has its embed.
|
||||
* --write inserts the key before `sidebar:`. A page with no match is left
|
||||
* untouched, so re-runs are no-ops once every page has its video.
|
||||
*/
|
||||
import { join } from "node:path";
|
||||
|
||||
@@ -126,23 +125,21 @@ interface Page {
|
||||
text: string;
|
||||
}
|
||||
|
||||
/** Numbered solution pages without a `<YouTube` embed, by LC number. */
|
||||
/** The page's frontmatter block, `---` to `---`. */
|
||||
const FRONTMATTER = /^---\n[\s\S]*?\n---/;
|
||||
|
||||
/** Numbered solution pages without a `video:` key, by LC number. */
|
||||
async function pagesMissingVideo(): Promise<Page[]> {
|
||||
const pages: Page[] = [];
|
||||
for (const rel of await Array.fromAsync(new Bun.Glob("**/*.mdx").scan(DOCS))) {
|
||||
const text = await Bun.file(join(DOCS, rel)).text();
|
||||
const t = text.match(/^title: '(\d+)\./m);
|
||||
if (t && !text.includes("<YouTube")) pages.push({ num: Number(t[1]), rel, text });
|
||||
const fm = text.match(FRONTMATTER)?.[0] ?? "";
|
||||
if (t && !/^video:/m.test(fm)) pages.push({ num: Number(t[1]), rel, text });
|
||||
}
|
||||
return pages.sort((a, b) => a.num - b.num);
|
||||
}
|
||||
|
||||
function withEmbed(page: Page, video: Video): string {
|
||||
const embed = `<YouTube id="${video.id}" title="${video.title.replaceAll('"', "'")}" />`;
|
||||
const heading = /^## Explanation\s*$/m.test(page.text) ? "" : "## Explanation\n\n";
|
||||
return `${page.text.replace(/\s*$/, "\n")}\n${heading}${embed}\n`;
|
||||
}
|
||||
|
||||
// ── main ─────────────────────────────────────────────────────────
|
||||
|
||||
const missing = await pagesMissingVideo();
|
||||
@@ -162,7 +159,8 @@ for (const page of missing) {
|
||||
found++;
|
||||
let status = "";
|
||||
if (WRITE) {
|
||||
await Bun.write(join(DOCS, page.rel), withEmbed(page, video));
|
||||
const next = page.text.replace(FRONTMATTER, (fm) => fm.replace(/^sidebar:$/m, `video: ${JSON.stringify(video.id)}\nsidebar:`));
|
||||
await Bun.write(join(DOCS, page.rel), next);
|
||||
status = "written";
|
||||
}
|
||||
rows.push([page.rel, `https://youtu.be/${video.id}`, `${video.title} (${video.channel})`, status]);
|
||||
|
||||
Reference in new issue
Block a user