Skip to content

fix(npm): clean node_modules after deno remove#34110

Merged
bartlomieju merged 5 commits into
mainfrom
orch/issue-101
Jun 1, 2026
Merged

fix(npm): clean node_modules after deno remove#34110
bartlomieju merged 5 commits into
mainfrom
orch/issue-101

Conversation

@fibibot

@fibibot fibibot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Fixes #34073.
Closes bartlomieju/orchid-inbox#101

This updates the local npm installer cleanup path so deno remove can reconcile an existing managed node_modules even when the updated npm resolution has zero packages. It also cleans scoped package entries in both isolated and hoisted layouts.

The regression spec now uses npm imports in deno.json with nodeModulesDir: "auto", removes a scoped package, then removes the last package and checks node_modules, deno.json, and deno.lock.

Verification:

  • deno fmt --check tests/specs/remove/node_modules_cleanup/__test__.jsonc tests/specs/remove/node_modules_cleanup/deno.json
  • git diff --check
  • Unable to run ./x test-spec remove/node_modules_cleanup because this VM does not have cargo installed.
  • Unable to complete ./x fmt --check because this VM does not have rustfmt installed.

AI disclosure: This PR was authored with assistance from OpenAI Codex.

@fibibot fibibot changed the title Fix denoland/deno#34073: deno remove leaves removed npm packages in node_modules fix(npm): clean node_modules after deno remove May 16, 2026
@fibibot

fibibot commented May 16, 2026

Copy link
Copy Markdown
Contributor Author

CI is green. @bartlomieju this is ready for review.

@bartlomieju bartlomieju left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed this — solid fix, root cause is correctly diagnosed and the three changes hang together well.

Correctness (traced by hand, looks right):

  • node_modules_package_actual_dir_to_name: the real bug. For a scoped target like .deno/@[email protected]/node_modules/@denotest/add the old two-.parent() walk returned "node_modules" instead of the folder-id, so it never matched keep_names. The new grandparent-is-node_modules check is correct, and the added unit test covers both scoped and unscoped.
  • remove_unused_node_modules_symlinks: scoped symlinks live under a @scope/ subdir that the old top-level-only loop skipped (the dir itself isn't a symlink). Recursing into @-prefixed dirs and pruning the empty scope dir is the right fix; .deno/.bin are .-prefixed so they're untouched.
  • The early-return changes in local.rs/hoisted.rs plus the lib.rs cache_packages trigger correctly handle removing the last package (empty snapshot used to bail before cleanup, and maybe_cache_packages short-circuited on uncached.is_empty()).

Points before merge (non-blocking):

  1. hoisted.rs changes aren't covered by a test. The spec test uses nodeModulesDir: "auto" (isolated layout → local.rs), so the scoped cleanup added to cleanup_hoisted_packages is never exercised. Could you add a hoisted-layout case, or confirm it was manually verified? In particular the expected_names @scope/pkg format assumption.
  2. lib.rs perf nuance: every PackageCaching::All install where everything is already cached now re-runs cache_packagessync_resolution_with_fs instead of short-circuiting. It's guarded by setup_cache downstream so should be cheap, but it's a behavior change on the install hot path worth a line in the description.
  3. nit: after removing the last package, fs_create_dir_all recreates an empty node_modules/.deno/node_modules, leaving an empty node_modules/.deno behind. The test only asserts specific entries are gone, not that node_modules is fully cleaned.

Overall LGTM pending the hoisted test coverage.

Add a sibling spec test that exercises deno remove cleanup under the
hoisted node_modules linker, covering the scoped-package removal and
empty scope-directory pruning in cleanup_hoisted_packages. Document why
maybe_cache_packages re-syncs node_modules for All caching even when
every package is already cached.
@bartlomieju
bartlomieju merged commit 81cdb68 into main Jun 1, 2026
136 checks passed
@bartlomieju
bartlomieju deleted the orch/issue-101 branch June 1, 2026 13:00
littledivy pushed a commit to crowlKats/deno that referenced this pull request Jun 10, 2026
Fixes denoland#34073.

This updates the local npm installer cleanup path so `deno remove` can
reconcile an existing managed `node_modules` even when the updated npm
resolution has zero packages. It also cleans scoped package entries in
both isolated and hoisted layouts.

The regression spec now uses npm imports in `deno.json` with
`nodeModulesDir: "auto"`, removes a scoped package, then removes the
last package and checks `node_modules`, `deno.json`, and `deno.lock`.

---------

Co-authored-by: fibibot <[email protected]>
Co-authored-by: Bartek Iwańczuk <[email protected]>
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.

deno remove should delete packages inside node_modules

2 participants