denoland / denoland/deno_graph

Lockfile pin to a JSR pre-release is dropped once upstream publishes a stable version

Open
#666 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
137
Forks
47
Avg merge
2d 7h
Merged PRs (30d)
4

Description

A lockfile entry mapping a wildcard requirement to a pre-release version stops being honored the moment the package publishes a stable release, so an upstream publish silently changes an existing build.

### How it happens

`jsr:@scope/a` parses to a `*` requirement, which never matches a pre-release in semver. For a package with no usable stable release, `resolve_version` falls back to considering pre-releases, so `@scope/a` -> `@scope/a@1.0.0-rc.2` gets written to the lockfile.

Once upstream publishes `2.0.0`, the package has a usable stable release, so the fallback no longer applies. Step 1 of `resolve_version` (in-graph/lockfile unification) then evaluates the pinned `1.0.0-rc.2` against `*`, which does not match, and resolution falls through to step 2 and picks `2.0.0` — despite the lockfile.

### Repro

Verified against `prerelease-fallback` (`tests/integration_test.rs`):

```rust
builder
.with_loader(|loader| {
loader.remote.add_source_with_text("file:///mod.ts", "import \"jsr:@scope/a\";");
// upstream has since published a stable release
add_jsr_package(loader, "@scope/a", &[
TestJsrVersion::new("1.0.0-rc.2"),
TestJsrVersion::new("2.0.0"),
]);
})
// the lockfile pinned the pre-release back when it was the only version
.lockfile_jsr_packages(BTreeMap::from([(
PackageReq::from_str("@scope/a").unwrap(),
PackageNv::from_str("@scope/a@1.0.0-rc.2").unwrap(),
)]));
```

Resolves to `@scope/a@2.0.0`; expected `@scope/a@1.0.0-rc.2`.

### Why it is not fixed in #661

The obvious fix — letting step 1 consider pre-releases for wildcard requirements — is one flag, but `existing_versions` cannot distinguish a lockfile pin from a version another requirement resolved a moment earlier in the same pass. So the same flag also means a bare `import "jsr:@scope/a"` unifies onto a pre-release whenever any *other* import in the graph opted into one, which is a worse surprise than the bug it fixes.

Fixing this properly means teaching step 1 that difference, rather than widening what a wildcard requirement matches.

Pre-existing; not introduced by #661.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with resolve_version and existing_versions, then run the prerelease-fallback case in tests/integration_test.rs using the lockfile_jsr_packages setup shown here. Trace how step 1 unifies in-graph versions and distinguish the lockfile pin from versions resolved by another import. Done means the pinned @scope/a@1.0.0-rc.2 remains selected after 2.0.0 is published without making a bare wildcard adopt unrelated pre-releases.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.