/** * 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. Files git reports as modified, added or * untracked come first, newest edit first — the problem being worked on is * the focused option, so Enter runs it without a search. The rest are ordered * 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; } /** * work/-relative paths of solutions git sees as modified, added or untracked, * mapped to their mtime. Empty outside a git checkout. */ function dirtySolutions(scanned: Set): Map { const git = Bun.spawnSync( ["git", "status", "--porcelain", "-z", "--untracked-files=all", "--", WORK_DIR], { cwd: ROOT }, ); const dirty = new Map(); if (!git.success) return dirty; const entries = git.stdout.toString().split("\0"); for (let i = 0; i < entries.length; i++) { const entry = entries[i]!; // renames/copies carry their source path as the next entry; skip it if (/[RC]/.test(entry.slice(0, 2))) i++; const rel = relative(WORK_DIR, join(ROOT, entry.slice(3))); // deletions and non-solution files are not in the scan if (scanned.has(rel)) dirty.set(rel, statSync(join(WORK_DIR, rel)).mtimeMs); } return dirty; } /** 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 dirty = dirtySolutions(new Set(scanned)); const files = scanned.sort((a, b) => { const ma = dirty.get(a); const mb = dirty.get(b); if (ma !== mb) { if (ma === undefined) return 1; if (mb === undefined) return -1; return mb - ma; } 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 ? `${dirty.has(f) ? "uncommitted · " : ""}${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); }