inspired by madge (a JS implementation), but wanted to make it faster and output more comprehensive cycles.
cdd [OPTIONS] [DIR]
cdd -- --exclude node_modules ./src
.ts
, .tsx
, .js
, .jsx
, .cjs
, .mjs
-e, --exclude <exclude> Directories to exclude
-d, --debug Enable debug logging
-n, --numberOfCycles <number_of_cycles> Specify the expected number of cycles [default: 0]
-s, --silent Enable silent output
-h, --help Print help
-V, --version Print version
- Parse all files in the directory and extract all imports
- Create a graph of the imports
- Find all cycles in the graph
- Output the cycles
If a cycle is detected will return a non-zero exit code. If no cycles are detected will return a zero exit code.
An example output of the cycle could be:
✖ Found 1 circular dependencies!
1) packages/api/src/a.ts > packages/api/src/c/index.ts > packages/api/src/c/a.ts > packages/api/src/c/b.ts > packages/api/src/b.ts
This can be interpreted as:
packages/api/src/a.ts
importspackages/api/src/c/index.ts
packages/api/src/c/index.ts
importspackages/api/src/c/a.ts
packages/api/src/c/a.ts
importspackages/api/src/c/b.ts
packages/api/src/c/b.ts
importspackages/api/src/b.ts
packages/api/src/b.ts
importspackages/api/src/a.ts
- Identify Shared Responsibilities Determine if any modules share common functionalities that can be abstracted into separate modules. This often helps in reducing direct dependencies.
- Refactor to Remove Direct Dependencies Here's how you can approach refactoring based on your detected cycle: Extract Common Functionality:
a. If both a.ts
and b.ts
share some logic, extract it into a new module (e.g., common.ts
).
Decouple Modules Using Interfaces:
b. Instead of directly importing modules, use interfaces or abstractions to define dependencies. Introduce an Intermediary Module:
c. Create a new module that coordinates interactions between a.ts
and b.ts
, thereby eliminating direct circular imports.
- Test and Validate After refactoring, test your changes to ensure that the circular dependency has been resolved. Run your tests and check for any regressions.
unlike madge, i chose to use a long chain of dependencies to represent the cycle instead of multiple smaller cycles because
- Single comprehensive cycle output is easier to understand and debug.
- It provides a clear picture of the dependencies that need to be resolved.
Example:
Multiple Smaller Cycles:
a.ts > b.ts > a.ts
a.ts > c/index.ts > c/b.ts > c/a.ts > a.ts
a.ts > c/index.ts > c/a.ts > a.ts
- Single Comprehensive Cycle (SCC):
a.ts > c/index.ts > c/a.ts > c/b.ts > b.ts > a.ts
Install Homebrew Tools for Cross-Compilation Use Homebrew to install a Linux-compatible gcc toolchain.
brew tap messense/macos-cross-toolchains
brew install x86_64-unknown-linux-gnu
This installs the x86_64-unknown-linux-gnu-gcc cross-compiler, which is required.
Add the Toolchain to the Path Update your environment to include the cross-compiler.
export CC_x86_64_unknown_linux_gnu=x86_64-unknown-linux-gnu-gcc
export CXX_x86_64_unknown_linux_gnu=x86_64-unknown-linux-gnu-g++
Add these lines to your shell config (e.g., .zshrc or .bashrc) if you plan to use this often.
Install Rust Target for Linux Use rustup to install the x86_64-unknown-linux-gnu target.
rustup target add x86_64-unknown-linux-gnu
Build the Project for the Target Use the --target flag to cross-compile the binary.
cargo build --release --target x86_64-unknown-linux-gnu