From 6a6d5118b48983a330346714b0e9b31c95476b0e Mon Sep 17 00:00:00 2001 From: Max Howell Date: Tue, 1 Apr 2025 17:42:39 -0400 Subject: [PATCH] Support `@latest` Closes #1160 --- .github/workflows/ci.yml | 5 ++ Cargo.lock | 4 +- crates/cli/Cargo.toml | 4 +- crates/cli/src/resolve.rs | 91 +++++++++++++++++++++++++++---------- crates/cli/src/x.rs | 2 +- crates/lib/Cargo.toml | 2 +- crates/lib/src/inventory.rs | 10 ++-- crates/lib/src/lib.rs | 5 +- 8 files changed, 87 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8727e3bb2..dbd0caf79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -180,6 +180,11 @@ jobs: - name: --shebang test 2 run: test $(pkgx -q! echo fail hi) = hi + - name: '@latest' + run: | + pkgx semverator eq $(pkgx krampus=0.2.0 --version) 0.2.0 + pkgx semverator gt $(pkgx krampus@latest --version) 0.2.0 + - uses: coverallsapp/github-action@v2 with: path-to-lcov: lcov.info diff --git a/Cargo.lock b/Cargo.lock index 49a28fcfa..d6025a9a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -782,7 +782,7 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libpkgx" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", "async-compression", @@ -1065,7 +1065,7 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "pkgx" -version = "2.5.0" +version = "2.6.0" dependencies = [ "console", "indicatif", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index e38d84f11..993998a2d 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -3,7 +3,7 @@ name = "pkgx" description = "Run anything" authors = ["Max Howell ", "Jacob Heider "] license = "Apache-2.0" -version = "2.5.0" +version = "2.6.0" edition = "2021" repository = "https://github.com/pkgxdev/pkgx" @@ -14,7 +14,7 @@ regex = "1.11.1" indicatif = "0.17.9" nix = { version = "0.29.0", features = ["process"] } serde_json = "1.0.135" -libpkgx = { version = "0.5.0", path = "../lib" } +libpkgx = { version = "0.6.0", path = "../lib" } console = { version = "0.15", default-features = false, features = [ "ansi-parsing", ] } diff --git a/crates/cli/src/resolve.rs b/crates/cli/src/resolve.rs index 947644ea9..a2c46971e 100644 --- a/crates/cli/src/resolve.rs +++ b/crates/cli/src/resolve.rs @@ -4,6 +4,7 @@ use libpkgx::{ install_multi::install_multi, pantry_db, sync, types::{Installation, PackageReq}, + VersionRange, }; use rusqlite::Connection; @@ -23,34 +24,24 @@ pub async fn resolve( let mut pkgs = vec![]; for pkgspec in plus { - let PackageReq { - project: project_or_cmd, - constraint, - } = PackageReq::parse(pkgspec)?; - if config + let mut pkgspec = parse_pkgspec(pkgspec)?; + + if !config .pantry_dir .join("projects") - .join(project_or_cmd.clone()) + .join(pkgspec.project()) .is_dir() { - pkgs.push(PackageReq { - project: project_or_cmd, - constraint, - }); - } else { - let project = which::which(&project_or_cmd, conn, &pkgs).await?; - pkgs.push(PackageReq { - project, - constraint, - }); + let project = which::which(&pkgspec.project(), conn, &pkgs).await?; + pkgspec.set_project(project); } + + pkgs.push(pkgspec.pkgreq(config).await); } if find_program { - let PackageReq { - constraint, - project: cmd, - } = PackageReq::parse(&args[0])?; + let mut pkgspec = parse_pkgspec(&args[0])?; + let cmd = pkgspec.project(); args[0] = cmd.clone(); // invoke eg. `node` rather than eg. `node@20` @@ -69,10 +60,9 @@ pub async fn resolve( Ok(project) => Ok(project), }?; - pkgs.push(PackageReq { - project, - constraint, - }); + pkgspec.set_project(project.clone()); + + pkgs.push(pkgspec.pkgreq(config).await); } let companions = pantry_db::companions_for_projects( @@ -97,3 +87,56 @@ pub async fn resolve( Ok((installations, graph)) } + +enum Pkgspec { + Req(PackageReq), + Latest(String), +} + +impl Pkgspec { + fn project(&self) -> String { + match self { + Pkgspec::Req(req) => req.project.clone(), + Pkgspec::Latest(project) => project.clone(), + } + } + + fn set_project(&mut self, project: String) { + match self { + Pkgspec::Req(req) => req.project = project, + Pkgspec::Latest(_) => *self = Pkgspec::Latest(project), + } + } + + async fn constraint(&self, config: &Config) -> VersionRange { + match self { + Pkgspec::Req(req) => req.constraint.clone(), + Pkgspec::Latest(project) => match libpkgx::inventory::ls(project, config).await { + Ok(versions) if !versions.is_empty() => { + let vmax = versions.iter().max(); + VersionRange::parse(&format!("={}", vmax.unwrap())) + } + _ => VersionRange::parse("*"), + } + .unwrap(), + } + } + + async fn pkgreq(&self, config: &Config) -> PackageReq { + let project = self.project(); + let constraint = self.constraint(config).await; + PackageReq { + project, + constraint, + } + } +} + +fn parse_pkgspec(pkgspec: &str) -> Result> { + if let Some(project) = pkgspec.strip_suffix("@latest") { + Ok(Pkgspec::Latest(project.to_string())) + } else { + let pkgspec = PackageReq::parse(pkgspec)?; + Ok(Pkgspec::Req(pkgspec)) + } +} diff --git a/crates/cli/src/x.rs b/crates/cli/src/x.rs index d40cb910d..27d8c4221 100644 --- a/crates/cli/src/x.rs +++ b/crates/cli/src/x.rs @@ -91,7 +91,7 @@ pub async fn exec( ); env.insert( - "PKGX_VERSION".to_string(), + construct_platform_case_aware_env_key("PKGX_VERSION".to_string()), env!("CARGO_PKG_VERSION").to_string(), ); diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index a4d7051c5..a496e6abc 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -3,7 +3,7 @@ name = "libpkgx" description = "Install and run `pkgx` packages" authors = ["Max Howell ", "Jacob Heider "] license = "Apache-2.0" -version = "0.5.0" +version = "0.6.0" edition = "2021" repository = "https://github.com/pkgxdev/pkgx" diff --git a/crates/lib/src/inventory.rs b/crates/lib/src/inventory.rs index c18cfbb46..654e08c00 100644 --- a/crates/lib/src/inventory.rs +++ b/crates/lib/src/inventory.rs @@ -26,7 +26,7 @@ impl Error for DownloadError {} // Select function to pick a version pub async fn select(rq: &PackageReq, config: &Config) -> Result, Box> { - let versions = ls(rq, config).await?; + let versions = ls(&rq.project, config).await?; Ok(versions .iter() @@ -36,13 +36,13 @@ pub async fn select(rq: &PackageReq, config: &Config) -> Result, } // Get function to fetch available versions -pub async fn ls(rq: &PackageReq, config: &Config) -> Result, Box> { +pub async fn ls(project: &String, config: &Config) -> Result, Box> { let base_url = config.dist_url.clone(); let (platform, arch) = host(); let url = Url::parse(&format!( "{}/{}/{}/{}/versions.txt", - base_url, rq.project, platform, arch + base_url, project, platform, arch ))?; let rsp = build_client()?.get(url.clone()).send().await?; @@ -64,11 +64,11 @@ pub async fn ls(rq: &PackageReq, config: &Config) -> Result, Box