Dependency Graph
Overview
Section titled “Overview”Dependency Graph is a lightweight, composable set of Bash scripts that turns an npm monorepo’s internal dependency relationships into a visual Graphviz diagram. It was originally built to tame the complexity of a large audio-platform monorepo (ECAS) with dozens of interdependent packages — giving the team an at-a-glance picture of how everything fits together and catching missing package.json entries before they cause runtime errors.
The Problem
Section titled “The Problem”In a monorepo with many packages it becomes surprisingly easy to import a dependency in source code without adding it to the package’s package.json. Everything works locally because hoisted node_modules hide the mistake — until a CI build or a consumer of the package hits a missing-dependency error. On top of that, understanding which packages depend on which quickly becomes impossible once you pass a dozen or so packages.
How It Works
Section titled “How It Works”The toolchain is a Unix-style pipeline of small, single-purpose scripts chained together by main.sh:
# 1. Walk every package.json, extract matching deps./find_dependencies.sh "$monorepo/packages/" deps.txt "ecas"
# 2. Convert the edge list into a Graphviz DOT file./generate_dot_file.sh deps.txt graph.dot
# 3. Render the DOT file to a PNG./generate_png.sh graph.dot graph.pngEach step produces a human-readable intermediate file, so you can inspect or transform the data at any point.
Dependency Extraction
Section titled “Dependency Extraction”process_package.sh is where the clever part lives. It builds a single jq filter on the fly that merges dependencies and devDependencies, then selects only entries whose key matches a user-supplied regex:
str="select(.dependencies != null or .devDependencies != null) | .name as \$parent | (.dependencies // {}) + (.devDependencies // {}) | to_entries[] | select(.key | test(\"${match}\"; \"i\")) | \"\(\$parent) \(.key)\""
jq -r "${str}" "$2"This means you can easily scope the graph to just your own packages (ecas), or use a more advanced pattern like ^(?!.*ecas-docs).*ecas.*$ to exclude documentation packages.
Unlisted-Import Checker
Section titled “Unlisted-Import Checker”A separate check-deps.sh script statically analyses TypeScript/TSX source files to find every import ... from "..." statement, extracts the package name (handling scoped @org/pkg packages correctly), and cross-references against the declared dependencies. Any missing entry is reported with colour-coded terminal output:
ERROR: [ecas-engine] Missing imports: @ecas/core some-unlisted-lib
SUCCESS: [ecas-player]check-all.sh wraps this into a single command that iterates every package in the monorepo — ideal for a CI gate.
Features
Section titled “Features”- Visual Dependency Graph — Generates a PNG diagram of inter-package dependencies via Graphviz DOT
- Regex-Based Filtering — Scope the graph to packages matching any regex, including negative lookaheads
- Unlisted-Import Detection — Statically parses TS/TSX imports and flags anything not declared in
package.json - Scoped Package Awareness — Correctly handles
@org/packagestyle npm scopes when checking imports - UUID-Based Output — Each run produces uniquely named intermediate files, so parallel runs never collide
- Composable Pipeline — Each script does one thing and can be used independently or swapped out
Tech Stack
Section titled “Tech Stack”| Language | Bash |
| Graph Rendering | Graphviz (dot) |
| JSON Processing | jq |
| Static Analysis | AWK + grep over TS/TSX sources |
| License | MIT |
# Install prerequisites (macOS)brew install graphviz jq
# Generate a dependency graph for all "ecas" packagesbash main.sh "../ecas" "./output" "ecas"
# Check a single package for unlisted importsbash check-deps.sh "../ecas/packages/ecas-engine"
# Check every package in the monorepobash check-all.sh "../ecas/packages"Source Code
Section titled “Source Code”The source code is available on the project’s GitHub repository.