Skip to content

feat: reverse pnpm why tree and improve list/why output#10615

Merged
zkochan merged 37 commits intomainfrom
perf/list-peer-hashes
Feb 15, 2026
Merged

feat: reverse pnpm why tree and improve list/why output#10615
zkochan merged 37 commits intomainfrom
perf/list-peer-hashes

Conversation

@zkochan
Copy link
Member

@zkochan zkochan commented Feb 13, 2026

Summary

  • pnpm why now shows a reverse dependency tree. The searched package appears at the root with its dependants as branches, walking back to workspace roots. This replaces the previous forward-tree output which was noisy and hard to read for deeply nested dependencies.
  • Replaced archy with a new @pnpm/text.tree-renderer package that renders trees using box-drawing characters (├──, └──, │) and supports grouped sections, dim connectors, and deduplication markers.
  • Show peer dependency hash suffixes in pnpm list and pnpm why output to distinguish between different peer-dep variants of the same package.
  • Improved pnpm list visual output: bold importer nodes, dimmed workspace paths, dependency grouping, package count summary, and deterministic sort order.
  • Added --long support to pnpm why and the ability to read package manifests from the CAS store.
  • Deduplicated shared code between list and why commands into a common module, and reused getPkgInfo in the why tree builder.

Test plan

  • Unit tests for @pnpm/text.tree-renderer (new package)
  • Unit tests for renderWhyTree
  • Updated integration tests for pnpm list and pnpm why
  • Updated recursive list tests

