Skip to content

Commit ab33122

Browse files
authored
Merge pull request #11 from dev-five-git/fix-prerelease
Fix prerelease issue
2 parents c360634 + f07d4e6 commit ab33122

File tree

8 files changed

+440
-18
lines changed

8 files changed

+440
-18
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"bridge/node/package.json":"Patch","crates/core/Cargo.toml":"Patch","crates/rust/Cargo.toml":"Patch","bridge/python/pyproject.toml":"Patch","crates/node/Cargo.toml":"Patch","crates/cli/Cargo.toml":"Patch","crates/python/Cargo.toml":"Patch"},"note":"Impl prerelease issue","date":"2026-04-17T07:29:42.535002100Z"}

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ tracing.workspace = true
3030
tracing-subscriber.workspace = true
3131
futures.workspace = true
3232
owo-colors.workspace = true
33+
semver = "1.0"
3334

3435
[dev-dependencies]
3536
wiremock.workspace = true

crates/cli/src/lib.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,28 @@ fn compute_updates(
339339
.current_req
340340
.trim_start_matches(|c: char| !c.is_ascii_digit());
341341

342+
// Safety net: never suggest a downgrade. When both current and selected
343+
// can be parsed as semver, skip this dependency if selected <= current.
344+
// This guards against the registry-filtering path returning an older
345+
// stable when the user is already on a higher prerelease (e.g.
346+
// `sea-orm 2.0.0-rc.37` -> `1.1.20` should NOT be reported).
347+
//
348+
// For ranges like `^1` / `0.6` where we cannot fully parse as semver,
349+
// we fall through to the string-based precision comparison below.
350+
if let (Ok(cur_ver), Ok(sel_ver)) = (
351+
semver::Version::parse(current_bare),
352+
semver::Version::parse(selected),
353+
) && sel_ver <= cur_ver
354+
{
355+
trace!(
356+
package = %dep.name,
357+
current = %dep.current_req,
358+
selected = %selected,
359+
"skipping: selected version is not newer than current"
360+
);
361+
continue;
362+
}
363+
342364
// Preserve precision: if the user wrote "0.6" (2 segments), truncate the
343365
// resolved version to 2 segments before comparing. This respects the user's
344366
// intent to pin only at that granularity.
@@ -894,6 +916,109 @@ mod tests {
894916
assert_eq!(updates[0].to, "0.25.11");
895917
}
896918

919+
#[test]
920+
fn test_compute_updates_blocks_downgrade_from_prerelease_to_stable() {
921+
// Regression test for the sea-orm 2.0.0-rc.37 -> 1.1.20 bug.
922+
// When the current version is a higher prerelease (2.0.0-rc.37) and
923+
// the registry filtering returns an older stable (1.1.20), the
924+
// safety net MUST skip this update instead of suggesting a downgrade.
925+
let deps = vec![DependencySpec {
926+
name: "sea-orm".to_owned(),
927+
current_req: "2.0.0-rc.37".to_owned(),
928+
section: DependencySection::Dependencies,
929+
}];
930+
let resolved = vec![(
931+
0,
932+
Ok(ResolvedVersion {
933+
latest: Some("1.1.20".to_owned()),
934+
selected: Some("1.1.20".to_owned()),
935+
}),
936+
)];
937+
let updates = compute_updates(&deps, &resolved);
938+
assert!(
939+
updates.is_empty(),
940+
"must not suggest downgrade from 2.0.0-rc.37 to 1.1.20, got: {updates:?}"
941+
);
942+
}
943+
944+
#[test]
945+
fn test_compute_updates_blocks_downgrade_same_major() {
946+
// Current is newer stable; registry returned something older. Skip.
947+
let deps = vec![DependencySpec {
948+
name: "pkg".to_owned(),
949+
current_req: "2.5.0".to_owned(),
950+
section: DependencySection::Dependencies,
951+
}];
952+
let resolved = vec![(
953+
0,
954+
Ok(ResolvedVersion {
955+
latest: Some("2.4.0".to_owned()),
956+
selected: Some("2.4.0".to_owned()),
957+
}),
958+
)];
959+
let updates = compute_updates(&deps, &resolved);
960+
assert!(updates.is_empty(), "must not downgrade 2.5.0 -> 2.4.0");
961+
}
962+
963+
#[test]
964+
fn test_compute_updates_allows_prerelease_to_prerelease_upgrade() {
965+
// Current: 2.0.0-rc.37, Selected: 2.0.0-rc.40 → valid upgrade.
966+
let deps = vec![DependencySpec {
967+
name: "sea-orm".to_owned(),
968+
current_req: "2.0.0-rc.37".to_owned(),
969+
section: DependencySection::Dependencies,
970+
}];
971+
let resolved = vec![(
972+
0,
973+
Ok(ResolvedVersion {
974+
latest: Some("2.0.0-rc.40".to_owned()),
975+
selected: Some("2.0.0-rc.40".to_owned()),
976+
}),
977+
)];
978+
let updates = compute_updates(&deps, &resolved);
979+
assert_eq!(updates.len(), 1);
980+
assert_eq!(updates[0].to, "2.0.0-rc.40");
981+
}
982+
983+
#[test]
984+
fn test_compute_updates_allows_prerelease_to_stable_upgrade() {
985+
// Current: 2.0.0-rc.37 (prerelease), Selected: 2.0.0 (stable) → semver: stable > prerelease of same version.
986+
let deps = vec![DependencySpec {
987+
name: "sea-orm".to_owned(),
988+
current_req: "2.0.0-rc.37".to_owned(),
989+
section: DependencySection::Dependencies,
990+
}];
991+
let resolved = vec![(
992+
0,
993+
Ok(ResolvedVersion {
994+
latest: Some("2.0.0".to_owned()),
995+
selected: Some("2.0.0".to_owned()),
996+
}),
997+
)];
998+
let updates = compute_updates(&deps, &resolved);
999+
assert_eq!(updates.len(), 1);
1000+
assert_eq!(updates[0].to, "2.0.0");
1001+
}
1002+
1003+
#[test]
1004+
fn test_compute_updates_equal_semver_skipped() {
1005+
// Exact same version: must skip (not a "downgrade", but not an upgrade either).
1006+
let deps = vec![DependencySpec {
1007+
name: "pkg".to_owned(),
1008+
current_req: "1.2.3".to_owned(),
1009+
section: DependencySection::Dependencies,
1010+
}];
1011+
let resolved = vec![(
1012+
0,
1013+
Ok(ResolvedVersion {
1014+
latest: Some("1.2.3".to_owned()),
1015+
selected: Some("1.2.3".to_owned()),
1016+
}),
1017+
)];
1018+
let updates = compute_updates(&deps, &resolved);
1019+
assert!(updates.is_empty());
1020+
}
1021+
8971022
#[test]
8981023
fn test_compute_updates_preserves_section() {
8991024
let deps = vec![DependencySpec {

crates/node/src/patcher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ impl JsonPatcher {
153153

154154
// Sort descending by start position
155155
let mut sorted: Vec<&Patch> = patches.iter().collect();
156-
sorted.sort_by(|a, b| b.start.cmp(&a.start));
156+
sorted.sort_by_key(|p| std::cmp::Reverse(p.start));
157157

158158
// Check for overlapping patches
159159
for window in sorted.windows(2) {

0 commit comments

Comments
 (0)