Render a project's dependency tree as an animated ASCII solar system. Each package orbits at a distance set by how deep it sits in the dependency graph, and its orbit visibly decays the longer it's gone since a release — so you can step through the animation frame by frame and watch technical debt drift toward the sun.
Under the hood, a project's package.json and lockfile are parsed into a
depth-ranked dependency tree, with each resolved package annotated with its
release age. That tree renders as an ASCII frame: the project is the sun at
the centre, each dependency orbits at a radius set by its depth, and older,
more neglected releases have visibly decayed inward. Beyond today's static
frame, the tool can also step or play through a synthetic time axis built
from the tree's own release dates — from each package's oldest release up
to today — so you can watch the decay happen frame by frame instead of only
seeing where it ended up.
git clone <this-repo>
cd orrery
npm install
npm link # optional: makes the `orrery` command available globallyRequires Node.js 18 or later. There are no runtime dependencies — parsing
package.json and an npm lockfile only needs what Node ships with.
Run it from inside any Node project that has a package-lock.json (or
npm-shrinkwrap.json):
orrery --dir /path/to/projectThis prints one ASCII frame: @ is the project itself at the centre, and
each dependency is drawn as a point orbiting it — further out the deeper it
sits in the dependency graph, and pulled inward the longer it's been since
its resolved version was released:
*— released recentlyo— aging.— stale, and visibly drifted toward the sun?— couldn't be resolved from the lockfile at allx— a circular dependency, cut off rather than expanded forever
orrery --json # print the dependency tree as JSON instead
orrery --offline # skip release-age lookups entirely
orrery --width 81 --height 31 # render a bigger canvas
orrery --help # show usageInstead of just today's frame, orrery can step or play through a synthetic
time axis: evenly spaced points from the oldest release date found anywhere
in the tree up to today. The last frame always matches the static frame
above exactly.
orrery --play # animate through the whole axis once
orrery --play --frames 40 --interval 40 # more frames, faster playback
orrery --frame 0 # render just the axis's oldest point
orrery --frame 5 --frames 12 # render one specific frame--frames <n>sets how many points are on the axis (default 24).--interval <ms>sets the delay between frames while playing (default 120).--frame <n>renders a single frame (0-based) instead of animating — useful for scripting or generating a specific still.- A project built with
--offlinehas no release dates to animate, so its time axis collapses to the single frame already shown by default.
Three flags exist for driving orrery from a script rather than a terminal:
orrery --range 0:5 # render frames 0-5 back-to-back, no delay
orrery --range 0:23 --json # same, as a JSON array of frame records
orrery --highlight left-pad # mark one package with a '#' glyph
orrery --snapshot ./frame.txt # write the render to a file, not stdout
orrery --range 0:5 --snapshot ./ci.txt # capture a whole range to one file--range <start>:<end>renders every frame fromstarttoend(0-based, inclusive) on the synthetic time axis immediately, with no delay between them — unlike--play, which is meant to be watched in real time. Combining--rangewith--playor--frameis rejected.--highlight <name>marks every node whose package name matches with a#glyph in the ASCII render, regardless of its resolved or decay state. With--json, matching nodes instead carry"highlighted": trueso a script can find them without re-parsing the ASCII grid.--snapshot <path>writes the rendered output — a single frame, or every frame in a--range— to a file instead of stdout, and prints a short confirmation line in its place. This is what makes a CI screenshot deterministic and diffable: runorrery --snapshot, then compare the file against a checked-in golden copy.--snapshotcannot be combined with--play, since playback is inherently a real-time, interactive loop.
By default, orrery looks up each distinct package's publish dates from the
public npm registry (https://registry.npmjs.org/<package>) — one request
per package name, reused for every version and every place that package
appears in the tree. Pass --offline to render with no network access at
all; every package is then drawn as if freshly released, since its age is
unknown rather than assumed.
With --json, each tree node has a name, resolved version, depth
(distance from the project root along the dependency graph — not physical
node_modules nesting), and, unless --offline was passed, a releaseDate
and ageDays for its resolved version.
Only npm's own lockfile formats (package-lock.json versions 1 through 3,
and npm-shrinkwrap.json) are supported for now.
Built autonomously and gated on passing tests: every change to this project is verified — build, tests, and a security review — before it ships.