File and package cycles
Loops between files inside each package, and between the packages of an npm, yarn, pnpm or bun workspace. One representative cycle per tangle, so the report is a to-do list rather than thousands of overlapping loops.
npm install --save-dev jscyclesnpx jscyclesOn a checkout of ts-morph:
$ jscycles packages✓ No inter-package cycles
✗ @ts-morph/common: 2 cycles src/collections/SortedKeyValueArray.ts -> src/utils/index.ts -> … -> src/collections/SortedKeyValueArray.ts src/typescript/index.ts -> src/typescript/tsInternal.ts -> src/typescript/index.ts
✗ ts-morph: 3 cycles … src/Project.ts -> src/ProjectContext.ts -> src/Project.ts
Summary: 3 packages, 2 with file cycles (5 total), 0 inter-package cyclesFile and package cycles
Loops between files inside each package, and between the packages of an npm, yarn, pnpm or bun workspace. One representative cycle per tangle, so the report is a to-do list rather than thousands of overlapping loops.
Resolves like TypeScript
Path aliases, extends chains through node_modules, declaration files,
and .js specifiers that point at .ts sources.
Never a false clean
Exit status 0 means no cycles, 1 means cycles, 2 means the scan could
not finish. A file that cannot be parsed fails the scan instead of leaving
a hole in the graph.
Fast, with no runtime
A single Rust binary that parses with tree-sitter in parallel. Prebuilt for Linux (static, Alpine too), macOS and Windows; nothing runs at install time.
Median of five runs on an Apple M3 Pro, analyzing each repository’s packages
directory (method):
| Repository | Source files | jscycles 0.2.0 | madge 8.0.0 | dpdm 3.14.0 |
|---|---|---|---|---|
| nest | 1,068 | 0.08 s | 2.04 s | 0.86 s |
| typeorm | 3,609 | 0.28 s | 2.97 s | 1.48 s |
| excalidraw | 631 | 0.11 s | 2.63 s | 0.87 s |
| ts-morph | 1,171 | 0.08 s | 1.64 s | 0.83 s |