fix(add): handle version tags like @latest in deno add for JSR packages#32859
Conversation
…ckages When `deno add jsr:@std/cli@latest` is used, the version tag "latest" was passed to `deno_semver::VersionReq::matches()` which panics on tags. Fix by detecting tags via `version_req.tag()` in the add command's version selection and resolving to the latest version instead. Closes denoland#31298 Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
npm handles dist-tags (latest, next, canary, etc.) natively via registry metadata, so only intercept tags for JSR where VersionReq::matches would panic. Also add the fallback registry hint when a tagged JSR package is not found. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
fibibot
left a comment
There was a problem hiding this comment.
LGTM. The bug is real — deno_semver::VersionReq::matches() panics with "programming error: cannot use matches with a tag: latest" when handed a tag-shaped version, and find_package_and_select_version_for_req was forwarding @latest straight into that path via main_resolver.req_to_nv(req). Verified the fix:
- Tag detection at
cli/tools/pm/mod.rs:725-726:req.version_req.tag().is_some()correctly intercepts the tag case. Thematches!(&add_package_req.value, AddRmPackageReqValue::Jsr(_))guard limits the early return to JSR — npm goes through its ownreq_to_nvwhich natively resolves dist-tags via the registry, so the unchanged path still works there. ✓ latest_versionfor JSR atcli/tools/pm/mod.rs:675-685: pulls package info, filters out yanked versions, picks the highest viabest_version. Returns the right thing for@latestsemantics. ✓- Caret pin (
format!("^{}", &version)): matchesdeno add's default pinning style elsewhere — same shape user gets when runningdeno add jsr:@scope/pkgwithout a version. ✓ - NotFound fallback mirrors the existing post-tag-resolution path: tries
fallback_resolver.req_to_nvand returnshelp_if_found_in_fallbackif found there (e.g., user typedjsr:foo@latestbut onlynpm:fooexists),Nonehelp otherwise. Could be folded into one expression as @scarf005 suggested inline, but functionally identical.
Test coverage: the existing tests/specs/add/dist_tag/__test__.jsonc was a single-step test for npm's @latest. The PR splits it into npm_latest + jsr_latest cases and adds add_jsr.out asserting the expected Add jsr:@denotest/add@[WILDCARD] output. The test would have crashed (panic) on 9a9a7151's code without the fix, so it's a real regression test. ✓
One minor scope note (not blocking): the early return treats any JSR tag as "resolve to latest", so jsr:@scope/pkg@beta (an unsupported tag on JSR) silently picks the latest version rather than erroring with "JSR doesn't support dist-tags". That's a behavioral improvement over the panic, and @latest is by far the dominant use case — but worth a follow-up to error on non-latest JSR tags if anyone hits the surprise path.
CI: 110 success, 1 skip (publish canary), 0 fail. All test integration, test specs, and test unit shards green across all 4 platforms × debug + release. ✓
Fold the JSR @latest path into the shared version-selection flow instead of a separate early return: it now produces a PackageNv and reuses the existing not-found fallback and range-symbol/Selected logic (also honoring --save-exact). Reject non-'latest' JSR tags with a clear error rather than silently resolving them to the latest version, and add a spec test for it.
Summary
deno add jsr:@std/cli@latestpanics with:The version tag
latestwas passed through todeno_semver::VersionReq::matches()which panics on tags. Fix bydetecting tags via
version_req.tag()in the add command's versionselection (
find_package_and_select_version_for_req) and resolvingto the latest version via
latest_version()instead.This matches the pattern used elsewhere in the codebase (LSP, cache_deps,
outdated) where
tag()is checked before callingmatches().Closes #31298