Skip to main content

Migrating from dependency-cruiser

dependency-cruiser validates and visualizes dependencies using custom forbidden rules. rev-dep expresses the same constraints as dedicated checks and runs them as a fast single pass.

Why migrate​

What carries over, what changes​

  • Covered well: circular detection, orphan detection, path-to-path forbidden rules, and reachability rules in both directions - deny (entry point must not reach a target) and whitelist (only specific entry points may reach a target).
  • Not replaced - visualization. dependency-cruiser renders graphs (dot/SVG, mermaid, HTML). rev-dep has no graph rendering - it answers reachability as text via the exploratory toolkit. Keep dependency-cruiser if the diagrams matter.
  • Rule model differs. dependency-cruiser is one generic rule engine (from → to); rev-dep splits the intent across dedicated checks: module boundaries (file-pattern → file-pattern), restricted imports (entry points → denied targets), and restricted importers (the reverse - a whitelist of entry points allowed to reach a target).

Feature mapping​

dependency-cruiser rulerev-dep
{ circular: true }circularImportsDetection
{ orphan: true }orphanFilesDetection
forbidden from/to between foldersmoduleBoundaries (pattern / deny / allow)
forbidden with reachable from entry pointsrestrictedImportsDetection (entryPoints / denyFiles / denyModules)
forbidden with negated from (pathNot) + to.reachable - only specific sources may reach a target / banned packagerestrictedImportersDetection (allowedEntryPoints / files / modules)
"not-to-dev-dep" (prod using devDependencies)devDepsUsageOnProdDetection
imports of undeclared packagesmissingNodeModulesDetection
graph image (dot/mermaid/HTML)- (no image output)

Translating your config​

A .dependency-cruiser.js with a few rules:

module.exports = {
forbidden: [
{ name: 'no-circular', severity: 'error', from: {}, to: { circular: true } },
{ name: 'no-orphans', severity: 'error', from: { orphan: true }, to: {} },
{
name: 'ui-not-to-api',
severity: 'error',
from: { path: '^src/ui' },
to: { path: '^src/api' }
},
{
// transitive reachability: server-only code must not be reachable from pages
name: 'pages-not-to-server',
severity: 'error',
from: { path: '^src/pages' },
to: { path: '^src/server', reachable: true }
}
]
};

becomes a rev-dep.config.jsonc:

{
"rules": [
{
"path": ".",
"prodEntryPoints": ["src/index.ts"],
// Each detector accepts an array, so you can define several of the same
// check with different settings. A single item works just as well.
"circularImportsDetection": [
{
"enabled": true
}
],
"orphanFilesDetection": [
{
"enabled": true
}
],
"restrictedImportsDetection": [
{
// dependency-cruiser's `reachable: true` rule
"enabled": true,
"entryPoints": ["src/pages/**"],
"denyFiles": ["src/server/**"]
}
],
"moduleBoundaries": [
{
"name": "ui-not-to-api",
"pattern": "src/ui/**",
"deny": ["src/api/**"]
}
]
}
]
}

dependency-cruiser uses regular expressions in path/pathNot; rev-dep uses glob patterns. A plain from/to rule becomes a moduleBoundaries rule (file-to-file), while a rule with reachable: true becomes a restrictedImportsDetection check (transitive reachability from entry points).

Whitelisting importers (the reverse direction)​

dependency-cruiser expresses "only these may reach X" by negating the from side of a reachability rule with pathNot - forbid everything except the allowed source from reaching the target:

module.exports = {
forbidden: [
{
// only the legacy shell may reach legacy code
name: 'legacy-only-from-shell',
severity: 'error',
from: { pathNot: '^src/(legacy|legacy-shell)' },
to: { path: '^src/legacy', reachable: true }
},
{
// only the legacy shell may pull in the banned `moment` dependency
name: 'moment-only-from-shell',
severity: 'error',
from: { pathNot: '^src/legacy-shell' },
to: { path: 'node_modules/moment', reachable: true }
}
]
};

rev-dep has a dedicated check for this whitelist direction - restrictedImportersDetection. List the targets (files and/or modules) and the allowedEntryPoints permitted to reach them; every other entry point that reaches a target is flagged:

{
"rules": [
{
"path": ".",
"prodEntryPoints": ["src/**/main.ts"],
"restrictedImportersDetection": [
{
"enabled": true,
"files": ["src/legacy/**"],
"modules": ["moment"],
"allowedEntryPoints": ["src/legacy-shell/**"]
}
]
}
]
}

This is especially handy after a migration: with the legacy boundary already drawn, the same check doubles as a ratchet that keeps a banned dependency (modules) from being reintroduced, and stops new entry points from coupling to the legacy surface (files).

One semantic difference to keep in mind: dependency-cruiser's from matches any module on the reaching side, while rev-dep applies the whitelist to the rule's entry points (prodEntryPoints + devEntryPoints). That is usually what you want - you care which app surfaces are allowed to depend on the legacy code or the banned package, not which intermediate file happens to sit on the path.

Running it​

# dependency-cruiser
npx depcruise src --config

# rev-dep
rev-dep config run

Next steps​