@zkochan zkochan changed the title Perf/list peer hashes feat: show peer hashes in list output Feb 13, 2026
@zkochan zkochan force-pushed the perf/list-peer-hashes branch from 827cead to 8e394cd Compare February 13, 2026 21:16
zkochan and others added 3 commits February 13, 2026 22:20
Display a short hash (#xxxx) next to packages that have peer dependency
resolutions, but only when the same name@version appears with multiple
distinct peer variants in the output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zkochan zkochan force-pushed the perf/list-peer-hashes branch from 8e394cd to e38b935 Compare February 13, 2026 23:10
zkochan and others added 18 commits February 14, 2026 02:33
Extract DEDUPED_LABEL to peerVariants.ts and simplify deduped rendering
in pnpm list to match the why output style (no child count node).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ation

Sort edges in the data layer (getTree, buildWhyTree) so that the
alphabetically-first occurrence of a package is always fully expanded
while later duplicates get [deduped]. Replace localeCompare with
lexCompare from @pnpm/util.lex-comparator for consistency. Remove
redundant ramda-based sorting from renderTree.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The readManifest callback passed to finder functions in `pnpm why` now
reads the actual package.json from the content-addressable store using
the integrity hash from the lockfile. This works correctly for all
configurations including enableGlobalVirtualStore.

Also preserves finder string messages in the why output (searchMessage
field on WhyPackageResult) and removes the now-unused
virtualStoreDirMaxLength parameter from buildWhyTrees/whyForPackages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract shared CLI helpers (computeInclude, resolveFinders, determineReportAs,
shorthands, help options) into common.ts. Move collectHashes to peerVariants.ts.
Unify readManifest to use CAFS store-based reading in both list and why paths,
with filesystem fallback for list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…m why

- Extract shared resolvePackagePath function for both list and why commands,
  handling global virtual store symlink resolution correctly
- Add --long flag support to pnpm why (description, repository, homepage, path)
  reusing getPkgInfo from the list package to avoid code duplication
- Use projectPaths in buildWhyTrees to respect --filter/--recursive flags

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ifest logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zkochan zkochan changed the title feat: show peer hashes in list output feat: reverse pnpm why tree and improve list/why output Feb 14, 2026
@zkochan zkochan requested a review from gluxon as a code owner February 15, 2026 00:29
Copilot AI review requested due to automatic review settings February 15, 2026 00:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR significantly refactors the pnpm list and pnpm why commands to improve their output and usability.

Purpose:
Replace the archy package with a custom tree renderer (@pnpm/text.tree-renderer) and reverse the pnpm why output to show a bottom-up dependency tree (from the searched package back to workspace roots), making it easier to understand why a package is installed.

Changes:

  • Introduces @pnpm/text.tree-renderer package with box-drawing characters and grouped sections
  • Reverses pnpm why tree to show dependants walking back to importers
  • Shows peer dependency hash suffixes to distinguish peer-dep variants
  • Adds visual improvements: bold importers, dimmed paths, dependency grouping, package count summary
  • Deduplicates shared code between list and why into common.ts

Reviewed changes

Copilot reviewed 42 out of 43 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
text/tree-renderer/* New package for rendering tree structures with box-drawing characters and grouped sections
reviewing/list/src/renderWhyTree.ts New module for rendering reverse dependency trees for pnpm why
reviewing/list/src/renderTree.ts Updated to use new tree renderer and add peer hash suffixes
reviewing/list/src/peerVariants.ts New module with shared functions for peer dependency variant handling
reviewing/list/src/index.ts Added whyForPackages function for the new why implementation
reviewing/dependencies-hierarchy/src/buildWhyTree.ts New module that builds reverse dependency trees
reviewing/dependencies-hierarchy/src/{resolvePackagePath,readManifestFromCafs,peersSuffixHash}.ts New helper modules for path resolution and manifest reading
reviewing/dependencies-hierarchy/src/getPkgInfo.ts Refactored to use new helper functions and support reading manifests from CAFS
reviewing/dependencies-hierarchy/src/getTree.ts Added deterministic sorting and peer hash tracking
reviewing/plugin-commands-listing/src/common.ts New shared module with common functions for list and why commands
reviewing/plugin-commands-listing/src/{list,why}.ts Refactored to use shared code and updated why handler
reviewing/plugin-commands-listing/test/* Updated tests to match new output format
packages/render-peer-issues/* Updated to use new tree renderer
dedupe/issues-renderer/* Updated to use new tree renderer
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

zkochan and others added 8 commits February 15, 2026 01:53
- buildDependenciesHierarchy → buildDependenciesTree
- buildWhyTrees → buildDependentsTree
- DependenciesHierarchy → DependenciesTree
- WhyPackageResult → DependentsTree
- WhyDependant → Dependent (American spelling)
- dependants → dependents (field names, variables, comments)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Skip reading currentLockfile when checkWantedLockfileOnly is true
- Remove defensive readManifest fallback (match buildDependenciesTree)
- Hoist WalkContext creation out of the match loop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use options objects instead of 4+ positional arguments
- Fix import ordering (stdlib alphabetical, externals before third-party)
- Move helper function after its callers (hoisting convention)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dentNode

Symmetric naming for tree node types: DependencyNode for the forward
tree, DependentNode for the reverse tree.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 46 out of 47 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Replace MD5 with SHA-256 in peersSuffixHash for FIPS compatibility
- Fix listSummary double-counting deduped packages
- Fix unsafe non-null assertion on importerInfoMap.get()
- Fix import spacing in renderDependentsTree
- Fix comment describing npm-alias display format
- Fix backtick escaping in tree-renderer JSDoc
- Move lockfile reading out of buildDependentsTree into the caller
  so the lockfile is read once and passed down, eliminating the
  duplicate read and the lockfile sync issue between whyForPackages
  and buildDependentsTree

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 46 out of 47 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…Summary grouping

- Sort trees by name, semver version, and peersSuffixHash for deterministic output
- Add serialized ID tiebreaker to edge sorting in walkReverse
- Fix tree-renderer: use flattened items.length for continuation chars and
  hasRenderableChildren() for connector detection (handles empty groups)
- Rewrite whySummary to group by package name so multi-package why queries
  produce per-package summary lines
- Add tests for tree-renderer empty group edge cases and whySummary variants

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

rootLabelParts.push(result.searchMessage)
}
if (opts.long && result.path) {
const pkg = await getPkgInfo({ name: result.name, version: result.version, path: result.path, alias: undefined })
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getPkgInfo function in reviewing/list/src/getPkgInfo.ts has a parameter alias that can be undefined, but this differs from the function signature in reviewing/dependencies-hierarchy/src/getPkgInfo.ts where alias is required (not optional). When calling this function from renderDependentsTree.ts at lines 20 and 120 with alias: undefined, there may be a type mismatch if TypeScript strict null checks are enabled. Consider making the parameter consistently optional across both implementations or providing a default value.

Suggested change
const pkg = await getPkgInfo({ name: result.name, version: result.version, path: result.path, alias: undefined })
const pkg = await getPkgInfo({ name: result.name, version: result.version, path: result.path })

Copilot uses AI. Check for mistakes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zkochan zkochan merged commit 7d5ada0 into main Feb 15, 2026
13 checks passed
@zkochan zkochan deleted the perf/list-peer-hashes branch February 15, 2026 13:38
zkochan added a commit that referenced this pull request Feb 15, 2026
- **`pnpm why` now shows a reverse dependency tree.** The searched package appears at the root with its dependants as branches, walking back to workspace roots. This replaces the previous forward-tree output which was noisy and hard to read for deeply nested dependencies.
- **Replaced `archy` with a new `@pnpm/text.tree-renderer` package** that renders trees using box-drawing characters (├──, └──, │) and supports grouped sections, dim connectors, and deduplication markers.
- **Show peer dependency hash suffixes** in `pnpm list` and `pnpm why` output to distinguish between different peer-dep variants of the same package.
- **Improved `pnpm list` visual output:** bold importer nodes, dimmed workspace paths, dependency grouping, package count summary, and deterministic sort order.
- **Added `--long` support to `pnpm why`** and the ability to read package manifests from the CAS store.
- **Deduplicated shared code** between `list` and `why` commands into a common module, and reused `getPkgInfo` in the why tree builder.
zkochan added a commit that referenced this pull request Feb 15, 2026
- **`pnpm why` now shows a reverse dependency tree.** The searched package appears at the root with its dependants as branches, walking back to workspace roots. This replaces the previous forward-tree output which was noisy and hard to read for deeply nested dependencies.
- **Replaced `archy` with a new `@pnpm/text.tree-renderer` package** that renders trees using box-drawing characters (├──, └──, │) and supports grouped sections, dim connectors, and deduplication markers.
- **Show peer dependency hash suffixes** in `pnpm list` and `pnpm why` output to distinguish between different peer-dep variants of the same package.
- **Improved `pnpm list` visual output:** bold importer nodes, dimmed workspace paths, dependency grouping, package count summary, and deterministic sort order.
- **Added `--long` support to `pnpm why`** and the ability to read package manifests from the CAS store.
- **Deduplicated shared code** between `list` and `why` commands into a common module, and reused `getPkgInfo` in the why tree builder.
zkochan added a commit to teambit/bit that referenced this pull request Feb 17, 2026
`bit why` now shows a reverse dependency tree. The searched package
appears at the root with its dependents as branches, walking back to
workspace components. This replaces the previous forward-tree output
which was noisy and hard to read for deeply nested dependencies.

Also with the complete rewrite of the dependency tree builder we have
fixed the out-of-memory errors that were frequent with the why command.
The command also became a lot faster.

## Proposed Changes

Related PRs:
- pnpm/pnpm#10582
- pnpm/pnpm#10586
- pnpm/pnpm#10615
- pnpm/pnpm#10616
- pnpm/pnpm#10627
- pnpm/pnpm#10629
- pnpm/pnpm#10596

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants