Dependency Graph Validation
dependency-cruiser configuration reference -- forbidden rules, orphan detection, resolution options, and how to run it locally.
dependency-cruiser is a static analysis tool that validates JavaScript/TypeScript import graphs against configurable rules. It parses actual import and require statements across the entire codebase -- including dynamic imports and re-exports -- and checks them against forbidden patterns. With 978K weekly npm downloads, it is the most widely adopted import graph analysis tool in the JavaScript ecosystem.
Running Locally
pnpm dep-cruise
This expands to depcruise apps packages --config .dependency-cruiserrc.mjs, scanning all source directories against the rules in the configuration file.
Configuration
The full configuration lives in .dependency-cruiserrc.mjs at the monorepo root.
Forbidden Rules (severity: error)
Six rules enforce the 5-layer package hierarchy. All are error severity -- violations block CI.
no-circular -- Circular dependencies between packages break Turborepo build ordering. This catches any import cycle across the entire graph, not just direct A-imports-B-imports-A cycles.
{ name: "no-circular", from: {}, to: { circular: true } }
no-leaf-to-higher -- Leaf packages (utils, validators, design-tokens) cannot import from any higher layer.
{
from: { path: "^packages/(utils|validators|design-tokens)/" },
to: { path: "^(apps/|packages/(api|mcp|ai|auth|cache|logger|search|db)/)" }
}
no-core-to-service-or-app -- Core layer (db) cannot import from service (api, mcp) or app layers.
{
from: { path: "^packages/db/" },
to: { path: "^(apps/|packages/(api|mcp)/)" }
}
no-core-to-infra -- Core layer cannot import from infrastructure packages (ai, auth, cache, search).
{
from: { path: "^packages/db/" },
to: { path: "^packages/(ai|auth|cache|search)/" }
}
no-infra-to-service-or-app -- Infrastructure packages cannot import from service or app layers.
{
from: { path: "^packages/(ai|auth|cache|logger|search)/" },
to: { path: "^(apps/|packages/(api|mcp)/)" }
}
no-service-to-app -- Service packages (api, mcp) cannot import from the app layer.
{
from: { path: "^packages/(api|mcp)/" },
to: { path: "^apps/" }
}
Orphan Detection (severity: warn)
The no-orphans rule warns about files that nothing imports and that import nothing -- likely dead code. This is warn rather than error because legitimate orphans exist.
Excluded patterns (files that are orphans by design):
| Pattern | Why |
|---|---|
| Dotfiles | Configuration files |
*.d.ts | Type declaration files |
tsconfig.json, vitest.config.ts, eslint.config.* | Tooling configuration |
__tests__/, .test., .spec. | Test files |
seed, migrate | Database scripts |
index.ts, route.ts, server.ts | Entry points |
layout.tsx, page.tsx, middleware.ts | Next.js App Router conventions |
instrumentation.ts, global-error.tsx, not-found.tsx | Next.js special files |
postcss.config.* | PostCSS config |
trovella-logo.tsx | Used via @/ alias that dependency-cruiser cannot resolve |
source.config.ts | Fumadocs MDX source config (consumed by plugin) |
content/ | MDX files compiled by Fumadocs, not imported via module resolution |
Resolution Options
options: {
doNotFollow: { path: "node_modules" },
exclude: { path: "coverage/" },
tsPreCompilationDeps: true,
enhancedResolveOptions: {
exportsFields: ["exports"],
conditionNames: ["import", "require", "node", "default"],
},
}
tsPreCompilationDeps: true-- resolves TypeScript path aliases andtsconfig.jsonpaths before analysisenhancedResolveOptions-- respectspackage.jsonexportsfields and standard Node.js conditions
What dependency-cruiser Catches That ESLint Misses
ESLint no-restricted-imports sees one file at a time. It catches direct import { db } from "@repo/db" but cannot detect:
- Dynamic imports:
const mod = await import("@repo/db")-- dependency-cruiser follows these - Re-exports: Package A re-exports from Package B, and Package C imports from Package A -- ESLint only sees the A-to-C import, not the transitive B-to-C dependency
- Transitive chains: A imports B imports C -- if A-to-C is forbidden, only the whole-graph analysis catches it
CI Integration
dependency-cruiser runs as part of pnpm ci:check (the pre-push quality gate) and in the GitHub Actions quality job. It runs after pnpm lint and before pnpm lint:dead-code.
A non-zero exit code from pnpm dep-cruise blocks the PR from merging.
Related Pages
- Layer Hierarchy -- the 5-layer package DAG and allowed import directions
- Delivery -- Pipeline -- where dependency-cruiser runs in the CI pipeline
Layer Hierarchy
The 5-layer package DAG -- what each layer contains, allowed import directions, and how violations are caught by both ESLint and dependency-cruiser.
Dead Code Detection
Knip configuration reference -- per-workspace entry points, exclusions, and why dead code detection matters for AI-generated codebases.