Dependency graph
Interpret locked package instances, dependency paths, duplicate versions, filters, and graph exports.
The dependency graph shows the package instances and relationships recorded in lpm.lock. Use it to explain dependencies, compare versions, or export a report.
# Inspect the project and its direct dependencies
lpm graph --depth 2
# Explain every path to a package
lpm why lodash
# Export node identities and edges
lpm graph --json > dependencies.jsonlpm graph reads local project files. It does not resolve packages, contact registries, or require node_modules.
What a node means
The project root and each resolved package instance are nodes. A package instance identifies one selection, including its source and dependency context.
| Node | Meaning |
|---|---|
| Project root | The project identity from package.json. It does not count as a package. |
| Direct package | An instance selected by a dependency key in the active manifest sections. |
| Transitive package | An instance reached through another package. |
| Duplicate package | A package name with more than one resolved version. |
Two instances can share a name and version but have different peers or sources. Graph JSON keeps these instances separate.
The key field identifies a node within the report. Current lockfiles use an instance suffix when several nodes share a name and version. Treat keys as opaque values from that graph snapshot.
A repeated path to one instance is not a duplicate version. Multiple instances of the same version also do not increase the duplicate-version count.
What an edge means
An edge connects a package to a resolved dependency or peer. Graph output combines relationships that lead to the same target node.
For example, two local alias names that select one instance produce one graph edge. dependency_count counts distinct target nodes.
The graph does not label edges as regular, optional, or peer dependencies. It also does not expose each local alias slot. For edge kinds and local alias slots, use the lockfile format.
Locked state and installed state
The lockfile supplies package versions, sources, and relationships. The manifest supplies the project identity and the selected direct dependency names.
This is a view of resolved state. It does not prove that package files exist, scripts ran, or every locked platform-specific package is installed.
If the manifest changed after the last install, update the lockfile before you inspect the result:
lpm install
lpm graphIn a workspace member, the graph uses that member's recorded view. Sibling-only dependencies do not appear. At the workspace root, the graph starts from root dependencies.
A directory with an exported lpm.lock and no manifest also supports inspection. All packages in the selected lockfile view become direct children of a synthetic project node. A workspace union still selects its root view.
See lockfile ownership for ancestor selection and malformed-lockfile behavior.
Select a dependency
A package argument selects a subtree:
lpm graph react
lpm graph react@19.0.0A name selects the nearest matching instance. If several instances have the same depth, the first sorted key wins. This does not necessarily select the newest version.
A name and version select a coordinate. If several instances share that coordinate, the command reports ambiguity.
Copy an exact key from the error or graph JSON:
lpm graph --json
lpm graph '<name>@<version>#<instance-id>'The selected package becomes the graph root at level 1. It still counts as a package.
Trace dependency paths
lpm why accepts a canonical package name and includes all matching versions and instances:
lpm why lodash
lpm graph --why lodash --jsonWhy lists paths from the selected graph root to the matching package. Paths do not repeat a node, so cycles do not cause endless traversal.
Recorded override and patch changes can accompany the explanation. Those records describe the installation that wrote them.
If no package matches, why succeeds with a not-found result. Dense graphs can produce large reports because the number of paths can grow exponentially.
For JSON consumers, paths contains readable name@version labels. Different instance paths can have identical labels. The parallel path_keys array preserves exact node identity:
lpm why lodash --json > why-lodash.jsonpaths[i][j] and path_keys[i][j] describe the same step. Both arrays contain path_count paths. An absent package produces empty arrays.
Use path_keys to join an explanation to the nodes[].key and edges fields from the same graph scope.
Interpret depth and filters
lpm graph --depth 3
lpm graph --filter react
lpm graph --prod
lpm graph --dev| Filter | Effect |
|---|---|
--depth <N> | Keep nodes whose shortest path from the graph root fits within N levels. |
--filter <NAME> | Keep matching dependencies, their descendants, and the paths that connect them to the root. |
--prod | Select production and optional direct dependencies, then keep reachable packages. |
--dev | Select development direct dependencies, then keep reachable packages. |
The graph root is level 1. JSON node depth starts at 0. After filtering, depths and statistics describe the retained graph.
The name filter is a case-sensitive substring match. For example, press matches express. The project name does not count as a dependency match.
Depth limits select nodes by shortest distance. A shared node can also appear through a longer retained path in the terminal tree. Tree indentation can therefore exceed the selected depth.
All formats use the same selected nodes and edges. --prod and --dev cannot be combined. Shared transitive packages remain when a selected root requires them.
Read counts and registry labels
lpm graph --format stats| Result | Meaning |
|---|---|
| Package count | Package instances in the result, excluding a synthetic project root. |
LPM / lpm_packages | Instances attributed to the LPM.dev Registry. |
npm / npm_packages | Instances attributed to npmjs.org or registry.npmjs.org. |
| Maximum depth | The largest shortest-path level in the result. |
| Duplicates | Names with multiple resolved versions in the result. |
Custom registries and non-registry sources have unknown attribution. They count toward the total, but not the two named registry counts. unknown does not indicate a security verdict.
Export a report
| Format | Destination | Use |
|---|---|---|
tree | Standard output | Read dependencies in the terminal. |
dot | Standard output | Create a Graphviz diagram. |
mermaid | Standard output | Include a diagram in Markdown. |
json | Standard output | Process graph data in scripts or tools. |
stats | Standard output | Read counts, depth, and duplicate versions. |
html | <project>/.lpm/graph.html | Explore an interactive graph. |
lpm graph --format dot | dot -Tpng > dependencies.png
lpm graph --format mermaid > dependencies.mmd
lpm graph --format html --no-openHTML opens in the default browser unless you pass --no-open. The HTML file belongs to the selected project, including a workspace member.
Read JSON graph data
lpm graph --json| Field | Meaning |
|---|---|
success | Whether the command produced a graph. |
root | Selected root label (name@version), or an empty string if no root remains. |
packages | Package count, excluding the synthetic project root. |
lpm_packages, npm_packages | Counts by recognized registry. |
max_depth | Maximum graph level, starting at 1. An empty graph has depth 0. |
duplicates | Duplicate names and their distinct versions. |
nodes | Project and package nodes. |
edges | Connections with from and to node keys. |
Each node contains:
| Field | Meaning |
|---|---|
key | Opaque identity within this report. |
name, version | Package coordinates or project identity. |
registry | lpm, npm, or unknown. |
depth | Shortest distance from the current root, starting at 0. Unreachable rows also use 0. |
is_direct | Whether the selected manifest sections identify this package as direct. Without a manifest, all packages are direct. |
is_duplicate | Whether another version has the same name. |
is_root | Whether this node is the current graph root. |
dependency_count | Number of distinct child targets. |
deps | Keys of child targets, including resolved peers. |
The node with is_root: true supplies the exact root key.
Unfiltered JSON can retain locked rows with no path from the current manifest roots. These rows have depth: 0, and why finds no paths to them. This can occur after manifest changes.
Why has a separate JSON shape: success, target, found, path_count, paths, path_keys, applied_overrides, and applied_patches.
Recovery
If a usable lockfile is missing, run lpm install from the project. If an owning lockfile is malformed, restore a valid copy.
Older schemas can retain aliases, peers, and root selections without exact package-instance identities. If a legacy edge matches several sources, graph reports ambiguity before applying filters. An exact package key cannot bypass an ambiguous legacy edge. An online install writes the current graph format.
If a package is absent from a filtered result, inspect the complete graph:
lpm graph --jsonFor a subtree query, copy an exact key from JSON or the ambiguity error. The terminal tree uses readable labels that can repeat across instances.
If the browser cannot open the report, generate it with --no-open. Then open the printed HTML path in another browser.
See also
lpm graph— command syntax, tasks, and flagslpm query— inspect package properties and relationshipslpm audit— inspect security findings- Lockfile — authoritative resolved state
- Resolver — package selection and peer contexts
- Overrides — replace dependency selections
- Patching dependencies — maintain local package changes