Skip to content

fix(npm): fall back for age-filtered npm dist-tags#35561

Merged
nathanwhit merged 3 commits into
denoland:mainfrom
nathanwhit:codex/npm-dist-tag-age-fallback
Jun 29, 2026
Merged

fix(npm): fall back for age-filtered npm dist-tags#35561
nathanwhit merged 3 commits into
denoland:mainfrom
nathanwhit:codex/npm-dist-tag-age-fallback

Conversation

@nathanwhit

Copy link
Copy Markdown
Member

Summary

Fixes #35552.

When an npm dist-tag points at a version newer than the configured minimum dependency age, retry resolution with a semver range of <= tagged_version instead of failing the tag resolution outright.

This applies to all dist-tags, not only latest, and matches the npm-pick-manifest/pnpm behavior we checked for tags like latest, canary, and next.

Exact versions stay exact: if [email protected] is too new, Deno still errors instead of falling back to an older version.

Root Cause

The default minimum dependency age can reject a freshly-published dist-tag target before npm resolution gets a chance to select an older acceptable version. Deno only had a special fallback for latest, and that fallback was too broad for prerelease-only package streams.

For @typescript/native-preview@latest, this meant the fresh latest prerelease was rejected and resolution failed instead of selecting the newest mature version at or below the tagged version.

Validation

  • cargo fmt --check
  • cargo test -p deno_npm tag_too_new -- --nocapture
  • cargo test -p deno_npm test_exact_version_too_new_does_not_use_lte_fallback -- --nocapture
  • cargo test -p deno_npm

@nathanwhit
nathanwhit marked this pull request as ready for review June 27, 2026 09:22
@bartlomieju

Copy link
Copy Markdown
Member

Review

Clean, well-scoped fix. The wildcard → <= tagged_version change is the right call: the old latest fallback could select a version newer than the tagged one, and capping at the tagged version matches npm-pick-manifest/pnpm. Prerelease semantics and the exact-version strictness are both correctly handled and tested. Approve.

A couple of minor, non-blocking suggestions:

1. unwrap() on the constructed range (libs/npm/resolution/common.rs):

VersionReq::parse_from_npm(&format!("<={tagged_version}")).unwrap()

In practice tagged_version is always a valid Version, so this should never fail — but it couples panic-safety to the assumption that Version's Display always round-trips through parse_from_npm as a <= comparator, including edge cases like build metadata (1.0.0+build). Worth a one-line comment explaining why the unwrap is safe, or building the VersionReq from the parsed Version directly to avoid the string round-trip.

2. Dead DistTagNotFound branch in resolve_best_matching_dist_tag_fallback_version_info: this fn is only reached after tag_to_version_info already returned DistTagVersionTooNew, which guarantees the tag exists in dist_tags. The else is harmless defensive code, but a brief // unreachable in practice; defensive comment would clarify intent.

Test coverage is unit-level only (no spec test reproducing the @typescript/native-preview@latest case from #35552), but since this is pure resolution logic the four deno_npm tests are adequate. The test helper refactor is a nice cleanup.

bartlomieju and others added 2 commits June 29, 2026 14:23
Address review: construct the `<= tagged_version` VersionReq directly from
the parsed Version instead of formatting and reparsing, removing the unwrap
and avoiding round-trip edge cases (e.g. build metadata). Note the
DistTagNotFound branch is defensive and unreachable in practice.
When the `<= tagged_version` fallback also finds no version satisfying
the minimum dependency age, surface the original error that names the
dist-tag and the too-new version it maps to, rather than the generic
"version req not matched" message for the synthesized `<=` range.

Add unit coverage for a dist-tag whose version carries build metadata
(exercising the directly-built range) and for the no-allowed-lower-
version error, plus a specs regression test that installs the
prerelease-only `@latest` scenario from denoland#35552 end to end.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@nathanwhit
nathanwhit merged commit 1d08c79 into denoland:main Jun 29, 2026
136 checks passed
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.

Unable to Resolve TypeScript Native Preview (Latest)

2 participants