From cd5f41693955cf58646bdfc5c536f7bdc22bfb25 Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Mon, 5 Oct 2026 15:46:55 -0400 Subject: [PATCH] style(cli): reformat sync script for readability and consistency --- apps/cli/sync.ts | 219 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 166 insertions(+), 53 deletions(-) diff --git a/apps/cli/sync.ts b/apps/cli/sync.ts index d062262..f6d0c83 100755 --- a/apps/cli/sync.ts +++ b/apps/cli/sync.ts @@ -104,11 +104,15 @@ interface Solution { // ── work/ parsing ──────────────────────────────────────────────── /** Split a source file into (header comment text, code). */ -function splitSource(src: string, lang: Lang): { header: string; code: string } { +function splitSource( + src: string, + lang: Lang, +): { header: string; code: string } { let header: string, code: string; if (lang === "js") { const end = src.indexOf("*/"); - if (!src.trimStart().startsWith("/*") || end === -1) throw new Error("missing /* header */"); + if (!src.trimStart().startsWith("/*") || end === -1) + throw new Error("missing /* header */"); header = src .slice(src.indexOf("/*") + 2, end) .split("\n") @@ -140,7 +144,11 @@ function paragraphs(lines: string[]): string[] { flush(); continue; } - if (/^([•-] |Input:|Output:|Explanation:|Example \d+:|Constraints:|Follow[- ]?ups?:)/.test(text)) { + if ( + /^([•-] |Input:|Output:|Explanation:|Example \d+:|Constraints:|Follow[- ]?ups?:)/.test( + text, + ) + ) { flush(); cur = text; } else { @@ -158,7 +166,10 @@ function parseHeader(header: string): Header { const num = Number.parseInt(titleLine, 10); const title = titleLine.replace(/^\d+\.\s*/, ""); const difficulty = - lines.find((l) => l.trim().startsWith("Difficulty:"))?.split(":")[1]?.trim() ?? ""; + lines + .find((l) => l.trim().startsWith("Difficulty:")) + ?.split(":")[1] + ?.trim() ?? ""; // Body: everything after the ───── separator. const sep = lines.findIndex((l) => /^[─]{3,}/.test(l.trim())); @@ -288,10 +299,18 @@ async function readmeTopics(): Promise> { const p = line.match(/^## Phase ([IVX]+) — (.+?) · /); if (p) { const name = p[2]!.replaceAll("&", "&"); - phase = { order: ROMAN.indexOf(p[1]!) + 1, name: PHASE_ALIASES[name] ?? name }; + phase = { + order: ROMAN.indexOf(p[1]!) + 1, + name: PHASE_ALIASES[name] ?? name, + }; } const t = line.match(/(\d+)\.\s*([^<]+)<\/strong>/); - if (t) topic = { order: Number(t[1]), name: TOPIC_ALIASES[t[2]!.trim()] ?? t[2]!.trim(), phase }; + if (t) + topic = { + order: Number(t[1]), + name: TOPIC_ALIASES[t[2]!.trim()] ?? t[2]!.trim(), + phase, + }; if (!topic) continue; const n = line.match(/(\d+)<\/td>/); if (n && !map.has(Number(n[1]))) map.set(Number(n[1]), topic); @@ -348,10 +367,16 @@ function phaseMeta(phase: Phase): string { */ function groupMeta(topic: Topic): string { const icon = TOPIC_ICONS[topic.name]; - if (!icon) console.warn(`no icon for group ${JSON.stringify(topic.name)}: add it to TOPIC_ICONS`); - const fields = [`title: ${JSON.stringify(topic.name)}`, `order: ${topic.order ?? 0}`]; + if (!icon) + console.warn( + `no icon for group ${JSON.stringify(topic.name)}: add it to TOPIC_ICONS`, + ); + const fields = [ + `title: ${JSON.stringify(topic.name)}`, + `order: ${topic.order ?? 0}`, + ]; if (icon) fields.push(`icon: ${JSON.stringify(icon)}`); - fields.push(`display: "page"`); + fields.push(`display: "group"`); return `import { defineMeta } from "blume";\n\nexport default defineMeta({ ${fields.join(", ")} });\n`; } @@ -378,17 +403,24 @@ async function fetchTopicTags(sol: Solution): Promise { referer: `https://leetcode.com/problems/${sol.slug}/`, }, body: JSON.stringify({ - query: "query topicTags($titleSlug: String!) { question(titleSlug: $titleSlug) { topicTags { name slug } } }", + query: + "query topicTags($titleSlug: String!) { question(titleSlug: $titleSlug) { topicTags { name slug } } }", variables: { titleSlug: sol.slug }, }), }); - if (!res.ok) throw new Error(`POST /graphql -> ${res.status} ${res.statusText}`); - const data = (await res.json()) as { data?: { question?: { topicTags?: TopicTag[] } | null } }; + if (!res.ok) + throw new Error(`POST /graphql -> ${res.status} ${res.statusText}`); + const data = (await res.json()) as { + data?: { question?: { topicTags?: TopicTag[] } | null }; + }; const tags = data.data?.question?.topicTags; - if (!Array.isArray(tags)) throw new Error("POST /graphql -> no such question"); + if (!Array.isArray(tags)) + throw new Error("POST /graphql -> no such question"); return tags.map(({ name, slug }) => ({ name, slug })); } catch (err) { - console.warn(`no topics for ${sol.num}.${sol.slug}: ${err instanceof Error ? err.message : err}`); + console.warn( + `no topics for ${sol.num}.${sol.slug}: ${err instanceof Error ? err.message : err}`, + ); return undefined; } } @@ -396,15 +428,30 @@ async function fetchTopicTags(sol: Solution): Promise { /** The frontmatter `topics:` block, one flow mapping per tag (JSON strings are valid YAML). */ function topicsYaml(tags: TopicTag[]): string { if (tags.length === 0) return "topics: []"; - return ["topics:", ...tags.map((t) => ` - { name: ${JSON.stringify(t.name)}, slug: ${JSON.stringify(t.slug)} }`)].join("\n"); + return [ + "topics:", + ...tags.map( + (t) => + ` - { name: ${JSON.stringify(t.name)}, slug: ${JSON.stringify(t.slug)} }`, + ), + ].join("\n"); } /** The frontmatter's `topics:` as sync wrote them (or a human edited them); [] when absent. */ function frontmatterTopics(page: string): TopicTag[] { const fm = page.match(/^---\n([\s\S]*?)\n---/)?.[1]; const data: unknown = fm ? Bun.YAML.parse(fm) : undefined; - if (!data || typeof data !== "object" || !("topics" in data) || !Array.isArray(data.topics)) return []; - return data.topics.filter((t): t is TopicTag => typeof t?.name === "string" && typeof t?.slug === "string"); + if ( + !data || + typeof data !== "object" || + !("topics" in data) || + !Array.isArray(data.topics) + ) + return []; + return data.topics.filter( + (t): t is TopicTag => + typeof t?.name === "string" && typeof t?.slug === "string", + ); } // ── GitHub problem issues ──────────────────────────────────────── @@ -422,7 +469,9 @@ function problemIssues(): Promise | undefined> { try { const gh = await github(); const urls = new Map(); - for await (const raw of gh.list(`/repos/${gh.repo}/issues?labels=problem&state=all`)) { + for await (const raw of gh.list( + `/repos/${gh.repo}/issues?labels=problem&state=all`, + )) { if (!raw || typeof raw !== "object" || "pull_request" in raw) continue; if (!("title" in raw) || typeof raw.title !== "string") continue; if (!("html_url" in raw) || typeof raw.html_url !== "string") continue; @@ -431,7 +480,9 @@ function problemIssues(): Promise | undefined> { } return urls; } catch (err) { - console.warn(`no problem issues: ${err instanceof Error ? err.message : err}`); + console.warn( + `no problem issues: ${err instanceof Error ? err.message : err}`, + ); return undefined; } })(); @@ -448,7 +499,10 @@ async function issueUrl(sol: Solution): Promise { if (!topics.has(sol.num)) return undefined; const urls = await problemIssues(); const url = urls?.get(sol.num); - if (urls && !url) console.warn(`no problem issue titled "LC ${sol.num} · …" for ${sol.num}.${sol.slug}`); + if (urls && !url) + console.warn( + `no problem issue titled "LC ${sol.num} · …" for ${sol.num}.${sol.slug}`, + ); return url; } @@ -458,13 +512,15 @@ async function issueUrl(sol: Solution): Promise { function identifiers(sol: Solution): Set { const ids = new Set(["n", "m", "k"]); for (const code of Object.values(sol.code)) { - for (const m of code.matchAll(/@param\s*\{[^}]*\}\s*(\w+)/g)) ids.add(m[1]!); + for (const m of code.matchAll(/@param\s*\{[^}]*\}\s*(\w+)/g)) + ids.add(m[1]!); for (const m of code.matchAll(/def \w+\(self,?\s*([^)]*)\)/g)) for (const arg of m[1]!.split(",")) if (arg.trim()) ids.add(arg.split(":")[0]!.trim()); - for (const m of code.matchAll(/(?:var|const|let) (\w+) = function\s*\(([^)]*)\)/g)) - for (const arg of m[2]!.split(",")) - if (arg.trim()) ids.add(arg.trim()); + for (const m of code.matchAll( + /(?:var|const|let) (\w+) = function\s*\(([^)]*)\)/g, + )) + for (const arg of m[2]!.split(",")) if (arg.trim()) ids.add(arg.trim()); } return ids; } @@ -521,7 +577,11 @@ function backtickify(text: string, ids: Set): string { while (j > i && /^[+\-*/=%]$/.test(toks[j]!.core)) j--; const span = toks .slice(i, j + 1) - .map((tok, idx, arr) => (idx === arr.length - 1 ? `${tok.pre}${tok.core}` : `${tok.pre}${tok.core}${tok.post}`)) + .map((tok, idx, arr) => + idx === arr.length - 1 + ? `${tok.pre}${tok.core}` + : `${tok.pre}${tok.core}${tok.post}`, + ) .join(" "); out.push(`\`${span}\`${toks[j]!.post}`); i = j + 1; @@ -549,14 +609,22 @@ function warningText(h: Header): string { return ( h.statement .slice(1) - .find((p) => /^(You must|Your solution must|Your algorithm|Notice that|Could you)/.test(p)) ?? "" + .find((p) => + /^(You must|Your solution must|Your algorithm|Notice that|Could you)/.test( + p, + ), + ) ?? "" ); } /** Sidebar/page icon for every problem page (Lucide, resolved by Blume). */ const PAGE_ICON = "dot"; -function renderPage(sol: Solution, tags: TopicTag[] | undefined, issue: string | undefined): string { +function renderPage( + sol: Solution, + tags: TopicTag[] | undefined, + issue: string | undefined, +): string { const h = sol.header; const ids = identifiers(sol); const description = h.statement[0]?.replace(/\.\s*$/, "") ?? ""; @@ -565,7 +633,9 @@ function renderPage(sol: Solution, tags: TopicTag[] | undefined, issue: string | parts.push( "---", `title: '${h.num}. ${h.title.replaceAll("'", "''")}'`, - /[:#]/.test(description) ? `description: '${description.replaceAll("'", "''")}'` : `description: ${description}`, + /[:#]/.test(description) + ? `description: '${description.replaceAll("'", "''")}'` + : `description: ${description}`, `icon: ${PAGE_ICON}`, ...(tags ? [topicsYaml(tags)] : []), ...(issue ? [`issue: ${JSON.stringify(issue)}`] : []), @@ -587,7 +657,8 @@ function renderPage(sol: Solution, tags: TopicTag[] | undefined, issue: string | parts.push(`- Explanation: ${backtickify(ex.explanation[0]!, ids)}`); else if (ex.explanation.length > 1) { parts.push("- Explanation:"); - for (const item of ex.explanation) parts.push(` - ${backtickify(item, ids)}`); + for (const item of ex.explanation) + parts.push(` - ${backtickify(item, ids)}`); } parts.push(""); }); @@ -612,7 +683,10 @@ for await (const rel of new Bun.Glob("**/*.{js,py}").scan(WORK)) { } const num = Number(m[1]); const slug = m[2]!; - const { header, code } = splitSource(await Bun.file(join(WORK, rel)).text(), lang); + const { header, code } = splitSource( + await Bun.file(join(WORK, rel)).text(), + lang, + ); const parsed = parseHeader(header); const existing = solutions.get(num); if (existing) { @@ -661,7 +735,10 @@ async function ensureTopics(page: string, sol: Solution): Promise { if (!fm || /^topics:/m.test(fm[0])) return page; const tags = await fetchTopicTags(sol); if (!tags) return page; - return fm[0].replace(/^sidebar:$/m, `${topicsYaml(tags)}\nsidebar:`) + page.slice(fm[0].length); + return ( + fm[0].replace(/^sidebar:$/m, `${topicsYaml(tags)}\nsidebar:`) + + page.slice(fm[0].length) + ); } /** Insert `issue:` before `sidebar:` when the frontmatter lacks it; an existing URL is kept as is. */ @@ -670,7 +747,10 @@ async function ensureIssue(page: string, sol: Solution): Promise { if (!fm || /^issue:/m.test(fm[0])) return page; const url = await issueUrl(sol); if (!url) return page; - return fm[0].replace(/^sidebar:$/m, `issue: ${JSON.stringify(url)}\nsidebar:`) + page.slice(fm[0].length); + return ( + fm[0].replace(/^sidebar:$/m, `issue: ${JSON.stringify(url)}\nsidebar:`) + + page.slice(fm[0].length) + ); } /** @@ -693,7 +773,9 @@ function solutionEnd(page: string, from: number): number { // Existing docs pages, keyed by leetcode number from the frontmatter title. const pages = new Map(); // num -> path relative to DOCS -for (const rel of (await Array.fromAsync(new Bun.Glob("**/*.mdx").scan(DOCS))).sort()) { +for (const rel of ( + await Array.fromAsync(new Bun.Glob("**/*.mdx").scan(DOCS)) +).sort()) { const text = await Bun.file(join(DOCS, rel)).text(); const t = text.match(/^title: '(\d+)\./m); if (t) pages.set(Number(t[1]), rel); @@ -713,11 +795,12 @@ for (const sol of ordered) { // (0N-)//(problems)/ folder (the topic is a real route segment, // so its index.mdx is the landing page; `(problems)` adds none) + // leetcode-number prefix: numeric sidebar order, prefix stripped from the URL. - const topic = topics.get(sol.num) ?? topicByName.get(sol.category) ?? { name: sol.category }; + const topic = topics.get(sol.num) ?? + topicByName.get(sol.category) ?? { name: sol.category }; const dir = topicDir(topic); groups.set(dir, topic); if (topic.phase) phases.set(phaseDir(topic.phase), topic.phase); - const name = `${dir}/(problems)/${sol.num}_${sol.slug}.mdx`; + const name = `${dir}/${sol.num}_${sol.slug}.mdx`; const existing = pages.get(sol.num); await mkdir(join(DOCS, dirname(name)), { recursive: true }); @@ -730,8 +813,12 @@ for (const sol of ordered) { continue; } const tail = page.slice(solutionEnd(page, at)); - const body = page.slice(0, at) + solutionSection(sol) + (tail ? `\n${tail}` : ""); - const next = await ensureIssue(await ensureTopics(ensureIcon(ensureLabel(body, sol.header)), sol), sol); + const body = + page.slice(0, at) + solutionSection(sol) + (tail ? `\n${tail}` : ""); + const next = await ensureIssue( + await ensureTopics(ensureIcon(ensureLabel(body, sol.header)), sol), + sol, + ); const actions: string[] = []; if (existing !== name) { await rename(join(DOCS, existing), join(DOCS, name)); @@ -745,14 +832,20 @@ for (const sol of ordered) { report.push([workRef, name, actions.join(" + ") || "skipped (in sync)"]); written = next; } else { - const page = renderPage(sol, await fetchTopicTags(sol), await issueUrl(sol)); + const page = renderPage( + sol, + await fetchTopicTags(sol), + await issueUrl(sol), + ); await Bun.write(join(DOCS, name), page); report.push([workRef, name, "created"]); written = page; } for (const tag of frontmatterTopics(written)) tags.set(tag.slug, tag); // The route drops the phase `(group)` and the page's number prefix. - redirects.push(` { from: "/${sol.num}", to: ${JSON.stringify(`/${slugify(topic.name)}/${sol.slug}`)} },`); + redirects.push( + ` { from: "/${sol.num}", to: ${JSON.stringify(`/${slugify(topic.name)}/${sol.slug}`)} },`, + ); } // Script-owned short links, one per page; imported by blume.config.ts. @@ -765,9 +858,16 @@ for (const sol of ordered) { "];", "", ].join("\n"); - if (!(await Bun.file(REDIRECTS).exists()) || (await Bun.file(REDIRECTS).text()) !== module) { + if ( + !(await Bun.file(REDIRECTS).exists()) || + (await Bun.file(REDIRECTS).text()) !== module + ) { await Bun.write(REDIRECTS, module); - report.push([`${redirects.length} redirects`, "apps/docs/redirects.ts", "written"]); + report.push([ + `${redirects.length} redirects`, + "apps/docs/redirects.ts", + "written", + ]); } } @@ -776,16 +876,29 @@ for (const sol of ordered) { // term's icon is also the icon on every problem-page badge linking to its // card, so it must not be a topic group's (`TOPIC_ICONS`), so a term badge // never reads as a topic's, nor another term's. -for (const tag of [...tags.values()].sort((a, b) => a.slug.localeCompare(b.slug))) { +for (const tag of [...tags.values()].sort((a, b) => + a.slug.localeCompare(b.slug), +)) { if (!TOPIC_LANDINGS[tag.slug] && !APPENDIX[tag.slug]) - console.warn(`tag ${tag.slug}: no Appendix term; add ${JSON.stringify(tag.name)} to apps/docs/appendix.ts`); + console.warn( + `tag ${tag.slug}: no Appendix term; add ${JSON.stringify(tag.name)} to apps/docs/appendix.ts`, + ); } const topicIcons = new Set(Object.values(TOPIC_ICONS)); const termIcons = new Map(); // icon -> the term that took it first for (const [slug, term] of Object.entries(APPENDIX)) { - if (TOPIC_LANDINGS[slug]) console.warn(`appendix ${slug}: a campaign topic teaches it now; delete its term`); - if (topicIcons.has(term.icon)) console.warn(`appendix ${slug}: icon ${JSON.stringify(term.icon)} is a topic's (TOPIC_ICONS); pick another`); - else if (termIcons.has(term.icon)) console.warn(`appendix ${slug}: icon ${JSON.stringify(term.icon)} is ${termIcons.get(term.icon)}'s too; pick another`); + if (TOPIC_LANDINGS[slug]) + console.warn( + `appendix ${slug}: a campaign topic teaches it now; delete its term`, + ); + if (topicIcons.has(term.icon)) + console.warn( + `appendix ${slug}: icon ${JSON.stringify(term.icon)} is a topic's (TOPIC_ICONS); pick another`, + ); + else if (termIcons.has(term.icon)) + console.warn( + `appendix ${slug}: icon ${JSON.stringify(term.icon)} is ${termIcons.get(term.icon)}'s too; pick another`, + ); else termIcons.set(term.icon, slug); } @@ -800,14 +913,13 @@ for (const [dir, phase] of phases) { // Script-owned folder metas — one per live topic folder — rewritten only when // they differ. -const metas: [label: string, dir: string, meta: string][] = [...groups].map(([dir, topic]) => [ - `topic ${topic.order ?? "-"}`, - dir, - groupMeta(topic), -]); +const metas: [label: string, dir: string, meta: string][] = [...groups].map( + ([dir, topic]) => [`topic ${topic.order ?? "-"}`, dir, groupMeta(topic)], +); for (const [label, dir, meta] of metas) { const path = join(DOCS, dir, "meta.ts"); - if ((await Bun.file(path).exists()) && (await Bun.file(path).text()) === meta) continue; + if ((await Bun.file(path).exists()) && (await Bun.file(path).text()) === meta) + continue; await Bun.write(path, meta); report.push([label, `${dir}/meta.ts`, "written"]); } @@ -835,4 +947,5 @@ const w0 = Math.max(...report.map((r) => r[0].length), 9); const w1 = Math.max(...report.map((r) => r[1].length), 9); console.log(`${"work file".padEnd(w0)} ${"docs page".padEnd(w1)} status`); console.log(`${"─".repeat(w0)} ${"─".repeat(w1)} ${"─".repeat(20)}`); -for (const [a, b, c] of report) console.log(`${a.padEnd(w0)} ${b.padEnd(w1)} ${c}`); +for (const [a, b, c] of report) + console.log(`${a.padEnd(w0)} ${b.padEnd(w1)} ${c}`);