Commit

A package's latest version is its highest, not the last one published

An import publishes every tag at once, so psr/log read as 1.0.0 instead of 3.0.2. Listings now pick the highest stable version by number, then the highest pre-release, and fall back to the newest published.

syntaqxcommitted Parent840c24dBrowse files
2 files+50−20/2 viewed
+49−1
6262 pub version_count: u32,
6363 pub bytes: u64,
6464 pub latest_tag: Option<String>,
65+ /// Every version, newest published first, one per line: the summary
66+ /// picks the highest of them (see `newest_version`).
6567 pub latest_version: Option<String>,
6668 }
6769
70+/// The version a listing calls latest: the highest stable one by number
71+/// (`v3.0.2` over `1.0.0`, whatever order they were published in, as an
72+/// import publishes every tag at once), else the highest pre-release, else
73+/// the newest published when none reads as a number.
74+pub fn newest_version(versions: &str) -> Option<String> {
75+ // Stable over pre-release, then by number, then pre-releases by label.
76+ let parse = |version: &str| -> Option<(bool, Vec<u64>, String)> {
77+ let bare = version.strip_prefix('v').unwrap_or(version);
78+ let (core, pre) = match bare.split_once(['-', '+']) {
79+ Some((core, rest)) if bare.as_bytes()[core.len()] == b'-' => (core, rest.to_owned()),
80+ Some((core, _)) => (core, String::new()),
81+ None => (bare, String::new()),
82+ };
83+ let parts = core.split('.').map(|part| part.parse::<u64>().ok()).collect::<Option<Vec<_>>>()?;
84+ Some((pre.is_empty(), parts, pre))
85+ };
86+ let list: Vec<&str> = versions.lines().map(str::trim).filter(|v| !v.is_empty()).collect();
87+ list.iter()
88+ .filter_map(|v| parse(v).map(|key| (key, *v)))
89+ .max_by(|a, b| a.0.cmp(&b.0))
90+ .map(|(_, v)| v.to_owned())
91+ .or_else(|| list.first().map(|v| (*v).to_owned()))
92+}
93+
94+#[cfg(test)]
95+mod newest_tests {
96+ use super::newest_version;
97+
98+ #[test]
99+ fn the_latest_is_the_highest_stable_version_not_the_last_published() {
100+ assert_eq!(newest_version("1.0.0
101+3.0.2
102+2.0.0
103+3.0.0").as_deref(), Some("3.0.2"));
104+ assert_eq!(newest_version("v1.10.0
105+v1.9.3").as_deref(), Some("v1.10.0"));
106+ assert_eq!(newest_version("4.0.0-beta.1
107+3.0.2").as_deref(), Some("3.0.2"));
108+ assert_eq!(newest_version("4.0.0-beta.1
109+4.0.0-alpha").as_deref(), Some("4.0.0-beta.1"));
110+ assert_eq!(newest_version("dev-main
111+nightly").as_deref(), Some("dev-main"));
112+ assert_eq!(newest_version(""), None);
113+ }
114+}
115+
68116 #[derive(Clone, Debug, Deserialize)]
69117 pub struct BlobRow {
70118 pub digest: String,
234282 (SELECT COALESCE(SUM(b.size), 0) FROM blobs b WHERE b.digest IN \
235283 (SELECT vf.digest FROM version_files vf JOIN versions v ON v.id = vf.version_id WHERE v.package_id = p.id)) AS bytes, \
236284 (SELECT t.tag FROM tags t WHERE t.package_id = p.id ORDER BY t.tag = 'latest' DESC, t.updated_at DESC LIMIT 1) AS latest_tag, \
237− (SELECT v.version FROM versions v WHERE v.package_id = p.id ORDER BY v.published_at DESC LIMIT 1) AS latest_version \
285+ (SELECT GROUP_CONCAT(version, char(10)) FROM (SELECT v.version FROM versions v WHERE v.package_id = p.id ORDER BY v.published_at DESC)) AS latest_version \
238286 FROM packages p WHERE p.workspace = ? AND p.workspace_deleted_at IS NULL",
239287 PACKAGE_COLUMNS.split(", ").map(|c| format!("p.{c}")).collect::<Vec<_>>().join(", ")
240288 );
+1−1
358358 }),
359359 description: p.description.clone(),
360360 versions: row.version_count,
361− latest: row.latest_tag.clone().or_else(|| row.latest_version.clone()),
361+ latest: row.latest_tag.clone().or_else(|| row.latest_version.as_deref().and_then(db::newest_version)),
362362 size: row.bytes,
363363 downloads: p.downloads,
364364 created_at: p.created_at.clone(),