Validates and visualizes dependencies with custom rules for JavaScript, TypeScript, and CoffeeScript projects
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive rule system for validating dependencies against architectural constraints, circular dependencies, and custom business rules.
Define validation rules that enforce architectural constraints and best practices.
interface IFlattenedRuleSet {
/** Rules that forbid certain dependency patterns */
forbidden?: IRegularForbiddenRuleType[];
/** Rules that allow only certain dependency patterns */
allowed?: IRegularAllowedRuleType[];
/** Rules that require certain dependency patterns to exist */
required?: IRegularRequiredRuleType[];
/** Default severity level for allowed rule violations */
allowedSeverity?: SeverityType;
/** Global options for rule processing */
options?: IRuleSetOptions;
}
type SeverityType = "error" | "warn" | "info" | "ignore";Usage Examples:
import { cruise } from "dependency-cruiser";
const ruleSet = {
forbidden: [
{
name: "no-circular",
comment: "Circular dependencies create maintenance problems",
severity: "error",
from: {},
to: { circular: true }
},
{
name: "no-orphans",
comment: "Orphaned modules may indicate dead code",
severity: "warn",
from: { orphan: true },
to: {}
}
],
allowed: [
{
name: "only-src-imports",
comment: "Source code should only import from src or node_modules",
from: { path: "^src/" },
to: { path: "^(src/|node_modules/)" }
}
],
options: {
doNotFollow: "node_modules"
}
};
const result = await cruise(
["src"],
{ validate: true, ruleSet }
);Rules that prevent specific dependency patterns.
interface IRegularForbiddenRuleType extends IBaseRuleType {
/** Criteria for the source module of a dependency */
from: IFromRestriction;
/** Criteria for the target module of a dependency */
to: IToRestriction;
/** Scope to apply rule to - 'module' (default) or 'folder' */
scope?: RuleScopeType;
}
interface IBaseRuleType {
/** Short name for the rule (appears in violation reports) */
name?: string;
/** Severity level for violations */
severity?: SeverityType;
/** Documentation explaining why the rule exists */
comment?: string;
}
type RuleScopeType = "module" | "folder";Forbidden Rule Examples:
// Prevent circular dependencies
{
name: "no-circular",
severity: "error",
from: {},
to: { circular: true }
}
// Prevent importing core modules
{
name: "no-core-modules",
severity: "warn",
from: { path: "^src/" },
to: { dependencyTypes: ["core"] }
}
// Prevent test imports in production code
{
name: "no-test-imports-in-src",
severity: "error",
from: { path: "^src/" },
to: { path: "test/" }
}
// Prevent deprecated npm packages
{
name: "no-deprecated",
severity: "warn",
from: {},
to: {
dependencyTypes: ["npm"],
path: "^(left-pad|event-stream)$"
}
}
// Prevent unstable folder dependencies
{
name: "no-unstable-deps",
severity: "error",
scope: "folder",
from: {},
to: { moreUnstable: true }
}Rules that permit only specific dependency patterns (whitelist approach).
interface IRegularAllowedRuleType extends IBaseRuleType {
/** Criteria for the source module of a dependency */
from: IFromRestriction;
/** Criteria for the target module of a dependency */
to: IToRestriction;
/** Scope to apply rule to - 'module' (default) or 'folder' */
scope?: RuleScopeType;
}Allowed Rule Examples:
// Only allow specific imports from external modules
{
name: "only-allowed-external",
severity: "error",
from: { path: "^src/" },
to: {
path: "^(lodash|react|@types/)",
dependencyTypes: ["npm"]
}
}
// Only allow local and npm dependencies
{
name: "only-local-and-npm",
severity: "error",
from: {},
to: { dependencyTypes: ["local", "npm"] }
}
// Restrict component imports
{
name: "components-only-from-index",
severity: "error",
from: { path: "^src/(?!components/)" },
to: {
path: "^src/components/",
pathNot: "^src/components/index"
}
}Rules that enforce the existence of specific dependency patterns.
interface IRegularRequiredRuleType extends IBaseRuleType {
/** Criteria for modules that must have the required dependency */
from: IFromRestriction;
/** Criteria for the required dependency target */
to: IRequiredToRestrictionType;
/** Scope to apply rule to - 'module' (default) or 'folder' */
scope?: RuleScopeType;
}Required Rule Examples:
// Require test files for all source modules
{
name: "test-required",
severity: "warn",
from: { path: "^src/.*\\.js$" },
to: {
path: "^test/.*\\.test\\.js$",
pathNot: "node_modules"
}
}
// Require logging in error handlers
{
name: "logging-required",
severity: "error",
from: { path: "error-handler" },
to: { path: "logger" }
}Detailed criteria for matching modules in rules.
interface IFromRestriction {
/** Path pattern (regular expression) for source modules */
path?: string;
/** Inverse path pattern - modules NOT matching this pattern */
pathNot?: string;
/** Whether to match orphaned modules */
orphan?: boolean;
/** Whether to match reachable modules */
reachable?: boolean;
/** License pattern for npm modules */
license?: string;
/** License pattern to exclude */
licenseNot?: string;
}
interface IToRestriction {
/** Path pattern (regular expression) for target modules */
path?: string;
/** Inverse path pattern - modules NOT matching this pattern */
pathNot?: string;
/** Whether to match circular dependencies */
circular?: boolean;
/** Whether to match dynamic imports */
dynamic?: boolean;
/** Whether to match exotic requires */
exoticallyRequired?: boolean;
/** Whether to match modules that could not be resolved */
couldNotResolve?: boolean;
/** Whether to match pre-compilation only dependencies */
preCompilationOnly?: boolean;
/** Whether to match type-only imports */
typeOnly?: boolean;
/** Whether to match more unstable dependencies (folder scope) */
moreUnstable?: boolean;
/** Dependency types to match */
dependencyTypes?: DependencyType[];
/** Dependency types to exclude */
dependencyTypesNot?: DependencyType[];
/** Module systems to match */
moduleSystems?: ModuleSystemType[];
/** Module systems to exclude */
moduleSystemsNot?: ModuleSystemType[];
/** License pattern for npm modules */
license?: string;
/** License pattern to exclude */
licenseNot?: string;
/** Whether to match reachable modules */
reachable?: boolean;
/** Whether to match via reachability pattern */
via?: IViaRestrictionType[];
/** Whether to match via reachability NOT pattern */
viaNot?: IViaRestrictionType[];
/** Whether to match via only specified patterns */
viaOnly?: IViaRestrictionType[];
}Rules can be applied at different scopes for architectural analysis.
Module Scope (Default):
{
name: "no-module-circular",
scope: "module", // or omit for default
from: {},
to: { circular: true }
}Folder Scope:
{
name: "no-folder-circular",
scope: "folder",
from: {},
to: { circular: true }
}
{
name: "stable-dependencies-principle",
scope: "folder",
from: {},
to: { moreUnstable: true }
}Complex rule patterns for sophisticated architectural constraints.
Layered Architecture:
{
forbidden: [
{
name: "no-skip-layers",
comment: "Don't skip layers in the architecture",
from: { path: "^src/presentation/" },
to: { path: "^src/data/" } // Skip business layer
},
{
name: "no-reverse-dependencies",
comment: "Lower layers shouldn't depend on higher layers",
from: { path: "^src/data/" },
to: { path: "^src/(presentation|business)/" }
}
]
}Domain-Driven Design:
{
forbidden: [
{
name: "no-cross-domain-deps",
comment: "Domains should not directly depend on each other",
from: { path: "^src/domains/user/" },
to: { path: "^src/domains/order/" }
}
],
allowed: [
{
name: "domains-via-shared-only",
comment: "Cross-domain communication via shared interfaces only",
from: { path: "^src/domains/" },
to: { path: "^src/(shared/|domains/[^/]+/)" }
}
]
}Reachability Rules:
{
forbidden: [
{
name: "no-unreachable-from-index",
comment: "All modules should be reachable from entry points",
from: {
path: "^src/",
reachable: false
},
to: {}
}
]
}Understanding how violations are reported and handled.
interface IViolation {
/** Type of violation */
type: ViolationType;
/** Source module involved in violation */
from: string;
/** Target module involved in violation */
to: string;
/** Rule that was violated */
rule: IRuleSummary;
/** Path through dependency graph (for cycles) */
cycle?: IMiniDependency[];
/** Via path for reachability violations */
via?: IMiniDependency[];
}
interface IRuleSummary {
/** Rule name */
name: string;
/** Rule severity */
severity: SeverityType;
/** Comment explaining the rule */
comment?: string;
}
type ViolationType = "dependency" | "module" | "cycle" | "reachability" | "instability";Processing Violations:
import { cruise } from "dependency-cruiser";
const result = await cruise(["src"], {
validate: true,
ruleSet: myRuleSet
});
// Check for violations
if (result.exitCode !== 0) {
console.log(`Found ${result.output.summary.error} errors`);
console.log(`Found ${result.output.summary.warn} warnings`);
// Process each violation
result.output.summary.violations.forEach(violation => {
console.log(`${violation.rule.severity}: ${violation.rule.name}`);
console.log(` ${violation.from} -> ${violation.to}`);
if (violation.rule.comment) {
console.log(` ${violation.rule.comment}`);
}
});
}Optimize rule processing for large codebases.
const optimizedOptions = {
validate: true,
ruleSet: myRuleSet,
// Skip analyses not needed by any rules
skipAnalysisNotInRules: true,
// Use caching for repeated analyses
cache: {
folder: "node_modules/.cache/dependency-cruiser",
strategy: "metadata"
},
// Exclude irrelevant paths early
doNotFollow: "node_modules",
exclude: ["test/fixtures", "docs"]
};Test rules before applying them to ensure they work as intended.
// Test rule on small subset first
const testResult = await cruise(
["src/core"], // Small subset
{
validate: true,
ruleSet: {
forbidden: [newRule] // Test single rule
}
}
);
// Validate rule catches expected violations
if (testResult.output.summary.violations.length === 0) {
console.log("Rule may be too permissive");
} else {
console.log("Rule found violations as expected");
}Install with Tessl CLI
npx tessl i tessl/npm-dependency-cruiser