We Didn't Use a Single npm Package. Here's What That Actually Cost Us. #HackathonRaptors submission — CodeMap: Repository Architecture & Change Analyzer Every hackathon has a team that ships fast by standing on the shoulders of npm install. We weren't that team. CodeMap's one hard rule, set before a single line of code was written, was: zero third-party dependencies — only Node's standard library. This is not a story about how clever that made us. It's a story about which parts of that decision were free, which parts we paid for in hours, and one specific bug that ate an entire afternoon. What we actually built CodeMap scans a JS/TS repository, builds a dependency graph from its imports, finds circular dependencies and architectural hotspots, and — the part I owned — cross-references all of that against real Git history to answer: "which file is both heavily depended-on and heavily changed right now?" That correlation, not the graph or the CLI alone, is the feature that actually tells you something a linter can't. The packages we made look unnecessary Here's the honest scorecard — the popular tool for each job, and what we used instead: Job The npm package everyone reaches for What we used instead Talking to Git simple-git, nodegit, isomorphic-git child_process.execSync("git ..."), parsed by hand Parsing JS/TS imports @babel/parser, TypeScript Compiler API A hand-rolled regex/tokenizer over import/require statements Graph + cycle detection graphlib ~80 lines of DFS with a visiting/visited set CLI argument parsing commander, yargs Manual process.argv slicing Dependency graph visualization D3.js, Chart.js Hand-generated SVG, positioned with basic layout math None of these are exotic choices to not need. That's what made this interesting: these are the packages you install on reflex, before you've asked whether you actually need their generality. What the standard library made genuinely painful Talking to Git without simple-git sounds trivial — it's "just" shelling out and reading text. It is, until you actually need the structured data those wrapper libraries give you for free: No typed output. simple-git hands you a parsed object. child_process.execSync hands you a raw string, and you own every byte of turning it back into structure. Shell-safety is your problem now. File paths can contain spaces, quotes, even parentheses. Building a command string means thinking about escaping every single time, not once inside a library. Git's own output format has undocumented edge cases that a wrapper library has already hit and fixed for you. We had to hit them ourselves. The edge case that ate an afternoon We use git log --name-status to get per-commit file changes, then split each line on tabs: status character, then filename. That works — for a while. Then a file got renamed, and the parser silently broke. A normal changed-file line looks like: M src/database.js One tab, two fields. Our splitter assumed that shape everywhere. But a renamed file doesn't follow it — Git emits: R100 src/old-name.js src/database.js Three fields, not two, and the status code carries a similarity percentage suffix (R100, R87, ...) instead of being a clean single letter. Our naive [status, file] = line.split("\t") quietly swallowed the second path and miscounted every rename as a single-file change — which meant our "most changed files" ranking was wrong in any repo with real refactor history, and we didn't notice until we ran it against a repo that actually had renames in it. The fix wasn't hard once found (check whether the status starts with R or C and handle the two-path case explicitly) — but finding it, in a script that "worked fine" on every fixture we'd written by hand, cost us most of an afternoon. Every fixture we'd written ourselves happened to avoid renames, because we never thought to rename a file while building test data. The bug wasn't in the parsing logic in isolation — it was in the gap between the inputs we imagined and the inputs Git actually produces. simple-git has almost certainly already eaten this bug for its users. We ate it ourselves, on the clock. The runner-up edge case, for the record: filenames with non-ASCII characters get quoted and octal-escaped by Git's default core.quotepath setting (a café.ts becomes "caf\303\251.ts" in raw log output) unless you know to ask for -z or disable quoting explicitly. We caught this one faster, but it's the same lesson twice: the stdlib gives you the pipe, not the plumbing. Was it worth it? For a hackathon judged on "give me a repository I don't understand, and I'll explain it" — yes, unambiguously. A node your-script.js with zero install step is a genuinely better demo experience than asking judges to wait on npm install. But we'd be lying if we said the constraint was free. It traded install-time convenience for debugging-time cost, and shifted bugs from "already fixed by a maintainer" to "discovered by us, live, against real data." If we did it again: same constraint, but we'd build our fixture data adversarially from day one — deliberately renaming, unicode-naming, and deleting-then-recreating files in our test fixtures — instead of writing fixtures that only exercised the happy path we'd already imagined. Built by the CodeMap team. Git analysis, integration, and testing: Sneha.
Created
September 4, 2026 06:38
-
-
Save sneha080106/77d0d5f05256c658f6e4a0464416dcaa to your computer and use it in GitHub Desktop.
Codemap-Writeup
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment