Skip to content

feat: add --package-json flag to deno add/install/remove/uninstall#33199

Merged
bartlomieju merged 3 commits into
mainfrom
feature/add-packagejson-flag
May 6, 2026
Merged

feat: add --package-json flag to deno add/install/remove/uninstall#33199
bartlomieju merged 3 commits into
mainfrom
feature/add-packagejson-flag

Conversation

@bartlomieju

Copy link
Copy Markdown
Member

Summary

  • Adds a --package-json flag to deno add, deno install, deno remove, and deno uninstall subcommands
  • When set, forces all dependency management to use package.json instead of deno.json, bypassing the distance-based config file heuristic
  • Creates a new package.json if one doesn't exist
  • JSR packages are converted to their npm-compatible form (npm:@jsr/...) when written to package.json

Test plan

  • New spec test tests/specs/add/package_json_flag — verifies both npm and jsr deps go to package.json, and that package.json is created when missing
  • New spec test tests/specs/remove/package_json_flag — verifies removal from package.json
  • Existing flag parsing unit tests updated and passing
  • Existing spec tests for add/remove still pass

🤖 Generated with Claude Code

Add a `--package-json` flag that forces dependency management to use
package.json instead of deno.json, bypassing the distance-based config
file heuristic. When set, all packages (including jsr: prefixed) are
written to package.json, and a new package.json is created if one
doesn't exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@bartlomieju bartlomieju added this to the 2.8.0 milestone Apr 25, 2026

@fibibot fibibot 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.

The flag plumbing and the load_configs split look right — force_package_json: true correctly returns (factory, Some(npm_config), None) with the package.json created if missing, the add path falls into the npm_config.as_mut().unwrap() branch unconditionally, and remove zeroes out deno_config from the iterator. The --package-json clap arg conflicts (global for install/uninstall, entrypoint for install) match Node's mental model. Tests look comprehensive on paper.

One concrete blocker, one process-y note:

  1. specs::remove::package_json_flag is failing CI on every platform (and was flagged flaky during the retry loop). The command is producing exactly Removed @denotest/esm-basic\n and the matcher rejects it because remove.out ends with [WILDCARD]\n, which requires at least one more chunk for the trailing literal \n to match. Drop the [WILDCARD] line from remove.out (the literal is enough) or have something else write to stdout/stderr after the remove notice.

  2. Also worth a Cargo.lock / merge-from-main pass — this PR has been sitting since Apr 8 and main has churned heavily through the cli/tools/pm area; rebasing might shake out other small interactions.

@@ -0,0 +1,2 @@
Removed @denotest/esm-basic
[WILDCARD]

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.

This [WILDCARD] line followed by a trailing newline is what is failing CI. The actual command output is just Removed @denotest/esm-basic\n with nothing after, so when the matcher consumes the literal first line and then [WILDCARD] greedily matches "", the trailing \n of the pattern has nothing left to match against. The CI debug output shows it as <FOUND>Removed·@denotest/esm-basic\n</FOUND><WILDCARD /> followed by COULD NOT FIND SEARCH TEXT (the trailing newline). Easiest fix: drop this [WILDCARD] line entirely so the .out file is just Removed @denotest/esm-basic\n. Other tests like tests/specs/add/npm_jsr_scope/npm.out get away with trailing [WILDCARD] only because they have additional Download … lines for [WILDCARD] to consume.

@lunadogbot lunadogbot 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.

Implementation looks good. Flag plumbed cleanly through AddFlags / RemoveFlags, conflict-with rules on install (entrypoint/global) and uninstall (global) make sense, and the load_configs branch correctly returns (factory, Some(npm_config), None) so deno_config is never touched when forced.

One minor UX nit (non-blocking): load_configs calls create_package_json whenever member_pkg_json() is None, which means deno remove --package-json foo on a project with no package.json will silently create an empty package.json as a side effect of a remove operation. Probably worth either erroring out or skipping creation in the remove path — but it's an edge case (user has to mistype the original add) and not a correctness issue. Happy to land as-is.

@fibibot fibibot 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.

Wiring of the new --package-json flag through deno add / install / remove / uninstall looks straightforward (forces deps into package.json, creates one if missing, converts JSR → npm:@jsr/...). Two things keep this from being merge-ready though.

CI is failing on the new spec test's expected output

test specs (1/2) debug macos-x86_64 (and the same shard across all 8 debug platforms) fails on specs::remove::package_json_flag with a fixture-vs-actual mismatch:

Expected (tests/specs/remove/package_json_flag/remove.out):
  Removed @denotest/esm-basic
  [WILDCARD]

Actual:
  Removed @denotest/esm-basic

The trailing [WILDCARD]\n in the .out file expects "any-char then a newline" after the "Removed" line, but the actual command output ends right after Removed @denotest/esm-basic\n with no further content. [WILDCARD] matches zero or more of any character, so it correctly matches "" — but then the matcher expects a literal \n (the newline at the end of the .out file) and there's nothing left. Two clean fixes:

  • Drop the [WILDCARD]\n line from tests/specs/remove/package_json_flag/remove.out so the expected ends right after Removed @denotest/esm-basic\n.
  • Or change the [WILDCARD] to [WILDCHARS(0)] if the intent was to allow optional trailing content.

The substance of the fix (the Removed message) is correct; just the fixture is over-asserting.

Escalation: this is a new public CLI flag

--package-json adds a user-facing flag to four subcommands (add/install/remove/uninstall) plus modifies the config-file resolution heuristic ("forces all dependency management to use package.json"). That's exactly the autonomous-APPROVE escalation list — new CLI flag plus a behavioral override of an existing resolution path. Even with you as the author, worth pinging another maintainer (e.g. @nathanwhit, who's been recently active in cli/tools/installer/local.rs) for the user-facing surface call before merge.

Stale

Last commit is 2026-04-08, so this needs a rebase against current main regardless — there's been substantial churn in cli/tools/{add,install,remove}.rs since (e.g. the categorize_installed_npm_deps refactor in #33756's wake). Easiest order: rebase, fix the .out mismatch, retrigger CI, then route for human review.

- add.out: replace [WILDCARD] inside [UNORDERED_START] (no longer
  allowed) with a [WILDCARD] after the block
- package.json.out: fix dependency order (now alphabetically sorted)
- remove.out: drop trailing [WILDCARD] line that caused matcher failure
- __test__.jsonc: fix empty package.json expectation (now pretty-printed)

@fibibot fibibot 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.

Substance is clean and well-scoped. The --package-json flag is added consistently across add/install/remove/uninstall with sensible conflict declarations (global for install/uninstall, entrypoint for install). load_configs(force_package_json: bool) handles both the use-existing-pkg.json path and the create-new-pkg.json path cleanly, and the add codepath correctly forces ALL packages (including jsr) into npm_config when the flag is set, with the standard npm:@jsr/<scope>__<name>@<version> rewrite (verified in package.json.out). Tests cover both add scenarios (existing + missing pkg.json) and the remove scenario.

The single CI failure is unrelated flake: test node_compat (1/3) release linux-x86_64 failed with parallel/test-cluster-send-deadlock.js timed out after 10000ms. This is the cluster/IPC subsystem, not the package-management code this PR touches — and the same shard passes on every other platform/mode (linux-aarch64, macos-x86_64/aarch64, windows-x86_64/aarch64 × debug + release). Same flaky-test pattern I've seen on other PRs today. Re-triggering this shard should clear it.

Not APPROVE-ing autonomously per the CI-must-be-green rule, but the substance is APPROVE-quality — once the flake is cleared via re-run, this should land cleanly.

Two non-blocking observations inline.

Comment thread cli/tools/pm/mod.rs
) -> Result<(), AnyError> {
let (_, npm_config, deno_config) = load_configs(&flags, || false)?;
let force_package_json = remove_flags.package_json;
let (_, npm_config, deno_config) =

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.

Subtle behavior worth confirming: when --package-json is set on remove, only package.json is cleared even if the same package exists in deno.json:

let mut configs = if force_package_json {
  [npm_config, None]  // skip deno_config
} else {
  [npm_config, deno_config]
};

The legacy state where a user has the same dep in BOTH package.json and deno.json (e.g., from a migration) won't be fully cleaned up by --package-json. Probably the right call (the user explicitly asked for package.json-only operations), but worth a sentence in the --help text or the eventual docs so users with mixed state don't get surprised. Non-blocking.

Comment thread cli/tools/pm/mod.rs
fn create_package_json(
flags: &Arc<Flags>,
options: &CliOptions,
) -> Result<CliFactory, AnyError> {

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.

std::fs::write(.../package.json, "{}\n") then immediately reading it back via the new CliFactory::from_flags(flags.clone()) to pick up the new file. The write is fine — but if a future change makes CliFactory::from_flags cache its config-file scan, this could break since the cwd snapshot would predate the file creation.

Not a current bug — from_flags does a fresh scan today. Just want to flag the implicit contract that from_flags re-reads cwd state on every call.

@fibibot fibibot 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.

LGTM — clears my prior CHANGES_REQUESTED at 109f6192 plus the APPROVE-pending hold from today's COMMENT at this same head. CI fully green now: 134 success, 2 skipping, 0 fail across all platforms × debug + release. The parallel/test-cluster-send-deadlock.js flake I noted earlier has cleared on re-run.

Both prior concerns addressed

  1. specs::remove::package_json_flag test failure (the original CHANGES_REQUESTED blocker): de41593c ("fix: update spec test expected outputs after rebase") fixed the matcher mismatch. The remove path now produces matching output.
  2. Cargo.lock / merge-from-main: 6281ddfe rebased onto main and updated lockfile interactions through cli/tools/pm.

Substance recap from earlier today

  • --package-json flag added consistently across add/install/remove/uninstall with sensible conflict declarations (global for install/uninstall, entrypoint for install).
  • load_configs(force_package_json) cleanly handles both use-existing and create-new pkg.json paths.
  • JSR→npm rewrite uses the standard @jsr/<scope>__<name> form (verified by package.json.out).
  • Comprehensive spec tests for both add (existing + missing pkg.json) and remove paths.

The two non-blocking inline observations (remove-with-mixed-state docs note; CliFactory::from_flags re-read assumption) stand as low-priority followups but don't gate merge.

@bartlomieju
bartlomieju merged commit fdca594 into main May 6, 2026
268 of 270 checks passed
@bartlomieju
bartlomieju deleted the feature/add-packagejson-flag branch May 6, 2026 08:23
littledivy pushed a commit to crowlKats/deno that referenced this pull request Jun 10, 2026
…enoland#33199)

- Adds a `--package-json` flag to `deno add`, `deno install`, `deno
remove`, and `deno uninstall` subcommands
- When set, forces all dependency management to use `package.json`
instead of `deno.json`, bypassing the distance-based config file
heuristic
- Creates a new `package.json` if one doesn't exist
- JSR packages are converted to their npm-compatible form
(`npm:@jsr/...`) when written to `package.json`
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.

3 participants