Skip to content

feat(pm): add --json flag to bun pm ls and bun list - #35251

Closed
khaled4vokalz wants to merge 1 commit into
oven-sh:mainfrom
khaled4vokalz:claude/feature/json-formatting-of-deps
Closed

khaled4vokalz wants to merge 1 commit into
oven-sh:mainfrom
khaled4vokalz:claude/feature/json-formatting-of-deps

Conversation

@khaled4vokalz

Copy link
Copy Markdown

What does this PR do?

Add a --json flag to bun pm ls (and its bun list alias) that emits
the dependency tree as JSON instead of the human-readable tree, in a
pnpm-compatible shape for easy scripting.

The output is a single root object with:

  • name, version, path for the current project
  • dependencies, devDependencies, optionalDependencies,
    peerDependencies buckets, each keyed by package name with
    { from, version, path } entries
  • transitiveDependencies (only with --all)

The four direct-dependency buckets are always emitted (empty object when a
bucket has no members) so consumers get a stable schema.
transitiveDependencies is omitted unless --all is passed.

Also documents --json in the bun pm/bun list help output and fixes
the alignment of the bun list header line, which was two columns short
of its siblings.

This is a port of the original Zig implementation (#26223), which was
closed after Bun's package manager was rewritten to Rust. Filtering
support is intentionally deferred to a follow-up.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

Closes #26222

How did you verify your code works?

  • Added unit tests

  • Testing the debug build in my local gives the below output (heroku cli):

    $ BUN_DEBUG_QUIET_LOGS=1 ../bun/build/debug/bun-debug list --json
    {
      "path": "/my/personal/workspace/cli",
      "dependencies": {
        "heroku": {
          "from": "heroku",
          "version": "workspace:packages/cli",
          "path": "/my/personal/workspace/cli/node_modules/heroku"
        }
      },
      "devDependencies": {
        "@actions/core": {
          "from": "@actions/core",
          "version": "1.11.1",
          "path": "/my/personal/workspace/cli/node_modules/@actions/core"
        },
        ...
        "tmp": {
          "from": "tmp",
          "version": "0.2.5",
          "path": "/my/personal/workspace/cli/node_modules/tmp"
        }
      }
    }

    and with --all flag the output is:

    $ BUN_DEBUG_QUIET_LOGS=1 ../bun/build/debug/bun-debug list --json --all
    {
      "path": "/my/personal/workspace/cli",
      "dependencies": {
        "heroku": {
          "from": "heroku",
          "version": "workspace:packages/cli",
          "path": "/my/personal/workspace/cli/node_modules/heroku"
        }
      },
      "devDependencies": {
        "@actions/core": {
          "from": "@actions/core",
          "version": "1.11.1",
          "path": "/my/personal/workspace/cli/node_modules/@actions/core"
        },
        ...
        "tmp": {
          "from": "tmp",
          "version": "0.2.5",
          "path": "/my/personal/workspace/cli/node_modules/tmp"
        }
      },
      "transitiveDependencies": {
        "@actions/exec": {
          "from": "@actions/exec",
          "version": "1.1.1",
          "path": "/my/personal/workspace/cli/node_modules/@actions/exec"
        },
        ...
        "@aws-sdk/region-config-resolver": {
          "from": "@aws-sdk/region-config-resolver",
          "version": "3.969.0",
          "path": "/my/personal/workspace/cli/node_modules/@aws-sdk/region-config-resolver"
        },
        ...
        "acorn": {
          "from": "acorn",
          "version": "8.15.0",
          "path": "/my/personal/workspace/cli/node_modules/acorn"
        },
        ...
        "zod-to-json-schema": {
          "from": "zod-to-json-schema",
          "version": "3.25.1",
          "path": "/my/personal/workspace/cli/node_modules/zod-to-json-schema"
        }
      }
    }

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

bun pm ls and bun list support --json, producing package metadata and categorized dependency information. --all adds transitive dependencies, with CLI tests covering output structure, aliases, flags, and installation paths.

Package listing JSON output

Layer / File(s) Summary
JSON listing generation and CLI integration
src/runtime/cli/package_manager_command.rs, src/install/PackageManager.rs
The ls command routes JSON requests through structured dependency generation, emits root metadata and dependency buckets, resolves physical installation paths, and documents the --json option.
JSON output test coverage
test/cli/install/bun-pm.test.ts
Tests validate metadata, dependency categorization, transitive dependency output with --all, the bun list alias, --trusted handling, and nested paths.

Suggested reviewers: robobun

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The bun list header-alignment fix is unrelated to the linked JSON-output feature. Move the bun list header-alignment tweak to a separate PR, or remove it to keep this change scoped to --json support.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding --json support to bun pm ls and bun list.
Description check ✅ Passed The description matches the template and covers the feature plus verification steps.
Linked Issues check ✅ Passed The PR implements the requested JSON output for bun pm ls/bun list, including direct and --all transitive dependencies.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/cli/package_manager_command.rs`:
- Around line 506-512: Update the json_output branch in the package-manager
command before print_ls_json and Global::exit to detect when --trusted is also
present and emit a warning to stderr that trusted-only filtering is unsupported
for JSON output and the flag is ignored; preserve the existing JSON listing
behavior and exit flow.
- Around line 924-961: Update build_dep_info to derive dep_info.path from the
resolved node_modules placement data used by installation, rather than joining
cwd, node_modules, and dep.name. Reuse the dependency’s actual
node_modules-folder/store location so isolated and hoisted layouts resolve the
correct package directory for the selected resolution.
- Around line 861-880: Update print_ls_json’s --all transitiveDependencies
construction to walk every node_modules tree via the same tree::Iterator-based
directory traversal used by non-JSON --all output, rather than relying on
lockfile.buffers.hoisted_dependencies. Preserve dependency filtering,
resolution, naming, and deduplication while including nested or shadowed
packages visible at any depth.

In `@test/cli/install/bun-pm.test.ts`:
- Around line 973-1146: Extend the “bun pm ls --json separates dependencies and
devDependencies” coverage with non-empty optionalDependencies and
peerDependencies entries, install them through the existing dummy registry
setup, and assert the JSON output places each package in its corresponding
optionalDependencies and peerDependencies bucket while excluding them from
unrelated buckets. Keep the existing dependency/devDependency assertions intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 35932a2b-170f-42b1-9591-9550219a2355

📥 Commits

Reviewing files that changed from the base of the PR and between 892b1da and 70d5591.

📒 Files selected for processing (3)
  • src/install/PackageManager.rs
  • src/runtime/cli/package_manager_command.rs
  • test/cli/install/bun-pm.test.ts

Comment thread test/cli/install/bun-pm.test.ts Resolved
@khaled4vokalz
khaled4vokalz force-pushed the claude/feature/json-formatting-of-deps branch from ce74217 to 70d5591 Compare July 23, 2026 08:54
Add a `--json` flag to `bun pm ls` (and its `bun list` alias) that emits
the dependency tree as JSON instead of the human-readable tree, in a
pnpm-compatible shape for easy scripting.

The output is a single root object with:
- `name`, `version`, `path` for the current project
- `dependencies`, `devDependencies`, `optionalDependencies`,
`peerDependencies` buckets, each keyed by package name with
`{ from, version, path }` entries
- `transitiveDependencies` (only with `--all`)

The four direct-dependency buckets are always emitted (empty object when a
bucket has no members) so consumers get a stable schema.
`transitiveDependencies` is omitted unless `--all` is passed.

Also documents `--json` in the `bun pm`/`bun list` help output and fixes
the alignment of the `bun list` header line, which was two columns short
of its siblings.

This is a port of the original Zig implementation (oven-sh#26223), which was
closed after Bun's package manager was rewritten to Rust. Filtering
support is intentionally deferred to a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@khaled4vokalz
khaled4vokalz force-pushed the claude/feature/json-formatting-of-deps branch from 70d5591 to 32599e6 Compare July 23, 2026 14:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/cli/package_manager_command.rs`:
- Around line 880-902: Update the transitive dependency emission in the show_all
branch around transitive_deps.put so duplicate package names are handled
explicitly instead of silently replacing prior entries. Preserve every
same-named package from different node_modules locations by aggregating their
dependency objects under one name, or reject duplicates deterministically; do
not emit a single non-representative value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0cf7d3d7-8d36-418e-b3c0-0045ea495790

📥 Commits

Reviewing files that changed from the base of the PR and between 70d5591 and 32599e6.

📒 Files selected for processing (3)
  • src/install/PackageManager.rs
  • src/runtime/cli/package_manager_command.rs
  • test/cli/install/bun-pm.test.ts

Comment on lines +880 to +902
// Transitive deps (only with --all). Sourced from the same tree walk that
// produced `dep_folders`, so every edge carries its real install path.
if show_all {
let mut sorted_transitive: Vec<DependencyID> = dep_folders
.keys()
.copied()
.filter(|id| !direct_dep_ids.contains(id))
.collect();
sorted_transitive.sort_unstable_by(|a, b| by_name.cmp(*a, *b));

for &dependency_id in &sorted_transitive {
let package_id = resolutions_buf[dependency_id as usize];
if package_id as usize >= lockfile.packages.len() {
continue;
}
let dep = &dependencies[dependency_id as usize];
let folder_rel = dep_folders.get(&dependency_id).copied().unwrap_or(b"node_modules");
let dep_expr =
build_dep_info(dep, &resolutions[package_id as usize], string_bytes, cwd, folder_rel)?;
let name = crate::cli::cli_dupe(dep.name.slice(string_bytes));
transitive_deps.put(bump, name, dep_expr)?;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect E::Object::put semantics for duplicate keys
rg -nP 'fn put\b' src/ast -A20
# Check how print_json handles an object with repeated property keys
rg -nP 'EObject|properties' src/js_printer/lib.rs -n | head -40

Repository: oven-sh/bun

Length of output: 7605


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- package_manager_command context ---\n'
sed -n '820,915p' src/runtime/cli/package_manager_command.rs

printf '\n--- E::Object put implementation ---\n'
sed -n '1300,1355p' src/ast/e.rs

printf '\n--- as_property implementation ---\n'
sed -n '1180,1305p' src/ast/e.rs

printf '\n--- JSON printer object handling ---\n'
rg -n 'EObjectJSON|EObject|properties' src/js_printer src/runtime -g '*.rs' -A6 -B3 | head -200

Repository: oven-sh/bun

Length of output: 240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- package_manager_command context ---'
sed -n '820,915p' src/runtime/cli/package_manager_command.rs

echo
echo '--- E::Object put implementation ---'
sed -n '1300,1355p' src/ast/e.rs

echo
echo '--- as_property implementation ---'
sed -n '1180,1305p' src/ast/e.rs

echo
echo '--- JSON printer object handling ---'
rg -n 'EObjectJSON|EObject|properties' src/js_printer src/runtime -g '*.rs' -A6 -B3 | head -200

Repository: oven-sh/bun

Length of output: 24092


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- JSON printer object property iteration ---'
sed -n '3612,3655p' src/js_printer/lib.rs

echo
echo '--- JS printer object property iteration ---'
sed -n '4340,4575p' src/js_printer/lib.rs

echo
echo '--- package manager command tests around nested paths / duplicate names ---'
sed -n '1,220p' src/runtime/cli/package_manager_command.rs | rg -n 'nested|duplicate|transitive|package|name|node_modules|Conflict|diamond' -A5 -B5 || true
find . -path '*/tests/*' -o -path '*/test/*' -o -name '*package_command*test*' -o -name '*PackageCmd*' | head -50
rg -n 'transitiveDependencies|transitive deps|package_manager_command|nested' . -g '*.rs' -g '*.test.*' -g '*.spec.*' -A6 -B3 | head -240

Repository: oven-sh/bun

Length of output: 20337


Avoid one-value-over-one-value merging in transitiveDependencies.

E::Object::put replaces an existing same-named property, so installing the same transitive package in multiple node_modules locations yields a non-deterministic surviving entry. Either report multiple values under each name (for example [{ path: "...", ... }, { path: "...", ... }]) or reject duplicate names deterministically rather than emitting JSON with a single non-representative nested package.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/cli/package_manager_command.rs` around lines 880 - 902, Update
the transitive dependency emission in the show_all branch around
transitive_deps.put so duplicate package names are handled explicitly instead of
silently replacing prior entries. Preserve every same-named package from
different node_modules locations by aggregating their dependency objects under
one name, or reject duplicates deterministically; do not emit a single
non-representative value.

@robobun

robobun commented Aug 22, 2026 •

Copy link
Copy Markdown
Collaborator

Thank you for this PR, and for porting it from the Zig version after the rewrite.

--json for bun pm ls is landing as part of #40045, which adds --json to the remaining bun pm commands in one change. We adopted your grouped shape there (commits 8a13478 and b8e54ca): the root's dependencies are split into dependencies, devDependencies, optionalDependencies and peerDependencies, keyed by name, with from, version and the absolute path on each entry, and the root carries name, version and path. Your tests for the grouping were carried over as well. The commit credits you with a Co-authored-by trailer.

Three differences from this PR: the document is an array with the root as its only element, as pnpm ls --json prints it, workspace packages get their own workspaces group, and --all is rejected with --json for now. The --all list was built from the hoisted node_modules layout, and under the isolated linker those folders do not exist on disk, so a tree view needs a different data source and can be added later as an additive key.

Closing this PR in favor of #40045. The grouped text output that #8283 also asks for is still open for discussion on that issue.

@robobun robobun closed this Aug 22, 2026
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.

Feature request: Add --json flag support to bun pm ls / bun list

2 participants