diff --git a/.omp/commands/sync-solutions.md b/.omp/commands/sync-solutions.md index 1f95c28..d29dc25 100644 --- a/.omp/commands/sync-solutions.md +++ b/.omp/commands/sync-solutions.md @@ -2,7 +2,7 @@ description: Sync work/ leetcode solutions into docs/solutions/ pages (gold standard format) --- -Sync every leetcode solution under `work/` into a docs page under `docs/solutions/`, formatted like the gold standard `docs/solutions/344-reverse-string.mdx`. Extra focus (optional): $@ +Sync every leetcode solution under `work/` into a docs page under `docs/solutions/`, formatted like the gold standard `docs/solutions/(two-pointers)/344-reverse-string.mdx`. Extra focus (optional): $@ ## Run the script @@ -16,12 +16,13 @@ bun run sync - Globs `work/**/*.{js,py}` (`..{js,py}` under `work///`) and groups the two language variants of a problem by leetcode number. - Parses each file's header comment (js block comment / py docstring) into title, difficulty, statement, examples, constraints, and follow-up. -- New problems get a full page at `docs/solutions/-.mdx`: frontmatter, `` topic (README curriculum table, falling back to the `work/` subdirectory), `::::warning` for follow-ups/special requirements, examples, constraints, and the solution code verbatim. -- The filename prefix is the leetcode problem number — it orders the sidebar numerically and is stripped from the URL (`344-reverse-string.mdx` → `/solutions/reverse-string`). The script renames any page whose filename drifts from `-.mdx`. +- New problems get a full page at `docs/solutions/()/-.mdx`: frontmatter, `` topic (README curriculum table, falling back to the `work/` subdirectory), `::::warning` for follow-ups/special requirements, examples, constraints, and the solution code verbatim. +- `()` is the kebab-cased `work/` subdirectory (`(array)`, `(hash-table)`, `(two-pointers)`, …) and becomes the sidebar group; the leetcode-number filename prefix orders pages numerically. Both are stripped from the URL (`(two-pointers)/344-reverse-string.mdx` → `/solutions/reverse-string`). The script moves any page whose path drifts from this scheme. +- Frontmatter `title` keeps the number (`'344. Reverse String'`); `sidebar.label` is the number-free problem name. The script backfills a missing `sidebar.label` on existing pages but never overwrites one. - Already-ported pages (frontmatter title starts with the leetcode number) only get their `## Solution` section regenerated — curated prose is never touched. - Problems solved in both languages render as a `` (Python first, then JavaScript); single-language solutions render as a plain fence. -The script prints the report table: work file → docs page → created / renamed / updated / skipped (already in sync). +The script prints the report table: work file → docs page → created / moved / updated / skipped (already in sync). ## Review diff --git a/scripts/sync.ts b/scripts/sync.ts index 17c71e0..f274348 100755 --- a/scripts/sync.ts +++ b/scripts/sync.ts @@ -10,7 +10,7 @@ * renders as a (Python first); single-language solutions * render as a plain fence. */ -import { readdir, rename } from "node:fs/promises"; +import { mkdir, rename } from "node:fs/promises"; import { basename, join } from "node:path"; const ROOT = join(import.meta.dir, ".."); @@ -284,6 +284,7 @@ function renderPage(sol: Solution, topic: string): string { `title: '${h.num}. ${h.title.replaceAll("'", "''")}'`, /[:#]/.test(description) ? `description: '${description.replaceAll("'", "''")}'` : `description: ${description}`, "sidebar:", + ` label: '${h.title.replaceAll("'", "''")}'`, ` badge: '${h.difficulty}'`, "---", "", @@ -346,12 +347,20 @@ for await (const rel of new Bun.Glob("**/*.{js,py}").scan(WORK)) { const topics = await readmeTopics(); +/** Insert a number-free `sidebar.label` into existing frontmatter when missing. */ +function ensureLabel(page: string, h: Header): string { + const fm = page.match(/^---\n[\s\S]*?\n---/); + if (!fm || /^ {2}label: /m.test(fm[0])) return page; + const label = ` label: '${h.title.replaceAll("'", "''")}'`; + return page.replace(/^sidebar:$/m, `sidebar:\n${label}`); +} + // Existing docs pages, keyed by leetcode number from the frontmatter title. -const pages = new Map(); // num -> filename -for (const f of (await readdir(DOCS)).filter((f) => f.endsWith(".mdx")).sort()) { - const text = await Bun.file(join(DOCS, f)).text(); +const pages = new Map(); // num -> path relative to DOCS +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]), f); + if (t) pages.set(Number(t[1]), rel); } const report: [string, string, string][] = []; @@ -360,8 +369,11 @@ const ordered = [...solutions.values()].sort((a, b) => a.num - b.num); for (const sol of ordered) { const langs = Object.keys(sol.code).sort().join("+"); const workRef = `${sol.num}.${sol.slug} (${langs})`; - const name = `${sol.num}-${sol.slug}.mdx`; // prefix = leetcode number (numeric sidebar order, stripped from the URL) + // (category) group folder + leetcode-number prefix: numeric sidebar order, both stripped from the URL. + const group = `(${sol.category.toLowerCase().replaceAll(" ", "-")})`; + const name = `${group}/${sol.num}-${sol.slug}.mdx`; const existing = pages.get(sol.num); + await mkdir(join(DOCS, group), { recursive: true }); if (existing) { const page = await Bun.file(join(DOCS, existing)).text(); @@ -370,15 +382,15 @@ for (const sol of ordered) { report.push([workRef, existing, "ERROR: no '## Solution' heading"]); continue; } - const next = page.slice(0, at) + solutionSection(sol); + const next = ensureLabel(page.slice(0, at) + solutionSection(sol), sol.header); const actions: string[] = []; if (existing !== name) { await rename(join(DOCS, existing), join(DOCS, name)); - actions.push("renamed"); + actions.push("moved"); } if (next !== page) { await Bun.write(join(DOCS, name), next); - actions.push("updated (solution)"); + actions.push("updated"); } report.push([workRef, name, actions.join(" + ") || "skipped (in sync)"]); } else {