/** * Shared work/ solution picker for the test and submit entries: fuzzy-select * a solution file, then hand it to leetcode-cli. * * Every bucket is listed — a 3-day or 7-day re-solve is tested and submitted * exactly like a first solve — and the hint says which one a file belongs to. * The db's per-solution timestamps are keyed by LC number, so the buckets of * one problem share them. * * Every run backfills the db's solutions table with created_at (from the * file's birthtime, mtime when the filesystem has none) so ordering works for * files that predate the db. Ordering is per command — test lists the most * recently tested first (never-tested after, by name); submit lists the most * recently created first. Nothing is ever hidden. Exit 0 of leetcode-cli * stamps last_tested / last_submitted. * * Library: side-effect-free on import — nothing scans the filesystem until * runOnSolution() is called. */ import { autocomplete, cancel, isCancel, log } from "@clack/prompts"; import { statSync } from "node:fs"; import { basename, join, relative } from "node:path"; import { problemDb } from "./db"; import { ROOT, WORK as WORK_DIR, parseWorkPath } from "./work"; /** Leading LC number of a work/ file, or undefined for unnumbered names. */ function lcNumber(rel: string): number | undefined { const m = basename(rel).match(/^(\d+)\./); return m ? Number(m[1]) : undefined; } /** Fuzzy-pick a solution under work/ and run `leetcode ` on it. */ export async function runOnSolution(command: "test" | "submit", message: string): Promise { const db = problemDb(); const scanned = [...new Bun.Glob("**/*.{js,ts,py,java,c,cpp,go,rs,rb,swift,kt,cs}").scanSync({ cwd: WORK_DIR })]; // register creation times so files older than the db still sort correctly for (const f of scanned) { const num = lcNumber(f); if (num === undefined) continue; const stat = statSync(join(WORK_DIR, f)); db.ensureSolution(num, new Date(stat.birthtimeMs || stat.mtimeMs).toISOString()); } const activity = db.solutionActivity(); // test → most recently tested first; submit → most recently created first const sortKey = (f: string) => { const num = lcNumber(f); const row = num === undefined ? undefined : activity.get(num); return (command === "test" ? row?.lastTested : row?.createdAt) ?? undefined; }; const files = scanned .sort((a, b) => { const ka = sortKey(a); const kb = sortKey(b); if (ka !== kb) { if (ka === undefined) return 1; if (kb === undefined) return -1; return kb.localeCompare(ka); // ISO timestamps: lexicographic = chronological } return a.localeCompare(b, undefined, { numeric: true }); }); if (files.length === 0) { log.error(`No solution files found in ${relative(process.cwd(), WORK_DIR)}/`); process.exit(1); } const picked = await autocomplete({ message, placeholder: "Type to search...", maxItems: 12, options: files.map((f) => { const parsed = parseWorkPath(f); const stage = parsed?.bucket === 1 ? "first solve" : `${parsed?.bucket}-day review`; return { value: f, label: basename(f), hint: parsed ? `${stage} · ${parsed.difficulty} · ${parsed.category}` : f, }; }), filter: (search, option) => { const haystack = option.value.toLowerCase(); return search .toLowerCase() .split(/\s+/) .every((token) => haystack.includes(token)); }, }); if (isCancel(picked)) { cancel("Nothing selected."); process.exit(0); } const proc = Bun.spawn( [ // bun installs workspace-dep bins into this package's node_modules, not the root's join(import.meta.dir, "node_modules", ".bin", "leetcode"), command, join(WORK_DIR, picked), ], { cwd: ROOT, stdio: ["inherit", "inherit", "inherit"] }, ); const code = await proc.exited; const num = lcNumber(picked); if (code === 0 && num !== undefined) { if (command === "test") db.markTested(num); else db.markSubmitted(num); } db.close(); process.exit(code); }